Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upload zip to server #64

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM tiangolo/uwsgi-nginx-flask:python2.7

COPY ./ ./

EXPOSE 5000

RUN pip install -r requirements.txt

CMD gunicorn -c config.py wsgi
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ Commands:
clean Cleans up any temporary files (including...
```

## Deployment

If for any reason you need to use this tool on a server (maybe for your HR manager to easily use) you can build and deploy a docker image.

Build
`docker build -t slack-export-viewer .`

Run
`docker run -p 5000:5000 -d slack-export-viewer`

### Examples

```
Expand Down
3 changes: 3 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# config for gunicorn server

bind = '0.0.0.0:5000'
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ click
flask
markdown2
emoji
gunicorn
95 changes: 73 additions & 22 deletions slackviewer/app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import flask
from flask import request, redirect, url_for, flash
from slackviewer.archive import extract_archive
from slackviewer.reader import Reader


app = flask.Flask(
Expand All @@ -7,14 +10,19 @@
static_folder="static"
)

reader = Reader()

# these functions only fire when the route is navigated to
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need this comment?


@app.route("/channel/<name>/")
def channel_name(name):
messages = flask._app_ctx_stack.channels[name]
channels = list(flask._app_ctx_stack.channels.keys())
groups = list(flask._app_ctx_stack.groups.keys())
dm_users = list(flask._app_ctx_stack.dm_users)
mpim_users = list(flask._app_ctx_stack.mpim_users)
if not hasattr(reader, 'channels'):
return flask.render_template("404.html")
messages = reader.channels[name]
channels = list(reader.channels.keys())
groups = list(reader.groups.keys())
dm_users = list(reader.dm_users)
mpim_users = list(reader.mpim_users)

return flask.render_template("viewer.html", messages=messages,
name=name.format(name=name),
Expand All @@ -26,11 +34,13 @@ def channel_name(name):

@app.route("/group/<name>/")
def group_name(name):
messages = flask._app_ctx_stack.groups[name]
channels = list(flask._app_ctx_stack.channels.keys())
groups = list(flask._app_ctx_stack.groups.keys())
dm_users = list(flask._app_ctx_stack.dm_users)
mpim_users = list(flask._app_ctx_stack.mpim_users)
if not hasattr(reader, 'channels'):
return flask.render_template("404.html")
messages = reader.groups[name]
channels = list(reader.channels.keys())
groups = list(reader.groups.keys())
dm_users = list(reader.dm_users)
mpim_users = list(reader.mpim_users)

return flask.render_template("viewer.html", messages=messages,
name=name.format(name=name),
Expand All @@ -42,11 +52,13 @@ def group_name(name):

@app.route("/dm/<id>/")
def dm_id(id):
messages = flask._app_ctx_stack.dms[id]
channels = list(flask._app_ctx_stack.channels.keys())
groups = list(flask._app_ctx_stack.groups.keys())
dm_users = list(flask._app_ctx_stack.dm_users)
mpim_users = list(flask._app_ctx_stack.mpim_users)
if not hasattr(reader, 'channels'):
return flask.render_template("404.html")
messages = reader.dms[id]
channels = list(reader.channels.keys())
groups = list(reader.groups.keys())
dm_users = list(reader.dm_users)
mpim_users = list(reader.mpim_users)

return flask.render_template("viewer.html", messages=messages,
id=id.format(id=id),
Expand All @@ -58,11 +70,13 @@ def dm_id(id):

@app.route("/mpim/<name>/")
def mpim_name(name):
messages = flask._app_ctx_stack.mpims[name]
channels = list(flask._app_ctx_stack.channels.keys())
groups = list(flask._app_ctx_stack.groups.keys())
dm_users = list(flask._app_ctx_stack.dm_users)
mpim_users = list(flask._app_ctx_stack.mpim_users)
if reader.channels is None:
return flask.render_template("404.html")
messages = reader.mpims[name]
channels = list(reader.channels.keys())
groups = list(reader.groups.keys())
dm_users = list(reader.dm_users)
mpim_users = list(reader.mpim_users)

return flask.render_template("viewer.html", messages=messages,
name=name.format(name=name),
Expand All @@ -72,9 +86,46 @@ def mpim_name(name):
mpim_users=mpim_users)


@app.route("/")
ALLOWED_EXTENSIONS = set(["zip"])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constants like this should be at the top of the file


def allowed_file(filename):
return "." in filename and \
filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two newlines between all module-level functions

@app.route("/", methods=["GET", "POST"])
def upload():
print('upload')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the request I have general for these print statements is, can you just clean them up a bit?

if request.method == "POST":
# check if the post request has the file part
print(request.files)
if "archive_file" not in request.files:
print('archive_file not in request.file')
flash("No file part")
return redirect(request.url)
file = request.files["archive_file"]
# if user does not select file, browser also
# submit a empty part without filename
if file.filename == "":
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if not file.filename works too

print("file name is empty")
flash("No selected file")
return redirect(request.url)
if file and allowed_file(file.filename):
print("made it and it should extract the archive here")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess there isn't great logging set up already, so print is fine, but at the same time, I want to avoid having "made it" etc. logged :P
Perhaps "Extracting archive..." is sufficient.

filename = file.filename
archive_path = extract_archive(filename)
reader.set_path(archive_path)
reader.get_all_messages()
return redirect(url_for("index"))

reader.reset()
return flask.render_template("upload.html", reader=reader)


@app.route("/channel/")
def index():
channels = list(flask._app_ctx_stack.channels.keys())
if not hasattr(reader, 'channels'):
return flask.render_template("404.html")
channels = list(reader.channels.keys())
if "general" in channels:
return channel_name("general")
else:
Expand Down
18 changes: 5 additions & 13 deletions slackviewer/main.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import webbrowser

import click
import flask

from slackviewer.app import app
from slackviewer.app import app, reader
from slackviewer.archive import extract_archive
from slackviewer.reader import Reader
from slackviewer.utils.click import envvar, flag_ennvar


Expand All @@ -14,17 +12,11 @@ def configure_app(app, archive, debug):
if app.debug:
print("WARNING: DEBUG MODE IS ENABLED!")
app.config["PROPAGATE_EXCEPTIONS"] = True
app.config["UPLOAD_FOLDER"] = "archives"

path = extract_archive(archive)
reader = Reader(path)

top = flask._app_ctx_stack
top.channels = reader.compile_channels()
top.groups = reader.compile_groups()
top.dms = reader.compile_dm_messages()
top.dm_users = reader.compile_dm_users()
top.mpims = reader.compile_mpim_messages()
top.mpim_users = reader.compile_mpim_users()
reader.set_path(path)
reader.get_all_messages()


@click.command()
Expand All @@ -50,7 +42,7 @@ def main(port, archive, ip, no_browser, test, debug):
configure_app(app, archive, debug)

if not no_browser and not test:
webbrowser.open("http://{}:{}".format(ip, port))
webbrowser.open("http://{}:{}/channel".format(ip, port))

if not test:
app.run(
Expand Down
51 changes: 40 additions & 11 deletions slackviewer/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,59 @@ class Reader(object):
Reader object will read all of the archives' data from the json files
"""

def __init__(self, PATH):
self._PATH = PATH
# TODO: Make sure this works
with io.open(os.path.join(self._PATH, "users.json"), encoding="utf8") as f:
self.__USER_DATA = {u["id"]: u for u in json.load(f)}
def __init__(self, PATH=''):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: PATH should be path

if not PATH == '':
self._PATH = PATH
with io.open(os.path.join(self._PATH, "users.json"), encoding="utf8") as f:
self.__USER_DATA = {u["id"]: u for u in json.load(f)}


##################
# Public Methods #
##################

def reset(self):
self.channels = None
self.groups = None
self.dms = None
self.dm_users = None
self.mpims = None
self.mpim_users = None
self._PATH = ''
self.__USER_DATA = None

def get_all_messages(self):
"""
This method is used to call all of the compile methods at once.
"""
self.compile_channels()
self.compile_groups()
self.compile_dm_messages()
self.compile_dm_users()
self.compile_mpim_messages()
self.compile_mpim_users()

def set_path(self, path):
"""
Sets the _PATH and readers the users json file to get the user data
"""
self._PATH = path
with io.open(os.path.join(self._PATH, "users.json"), encoding="utf8") as f:
self.__USER_DATA = {u["id"]: u for u in json.load(f)}

def compile_channels(self):

channel_data = self._read_from_json("channels.json")
channel_names = [c["name"] for c in channel_data.values()]

return self._create_messages(channel_names, channel_data)
self.channels = self._create_messages(channel_names, channel_data)

def compile_groups(self):

group_data = self._read_from_json("groups.json")
group_names = [c["name"] for c in group_data.values()]

return self._create_messages(group_names, group_data)
self.groups = self._create_messages(group_names, group_data)

def compile_dm_messages(self):
# Gets list of dm objects with dm ID and array of members ids
Expand All @@ -42,7 +71,7 @@ def compile_dm_messages(self):

# True is passed here to let the create messages function know that
# it is dm data being passed to it
return self._create_messages(dm_ids, dm_data, True)
self.dms = self._create_messages(dm_ids, dm_data, True)

def compile_dm_users(self):
"""
Expand All @@ -68,15 +97,15 @@ def compile_dm_users(self):
dm_members = {"id": dm["id"], "users": [self.__USER_DATA[m] for m in dm["members"]]}
all_dms_users.append(dm_members)

return all_dms_users
self.dm_users = all_dms_users


def compile_mpim_messages(self):

mpim_data = self._read_from_json("mpims.json")
mpim_names = [c["name"] for c in mpim_data.values()]

return self._create_messages(mpim_names, mpim_data)
self.mpims = self._create_messages(mpim_names, mpim_data)

def compile_mpim_users(self):
"""
Expand All @@ -100,7 +129,7 @@ def compile_mpim_users(self):
mpim_members = {"name": mpim["name"], "users": [self.__USER_DATA[m] for m in mpim["members"]]}
all_mpim_users.append(mpim_members)

return all_mpim_users
self.mpim_users = all_mpim_users


###################
Expand Down
61 changes: 61 additions & 0 deletions slackviewer/static/404.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
@import url('https://fonts.googleapis.com/css?family=Lato:400,900');

* {
font-family: 'Lato', sans-serif;
}

body {
padding: 0;
margin: 0;
height: 100vh;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #f5f5f5
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mismatched indentation in this file bothers me, please use an auto formatter of some kind in your editor to clean it up. Thanks. :)

}

.container {
padding: 20px;
width: 350px;
background-color: white;
box-shadow: 0 0 15px 0 rgba(0,0,0,0.05);
}

.container h1 {
margin: 4px;
}

.container h5 {
font-weight: 400;
color: #c8c8c8;
margin-top: 0;
}

.container form {
margin: 60px 30px 10px 0;
font-size: 14px;
}

.container form .submit {
background-color: #3f46ad;
color: #fff;
border-radius: 3px;
text-transform: uppercase;
font-size: 14px;
display: block;
margin-top: 16px;
padding: 11px 30px 12px;
border: none;

}

.container form .submit:hover {
cursor: pointer;
background-color: #393f9e;
}

.container form .submit:active, :focus {
outline: none
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading