mirror of
https://github.com/gryf/slack-backup.git
synced 2025-12-17 11:30:25 +01:00
Implementation of TextReporter
This commit is contained in:
@@ -5,8 +5,11 @@ Reporters module.
|
|||||||
There are several classes for specific format reporting, and also some of the
|
There are several classes for specific format reporting, and also some of the
|
||||||
slack conversation/convention parsers.
|
slack conversation/convention parsers.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
import os
|
import os
|
||||||
|
import errno
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
from slack_backup import objects as o
|
from slack_backup import objects as o
|
||||||
from slack_backup import utils
|
from slack_backup import utils
|
||||||
@@ -18,9 +21,51 @@ class Reporter(object):
|
|||||||
|
|
||||||
def __init__(self, args, query):
|
def __init__(self, args, query):
|
||||||
self.out = args.output
|
self.out = args.output
|
||||||
|
self.theme = args.theme
|
||||||
self.q = query
|
self.q = query
|
||||||
|
self.types = {"channel_join": self._msg_join,
|
||||||
|
"channel_leave": self._msg_leave,
|
||||||
|
"channel_topic": self._msg_topic,
|
||||||
|
"file_share": self._msg_file,
|
||||||
|
"me_message": self._msg_me}
|
||||||
|
self.symbols = {'plain': {'join': '->',
|
||||||
|
'leave': '<-',
|
||||||
|
'me': '*',
|
||||||
|
'file': '-',
|
||||||
|
'topic': '+',
|
||||||
|
'separator': '|'},
|
||||||
|
'unicode': {'join': '⮊',
|
||||||
|
'leave': '⮈',
|
||||||
|
'me': '🟊',
|
||||||
|
'file': '📂',
|
||||||
|
'topic': '🟅',
|
||||||
|
'separator': '│'}}
|
||||||
|
|
||||||
self.channels = self._get_channels(args.channels)
|
self.channels = self._get_channels(args.channels)
|
||||||
|
self.users = self.q(o.User).all()
|
||||||
|
self._re_first_idnick = re.compile(r'^(?P<replace>'
|
||||||
|
r'<@(?P<slackid>U[A-Z,0-9]+)\|.+>)')
|
||||||
|
self._re_first_id = re.compile('^(?P<replace>'
|
||||||
|
'<@(?P<slackid>U[A-Z,0-9]+)>)')
|
||||||
|
self._re_idnick = re.compile(r'.*(?P<replace>'
|
||||||
|
r'<@(?P<slackid>U[A-Z,0-9]+)\|.+>)')
|
||||||
|
self._re_id = re.compile('.*(?P<replace><@(?P<slackid>U[A-Z,0-9]+)>)')
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
"""Generate raport it's a dummmy one - for use with none reporter"""
|
||||||
|
return
|
||||||
|
|
||||||
|
def get_log_path(self, name):
|
||||||
|
"""Return relative log file name """
|
||||||
|
return os.path.join(self.out, name + self.ext)
|
||||||
|
|
||||||
|
def write_msg(self, message, log):
|
||||||
|
"""Write message to file"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def _get_symbol(self, item):
|
||||||
|
"""Return appropriate item depending on the selected theme"""
|
||||||
|
return self.symbols[self.theme][item]
|
||||||
|
|
||||||
def _get_channels(self, selected_channels):
|
def _get_channels(self, selected_channels):
|
||||||
"""
|
"""
|
||||||
@@ -36,19 +81,29 @@ class Reporter(object):
|
|||||||
if channel.name in selected_channels:
|
if channel.name in selected_channels:
|
||||||
result.append(channel)
|
result.append(channel)
|
||||||
|
|
||||||
return result
|
def _msg_join(self, msg, text):
|
||||||
|
"""return formatter for join"""
|
||||||
def generate(self):
|
|
||||||
"""Generate raport it's a dummmy one - for use with none reporter"""
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def get_log_path(self, name):
|
def _msg_leave(self, msg, text):
|
||||||
"""Return relative log file name """
|
"""return formatter for leave"""
|
||||||
return os.path.join(self.out, name + self.ext)
|
return
|
||||||
|
|
||||||
def write_msg(self, message, log):
|
def _msg_topic(self, msg, text):
|
||||||
"""Write message to file"""
|
"""return formatter for set topic"""
|
||||||
raise NotImplementedError()
|
return
|
||||||
|
|
||||||
|
def _msg_me(self, msg, text):
|
||||||
|
"""return formatter for /me"""
|
||||||
|
return
|
||||||
|
|
||||||
|
def _msg_file(self, msg, text):
|
||||||
|
"""return formatter for /me"""
|
||||||
|
return
|
||||||
|
|
||||||
|
def _filter_slackid(self, text):
|
||||||
|
"""filter out all of the id from slack"""
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
class TextReporter(Reporter):
|
class TextReporter(Reporter):
|
||||||
@@ -56,13 +111,9 @@ class TextReporter(Reporter):
|
|||||||
ext = '.log'
|
ext = '.log'
|
||||||
|
|
||||||
def __init__(self, args, query):
|
def __init__(self, args, query):
|
||||||
super().__init__(args, query)
|
super(TextReporter, self).__init__(args, query)
|
||||||
utils.makedirs(self.out)
|
utils.makedirs(self.out)
|
||||||
self._line = ""
|
self._max_len = 0
|
||||||
|
|
||||||
def _prepare_line(self, channel):
|
|
||||||
users = [m.user for m in channel.messages]
|
|
||||||
users = set([u.name for u in users])
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -70,7 +121,12 @@ class TextReporter(Reporter):
|
|||||||
"""Generate raport"""
|
"""Generate raport"""
|
||||||
for channel in self.channels:
|
for channel in self.channels:
|
||||||
log_path = self.get_log_path(channel.name)
|
log_path = self.get_log_path(channel.name)
|
||||||
self._prepare_line(channel)
|
self._set_max_len(channel)
|
||||||
|
try:
|
||||||
|
os.unlink(log_path)
|
||||||
|
except IOError as err:
|
||||||
|
if err.errno != errno.ENOENT:
|
||||||
|
raise
|
||||||
for message in self.q(o.Message).\
|
for message in self.q(o.Message).\
|
||||||
filter(o.Message.channel == channel).\
|
filter(o.Message.channel == channel).\
|
||||||
order_by(o.Message.ts).all():
|
order_by(o.Message.ts).all():
|
||||||
@@ -81,15 +137,133 @@ class TextReporter(Reporter):
|
|||||||
with open(log, "a") as fobj:
|
with open(log, "a") as fobj:
|
||||||
fobj.write(self._format_message(message))
|
fobj.write(self._format_message(message))
|
||||||
|
|
||||||
|
def _set_max_len(self, channel):
|
||||||
|
"""calculate max_len for sepcified channel"""
|
||||||
|
users = [m.user for m in channel.messages]
|
||||||
|
users = set([u.name for u in users])
|
||||||
|
|
||||||
|
self._max_len = 0
|
||||||
|
for user_name in users:
|
||||||
|
if len(user_name) > self._max_len:
|
||||||
|
self._max_len = len(user_name)
|
||||||
|
|
||||||
def _format_message(self, msg):
|
def _format_message(self, msg):
|
||||||
"""
|
"""
|
||||||
Check what kind of message we are dealing with and do appropriate
|
Check what kind of message we are dealing with and do appropriate
|
||||||
formatting
|
formatting
|
||||||
"""
|
"""
|
||||||
return msg.text
|
msg_txt = self._filter_slackid(msg.text)
|
||||||
return (msg.datetime().strftime("%Y-%m-%d %H:%M:%S"),
|
msg_txt = self._fix_newlines(msg_txt)
|
||||||
msg.user.name,
|
formatter = self.types.get(msg.type, self._msg)
|
||||||
msg.text)
|
if not msg_txt.strip():
|
||||||
|
logging.info("Skipping message from `%s' since it's empty",
|
||||||
|
msg.user.name)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return formatter(msg, msg_txt)
|
||||||
|
|
||||||
|
def _msg_join(self, msg, text):
|
||||||
|
"""return formatter for join"""
|
||||||
|
data = {'date': msg.datetime().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
'msg': text,
|
||||||
|
'max_len': self._max_len,
|
||||||
|
'separator': self._get_symbol('separator'),
|
||||||
|
'nick': self._get_symbol('join')}
|
||||||
|
return '{date} {nick:>{max_len}} {separator} {msg}\n'.format(**data)
|
||||||
|
|
||||||
|
def _msg_leave(self, msg, text):
|
||||||
|
"""return formatter for leave"""
|
||||||
|
data = {'date': msg.datetime().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
'msg': text,
|
||||||
|
'max_len': self._max_len,
|
||||||
|
'separator': self._get_symbol('separator'),
|
||||||
|
'nick': self._get_symbol('leave')}
|
||||||
|
return '{date} {nick:>{max_len}} {separator} {msg}\n'.format(**data)
|
||||||
|
|
||||||
|
def _msg_topic(self, msg, text):
|
||||||
|
"""return formatter for set topic"""
|
||||||
|
data = {'date': msg.datetime().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
'msg': text,
|
||||||
|
'max_len': self._max_len,
|
||||||
|
'separator': self._get_symbol('separator'),
|
||||||
|
'char': self._get_symbol('topic')}
|
||||||
|
return '{date} {char:>{max_len}} {separator} {msg}\n'.format(**data)
|
||||||
|
|
||||||
|
def _msg_me(self, msg, text):
|
||||||
|
"""return formatter for /me"""
|
||||||
|
data = {'date': msg.datetime().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
'msg': text,
|
||||||
|
'max_len': self._max_len,
|
||||||
|
'nick': msg.user.name,
|
||||||
|
'separator': self._get_symbol('separator'),
|
||||||
|
'char': self._get_symbol('me')}
|
||||||
|
return '{date} {char:>{max_len}} {separator} {nick} {msg}\n'.\
|
||||||
|
format(**data)
|
||||||
|
|
||||||
|
def _msg_file(self, msg, text):
|
||||||
|
"""return formatter for file"""
|
||||||
|
groups = self._re_first_idnick.match(msg.text).groupdict()
|
||||||
|
text = msg.text.replace(groups['replace'], '')
|
||||||
|
filename = msg.file.filepath
|
||||||
|
if filename:
|
||||||
|
filename = os.path.relpath(msg.file.filepath, start=self.out)
|
||||||
|
else:
|
||||||
|
filename = msg.file.url
|
||||||
|
|
||||||
|
if not filename:
|
||||||
|
logging.warning("Dude, we have a file object, but nothing has "
|
||||||
|
"found. Name of the file object is `i%s'",
|
||||||
|
msg.file.name)
|
||||||
|
filename = msg.file.name
|
||||||
|
|
||||||
|
text = self._filter_slackid(text)
|
||||||
|
text = self._fix_newlines(text)
|
||||||
|
|
||||||
|
data = {'date': msg.datetime().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
'msg': text,
|
||||||
|
'max_len': self._max_len,
|
||||||
|
'separator': self._get_symbol('separator'),
|
||||||
|
'filename': filename,
|
||||||
|
'nick': msg.user.name,
|
||||||
|
'char': self._get_symbol('file')}
|
||||||
|
return ('{date} {char:>{max_len}} {separator} {nick} '
|
||||||
|
'shared file "{filename}"{msg}\n'.format(**data))
|
||||||
|
|
||||||
|
def _msg(self, msg, text):
|
||||||
|
"""return formatter for /me"""
|
||||||
|
data = {'date': msg.datetime().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
'msg': text,
|
||||||
|
'max_len': self._max_len,
|
||||||
|
'separator': self._get_symbol('separator'),
|
||||||
|
'nick': msg.user.name}
|
||||||
|
return '{date} {nick:>{max_len}} {separator} {msg}\n'.format(**data)
|
||||||
|
|
||||||
|
def _filter_slackid(self, text):
|
||||||
|
"""filter out all of the id from slack"""
|
||||||
|
for pat in (self._re_first_idnick, self._re_first_id):
|
||||||
|
while pat.search(text):
|
||||||
|
groups = pat.search(text).groupdict('slackid')
|
||||||
|
user = [u for u in self.users
|
||||||
|
if u.slackid == groups['slackid']][0]
|
||||||
|
text = text.replace(groups['replace'], user.name + ":")
|
||||||
|
|
||||||
|
for pat in (self._re_idnick, self._re_id):
|
||||||
|
while pat.search(text):
|
||||||
|
groups = pat.search(text).groupdict('slackid')
|
||||||
|
user = [u for u in self.users
|
||||||
|
if u.slackid == groups['slackid']][0]
|
||||||
|
text = text.replace(groups['replace'], user.name)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _fix_newlines(self, text):
|
||||||
|
"""Shift text with new lines to the right with separator"""
|
||||||
|
shift = 19 # length of the date
|
||||||
|
shift += 1 # separator space
|
||||||
|
shift += self._max_len # length reserved for the nicks
|
||||||
|
shift += 1 # separator space
|
||||||
|
return text.replace('\n', '\n' + shift * ' ' +
|
||||||
|
self._get_symbol('separator') + ' ')
|
||||||
|
|
||||||
|
|
||||||
def get_reporter(args, query):
|
def get_reporter(args, query):
|
||||||
|
|||||||
51
tests/test_reporter.py
Normal file
51
tests/test_reporter.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from unittest import TestCase
|
||||||
|
try:
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
except ImportError:
|
||||||
|
from mock import MagicMock
|
||||||
|
|
||||||
|
from slack_backup import reporters as r
|
||||||
|
|
||||||
|
|
||||||
|
class FakeUser(object):
|
||||||
|
def __init__(self, slackid, name):
|
||||||
|
self.slackid = slackid
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
|
||||||
|
class TestReporter(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
|
||||||
|
users = [FakeUser('U111AAAAA', 'user1'),
|
||||||
|
FakeUser('U111BBBBB', 'user2'),
|
||||||
|
FakeUser('U111CCCCC', 'funky_username1'),
|
||||||
|
FakeUser('U111DDDDD', 'user-name°')]
|
||||||
|
|
||||||
|
args = MagicMock()
|
||||||
|
args.output = 'logs'
|
||||||
|
query1 = MagicMock()
|
||||||
|
query1.all = MagicMock(return_value=users)
|
||||||
|
query = MagicMock(return_value=query1)
|
||||||
|
|
||||||
|
self.reporter = r.TextReporter(args, query)
|
||||||
|
|
||||||
|
def test_regexp(self):
|
||||||
|
text = 'Cras vestibulum <@U111AAAAA|user1> erat ultrices neque.'
|
||||||
|
self.assertEqual(self.reporter._filter_slackid(text),
|
||||||
|
'Cras vestibulum user1 erat ultrices neque.')
|
||||||
|
|
||||||
|
text = ('Cras vestibulum <@U111AAAAA|user1> erat ultrices '
|
||||||
|
'<@U111AAAAA|user1> neque.')
|
||||||
|
self.assertEqual(self.reporter._filter_slackid(text),
|
||||||
|
'Cras vestibulum user1 erat ultrices user1 neque.')
|
||||||
|
|
||||||
|
text = ('<@U111BBBBB|user2>Praesent vel enim sed eros luctus '
|
||||||
|
'imperdiet.\nMauris neque ante, <@U111DDDDD> placerat at, '
|
||||||
|
'mollis vitae, faucibus quis, <@U111CCCCC>leo. Ut feugiat.')
|
||||||
|
|
||||||
|
# Praesent vel enim sed eros luctus imperdiet. Vivamus urna quam, congue
|
||||||
|
# vulputate, convallis non, cursus cursus, risus. Quisque aliquet. Donec
|
||||||
|
# vulputate egestas elit. Morbi dictum, sem sit amet aliquam.
|
||||||
Reference in New Issue
Block a user