* implemented chat protocol, explanation mechanics, reviewed marker for

already unpacked logs
* started to disect log parsers for using streams in future
* Qt Client started
* logstream planned.
This commit is contained in:
2015-04-10 20:33:55 +02:00
parent 0026d9b005
commit e43065cc00
9 changed files with 472 additions and 35 deletions

View File

@@ -5,12 +5,19 @@ L_NET = 'NET'
L_CHAT = 'CHAT'
class Log(object):
__slots__ = ['matcher', 'trash', 'reviewed']
matcher = None
trash = False
reviewed = False
@classmethod
def is_handler(cls, log):
return False
def unpack(self):
def unpack(self, force=False):
''' unpacks this log from its data and saves values '''
pass
def explain(self):
''' returns a String readable by humans explaining this Log '''
return ''

View File

@@ -1 +1,156 @@
# -*- coding: utf-8 -*-
from logs.base import Log, L_WARNING
import re
"""
Responsible for Chat Log.
ColorChart:
between 33-33-33 and FF-33 FF-33 FF-33
"""
class ChatLog(Log):
__slots__ = ['matcher', 'trash', '_match_id', 'values']
@classmethod
def is_handler(cls, log):
if log.get('logtype', None) == 'CHAT':
return cls._is_handler(log)
return False
@classmethod
def _is_handler(cls, log):
return False
def __init__(self, values=None):
self.values = values or {}
def unpack(self, force=False):
if self.reviewed and not force:
return True
self._match_id = None
# unpacks the data from the values.
if hasattr(self, 'matcher') and self.matcher:
matchers = self.matcher
if not isinstance(matchers, list):
matchers = [matchers,]
for i, matcher in enumerate(matchers):
m = matcher.match(self.values.get('log', ''))
if m:
self.values.update(m.groupdict())
self._match_id = i
self.reviewed = True
return True
# unknown?
self.trash = True
def explain(self):
''' returns a String readable by humans explaining this Log '''
return self.values.get('log', 'Unknown Chat Log')
class PrivateMessageReceived(ChatLog):
matcher = re.compile(r"^<\s\s\s\sPRIVATE From>\[\s*(?P<nickname>[^\]]+)\]\s(?P<message>.*)")
@classmethod
def _is_handler(cls, log):
if log.get('log', '').lstrip().startswith('< PRIVATE From>'):
return True
return False
def explain(self):
return '[From %(nickname)s]: %(message)s' % self.values
class PrivateMessageSent(ChatLog):
matcher = re.compile(r"^<\s\s\s\sPRIVATE To\s\s>\[\s*(?P<nickname>[^\]]+)\]\s(?P<message>.*)")
@classmethod
def _is_handler(cls, log):
if log.get('log', '').lstrip().startswith('< PRIVATE To >'):
return True
return False
def explain(self):
return '[To %(nickname)s]: %(message)s' % self.values
class ChatMessage(ChatLog):
matcher = re.compile(r"^<\s*#(?P<channel>[^>]+)>\[\s*(?P<nickname>[^\]]+)\]\s(?P<message>.*)")
@classmethod
def _is_handler(cls, log):
if log.get('log', '').lstrip().startswith('<'):
return True
return False
def explain(self):
return '[%(channel)s] <%(nickname)s>: %(message)s' % self.values
class ChatJoinChannel(ChatLog):
matcher = re.compile(r"^Join\schannel\s<\s*#(?P<channel>[^>]+)>")
@classmethod
def _is_handler(cls, log):
if log.get('log', '').lstrip().startswith('Join channel'):
return True
return False
def explain(self):
return '[joined %(channel)s]' % self.values
class ChatLeaveChannel(ChatLog):
matcher = re.compile(r"^Leave\schannel\s<\s*#(?P<channel>[^>]+)>")
@classmethod
def _is_handler(cls, log):
if log.get('log', '').lstrip().startswith('Leave channel'):
return True
return False
def explain(self):
return '[left %(channel)s]' % self.values
class ChatServerConnect(ChatLog):
# 00:12:47.668 CHAT| Connection to chat-server established
matcher = []
@classmethod
def _is_handler(cls, log):
if log.get('log', '').lstrip().startswith('Connection to'):
return True
return False
def unpack(self, force=False):
self.reviewed = True
return True
def explain(self):
return '[connected]'
class ChatServerDisconnect(ChatLog):
# 00:53:03.738 CHAT| Disconnect form chat-server (reason 0)
matcher = []
@classmethod
def _is_handler(cls, log):
if log.get('log', '').lstrip().startswith('Disconnect'):
return True
return False
def unpack(self, force=False):
self.reviewed = True
return True
def explain(self):
return '[disconnected]'
CHAT_LOGS = [
PrivateMessageReceived,
PrivateMessageSent,
ChatMessage, # private messages need to be before chatmessage.
ChatServerConnect,
ChatServerDisconnect,
ChatJoinChannel,
ChatLeaveChannel,
]

