From a5bba8e8ca8044a426af51ef9f05a793b8bfedbf Mon Sep 17 00:00:00 2001 From: Daniel Muckerman Date: Sat, 31 Oct 2020 19:55:57 -0400 Subject: [PATCH] Initial commit --- .gitignore | 18 ++++ Dockerfile | 13 +++ app.py | 218 ++++++++++++++++++++++++++++++++++++++++++ blurb.txt | 4 + bookmarklet.txt | 1 + pocket/readitlater.db | Bin 0 -> 81920 bytes requirements.txt | 8 ++ static/style.css | 197 ++++++++++++++++++++++++++++++++++++++ templates/article.j2 | 60 ++++++++++++ templates/close.j2 | 3 + templates/list.j2 | 93 ++++++++++++++++++ templates/login.j2 | 37 +++++++ templates/save.j2 | 118 +++++++++++++++++++++++ utils.py | 12 +++ 14 files changed, 782 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 app.py create mode 100644 blurb.txt create mode 100644 bookmarklet.txt create mode 100644 pocket/readitlater.db create mode 100644 requirements.txt create mode 100644 static/style.css create mode 100644 templates/article.j2 create mode 100644 templates/close.j2 create mode 100644 templates/list.j2 create mode 100644 templates/login.j2 create mode 100644 templates/save.j2 create mode 100644 utils.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b235d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +.envrc +.DS_Store +.vscode/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a6d698f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.7 + +LABEL maintainer="Dan Muckerman " + +WORKDIR /project +ADD . /project +RUN rm /project/.envrc +RUN rm -rf /project/env + +RUN apt-get update && apt-get install -y python3-dev libldap2-dev libsasl2-dev libssl-dev + +RUN pip install -r requirements.txt +CMD ["flask","run","--host=0.0.0.0"] \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..74553b1 --- /dev/null +++ b/app.py @@ -0,0 +1,218 @@ +from utils import clean_articles +import ldap as l +from ldap3 import Server, Connection, ALL, MODIFY_REPLACE +from flask import Flask, g, request, session, redirect, url_for, render_template +from flask_simpleldap import LDAP +from flask_bootstrap import Bootstrap +from readability import Document +from readabilipy import simple_json_from_html_string +import os +import sqlite3 +import requests +from requests.api import head +from utils import clean_articles + +app = Flask(__name__) +Bootstrap(app) +app.secret_key = 'asdf' +app.debug = True + +# Base +app.config['LDAP_REALM_NAME'] = 'OpenLDAP Authentication' +app.config['LDAP_HOST'] = os.environ.get('LDAP_HOST') +app.config['LDAP_BASE_DN'] = os.environ.get('LDAP_BASE_DN') +app.config['LDAP_USERNAME'] = os.environ.get('LDAP_USERNAME') +app.config['LDAP_PASSWORD'] = os.environ.get('LDAP_PASSWORD') + +# OpenLDAP +app.config['LDAP_OBJECTS_DN'] = 'dn' +app.config['LDAP_OPENLDAP'] = True +app.config['LDAP_USER_OBJECT_FILTER'] = '(&(objectclass=posixAccount)(uid=%s))' + +short_domain = os.environ.get('SHORT_DOMAIN') + +ldap = LDAP(app) + +server = Server(app.config['LDAP_HOST']) +conn = Connection(server, app.config['LDAP_USERNAME'], app.config['LDAP_PASSWORD'], auto_bind=True) + +@app.before_request +def before_request(): + g.user = None + if 'user_id' in session: + # This is where you'd query your database to get the user info. + g.user = {} + + +@app.route('/') +@ldap.login_required +def index(): + user_dict = ldap.get_object_details(session['user_id']) + + if 'user_id' in session: + user = {'dn': 'cn={},cn=usergroup,ou=users,dc=technicalincompetence,dc=club'.format(user_dict['cn'][0].decode('ascii')), + 'firstName': user_dict['givenName'][0].decode('ascii'), + 'lastName': user_dict['sn'][0].decode('ascii'), + 'email': user_dict['mail'][0].decode('ascii'), + 'userName': user_dict['uid'][0].decode('ascii'), + } + + conn = sqlite3.connect('pocket/readitlater.db') + c = conn.cursor() + + c.execute("SELECT article_id, url, title, byline FROM saved_articles INNER JOIN articles on saved_articles.article_id = articles.id WHERE user=? AND read=0 OR read IS NULL", (session['user_id'], )) + rows = c.fetchall() + + conn.commit() + conn.close() + + return render_template('list.j2', articles = clean_articles(rows)) + + +@app.route('/archived') +@ldap.login_required +def archived(): + conn = sqlite3.connect('pocket/readitlater.db') + c = conn.cursor() + + c.execute("SELECT article_id, url, title, byline FROM saved_articles INNER JOIN articles on saved_articles.article_id = articles.id WHERE user=? AND read=1", (session['user_id'], )) + rows = c.fetchall() + print(rows) + + conn.commit() + conn.close() + + return render_template('list.j2', articles = clean_articles(rows)) + + +@app.route('/save') +@ldap.login_required +def save(): + return render_template('save.j2') + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if g.user: + return redirect(url_for('index')) + if request.method == 'POST': + user = request.form['user'] + passwd = request.form['passwd'] + test = ldap.bind_user(user, passwd) + if test is None or passwd == '': + return render_template('login.j2', error='Invalid credentials') + else: + session['user_id'] = request.form['user'] + session['passwd'] = request.form['passwd'] + + if session['next']: + next = session['next'] + session['next'] = '' + return redirect(next) + return redirect('/') + return render_template('login.j2') + + +@ldap.login_required +@app.route('/article/') +def read_article(article_id): + conn = sqlite3.connect('pocket/readitlater.db') + c = conn.cursor() + + c.execute("SELECT * FROM articles where id=?", (article_id,)) + rows = c.fetchall() + conn.commit() + conn.close() + + if (len(rows) > 0): + return render_template('article.j2', article=rows[0]) + + return render_template('article.j2', article=()) + + +@ldap.login_required +@app.route('/add', methods=['GET', 'POST']) +def add_url(): + if not 'user_id' in session: + session['next'] = request.url + return redirect(url_for('login')) + if request.method == 'POST': + url = request.form['url'] + close = None + else: + url = request.args.get('url') + close = request.args.get('close') + conn = sqlite3.connect('pocket/readitlater.db') + c = conn.cursor() + + if url is not None and len(url) > 0: + headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'} + response = requests.get(url, headers=headers) + + article = simple_json_from_html_string(response.text, use_readability=True) + + c.execute("SELECT * FROM articles WHERE url=?", (url,)) + rows = c.fetchall() + + if (len(rows) == 0): + c.execute("INSERT INTO articles (url, content, title, byline) VALUES (?, ?, ?, ?)", (url, article['content'], article['title'], article['byline'])) + c.execute("SELECT * FROM articles WHERE url=?", (url,)) + rows = c.fetchall() + + article_id = rows[0][0] + + c.execute("SELECT * FROM saved_articles WHERE user=? AND article_id=?", (session['user_id'], article_id)) + rows = c.fetchall() + + if (len(rows) == 0): + c.execute("INSERT INTO saved_articles (user, article_id) VALUES (?, ?)", (session['user_id'], article_id)) + conn.commit() + conn.close() + + if close is not None and close == '1': + return render_template('close.j2') + return 'Saved' + conn.commit() + conn.close() + return 'Error' + +@ldap.login_required +@app.route('/delete/') +def delete_article(article_id): + conn = sqlite3.connect('pocket/readitlater.db') + c = conn.cursor() + + c.execute("DELETE FROM saved_articles WHERE user=? AND article_id=?", (session['user_id'], article_id)) + c.execute("SELECT * FROM saved_articles WHERE article_id=?", (article_id, )) + rows = c.fetchall() + + if (len(rows) == 0): + c.execute("DELETE FROM articles WHERE id=?", (article_id,)) + + conn.commit() + conn.close() + + return redirect(url_for('index')) + +@ldap.login_required +@app.route('/archive/') +def archive_article(article_id): + conn = sqlite3.connect('pocket/readitlater.db') + c = conn.cursor() + + c.execute("UPDATE saved_articles SET read=1 WHERE user=? AND article_id=?", (session['user_id'], article_id)) + + conn.commit() + conn.close() + + return redirect(url_for('index')) + + +@app.route('/logout') +def logout(): + session.pop('user_id', None) + return redirect(url_for('index')) + + +if __name__ == '__main__': + app.run() \ No newline at end of file diff --git a/blurb.txt b/blurb.txt new file mode 100644 index 0000000..785ff02 --- /dev/null +++ b/blurb.txt @@ -0,0 +1,4 @@ +Instapaper outdated +Pocket overrated +Long have we waited +Read TI Later _activated_ \ No newline at end of file diff --git a/bookmarklet.txt b/bookmarklet.txt new file mode 100644 index 0000000..e678410 --- /dev/null +++ b/bookmarklet.txt @@ -0,0 +1 @@ +javascript:(function(){var%20url%20=%20location.href;var%20otherWindow=window.open('about:blank','_blank');otherWindow.opener=null;otherWindow.location='http://127.0.0.1:5000/add?close=1&url='+encodeURIComponent(url);})(); \ No newline at end of file diff --git a/pocket/readitlater.db b/pocket/readitlater.db new file mode 100644 index 0000000000000000000000000000000000000000..52dcc42b9a098a7cd878a7fa7f4fc6b33c13be0a GIT binary patch literal 81920 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

