* 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:
@@ -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 ''
|
||||
|
||||
155
logs/chat.py
155
logs/chat.py
@@ -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,
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
11
logs/game.py
11
logs/game.py
@@ -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
|
||||
|
||||
@@ -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
28
logs/logstream.py
Normal 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
|
||||
"""
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user