View File

@@ -24,7 +24,7 @@ import re
from base import Log, L_CMBT
class CombatLog(Log):
__slots__ = ['matcher', 'trash', '_match_id', 'values']
__slots__ = Log.__slots__ + [ '_match_id', 'values']
@classmethod
def _log_handler(cls, log):
if log.get('log', '').strip().startswith(cls.__name__):
@@ -38,9 +38,11 @@ class CombatLog(Log):
return False
def __init__(self, values=None):
self.values = values
self.values = values or {}
def unpack(self):
def unpack(self, force=False):
if self.reviewed and not force:
return True
self._match_id = None
# unpacks the data from the values.
if hasattr(self, 'matcher') and self.matcher:
@@ -52,9 +54,15 @@ class CombatLog(Log):
if m:
self.values.update(m.groupdict())
self._match_id = i
self.reviewed = True
return True
# unknown?
self.trash = True
def explain(self):
''' returns a String readable by humans explaining this Log '''
return self.values.get('log', 'Unknown Combat Log')
# @todo: where does this come from?
class Action(CombatLog):
@@ -88,7 +96,12 @@ class Spell(CombatLog):
class Reward(CombatLog):
__slots__ = CombatLog.__slots__
matcher = re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)(?P<amount>\d+)\s(?P<reward_type>.*)\s+for\s(?P<reward_reason>.*)")
matcher = [
# ordinary reward:
re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)(?P<amount>\d+)\s(?P<reward_type>.*)\s+for\s(?P<reward_reason>.*)"),
# openspace reward (karma):
re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)\s+(?P<karma>[\+\-]\d+)\skarma\spoints\s+for\s(?P<reward_reason>.*)"),
]
class Participant(CombatLog):
__slots__ = CombatLog.__slots__
@@ -125,7 +138,7 @@ class AddStack(CombatLog):
class Cancel(CombatLog):
__slots__ = CombatLog.__slots__
matcher = re.compile(r"^Cancel\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\sfrom\s'(?P<source_name>[^']+)'")
matcher = re.compile(r"^Cancel\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\sfrom\s'(?P<source_name>[^']*)'")
class Scores(CombatLog):
__slots__ = CombatLog.__slots__
@@ -149,7 +162,9 @@ class GameEvent(CombatLog):
return True
return False
def unpack(self):
def unpack(self, force=False):
if self.reviewed and not force:
return True
self._match_id = None
# unpacks the data from the values.
# small override to remove trailing "="s in the matching.
@@ -162,6 +177,7 @@ class GameEvent(CombatLog):
if m:
self.values.update(m.groupdict())
self._match_id = i
self.reviewed = True
return True
# unknown?
self.trash = True

View File

@@ -59,7 +59,9 @@ class GameLog(Log):
def __init__(self, values=None):
self.values = values
def unpack(self):
def unpack(self, force=False):
if self.reviewed and not force:
return True
self._match_id = None
# unpacks the data from the values.
if hasattr(self, 'matcher') and self.matcher:
@@ -71,10 +73,15 @@ class GameLog(Log):
if m:
self.values.update(m.groupdict())
self._match_id = i
self.reviewed = True
return True
# unknown?
self.trash = True
def explain(self):
''' returns a String readable by humans explaining this Log '''
return self.values.get('log', 'Unknown Game Log')
class WarningLog(Log):
__slots__ = ['trash',]
trash = True

View File

@@ -7,6 +7,7 @@
from logfile import LogFile
from combat import COMBAT_LOGS
from game import GAME_LOGS
from chat import CHAT_LOGS
class LogFileResolver(LogFile):
''' dynamic logfile resolver '''
@@ -38,3 +39,11 @@ class GameLogFile(LogFile):
return klass(line)
return line
class ChatLogFile(LogFile):
''' Chat Log '''
def resolve(self, line):
for klass in CHAT_LOGS:
if klass.is_handler(line):
return klass(line)
return line

28
logs/logstream.py Normal file
View File

@@ -0,0 +1,28 @@
# LogStream
"""
A LogStream is supposed to:
- parse data feeded into it.
- yield new objects
- remember errors
LogStream.Initialize:
- initialize the logstream in some way.
LogStream.Next:
- once initialized, read your stream until you can yield a new class
the next function reads the read-stream ahead.
empty lines are omitted
it tries to match the data into a new class and yields it
if it runs into trouble, it just outputs the line for now.
InitializeString:
- init with a data blob
- nice for trying it on files
@TODO: look at how file streams in python are implemented and find a good generic solution
combine it with the lookup for "watching files being changed", to create a program which listens to the logs live
@see: monitor.py
@see: watchdog https://pypi.python.org/pypi/watchdog
"""

View File

@@ -2,14 +2,22 @@
Logging Session.
"""
import zipfile, logging, os
from logfiles import CombatLogFile, GameLogFile
from logfiles import CombatLogFile, GameLogFile, ChatLogFile
class LogSession(object):
"""
The Log-Session is supposed to save one directory of logs.
A basic logsession.
deal with data as it comes along, and output interpretable data for the outside world.
"""
pass
class LogFileSession(LogSession):
"""
The Log-File-Session is supposed to save one directory of logs.
It can parse its logs, and build up its internal structure into Battle Instances etc.
"""
VALID_FILES = ['combat.log', 'game.log', ] # extend this to other logs.
VALID_FILES = ['combat.log', 'game.log', 'chat.log' ] # extend this to other logs.
def __init__(self, directory):
''' if directory is a file, it will be handled as a compressed folder '''
@@ -78,6 +86,11 @@ class LogSession(object):
self.game_log.read()
self.game_log.parse()
self.files_parsed.append('game.log')
if 'chat.log' in files and not 'chat.log' in self.files_parsed:
self.chat_log = ChatLogFile(os.path.join(self.directory, 'chat.log'))
self.chat_log.read()
self.chat_log.parse()
self.files_parsed.append('chat.log')
def determine_owner(self):
''' determines the user in the parsed gamelog '''
@@ -138,7 +151,7 @@ class LogSessionCollector(object):
for f in os.listdir(self.initial_directory):
full_dir = os.path.join(self.initial_directory, f)
if os.path.isdir(full_dir) or full_dir.lower().endswith('.zip'):
self.sessions.append(LogSession(full_dir))
self.sessions.append(LogFileSession(full_dir))
def collect(self):
sessions = []
@@ -167,8 +180,8 @@ class LogSessionCollector(object):
if __name__ == '__main__':
l_raw = LogSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.17 15.50.28')
l_zip = LogSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.20 23.49.19.zip')
l_raw = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.17 15.50.28')
l_zip = LogFileSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.20 23.49.19.zip')
l_zip.parse_files()
print l_zip.combat_log.lines