#SOv$7HjqqsbQ>&p25U^)nK zCUhdQja>kKskXJ!X0RO_HxheJ5x4${Jm@`kOOe!3s#Z(&lG!S^N>;65)p!kKc4I8* zTkR6W+4AHigcNg1sMNO2ilwogKrj&MbdD1-{N&@m{48iP`vA{@d(~VYmo=j!(qRiy zYjuw0Si<%3WK?W{UVfhIa_qpTGH^UOAr+5}z!hW*nBcxgkJ@@ literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2e91653 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +Flask==1.1.2 +Flask-Bootstrap4==4.0.2 +Flask-Login==0.5.0 +Flask-SimpleLDAP==1.4.0 +python-ldap==3.2.0 +ldap3==2.7 +readability-lxml==0.8.1 +requests==2.24.0 \ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..04a4796 --- /dev/null +++ b/static/style.css @@ -0,0 +1,197 @@ +.wrapper { + display: flex; + width: 100%; + align-items: stretch; +} + +.toolbar-button { + padding-left: 2rem; + padding-top: .375rem; + line-height: 1.5; + color: white; +} + +.back-chevron { + line-height: 1.5 !important; + vertical-align: middle !important; + margin-bottom: 4px !important; +} + +#sidebar { + padding-left: 20px; + background-color: #ecedee; + height: 100vh; + padding-top: 20px; + border-right: 1px solid darkgray; +} + +#sidebar ul > li { + padding-bottom: 10px; +} + +#sidebar ul > li > a { + color: rgba(0,0,0,.5); +} + +#sidebar ul > li.active > a { + color: black; +} + +#content { + width: 100%; + padding: 20px; +} + +@media (prefers-color-scheme: dark) { + #sidebar { + padding-left: 20px; + background-color: #1a1d20; + height: 100vh; + padding-top: 20px; + border-right: 1px solid black; + } + + #sidebar ul > li { + padding-bottom: 10px; + } + + #sidebar ul > li > a { + color: rgba(255,255,255,.5); + } + + #sidebar ul > li.active > a { + color: white; + } + + body { + background-color: #111 !important; + color: #eee; + } + + .jumbotron { + background-color: #333 !important; + } + + .modal-content { + background-color: #111 !important; + color: #eee; + } + + .modal-header { + border-bottom: 1px solid #555 !important; + } + + .modal-header .close { + color: #eee !important; + text-shadow: 0 1px 0 #555 !important; + } + + .modal-footer { + border-top: 1px solid #555 !important; + } + + .bg-light { + background-color: #333 !important; + } + + .bg-white { + background-color: #000 !important; + } + + .bg-black { + background-color: #eee !important; + } + + .form-control { + display: block; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + padding: 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #dee2e6; + background-color: #000; + background-clip: padding-box; + border: 1px solid #6c757d; + border-radius: 0.25rem; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + } + + @media (prefers-reduced-motion: reduce) { + .form-control { + transition: none; + } + } + + .form-control::-ms-expand { + background-color: transparent; + border: 0; + } + + .form-control:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #dee2e6; + } + + .form-control:focus { + color: #dee2e6; + background-color: #191d21; + border-color: #b3d7ff; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); + } + + .form-control::-webkit-input-placeholder { + color: #6c757d; + opacity: 1; + } + + .form-control::-moz-placeholder { + color: #6c757d; + opacity: 1; + } + + .form-control::-ms-input-placeholder { + color: #6c757d; + opacity: 1; + } + + .form-control::placeholder { + color: #6c757d; + opacity: 1; + } + + .form-control:disabled, + .form-control[readonly] { + background-color: #343a40; + opacity: 1; + } + + .card { + background-color: #000; + border: 1px solid #6c757d; + } +} + +@media (max-width: 991.98px) { + #sidebar { + display: none !important; + } +} + +@media (min-width: 991.98px) { + .navbar-nav { + display: none !important; + } + + #sidebar { + min-width: 250px; + max-width: 250px; + display: block !important; + } + + #sidebar.active { + margin-left: -250px; + } +} \ No newline at end of file diff --git a/templates/article.j2 b/templates/article.j2 new file mode 100644 index 0000000..8643876 --- /dev/null +++ b/templates/article.j2 @@ -0,0 +1,60 @@ +{% extends "bootstrap/base.html" %} +{% block title %}Read TI Later{% endblock %} + +{% block styles %} +{{super()}} + +{% endblock %} + +{% block navbar %} +

