Author Topic: PS log statistics  (Read 538 times)

derula

  • Hydlaa Citizen
  • *
  • Posts: 405
  • My main: Jamona Shikon
    • View Profile
    • Ugly Horst Tld.
PS log statistics
« on: April 16, 2010, 12:48:36 pm »
Another waste of time:
Code: [Select]
begin
require 'parsedate'

class Date
module Format # :nodoc:
warn_level = $VERBOSE
$VERBOSE = nil
MONTHS = MONTHS.merge(
'januar'   => 1, 'februar'  => 2, 'märz'     => 3, 'mai'      => 5,
'juni'     => 6, 'juli'     => 7, 'oktober'  =>10, 'dezember' =>12
)
DAYS = DAYS.merge(
'sonntag'   =>0, 'montag'   => 1, 'dienstag' => 2, 'mittwoch' => 3,
'donnerstag'=>4, 'freitag'  => 5, 'samstag'  => 6
)
ABBR_MONTHS = ABBR_MONTHS.merge(
'mär'      => 3, 'apr'      => 4, 'mai'      => 5, 'okt'      =>10,
'dez'      =>12
)
ABBR_DAYS = ABBR_DAYS.merge(
'so'       => 0, 'mo'       => 1, 'di'       => 2, 'mi'       => 3,
'do'       => 4, 'fr'       => 5, 'sa'       => 6
)
$VERBOSE = warn_level
end
end
class PSLog < Array
class Line
COMMANDS = [:say, :shout, :tellnpc, :auction, :group, :guild, :tell, :channel, :action]
ACTIONS = [:me, :my]
attr_reader :direction, :name, :time, :command, :text, :action, :target
def initialize own_name, name, time, command, text, action = nil, target = nil
unless is_npc_action = (command == :action and name == nil)
name = own_name if name.downcase == 'you'
end
unless is_npc_action
@direction = (is_npc_action or (name != own_name)) ? :in : :out
@name = name
else
@direction = :unknown
end
@time = time
COMMANDS.include? command or raise "Invalid command #{command}"
@command = command
@text = text
if action
ACTIONS.include? action or raise "Invalid action #{action}"
@action = action
end
if [:tell, :channel].include? @command
@target = target
end
end
end
class Session < Array
attr_reader :start_time
def initialize time
@start_time = time
super()
end
end
def self.load name, &block
file = File.join File.expand_path('~'), '.PlaneShift', 'logs', (name.gsub(' ', '_') << '_chat.txt')
File.exist?(file) and new(name, file, &block)
end
TIMESTAMP = /^\((\d\d:\d\d:\d\d)\) /
ACTION = /^>/
CHAT_TAB = /^
(?:\[(NPC|Auction|Group|Guild|Tell)\]| # General chat tabs
   \[Channel\]\s\[\d+:\s([^\]]*)\])\s  # Channel chat tab
/x
NAME = /^([A-Z][a-z]*)(?: ((?:[A-Z][a-z]*-)*[A-Z][a-z]*))?/
SAY_SHOUT_SUFFIX = /^ (says|shouts|auctions): /
TELL_SUFFIX = /^ (?:tells you|tell ([A-Z][a-z]+)): /
MY_SUFFIX = /^'s /
ME_SUFFIX = /^ /
SEPARATOR = /^(=|-){48,}$/
COMMANDS = { "NPC" => :tellnpc, "Auction" => :auction, "Group" => :group,
"Guild" => :guild, "Tell" => :tell,
'says' => :say, 'shouts' => :shout, nil => :action
}
attr_reader :surnames
def initialize name, filename
@name = name
first, last = name.split(' ')
super()
session_start = /(.*) #{@name}/
day = lasthour = nil
@surnames = { first => last }
puts "Reading file #{filename}..."
File.foreach filename do |line|
if line.slice! TIMESTAMP
raise 'No session started in log file' if empty?
# Haben Chat-Zeile
h, m, s = $1.split(':').collect { |s| s.to_i }
if h < lasthour
day += 86400 # Einen Tag addieren
end
lasthour = h
# Command rekonstruieren, Name / Action / Target herausfinden
unless is_action = line.slice!(ACTION) != nil
line.slice! CHAT_TAB
tab, channel = $1, $2
end
line.slice! NAME
name = $1
@surnames[name] = $2 if $2
target = action = nil
command =
if is_action              # :action
command = :action
else
# Setzt $1 auf say oder shout, falls es sich um say/shout handelt
line.slice! SAY_SHOUT_SUFFIX
action =
if name and not (verb = $1)
if tab == "Tell" and line.slice!(TELL_SUFFIX)
target = $1
nil
elsif line.slice! MY_SUFFIX
:my
else
line.slice! ME_SUFFIX
:me
end
end
if tab and name        # Commands aus COMMANDS
COMMANDS[tab]
elsif channel           # :channel
target = channel
:channel
else                    # :say und :shout
# (/shout /m(e|y) wird zu /say :m(e|y), :mypet wird zu :my)
action ? :say : COMMANDS[verb]
end
end
line = Line.new(first, name, day + 3600*h+60*m+s, command, line.strip, action, target)
yield self[-1], line
self[-1] << line
elsif line[session_start]
time = Time.mktime(*ParseDate.parsedate($1))
a = time.to_a[2..5].reverse
lasthour = a[3]
day = Time.mktime(*a[0..2])
self << Session.new(time)
elsif not line[SEPARATOR]
puts "Failed to parse the following line:\n  #{line}"
end
end
puts "Parsing finished."
end
end

