1
0
mirror of https://github.com/gryf/slack-backup.git synced 2025-12-17 19:40:21 +01:00

7 Commits
v0.4.2 ... v0.5

Author SHA1 Message Date
64d4b09468 Fix for handling messages of different types than 'message' 2017-08-06 09:22:38 +02:00
5f9f290ba4 Fix for message comment.
If comment is sent by the user, different structure of the data is sent.
First of all, the type of this message is "message", but it contain
dictionary under 'comment' key, which can be confusing, which contain
needed data (like user id). For this kind of messages, in case of lack
of 'user' in main dict, dict['comment']['user'] will be used for getting
user identifier, while dict['text'] remains as a message text.
2017-02-13 19:57:31 +01:00
Roman Dobosz
08a0a82435 Changed absolute to relative for filepaths stored in File objects 2016-12-03 18:43:49 +01:00
Roman Dobosz
a42506dff9 Fix for new fnames in case of already existing ones 2016-12-03 18:14:28 +01:00
Roman Dobosz
0d7607cf3c Added log for updating specific channel messages 2016-12-02 17:46:27 +01:00
9ddd470b54 Move commands functions to its own module 2016-11-28 19:05:26 +01:00
feb773956c Fix for extension of config file 2016-11-28 18:25:41 +01:00
7 changed files with 147 additions and 127 deletions

View File

@@ -1,122 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Create backup for certain date for specified channel in slack Execute commands for slack-backup
""" """
import argparse from slack_backup import command
import logging
from slack_backup import client
from slack_backup import config
def setup_logger(args):
"""Setup logger format and level"""
level = logging.WARNING
if args.quiet:
level = logging.ERROR
if args.quiet > 1:
level = logging.CRITICAL
if args.verbose:
level = logging.INFO
if args.verbose > 1:
level = logging.DEBUG
logging.basicConfig(level=level,
format="%(asctime)s %(levelname)s: %(message)s")
def generate_raport(args):
"""Generate logs"""
slack = client.Client(args)
slack.generate_history()
def fetch_data(args):
"""Fetch and store data"""
slack = client.Client(args)
slack.update()
def main():
"""Main function"""
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(dest='parser')
subparser.required = True
fetch = subparser.add_parser('fetch', help='Update local db with Slack'
' data')
fetch.add_argument('-t', '--token', default=None, help='Slack token - '
'a string, which can be generated/obtained via '
'https://api.slack.com/docs/oauth-test-tokens page.')
fetch.add_argument('-u', '--user', default=None, help='Username for your '
'Slack account')
fetch.add_argument('-p', '--password', default=None, help='Password for '
'your Slack account.')
fetch.add_argument('-e', '--team', default=None, help='Team name, which '
'is part of slack url, for example: if url is '
'"https://team.slack.com" than "team" is a name of '
'the team.')
fetch.add_argument('-v', '--verbose', help='Be verbose. Adding more "v" '
'will increase verbosity', action="count",
default=None)
fetch.add_argument('-q', '--quiet', help='Be quiet. Adding more "q" will'
' decrease verbosity', action="count", default=None)
fetch.add_argument('-c', '--channels', default=None, nargs='+',
help='List of channels to perform actions on. '
'Default is all channels.')
fetch.add_argument('-d', '--database', default=None,
help='Path to the database file.')
fetch.add_argument('-i', '--config', default=None,
help='Use specific config file.')
fetch.set_defaults(func=fetch_data)
generate = subparser.add_parser('generate', help='Generate logs out of '
'data in provided database')
generate.add_argument('-o', '--output', default=None, help="Output "
"directory for store logs. All logs are organised "
"per channel. By default it's `logs' directory")
generate.add_argument('-f', '--format', default=None,
choices=('text', 'none'),
help='Output format. Default is none; only database '
'is updated by latest messages for all/selected '
'channels.')
generate.add_argument('-t', '--theme', default=None,
choices=('plain', 'unicode'),
help='Choose theme for text output. It doesn\'t '
'affect other output formats.')
generate.add_argument('-v', '--verbose', help='Be verbose. Adding more '
'"v" will increase verbosity', action="count",
default=None)
generate.add_argument('-q', '--quiet', help='Be quiet. Adding more "q" '
'will decrease verbosity', action="count",
default=None)
generate.add_argument('-c', '--channels', default=[], nargs='+',
help='List of channels to perform actions on. '
'Default is all channels.')
generate.add_argument('-d', '--database', default=None,
help='Path to the database file.')
generate.add_argument('-i', '--config', default=None,
help='Use specific config file.')
generate.set_defaults(func=generate_raport)
args = parser.parse_args()
cfg = config.Config()
msg = cfg.update(args)
setup_logger(args)
logging.info(msg)
args.func(args)
if __name__ == "__main__": if __name__ == "__main__":
main() command.main()

View File

@@ -10,7 +10,7 @@ except ImportError:
setup(name="slack-backup", setup(name="slack-backup",
packages=["slack_backup"], packages=["slack_backup"],
version="0.4.2", version="0.5",
description="Make copy of slack converstaions", description="Make copy of slack converstaions",
author="Roman Dobosz", author="Roman Dobosz",
author_email="gryf73@gmail.com", author_email="gryf73@gmail.com",

View File

@@ -112,6 +112,7 @@ class Client(object):
channels = all_channels channels = all_channels
for channel in channels: for channel in channels:
logging.info("Getting messages for channel `%s'", channel.name)
latest = self.q(o.Message).\ latest = self.q(o.Message).\
filter(o.Message.channel == channel).\ filter(o.Message.channel == channel).\
order_by(o.Message.ts.desc()).first() order_by(o.Message.ts.desc()).first()
@@ -146,10 +147,18 @@ class Client(object):
Create message with corresponding possible metadata, like reactions, Create message with corresponding possible metadata, like reactions,
files etc. files etc.
""" """
if data['type'] != 'message':
logging.info("Skipping message of type `%s'.", data['type'])
return
try:
user = self.q(o.User).\ user = self.q(o.User).\
filter(o.User.slackid == data['user']).one() filter(o.User.slackid == data['user']).one()
except KeyError:
user = self.q(o.User).\
filter(o.User.slackid == data['comment']['user']).one()
if data['type'] == 'message' and not data['text'].strip(): if not data['text'].strip():
logging.info("Skipping message from `%s' since it's empty", logging.info("Skipping message from `%s' since it's empty",
user.name) user.name)
return return
@@ -255,7 +264,7 @@ class Client(object):
if not database: if not database:
return 'assets' return 'assets'
path = os.path.dirname(os.path.abspath(database)) path = os.path.dirname(database)
return os.path.join(path, 'assets') return os.path.join(path, 'assets')
def _channels_list(self): def _channels_list(self):

118
slack_backup/command.py Normal file
View File

@@ -0,0 +1,118 @@
"""
Create backup for certain date for specified channel in slack
"""
import argparse
import logging
from slack_backup import client
from slack_backup import config
def setup_logger(args):
"""Setup logger format and level"""
level = logging.WARNING
if args.quiet:
level = logging.ERROR
if args.quiet > 1:
level = logging.CRITICAL
if args.verbose:
level = logging.INFO
if args.verbose > 1:
level = logging.DEBUG
logging.basicConfig(level=level,
format="%(asctime)s %(levelname)s: %(message)s")
def generate_raport(args):
"""Generate logs"""
slack = client.Client(args)
slack.generate_history()
def fetch_data(args):
"""Fetch and store data"""
slack = client.Client(args)
slack.update()
def main():
"""Main function"""
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(dest='parser')
subparser.required = True
fetch = subparser.add_parser('fetch', help='Update local db with Slack'
' data')
fetch.add_argument('-t', '--token', default=None, help='Slack token - '
'a string, which can be generated/obtained via '
'https://api.slack.com/docs/oauth-test-tokens page.')
fetch.add_argument('-u', '--user', default=None, help='Username for your '
'Slack account')
fetch.add_argument('-p', '--password', default=None, help='Password for '
'your Slack account.')
fetch.add_argument('-e', '--team', default=None, help='Team name, which '
'is part of slack url, for example: if url is '
'"https://team.slack.com" than "team" is a name of '
'the team.')
fetch.add_argument('-v', '--verbose', help='Be verbose. Adding more "v" '
'will increase verbosity', action="count",
default=None)
fetch.add_argument('-q', '--quiet', help='Be quiet. Adding more "q" will'
' decrease verbosity', action="count", default=None)
fetch.add_argument('-c', '--channels', default=None, nargs='+',
help='List of channels to perform actions on. '
'Default is all channels.')
fetch.add_argument('-d', '--database', default=None,
help='Path to the database file.')
fetch.add_argument('-i', '--config', default=None,
help='Use specific config file.')
fetch.set_defaults(func=fetch_data)
generate = subparser.add_parser('generate', help='Generate logs out of '
'data in provided database')
generate.add_argument('-o', '--output', default=None, help="Output "
"directory for store logs. All logs are organised "
"per channel. By default it's `logs' directory")
generate.add_argument('-f', '--format', default=None,
choices=('text', 'none'),
help='Output format. Default is none; only database '
'is updated by latest messages for all/selected '
'channels.')
generate.add_argument('-t', '--theme', default=None,
choices=('plain', 'unicode'),
help='Choose theme for text output. It doesn\'t '
'affect other output formats.')
generate.add_argument('-v', '--verbose', help='Be verbose. Adding more '
'"v" will increase verbosity', action="count",
default=None)
generate.add_argument('-q', '--quiet', help='Be quiet. Adding more "q" '
'will decrease verbosity', action="count",
default=None)
generate.add_argument('-c', '--channels', default=[], nargs='+',
help='List of channels to perform actions on. '
'Default is all channels.')
generate.add_argument('-d', '--database', default=None,
help='Path to the database file.')
generate.add_argument('-i', '--config', default=None,
help='Use specific config file.')
generate.set_defaults(func=generate_raport)
args = parser.parse_args()
cfg = config.Config()
msg = cfg.update(args)
setup_logger(args)
logging.info(msg)
args.func(args)

View File

@@ -58,7 +58,7 @@ class Config(object):
path = args.config path = args.config
locations = [path, locations = [path,
'./slack-backup.conf', './slack-backup.ini',
os.path.expandvars('$XDG_CONFIG_HOME/slack-backup.ini'), os.path.expandvars('$XDG_CONFIG_HOME/slack-backup.ini'),
os.path.expandvars('$HOME/.config/slack-backup.ini')] os.path.expandvars('$HOME/.config/slack-backup.ini')]

View File

@@ -55,8 +55,8 @@ class Download(object):
'file': self._files} 'file': self._files}
if filetype == 'file' and not self._authorized: if filetype == 'file' and not self._authorized:
logging.info("There was no (valid) credentials passed, therefore " logging.warning("There was no (valid) credentials passed, "
"file `%s' cannot be downloaded", url) "therefore file `%s' cannot be downloaded", url)
return return
splitted = url.split('/') splitted = url.split('/')
@@ -79,8 +79,10 @@ class Download(object):
count = 1 count = 1
while filetype != 'avatar' and os.path.exists(path): while filetype != 'avatar' and os.path.exists(path):
if count == 1:
base, ext = os.path.splitext(path) base, ext = os.path.splitext(path)
path = base + "%0.3d" % count + ext path = base + ".%0.3d" % count + ext
count += 1
return path return path

View File

@@ -324,7 +324,10 @@ MSGS = {'messages': [{"type": "message",
"<https://esm64.slack.com/files/name2/F3405RRB5/" "<https://esm64.slack.com/files/name2/F3405RRB5/"
"screenshot.png|Screenshot.png>", "screenshot.png|Screenshot.png>",
"ts": "1478107371.000052", "ts": "1478107371.000052",
"upload": True}], "upload": True},
{'type': 'something else',
'ts': '1502003415232.000001',
"wibblr": True}],
"ok": True, "ok": True,
"latest": "1479501075.000020", "latest": "1479501075.000020",
"has_more": True} "has_more": True}
@@ -389,7 +392,7 @@ class TestApiCalls(TestCase):
"C00000001").one() "C00000001").one()
msg, ts = cl._channels_history(channel, 0) msg, ts = cl._channels_history(channel, 0)
self.assertEqual(len(msg), 5) self.assertEqual(len(msg), 6)
self.assertEqual(ts, '1479501074.000032') self.assertEqual(ts, '1479501074.000032')
msg, ts = cl._channels_history(channel, ts) msg, ts = cl._channels_history(channel, ts)