+{% endblock %} + +{% block content %} +
+ +
+
+ {#
+ + +
+ +
+
+
#} +
+
+

{{ article[3] }}

+ View Original + {{ article[2] | safe}} +
+
+
+
+
+{% endblock %} + +{% block scripts %} +{{ super() }} + + +{% endblock %} \ No newline at end of file diff --git a/templates/close.j2 b/templates/close.j2 new file mode 100644 index 0000000..f40c222 --- /dev/null +++ b/templates/close.j2 @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/templates/list.j2 b/templates/list.j2 new file mode 100644 index 0000000..049cc7a --- /dev/null +++ b/templates/list.j2 @@ -0,0 +1,93 @@ +{% extends "bootstrap/base.html" %} +{% block title %}Read TI Later{% endblock %} + +{% block styles %} +{{super()}} + +{% endblock %} + +{% block navbar %} + +{% endblock %} + +{% block content %} +
+ + + + +
+
+
+
+ {% for article in articles|reverse %} +
+ {{ article[2] }} + {{ article[4] }} +
+ + Archive +      + Delete + +
+ {% endfor %} +
+
+
+
+
+{% endblock %} + +{% block scripts %} +{{ super() }} + + +{% endblock %} \ No newline at end of file diff --git a/templates/login.j2 b/templates/login.j2 new file mode 100644 index 0000000..e6919ce --- /dev/null +++ b/templates/login.j2 @@ -0,0 +1,37 @@ +{% extends "bootstrap/base.html" %} +{% block title %}Technical Incompetence Link Shortener{% endblock %} + +{% block styles %} +{{super()}} + +{% endblock %} + +{% block navbar %} + +{% endblock %} + +{% block content %} +
+
+

Sign in for our awesome service

+

Forgot your password? Too bad! We don't have emails working yet!

+
+
+ + {% if error is defined %} + + {% endif %} + +
+ +
+ +
+ +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/save.j2 b/templates/save.j2 new file mode 100644 index 0000000..ccb2990 --- /dev/null +++ b/templates/save.j2 @@ -0,0 +1,118 @@ +{% extends "bootstrap/base.html" %} +{% block title %}Read TI Later{% endblock %} + +{% block styles %} +{{super()}} + +{% endblock %} + +{% block navbar %} + +{% endblock %} + +{% block content %} +
+ + + + +
+ +
+
+
+ + +
+ +
+
+
+
+

Would you rather save on the go? Try our bookmarklet!

+ + javascript:(function(){var%20url%20=%20location.href;var%20otherWindow=window.open('about:blank','_blank');otherWindow.opener=null;otherWindow.location='{{ request.url_root }}add?close=1&url='+encodeURIComponent(url);})(); + +
+
+
+
+
+{% endblock %} + +{% block scripts %} +{{ super() }} + +{% endblock %} \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..7fb6d1e --- /dev/null +++ b/utils.py @@ -0,0 +1,12 @@ +from urllib.parse import urlparse + +def clean_articles(rows): + #article_id, url, title, byline + out = [] + + for row in rows: + parsed_uri = urlparse(row[1]) + result = '{uri.netloc}'.format(uri=parsed_uri) + out.append([row[0], row[1], row[2], row[3], result]) + + return out \ No newline at end of file