saved_chars = []
charname = /([^#{File::SEPARATOR}]*)_chat.txt/

Dir.glob File.join File.expand_path('~'), '.PlaneShift', 'logs', ('*_chat.txt') do |file|
file[charname]
saved_chars << [$1.gsub('_', ' '), file]
end

begin
puts "Please enter the number for the character whose logs are to be checked."
saved_chars.each_with_index do |char, i|
puts "#{i+1}) #{char[0]}"
end
end until (1..saved_chars.length).include?(i = gets.to_i)

linecount = 0
msg_by_dir = { :in => 0, :out => 0, :unknown => 0 }
msg_by_char = Hash.new { |hash, key| hash[key] = 0 }
log = PSLog.new *saved_chars[i-1] do |session, line|
linecount += 1
msg_by_dir[line.direction] += 1
msg_by_char[line.name] += 1 if line.name
end

unless log
puts "Error trying to load log for #{name}."
exit! 1
end

top_char = msg_by_char.sort{ |a, b| b[1] <=> a[1]}

max_session = [0]

play_time = log.inject(0) do |sum, session|
sl = (session.empty? ? 0 : (session[-1].time.to_i - session.start_time.to_i))
unless sl < max_session[0]
max_session = [sl, session.start_time]
end
sum + sl
end

puts "Your chatlog contains #{linecount} chat lines:"
puts "  #{msg_by_dir[:in]} incoming,"
puts "  #{msg_by_dir[:out]} outgoing and"
puts "  #{msg_by_dir[:unknown]} unknown (i.e. NPC/GM actions)."
puts ""
puts "Messages span across #{log.length} game sessions covering a total play"
puts "time of #{play_time/3600.0} hours; thus, the mean session length is"
puts "#{play_time/(60.0*log.length)} minutes. Your longest session was"
puts "#{max_session[0]/3600.0} hours long, it started #{max_session[1]}."
puts ""
puts "Your log contains messages from #{msg_by_char.length} different characters,"
puts "and surnames for #{log.surnames.length} of them are logged."
puts ""
puts "The top five characters in your log are:"
puts top_char[0..4].inject('') { |string, char|
string << char[0] << ' '
if log.surnames.include? char[0]
string << log.surnames[char[0]] << ' '
end
string << '(' << char[1].to_s << ")\n"
}

rescue Interrupt
puts "Aborted."
end

Save it as something.rb and run with "ruby something.rb". Only some sample statistics included that came to my mind, add more if you like. Won't fix bugs. Won't work correctly if your system locale isn't English or German (the dates are printed to the log according to system locale, and only dates in these two languages can be parsed).

Example output:

Code: [Select]
Parsing finished.
Your chatlog contains 95471 chat lines:
  58623 incoming,
  36084 outgoing and
  764 unknown (i.e. NPC/GM actions).

Messages span across 331 game sessions covering a total play
time of 618.291944444444 hours; thus, the mean session length is
112.077089627392 minutes. Your longest session was
17.4208333333333 hours long, it started Sat Jun 06 10:51:13 +0200 2009.

Your log contains messages from 988 different characters,
and surnames for 640 of them are logged.

The top five characters in your log are:
[truncated]

Rigwyn

  • Guest
Re: PS log statistics
« Reply #1 on: April 16, 2010, 01:07:13 pm »

If your looking to waste more time... some rather interesting info could be mined out of those logs with some fancy expressions.

ie.

A list of characters who's name you have heard ICly ( would need to be able to separate narrated and bracketed text from spoken text )
A list of items and what they have been auctioned for

A list of text patterns that most strongly correlate to a given character.

ie.. you might find that charcter/player X misspells the word "piece" incorrectly more often than anyone else.
-- Likewise you might want to make a list of other characters/players who make the same typo  ;)

This is would be very simple way to profile players ... a better way would be to use a bayesian filter.
-- the Spambayes filter spam filter is an excellent example of how to do this sort of filtering. in brief, text is broken into tokens, and each token is given a number that correlates it with spam.  ( training phase ). .. afterwards, new text is broken into tokens, and the tokens are checked to see if they correlate with spam or good mail.. the new message is then scored, and acted upon accordingly.

Using the same idea one might *train* such a filter to sniff out messages are have characteristics of a certain player etc...



derula

  • Hydlaa Citizen
  • *
  • Posts: 405
  • My main: Jamona Shikon
    • View Profile
    • Ugly Horst Tld.
Re: PS log statistics
« Reply #2 on: April 16, 2010, 01:33:58 pm »
Well I don't really want to waste more time on that :) If you want to add something yourself (or anyone else), a few hints: All data is in the variable "log". It contains objects of type "Session" which are basically arrays plus a start time. E.g. "p log[0].start_time" would display the time of the first recorded session. One session array contains objects of type Line which have the following attributes:
- direction (:in, :out or :unknown): Whether the line was incoming, outgoing or unknown
- name: The name of the speaker (or nil if direction==unknown)
- time: Date and time of the line
- command (:say, :shout, :tellnpc, :auction, :group, :guild, :tell, :channel, :action): The command used (except that /shout /me and /mypet can't be detected, and "/say /me shouts:" will become :shout). Action means a scripted action like /wave.
- text: The text of the line (excluding name and action verb, if applicable)
- action: :me, :my or nil (see also command)
- target: If applicable, the target of the action (if command==:tell or command==:tellnpc)

E.g. "puts log[-1][-1].name" prints the name of the character who wrote the most recent line in the log.