2017-09-04 22:04:03 +02:00
|
|
|
from flask import render_template, url_for, redirect, request, g,\
|
2017-09-16 18:25:20 +02:00
|
|
|
make_response
|
2017-08-31 18:59:09 +02:00
|
|
|
from datetime import datetime, timedelta, timezone
|
2017-09-20 23:02:36 +02:00
|
|
|
import libforget.twitter
|
|
|
|
import libforget.mastodon
|
2021-11-09 10:07:56 +01:00
|
|
|
import libforget.misskey
|
2017-09-20 23:02:36 +02:00
|
|
|
from libforget.auth import require_auth, csrf,\
|
2017-09-16 12:22:17 +02:00
|
|
|
get_viewer
|
2021-11-10 07:51:38 +01:00
|
|
|
from libforget.session import make_session
|
2021-11-10 12:33:55 +01:00
|
|
|
from model import Session, TwitterArchive, MastodonApp, MisskeyApp
|
2017-09-24 23:05:41 +02:00
|
|
|
from app import app, db, sentry, imgproxy
|
2017-07-30 13:53:14 +02:00
|
|
|
import tasks
|
2017-08-07 15:35:15 +02:00
|
|
|
from zipfile import BadZipFile
|
2017-08-09 11:32:32 +02:00
|
|
|
from twitter import TwitterError
|
2021-11-09 23:05:57 +01:00
|
|
|
from urllib.parse import urlparse
|
2017-08-09 14:27:39 +02:00
|
|
|
from urllib.error import URLError
|
2017-09-20 23:02:36 +02:00
|
|
|
import libforget.version
|
|
|
|
import libforget.settings
|
|
|
|
import libforget.json
|
2017-09-08 00:38:54 +02:00
|
|
|
import re
|
2017-07-25 09:52:24 +02:00
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-07-25 09:52:24 +02:00
|
|
|
@app.route('/')
|
2017-07-25 23:05:46 +02:00
|
|
|
def index():
|
2017-09-16 18:25:20 +02:00
|
|
|
viewer = get_viewer()
|
|
|
|
if viewer:
|
2017-08-29 14:46:32 +02:00
|
|
|
return render_template(
|
|
|
|
'logged_in.html',
|
2017-09-20 23:02:36 +02:00
|
|
|
scales=libforget.interval.SCALES,
|
2017-08-29 14:46:32 +02:00
|
|
|
tweet_archive_failed='tweet_archive_failed' in request.args,
|
2017-08-31 18:59:09 +02:00
|
|
|
settings_error='settings_error' in request.args,
|
2017-09-20 23:02:36 +02:00
|
|
|
viewer_json=libforget.json.account(viewer),
|
2017-08-31 18:59:09 +02:00
|
|
|
)
|
2017-07-28 00:08:20 +02:00
|
|
|
else:
|
2017-09-17 12:29:49 +02:00
|
|
|
return redirect(url_for('about'))
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-09-17 15:11:14 +02:00
|
|
|
|
2017-09-17 12:29:49 +02:00
|
|
|
@app.route('/about/')
|
|
|
|
def about():
|
2022-03-04 13:12:35 +01:00
|
|
|
blocklist = app.config.get('HIDDEN_INSTANCES', '').split()
|
|
|
|
mastodon_instances = libforget.mastodon.suggested_instances(blocklist=blocklist)
|
|
|
|
misskey_instances = libforget.misskey.suggested_instances(blocklist=blocklist)
|
2017-09-17 12:29:49 +02:00
|
|
|
return render_template(
|
|
|
|
'about.html',
|
2021-11-11 10:44:06 +01:00
|
|
|
mastodon_instances=mastodon_instances,
|
|
|
|
misskey_instances=misskey_instances,
|
2017-09-17 12:29:49 +02:00
|
|
|
twitter_login_error='twitter_login_error' in request.args)
|
2017-07-25 09:52:24 +02:00
|
|
|
|
2017-09-17 15:11:14 +02:00
|
|
|
|
2017-09-20 23:28:12 +02:00
|
|
|
@app.route('/about/privacy')
|
|
|
|
def privacy():
|
|
|
|
return render_template('privacy.html')
|
|
|
|
|
|
|
|
|
2017-07-25 23:05:46 +02:00
|
|
|
@app.route('/login/twitter')
|
2017-07-27 00:35:53 +02:00
|
|
|
def twitter_login_step1():
|
2017-08-08 16:18:39 +02:00
|
|
|
try:
|
2017-09-20 23:02:36 +02:00
|
|
|
return redirect(libforget.twitter.get_login_url(
|
2017-08-29 14:46:32 +02:00
|
|
|
callback=url_for('twitter_login_step2', _external=True),
|
2017-08-08 16:18:39 +02:00
|
|
|
**app.config.get_namespace("TWITTER_")
|
|
|
|
))
|
|
|
|
except (TwitterError, URLError):
|
2017-08-29 16:41:11 +02:00
|
|
|
if sentry:
|
|
|
|
sentry.captureException()
|
2017-08-29 14:46:32 +02:00
|
|
|
return redirect(
|
2017-09-17 12:29:49 +02:00
|
|
|
url_for('about', twitter_login_error='', _anchor='log_in'))
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-07-27 00:35:53 +02:00
|
|
|
|
2017-09-04 22:04:03 +02:00
|
|
|
def login(account_id):
|
|
|
|
session = Session(account_id=account_id)
|
|
|
|
db.session.add(session)
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
session.account.dormant = False
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
tasks.fetch_acc.s(account_id).apply_async(routing_key='high')
|
|
|
|
|
|
|
|
return session
|
|
|
|
|
|
|
|
|
2017-07-27 00:35:53 +02:00
|
|
|
@app.route('/login/twitter/callback')
|
|
|
|
def twitter_login_step2():
|
2017-08-08 16:18:39 +02:00
|
|
|
try:
|
2017-11-02 15:44:31 +01:00
|
|
|
oauth_token = request.args.get('oauth_token', '')
|
|
|
|
oauth_verifier = request.args.get('oauth_verifier', '')
|
2017-09-20 23:02:36 +02:00
|
|
|
token = libforget.twitter.receive_verifier(
|
2017-08-29 14:46:32 +02:00
|
|
|
oauth_token, oauth_verifier,
|
|
|
|
**app.config.get_namespace("TWITTER_"))
|
2017-07-30 13:53:14 +02:00
|
|
|
|
2017-09-04 22:04:03 +02:00
|
|
|
session = login(token.account_id)
|
2017-07-30 13:53:14 +02:00
|
|
|
|
2017-09-04 22:04:03 +02:00
|
|
|
g.viewer = session
|
|
|
|
return redirect(url_for('index'))
|
2017-11-02 15:44:31 +01:00
|
|
|
except Exception:
|
2017-08-29 16:41:11 +02:00
|
|
|
if sentry:
|
|
|
|
sentry.captureException()
|
2017-08-29 14:46:32 +02:00
|
|
|
return redirect(
|
2017-09-17 12:29:49 +02:00
|
|
|
url_for('about', twitter_login_error='', _anchor='log_in'))
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-07-25 23:05:46 +02:00
|
|
|
|
2017-07-31 04:51:11 +02:00
|
|
|
@app.route('/upload_tweet_archive', methods=('POST',))
|
|
|
|
def upload_tweet_archive():
|
2019-09-13 03:33:39 +02:00
|
|
|
return 403, 'Tweet archive support is temporarily disabled, see banner on the front page.'
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-07-31 04:51:11 +02:00
|
|
|
|
2017-08-03 16:05:28 +02:00
|
|
|
@app.route('/settings', methods=('POST',))
|
2017-08-25 10:50:11 +02:00
|
|
|
@csrf
|
2017-07-31 04:51:11 +02:00
|
|
|
@require_auth
|
|
|
|
def settings():
|
2017-08-12 12:26:35 +02:00
|
|
|
viewer = get_viewer()
|
|
|
|
try:
|
2017-09-20 23:02:36 +02:00
|
|
|
for attr in libforget.settings.attrs:
|
2017-08-07 16:26:25 +02:00
|
|
|
if attr in request.form:
|
2017-08-12 12:26:35 +02:00
|
|
|
setattr(viewer, attr, request.form[attr])
|
|
|
|
db.session.commit()
|
|
|
|
except ValueError:
|
2017-08-29 16:41:11 +02:00
|
|
|
if sentry:
|
|
|
|
sentry.captureException()
|
2017-08-12 12:26:35 +02:00
|
|
|
return 400
|
2017-07-31 18:29:09 +02:00
|
|
|
|
2017-08-03 16:05:28 +02:00
|
|
|
return redirect(url_for('index', settings_saved=''))
|
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-08-03 16:05:28 +02:00
|
|
|
@app.route('/disable', methods=('POST',))
|
2017-08-25 10:50:11 +02:00
|
|
|
@csrf
|
2017-08-03 16:05:28 +02:00
|
|
|
@require_auth
|
|
|
|
def disable():
|
|
|
|
g.viewer.account.policy_enabled = False
|
|
|
|
db.session.commit()
|
2017-07-31 18:29:09 +02:00
|
|
|
|
2017-08-03 16:05:28 +02:00
|
|
|
return redirect(url_for('index'))
|
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-08-03 16:05:28 +02:00
|
|
|
@app.route('/enable', methods=('POST',))
|
2017-08-25 10:50:11 +02:00
|
|
|
@csrf
|
2017-08-03 16:05:28 +02:00
|
|
|
@require_auth
|
|
|
|
def enable():
|
2017-08-29 14:46:32 +02:00
|
|
|
if 'confirm' not in request.form and not g.viewer.account.policy_enabled:
|
2017-08-03 21:37:00 +02:00
|
|
|
if g.viewer.account.policy_delete_every == timedelta(0):
|
|
|
|
approx = g.viewer.account.estimate_eligible_for_delete()
|
2017-08-29 14:46:32 +02:00
|
|
|
return render_template(
|
|
|
|
'warn.html',
|
|
|
|
message=f"""
|
|
|
|
You've set the time between deleting posts to 0. Every post
|
|
|
|
that matches your expiration rules will be deleted within
|
|
|
|
minutes.
|
|
|
|
{ ("That's about " + str(approx) + " posts.") if approx > 0
|
|
|
|
else "" }
|
|
|
|
Go ahead?
|
|
|
|
""")
|
2017-09-03 01:23:54 +02:00
|
|
|
if (not g.viewer.account.last_delete or
|
|
|
|
g.viewer.account.last_delete <
|
2017-08-31 18:59:09 +02:00
|
|
|
datetime.now(timezone.utc) - timedelta(days=365)):
|
2017-08-29 14:46:32 +02:00
|
|
|
return render_template(
|
|
|
|
'warn.html',
|
|
|
|
message="""
|
|
|
|
Once you enable Forget, posts that match your
|
|
|
|
expiration rules will be deleted <b>permanently</b>.
|
|
|
|
We can't bring them back. Make sure that you won't
|
|
|
|
miss them.
|
|
|
|
""")
|
2017-08-03 21:37:00 +02:00
|
|
|
|
2017-08-03 16:05:28 +02:00
|
|
|
g.viewer.account.policy_enabled = True
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
return redirect(url_for('index'))
|
2017-07-31 18:29:09 +02:00
|
|
|
|
|
|
|
|
2017-07-25 23:05:46 +02:00
|
|
|
@app.route('/logout')
|
2017-07-31 04:51:11 +02:00
|
|
|
@require_auth
|
2017-07-25 23:05:46 +02:00
|
|
|
def logout():
|
2017-07-27 01:17:28 +02:00
|
|
|
if(g.viewer):
|
|
|
|
db.session.delete(g.viewer)
|
|
|
|
db.session.commit()
|
2017-07-27 14:19:40 +02:00
|
|
|
g.viewer = None
|
2017-09-17 12:29:49 +02:00
|
|
|
return redirect(url_for('about'))
|
2017-08-11 22:20:24 +02:00
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2021-11-09 23:05:57 +01:00
|
|
|
def domain_from_url(url):
|
2021-11-09 23:43:09 +01:00
|
|
|
return urlparse(url).netloc.lower() or urlparse("//"+url).netloc.lower()
|
2021-11-09 23:05:57 +01:00
|
|
|
|
2017-08-18 22:31:30 +02:00
|
|
|
@app.route('/login/mastodon', methods=('GET', 'POST'))
|
2017-08-23 15:14:24 +02:00
|
|
|
def mastodon_login_step1(instance=None):
|
2017-08-23 11:42:32 +02:00
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
instance_url = (request.args.get('instance_url', None)
|
|
|
|
or request.form.get('instance_url', None))
|
2017-08-18 22:31:30 +02:00
|
|
|
|
2017-08-23 15:14:24 +02:00
|
|
|
if not instance_url:
|
2022-03-04 13:12:35 +01:00
|
|
|
blocklist = app.config.get('HIDDEN_INSTANCES', '').split()
|
2018-05-07 20:27:02 +02:00
|
|
|
instances = libforget.mastodon.suggested_instances(
|
|
|
|
limit=30,
|
2022-03-04 13:12:35 +01:00
|
|
|
min_popularity=1,
|
|
|
|
blocklist=blocklist,
|
2018-05-07 20:27:02 +02:00
|
|
|
)
|
2017-08-29 14:46:32 +02:00
|
|
|
return render_template(
|
|
|
|
'mastodon_login.html', instances=instances,
|
|
|
|
address_error=request.method == 'POST',
|
|
|
|
generic_error='error' in request.args
|
2017-08-23 15:14:24 +02:00
|
|
|
)
|
2017-08-18 22:31:30 +02:00
|
|
|
|
2021-11-09 23:05:57 +01:00
|
|
|
instance_url = domain_from_url(instance_url)
|
2017-08-18 22:31:30 +02:00
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
callback = url_for('mastodon_login_step2',
|
2017-08-29 16:45:00 +02:00
|
|
|
instance_url=instance_url, _external=True)
|
2017-08-18 22:31:30 +02:00
|
|
|
|
2017-08-29 16:41:11 +02:00
|
|
|
try:
|
2022-03-04 13:28:38 +01:00
|
|
|
mastoapp = libforget.mastodon.get_or_create_app(
|
2017-08-29 16:41:11 +02:00
|
|
|
instance_url,
|
|
|
|
callback,
|
|
|
|
url_for('index', _external=True))
|
2022-03-04 13:28:38 +01:00
|
|
|
db.session.merge(mastoapp)
|
2017-08-29 16:41:11 +02:00
|
|
|
|
|
|
|
db.session.commit()
|
2017-08-18 22:31:30 +02:00
|
|
|
|
2022-03-04 13:28:38 +01:00
|
|
|
return redirect(libforget.mastodon.login_url(mastoapp, callback))
|
2017-08-18 22:31:30 +02:00
|
|
|
|
2017-08-29 16:41:11 +02:00
|
|
|
except Exception:
|
|
|
|
if sentry:
|
|
|
|
sentry.captureException()
|
|
|
|
return redirect(url_for('mastodon_login_step1', error=True))
|
2017-08-18 22:31:30 +02:00
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
|
2017-08-29 16:45:00 +02:00
|
|
|
@app.route('/login/mastodon/callback/<instance_url>')
|
2017-08-29 14:46:32 +02:00
|
|
|
def mastodon_login_step2(instance_url):
|
2017-08-18 22:31:30 +02:00
|
|
|
code = request.args.get('code', None)
|
2022-03-04 13:28:38 +01:00
|
|
|
mastoapp = MastodonApp.query.get(instance_url)
|
|
|
|
if not code or not mastoapp:
|
2017-08-29 16:31:43 +02:00
|
|
|
return redirect(url_for('mastodon_login_step1', error=True))
|
2017-08-18 22:31:30 +02:00
|
|
|
|
2017-08-29 14:46:32 +02:00
|
|
|
callback = url_for('mastodon_login_step2',
|
2017-08-29 16:45:00 +02:00
|
|
|
instance_url=instance_url, _external=True)
|
2017-08-18 22:31:30 +02:00
|
|
|
|
2022-03-04 13:28:38 +01:00
|
|
|
token = libforget.mastodon.receive_code(code, mastoapp, callback)
|
2017-08-19 14:32:31 +02:00
|
|
|
account = token.account
|
2017-08-18 22:31:30 +02:00
|
|
|
|
2017-09-04 22:04:03 +02:00
|
|
|
session = login(account.id)
|
2017-08-23 11:42:32 +02:00
|
|
|
|
2017-08-18 22:31:30 +02:00
|
|
|
db.session.commit()
|
|
|
|
|
2017-09-04 22:04:03 +02:00
|
|
|
g.viewer = session
|
2018-05-07 23:50:37 +02:00
|
|
|
|
2019-03-15 21:09:22 +01:00
|
|
|
resp = redirect(url_for('index', _anchor='bump_instance'))
|
2018-05-07 23:50:37 +02:00
|
|
|
return resp
|
2017-08-31 20:46:38 +02:00
|
|
|
|
2017-09-03 00:51:54 +02:00
|
|
|
|
2021-11-09 10:07:56 +01:00
|
|
|
@app.route('/login/misskey', methods=('GET', 'POST'))
|
|
|
|
def misskey_login(instance=None):
|
|
|
|
instance_url = (request.args.get('instance_url', None)
|
|
|
|
or request.form.get('instance_url', None))
|
2022-03-04 13:12:35 +01:00
|
|
|
|
2021-11-09 10:07:56 +01:00
|
|
|
if not instance_url:
|
2022-03-04 13:12:35 +01:00
|
|
|
blocklist = app.config.get('HIDDEN_INSTANCES', '').split()
|
2021-11-09 10:07:56 +01:00
|
|
|
instances = libforget.misskey.suggested_instances(
|
|
|
|
limit = 30,
|
2022-03-04 13:12:35 +01:00
|
|
|
min_popularity = 1,
|
|
|
|
blocklist=blocklist,
|
2021-11-09 10:07:56 +01:00
|
|
|
)
|
|
|
|
return render_template(
|
2021-11-09 23:07:46 +01:00
|
|
|
'misskey_login.html', instances=instances,
|
2021-11-09 10:07:56 +01:00
|
|
|
address_error=request.method == 'POST',
|
|
|
|
generic_error='error' in request.args
|
|
|
|
)
|
|
|
|
|
2021-11-09 23:05:57 +01:00
|
|
|
instance_url = domain_from_url(instance_url)
|
2022-03-04 13:12:35 +01:00
|
|
|
|
2021-11-09 10:07:56 +01:00
|
|
|
callback = url_for('misskey_callback',
|
|
|
|
instance_url=instance_url, _external=True)
|
|
|
|
|
|
|
|
try:
|
2021-11-10 07:51:38 +01:00
|
|
|
session = make_session()
|
2022-03-04 13:28:38 +01:00
|
|
|
mkapp = libforget.misskey.get_or_create_app(
|
2021-11-09 10:07:56 +01:00
|
|
|
instance_url,
|
2021-11-10 12:33:55 +01:00
|
|
|
callback,
|
2021-11-10 07:51:38 +01:00
|
|
|
url_for('index', _external=True),
|
|
|
|
session)
|
2022-03-04 13:28:38 +01:00
|
|
|
db.session.merge(mkapp)
|
2021-11-09 10:07:56 +01:00
|
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
2022-03-04 13:28:38 +01:00
|
|
|
return redirect(libforget.misskey.login_url(mkapp, callback, session))
|
2021-11-09 10:07:56 +01:00
|
|
|
|
|
|
|
except Exception:
|
|
|
|
if sentry:
|
|
|
|
sentry.captureException()
|
|
|
|
return redirect(url_for('misskey_login', error=True))
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/login/misskey/callback/<instance_url>')
|
|
|
|
def misskey_callback(instance_url):
|
|
|
|
# legacy auth and miauth use different parameter names
|
|
|
|
token = request.args.get('token', None) or request.args.get('session', None)
|
2022-03-04 13:28:38 +01:00
|
|
|
mkapp = MisskeyApp.query.get(instance_url)
|
|
|
|
if not token or not mkapp:
|
2021-11-09 10:07:56 +01:00
|
|
|
return redirect(url_for('misskey_login', error=True))
|
|
|
|
|
2022-03-04 13:28:38 +01:00
|
|
|
token = libforget.misskey.receive_token(token, mkapp)
|
2021-11-09 10:07:56 +01:00
|
|
|
account = token.account
|
|
|
|
|
|
|
|
session = login(account.id)
|
|
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
g.viewer = session
|
|
|
|
|
|
|
|
resp = redirect(url_for('index', _anchor='bump_instance'))
|
|
|
|
return resp
|
|
|
|
|
|
|
|
|
2017-08-31 20:46:38 +02:00
|
|
|
@app.route('/sentry/setup.js')
|
|
|
|
def sentry_setup():
|
|
|
|
client_dsn = app.config.get('SENTRY_DSN').split('@')
|
|
|
|
client_dsn[:1] = client_dsn[0].split(':')
|
|
|
|
client_dsn = ':'.join(client_dsn[0:2]) + '@' + client_dsn[3]
|
|
|
|
resp = make_response(render_template(
|
2017-09-03 00:51:54 +02:00
|
|
|
'sentry.js', sentry_dsn=client_dsn))
|
2017-08-31 20:46:38 +02:00
|
|
|
resp.headers.set('content-type', 'text/javascript')
|
2017-12-19 16:30:54 +01:00
|
|
|
resp.headers.set('cache-control', 'public; max-age=3600')
|
2017-08-31 20:46:38 +02:00
|
|
|
return resp
|
2017-09-03 00:51:54 +02:00
|
|
|
|
|
|
|
|
|
|
|
@app.route('/dismiss', methods={'POST'})
|
|
|
|
@csrf
|
|
|
|
@require_auth
|
|
|
|
def dismiss():
|
|
|
|
get_viewer().reason = None
|
|
|
|
db.session.commit()
|
|
|
|
return redirect(url_for('index'))
|
2017-09-16 13:58:02 +02:00
|
|
|
|
|
|
|
|
2017-09-16 18:25:20 +02:00
|
|
|
@app.route('/avatar/<identifier>')
|
|
|
|
def avatar(identifier):
|
|
|
|
return imgproxy.respond(identifier)
|