ghfjklghjdkflhgjfklhgjkdflshgjdflshgjkdflshgjdfklshgjkdflghjkdflhgjkldfshjgkldfhjgldhfjkgldhfjgklfdhsjgklhfjkslghjdfklsg
mastodon why
This commit is contained in:
parent
340eb711d5
commit
d3d93c3cef
|
@ -1,11 +1,11 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: sans-serif;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
body > section, body > header, body > footer {
|
||||
|
@ -54,6 +54,10 @@ input[type=number]{
|
|||
max-width: 8ch;
|
||||
}
|
||||
|
||||
input, select, button {
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.viewer img.avatar {
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
|
@ -182,5 +186,7 @@ footer a {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
form aside {
|
||||
font-style: italic;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import mastodon
|
||||
from mastodon import Mastodon
|
||||
from model import MastodonApp, Account, OAuthToken
|
||||
from requests import head
|
||||
from app import db
|
||||
|
||||
def get_or_create_app(instance_url, callback, website):
|
||||
instance_url = instance_url
|
||||
app = MastodonApp.query.get(instance_url)
|
||||
try:
|
||||
head('https://{}'.format(instance_url)).raise_for_status()
|
||||
proto = 'https'
|
||||
except Exception:
|
||||
head('http://{}'.format(instance_url)).raise_for_status()
|
||||
proto = 'http'
|
||||
|
||||
if not app:
|
||||
client_id, client_secret = Mastodon.create_app('forget',
|
||||
scopes=('read', 'write'),
|
||||
api_base_url='{}://{}'.format(proto, instance_url),
|
||||
redirect_uris=callback,
|
||||
website=website,
|
||||
)
|
||||
app = MastodonApp()
|
||||
app.instance = instance_url
|
||||
app.client_id = client_id
|
||||
app.client_secret = client_secret
|
||||
app.protocol = proto
|
||||
return app
|
||||
|
||||
def anonymous_api(app):
|
||||
return Mastodon(app.client_id,
|
||||
client_secret = app.client_secret,
|
||||
api_base_url='{}://{}'.format(app.protocol, app.instance),
|
||||
)
|
||||
|
||||
def login_url(app, callback):
|
||||
return anonymous_api(app).auth_request_url(
|
||||
redirect_uris=callback,
|
||||
scopes=('read', 'write',)
|
||||
)
|
||||
|
||||
def receive_code(code, app, callback):
|
||||
api = anonymous_api(app)
|
||||
access_token = api.log_in(
|
||||
code=code,
|
||||
scopes=('read', 'write'),
|
||||
redirect_uri=callback,
|
||||
)
|
||||
|
||||
remote_acc = api.account_verify_credentials()
|
||||
acc = Account(
|
||||
#id = 'mastodon:{}:{}'.format(app.instance, remote_acc['username']),
|
||||
mastodon_instance = app.instance,
|
||||
mastodon_id = remote_acc['username'],
|
||||
screen_name = remote_acc['username'],
|
||||
display_name = remote_acc['display_name'],
|
||||
avatar_url = remote_acc['avatar'],
|
||||
reported_post_count = remote_acc['statuses_count'],
|
||||
)
|
||||
token = OAuthToken(account = acc, token = access_token)
|
||||
db.session.merge(acc, token)
|
||||
|
||||
return acc
|
||||
|
||||
|
||||
def get_api_for_acc(account):
|
||||
app = MastodonApp.query.get(account.mastodon_instance)
|
||||
for token in account.tokens:
|
||||
api = Mastodon(app.client_id,
|
||||
client_secret = app.client_secret,
|
||||
api_base_url = '{}://{}'.format(app.protocol, app.instance),
|
||||
access_token = token.token,
|
||||
)
|
||||
try:
|
||||
# api.verify_credentials()
|
||||
# doesnt error even if the token is revoked lol sooooo
|
||||
tl = api.timeline()
|
||||
#if 'error' in tl and tl['error'] == 'The access token was revoked':
|
||||
#ARRRRRGH
|
||||
except mastodon.MastodonAPIError as e:
|
||||
raise e
|
||||
return api
|
||||
|
||||
|
||||
def fetch_acc(account, cursor=None):
|
||||
pass
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
"""add mastodon apps
|
||||
|
||||
Revision ID: 7afc7b343323
|
||||
Revises: f63bf9e73bc9
|
||||
Create Date: 2017-08-18 20:36:00.104508
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '7afc7b343323'
|
||||
down_revision = 'f63bf9e73bc9'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('mastodon_app',
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('instance', sa.String(), nullable=False),
|
||||
sa.Column('client_id', sa.String(), nullable=False),
|
||||
sa.Column('client_secret', sa.String(), nullable=False),
|
||||
sa.Column('protocol', sa.Enum('http', 'https', name='enum_protocol'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('instance', name=op.f('pk_mastodon_app'))
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table('mastodon_app')
|
|
@ -0,0 +1,28 @@
|
|||
"""make token secret nullable
|
||||
|
||||
Revision ID: c80af843eed3
|
||||
Revises: fbdc10b29df9
|
||||
Create Date: 2017-08-18 21:25:17.933702
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'c80af843eed3'
|
||||
down_revision = 'fbdc10b29df9'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.alter_column('oauth_tokens', 'token_secret',
|
||||
existing_type=sa.VARCHAR(),
|
||||
nullable=True)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.alter_column('oauth_tokens', 'token_secret',
|
||||
existing_type=sa.VARCHAR(),
|
||||
nullable=False)
|
|
@ -0,0 +1,24 @@
|
|||
"""it's supposed to be plural, dummy
|
||||
|
||||
Revision ID: fbdc10b29df9
|
||||
Revises: 7afc7b343323
|
||||
Create Date: 2017-08-18 20:39:39.119165
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'fbdc10b29df9'
|
||||
down_revision = '7afc7b343323'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.execute('ALTER TABLE mastodon_app RENAME TO mastodon_apps')
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.execute('ALTER TABLE mastodon_apps RENAME TO mastodon_app')
|
41
model.py
41
model.py
|
@ -1,11 +1,8 @@
|
|||
from datetime import datetime
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
from app import db
|
||||
|
||||
from twitter import Twitter, OAuth
|
||||
import secrets
|
||||
from lib import decompose_interval
|
||||
from datetime import timedelta, datetime
|
||||
|
||||
class TimestampMixin(object):
|
||||
created_at = db.Column(db.DateTime, server_default=db.func.now(), nullable=False)
|
||||
|
@ -33,6 +30,30 @@ class RemoteIDMixin(object):
|
|||
def twitter_id(self, id):
|
||||
self.id = "twitter:{}".format(id)
|
||||
|
||||
@property
|
||||
def mastodon_instance(self):
|
||||
if not self.id:
|
||||
return None
|
||||
if self.service != "mastodon":
|
||||
raise Exception("tried to get mastodon instance for a {} {}".format(self.service, type(self)))
|
||||
return self.id.split(":", 1)[1].split('@')[1]
|
||||
|
||||
@mastodon_instance.setter
|
||||
def mastodon_instance(self, instance):
|
||||
self.id = "mastodon:{}@{}".format(self.mastodon_id, instance)
|
||||
|
||||
@property
|
||||
def mastodon_id(self):
|
||||
if not self.id:
|
||||
return None
|
||||
if self.service != "mastodon":
|
||||
raise Exception("tried to get mastodon id for a {} {}".format(self.service, type(self)))
|
||||
return self.id.split(":", 1)[1].split('@')[0]
|
||||
|
||||
@mastodon_id.setter
|
||||
def mastodon_id(self, id):
|
||||
self.id = "mastodon:{}@{}".format(id, self.mastodon_instance)
|
||||
|
||||
|
||||
@decompose_interval('policy_delete_every')
|
||||
@decompose_interval('policy_keep_younger')
|
||||
|
@ -123,7 +144,7 @@ class OAuthToken(db.Model, TimestampMixin):
|
|||
__tablename__ = 'oauth_tokens'
|
||||
|
||||
token = db.Column(db.String, primary_key=True)
|
||||
token_secret = db.Column(db.String, nullable=False)
|
||||
token_secret = db.Column(db.String, nullable=True)
|
||||
|
||||
account_id = db.Column(db.String, db.ForeignKey('accounts.id', ondelete='CASCADE', onupdate='CASCADE'), nullable=True, index=True)
|
||||
account = db.relationship(Account, backref=db.backref('tokens', order_by=lambda: db.desc(OAuthToken.created_at)))
|
||||
|
@ -181,3 +202,13 @@ class TwitterArchive(db.Model, TimestampMixin):
|
|||
if self.chunks_successful == self.chunks:
|
||||
return 'successful'
|
||||
return 'pending'
|
||||
|
||||
ProtoEnum = db.Enum('http', 'https', name='enum_protocol')
|
||||
|
||||
class MastodonApp(db.Model, TimestampMixin):
|
||||
__tablename__ = 'mastodon_apps'
|
||||
|
||||
instance = db.Column(db.String, primary_key=True)
|
||||
client_id = db.Column(db.String, nullable=False)
|
||||
client_secret = db.Column(db.String, nullable=False)
|
||||
protocol = db.Column(ProtoEnum, nullable=False)
|
||||
|
|
|
@ -3,10 +3,13 @@ amqp==2.2.1
|
|||
billiard==3.5.0.3
|
||||
Brotli==0.6.0
|
||||
celery==4.1.0
|
||||
certifi==2017.7.27.1
|
||||
chardet==3.0.4
|
||||
click==6.7
|
||||
cloudpickle==0.4.0
|
||||
contextlib2==0.5.5
|
||||
csscompressor==0.9.4
|
||||
dateutils==0.6.6
|
||||
doit==0.30.3
|
||||
Flask==0.12.2
|
||||
Flask-Limiter==0.9.5
|
||||
|
@ -14,12 +17,14 @@ Flask-Migrate==2.1.0
|
|||
Flask-SQLAlchemy==2.2
|
||||
gunicorn==19.7.1
|
||||
honcho==1.0.1
|
||||
idna==2.6
|
||||
itsdangerous==0.24
|
||||
Jinja2==2.9.6
|
||||
kombu==4.1.0
|
||||
limits==1.2.1
|
||||
Mako==1.0.7
|
||||
MarkupSafe==1.0
|
||||
Mastodon.py==1.0.8
|
||||
olefile==0.44
|
||||
Pillow==4.2.1
|
||||
psycopg2==2.7.3
|
||||
|
@ -29,8 +34,10 @@ python-editor==1.0.3
|
|||
pytz==2017.2
|
||||
raven==6.1.0
|
||||
redis==2.10.5
|
||||
requests==2.18.4
|
||||
six==1.10.0
|
||||
SQLAlchemy==1.1.13
|
||||
twitter==1.17.1
|
||||
urllib3==1.22
|
||||
vine==1.1.4
|
||||
Werkzeug==0.12.2
|
||||
|
|
68
routes.py
68
routes.py
|
@ -1,20 +1,18 @@
|
|||
from flask import render_template, url_for, redirect, request, g, Response, jsonify
|
||||
from datetime import datetime, timedelta
|
||||
import lib.twitter
|
||||
import lib.mastodon
|
||||
import lib
|
||||
from lib.auth import require_auth, require_auth_api
|
||||
from lib import set_session_cookie
|
||||
from lib import get_viewer_session, get_viewer
|
||||
from model import Account, Session, Post, TwitterArchive
|
||||
from model import Account, Session, Post, TwitterArchive, MastodonApp
|
||||
from app import app, db, sentry, limiter
|
||||
import tasks
|
||||
from zipfile import BadZipFile
|
||||
from twitter import TwitterError
|
||||
from urllib.error import URLError
|
||||
import version
|
||||
import lib.brotli
|
||||
import lib.settings
|
||||
import lib.interval
|
||||
|
||||
@app.before_request
|
||||
def load_viewer():
|
||||
|
@ -53,6 +51,19 @@ lib.brotli.brotli(app)
|
|||
@app.route('/')
|
||||
def index():
|
||||
if g.viewer:
|
||||
if g.viewer.account.service == 'mastodon':
|
||||
import lib.mastodon
|
||||
api = lib.mastodon.get_api_for_acc(g.viewer.account)
|
||||
me = api.account_verify_credentials()
|
||||
#return str(me)
|
||||
tl = api.timeline()
|
||||
return str(tl)
|
||||
if not api:
|
||||
raise Exception('frick!!!!!')
|
||||
else:
|
||||
raise Exception(api)
|
||||
|
||||
|
||||
return render_template('logged_in.html', scales=lib.interval.SCALES,
|
||||
tweet_archive_failed = 'tweet_archive_failed' in request.args,
|
||||
settings_error = 'settings_error' in request.args
|
||||
|
@ -212,3 +223,52 @@ def api_viewer_timers():
|
|||
next_delete=viewer.next_delete,
|
||||
next_delete_rel=lib.interval.relnow(viewer.next_delete),
|
||||
)
|
||||
|
||||
@app.route('/login/mastodon', methods=('GET', 'POST'))
|
||||
def mastodon_login_step1():
|
||||
if request.method == 'GET':
|
||||
return render_template('mastodon_login.html', generic_error = 'error' in request.args)
|
||||
|
||||
if not 'instance_url' in request.form or not request.form['instance_url']:
|
||||
return render_template('mastodon_login.html', address_error=True)
|
||||
|
||||
instance_url = request.form['instance_url'].split("@")[-1].lower()
|
||||
|
||||
callback = url_for('mastodon_login_step2', instance=instance_url, _external=True)
|
||||
|
||||
app = lib.mastodon.get_or_create_app(instance_url,
|
||||
callback,
|
||||
url_for('index', _external=True))
|
||||
db.session.merge(app)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return redirect(lib.mastodon.login_url(app, callback))
|
||||
|
||||
@app.route('/login/mastodon/callback/<instance>')
|
||||
def mastodon_login_step2(instance):
|
||||
code = request.args.get('code', None)
|
||||
app = MastodonApp.query.get(instance)
|
||||
if not code or not app:
|
||||
return redirect('mastodon_login_step1', error=True)
|
||||
|
||||
callback = url_for('mastodon_login_step2', instance=instance, _external=True)
|
||||
|
||||
account = lib.mastodon.receive_code(code, app, callback)
|
||||
|
||||
account = db.session.merge(account)
|
||||
|
||||
sess = Session(account = account)
|
||||
db.session.add(sess)
|
||||
db.session.commit()
|
||||
|
||||
g.viewer = sess
|
||||
return redirect(url_for('index'))
|
||||
|
||||
|
||||
@app.route('/debug')
|
||||
def debug():
|
||||
sess = Session(account = Account.query.filter_by(display_name = 'codltest').first())
|
||||
db.session.merge(sess)
|
||||
db.session.commit()
|
||||
return sess.id
|
||||
|
|
9
tasks.py
9
tasks.py
|
@ -47,9 +47,12 @@ def fetch_acc(id, cursor=None):
|
|||
print(f'fetching {acc}')
|
||||
try:
|
||||
if(acc.service == 'twitter'):
|
||||
cursor = lib.twitter.fetch_acc(acc, cursor, **flaskapp.config.get_namespace("TWITTER_"))
|
||||
if cursor:
|
||||
fetch_acc.si(id, cursor).apply_async()
|
||||
action = lib.twitter.fetch_acc
|
||||
elif(acc.service == 'mastodon'):
|
||||
action = lib.mastodon.fetch_acc
|
||||
cursor = action(acc, cursor, **flaskapp.config.get_namespace("TWITTER_"))
|
||||
if cursor:
|
||||
fetch_acc.si(id, cursor).apply_async()
|
||||
finally:
|
||||
db.session.rollback()
|
||||
acc.touch_fetch()
|
||||
|
|
|
@ -27,7 +27,10 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p>Sound good to you? <a href="/login/twitter">Log in with Twitter</a></p>
|
||||
<p>Sound good to you?
|
||||
<a href="/login/twitter">Log in with Twitter</a>
|
||||
<a href="{{ url_for('mastodon_login_step1') }}">Log in with Mastodon</a>
|
||||
</p>
|
||||
</section>
|
||||
<section class='policy'>
|
||||
<h2>Policy</h2>
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{% extends 'lib/layout.html' %}
|
||||
{% block body %}
|
||||
<section>
|
||||
<h2>Log in with Mastodon</h2>
|
||||
|
||||
{% if generic_error %}
|
||||
<div class='banner error'>Something went wrong while logging in. Try again?</div>
|
||||
{% endif %}
|
||||
|
||||
{% if address_error %}
|
||||
<div class='banner error'>This doesn't look like a mastodon instance url. Try again?</div>
|
||||
{% endif %}
|
||||
|
||||
<form method='post'>
|
||||
<label>Your mastodon address:
|
||||
<input type='text' name='instance_url' placeholder='example@glitch.social'/>
|
||||
</label>
|
||||
<input name='confirm' value='Log in' type='submit'/>
|
||||
<aside>
|
||||
Your mastodon address is your username, followed by an @ sign and the address of your mastodon instance.
|
||||
</aside>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue