Verify Mastodon posts have a favorite
This commit is contained in:
parent
0a1f5967af
commit
cb6dd98ce1
14 changed files with 450 additions and 78 deletions
|
|
@ -36,6 +36,9 @@ class User(BaseModel):
|
|||
mastodon_attn_list = TextField(null=True)
|
||||
mastodon_post_public = BooleanField(null=True, default=False)
|
||||
|
||||
verify_mastodon_favorite = BooleanField(null=False, default=False)
|
||||
verify_delay = IntegerField(null=True)
|
||||
|
||||
def mastodon_account(self):
|
||||
if self.mastodon_server is None or self.mastodon_username is None:
|
||||
return
|
||||
|
|
@ -148,6 +151,7 @@ class OrderStatus(BaseModel):
|
|||
confirmed_at = DateTimeField(null=True)
|
||||
created_at = DateTimeField()
|
||||
due_at = DateTimeField(null=True)
|
||||
verify_at = DateTimeField(null=True)
|
||||
mastodon_id = TextField()
|
||||
text = TextField()
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ def user_preferences_set(id, mastodon_post_public, mastodon_attn_list):
|
|||
)
|
||||
return q.execute()
|
||||
|
||||
def user_has_doms(id):
|
||||
return DomSubUsers.select().where(DomSubUsers.sub_id == id).count() > 0
|
||||
|
||||
def mastodon_server_get(name):
|
||||
return MastodonServer.get(name=name)
|
||||
|
||||
|
|
@ -91,6 +94,9 @@ def domsubusers_delete(sub, dom):
|
|||
def domsubusers_list(dom):
|
||||
return DomSubUsers.select().where(DomSubUsers.dom == dom)
|
||||
|
||||
def domsubusers_doms(sub):
|
||||
return [dsu.dom for dsu in DomSubUsers.select(DomSubUsers.dom).where(DomSubUsers.sub_id == sub.id)]
|
||||
|
||||
def repeat_get(orders_pool_id):
|
||||
try:
|
||||
return Repeat.get(orders_pool_id = orders_pool_id)
|
||||
|
|
@ -132,7 +138,7 @@ def skip_day_contains(user, date):
|
|||
q = SkipDay.select().where((SkipDay.user == user) & (SkipDay.date == date))
|
||||
return len(q) > 0
|
||||
|
||||
def order_status_put(orders_pool, user, mastodon_id, created_at, due_at, text, punishment_for=None):
|
||||
def order_status_put(orders_pool, user, mastodon_id, created_at, due_at, text, punishment_for=None, verify_at=None):
|
||||
return OrderStatus.create(
|
||||
orders_pool_id=orders_pool.id,
|
||||
user_id=user.id,
|
||||
|
|
@ -140,7 +146,8 @@ def order_status_put(orders_pool, user, mastodon_id, created_at, due_at, text, p
|
|||
created_at=created_at,
|
||||
due_at=due_at,
|
||||
text=text,
|
||||
punishment_for=punishment_for
|
||||
punishment_for=punishment_for,
|
||||
verify_at=verify_at
|
||||
)
|
||||
|
||||
def order_status_by_id(order_status_id):
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from settings import MASTODON_INSTANCE, MASTODON_ACCESS_TOKEN, MASTODON_VISIBILI
|
|||
|
||||
API_STATUSES = '/api/v1/statuses'
|
||||
API_STATUS_CONTEXT = '/api/v1/statuses/%(id)s/context'
|
||||
API_STATUS_FAVORITES = '/api/v1/statuses/%(id)s/favourited_by'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -64,3 +65,11 @@ class Mastodon:
|
|||
'id': id
|
||||
}
|
||||
)
|
||||
|
||||
async def statusFavorites(self, id):
|
||||
return await self.get(
|
||||
self.instance,
|
||||
API_STATUS_FAVORITES % {
|
||||
'id' : id
|
||||
}
|
||||
)
|
||||
|
|
|
|||
50
migrations/025_user_add_verify_mastodon_favorite.py
Normal file
50
migrations/025_user_add_verify_mastodon_favorite.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"""Peewee migrations -- 025_user_add_verify_mastodon_favorite.py.
|
||||
|
||||
Some examples (model - class or model name)::
|
||||
|
||||
> Model = migrator.orm['table_name'] # Return model in current state by name
|
||||
> Model = migrator.ModelClass # Return model in current state by name
|
||||
|
||||
> migrator.sql(sql) # Run custom SQL
|
||||
> migrator.run(func, *args, **kwargs) # Run python function with the given args
|
||||
> migrator.create_model(Model) # Create a model (could be used as decorator)
|
||||
> migrator.remove_model(model, cascade=True) # Remove a model
|
||||
> migrator.add_fields(model, **fields) # Add fields to a model
|
||||
> migrator.change_fields(model, **fields) # Change fields
|
||||
> migrator.remove_fields(model, *field_names, cascade=True)
|
||||
> migrator.rename_field(model, old_field_name, new_field_name)
|
||||
> migrator.rename_table(model, new_table_name)
|
||||
> migrator.add_index(model, *col_names, unique=False)
|
||||
> migrator.add_not_null(model, *field_names)
|
||||
> migrator.add_default(model, field_name, default)
|
||||
> migrator.add_constraint(model, name, sql)
|
||||
> migrator.drop_index(model, *col_names)
|
||||
> migrator.drop_not_null(model, *field_names)
|
||||
> migrator.drop_constraints(model, *constraints)
|
||||
|
||||
"""
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
import peewee as pw
|
||||
from peewee_migrate import Migrator
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
import playhouse.postgres_ext as pw_pext
|
||||
|
||||
|
||||
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your migrations here."""
|
||||
|
||||
migrator.add_fields(
|
||||
'user',
|
||||
|
||||
verify_mastodon_favorite=pw.BooleanField(default=False),
|
||||
verify_delay=pw.IntegerField(null=True))
|
||||
|
||||
|
||||
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your rollback migrations here."""
|
||||
|
||||
migrator.remove_fields('user', 'verify_mastodon_favorite', 'verify_delay')
|
||||
49
migrations/026_order_status_verify_at.py
Normal file
49
migrations/026_order_status_verify_at.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"""Peewee migrations -- 026_order_status_verify_at.py.
|
||||
|
||||
Some examples (model - class or model name)::
|
||||
|
||||
> Model = migrator.orm['table_name'] # Return model in current state by name
|
||||
> Model = migrator.ModelClass # Return model in current state by name
|
||||
|
||||
> migrator.sql(sql) # Run custom SQL
|
||||
> migrator.run(func, *args, **kwargs) # Run python function with the given args
|
||||
> migrator.create_model(Model) # Create a model (could be used as decorator)
|
||||
> migrator.remove_model(model, cascade=True) # Remove a model
|
||||
> migrator.add_fields(model, **fields) # Add fields to a model
|
||||
> migrator.change_fields(model, **fields) # Change fields
|
||||
> migrator.remove_fields(model, *field_names, cascade=True)
|
||||
> migrator.rename_field(model, old_field_name, new_field_name)
|
||||
> migrator.rename_table(model, new_table_name)
|
||||
> migrator.add_index(model, *col_names, unique=False)
|
||||
> migrator.add_not_null(model, *field_names)
|
||||
> migrator.add_default(model, field_name, default)
|
||||
> migrator.add_constraint(model, name, sql)
|
||||
> migrator.drop_index(model, *col_names)
|
||||
> migrator.drop_not_null(model, *field_names)
|
||||
> migrator.drop_constraints(model, *constraints)
|
||||
|
||||
"""
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
import peewee as pw
|
||||
from peewee_migrate import Migrator
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
import playhouse.postgres_ext as pw_pext
|
||||
|
||||
|
||||
def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your migrations here."""
|
||||
|
||||
migrator.add_fields(
|
||||
'order_status',
|
||||
|
||||
verify_at=pw.DateTimeField(null=True))
|
||||
|
||||
|
||||
def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
|
||||
"""Write your rollback migrations here."""
|
||||
|
||||
migrator.remove_fields('order_status', 'verify_at')
|
||||
165
orders.py
165
orders.py
|
|
@ -5,7 +5,7 @@ import asyncio
|
|||
from db.constants import TIMELINE_ORDER_CONFIRMED, TIMELINE_ORDER_NOT_PUNISHED, TIMELINE_ORDER_PUNISHED
|
||||
from util import make_session
|
||||
from generate import generate_order, generate_punishment
|
||||
from db.queries import order_status_by_id, order_status_put, order_status_confirm, timeline_event_put
|
||||
from db.queries import domsubusers_doms, order_status_by_id, order_status_put, order_status_confirm, timeline_event_put
|
||||
from mastodon import Mastodon
|
||||
from telegram.telegram import Telegram
|
||||
from settings import ENV
|
||||
|
|
@ -13,14 +13,17 @@ from util import timezone
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def order_mastodon_post(session, orders_pool, orders_str, repeats, due_at):
|
||||
async def order_mastodon_post(session, orders_pool, orders_str, repeats, due_at, verify_at=None):
|
||||
user = orders_pool.user
|
||||
|
||||
post = "Here are today's orders for @%s -\n\n" % user.mastodon_account()
|
||||
post += orders_str + "\n\n"
|
||||
if repeats > 1:
|
||||
post += f"These are the same orders from the last {repeats} days\n\n"
|
||||
post += "Proof of compliance is due by " + due_at.strftime("%I:%M %p") + "\n\n"
|
||||
post += "Proof of compliance is due by " + due_at.strftime("%I:%M %p") + "\n"
|
||||
if verify_at is not None:
|
||||
post += "Verification due by " + verify_at.strftime("%I:%M %p") + "\n"
|
||||
post += "\n"
|
||||
|
||||
if ENV == 'dev':
|
||||
post += "⚠️ DEV"
|
||||
|
|
@ -30,12 +33,15 @@ async def order_mastodon_post(session, orders_pool, orders_str, repeats, due_at)
|
|||
m = Mastodon(session)
|
||||
return await m.statusPost(post)
|
||||
|
||||
async def order_telegram_post(session, orders_pool, orders_str, repeats, due_at, m_url):
|
||||
async def order_telegram_post(session, orders_pool, orders_str, repeats, due_at, m_url, verify_at=None):
|
||||
post = "Here are your orders -\n\n"
|
||||
post += orders_str + "\n\n"
|
||||
if repeats > 1:
|
||||
post += f"These are the same orders from the last {repeats} days\n\n"
|
||||
post += "Proof of compliance is due by " + due_at.strftime("%I:%M %p") + "\n\n"
|
||||
post += "Proof of compliance is due by " + due_at.strftime("%I:%M %p") + "\n"
|
||||
if verify_at is not None:
|
||||
post += "Verification due by " + verify_at.strftime("%I:%M %p") + "\n"
|
||||
post += "\n"
|
||||
post += m_url
|
||||
if ENV == 'dev':
|
||||
post += "\n⚠️ DEV"
|
||||
|
|
@ -61,54 +67,64 @@ async def order_telegram_post_none(session, orders_pool):
|
|||
|
||||
async def order_issue(orders_pool):
|
||||
async with make_session() as session:
|
||||
if orders_pool.user.mastodon_username is None:
|
||||
logger.info('Cannot issue order without mastodon username')
|
||||
await order_telegram_post_need_mastodon(session, orders_pool)
|
||||
return { "reason": "Cannot issue order without mastodon username" }
|
||||
user = orders_pool.user
|
||||
|
||||
orders_info = generate_order(orders_pool)
|
||||
if user.mastodon_username is None:
|
||||
logger.info('Cannot issue order without mastodon username')
|
||||
await order_telegram_post_need_mastodon(session, orders_pool)
|
||||
return { "reason": "Cannot issue order without mastodon username" }
|
||||
|
||||
if 'orders' not in orders_info:
|
||||
logger.info(f"{orders_pool} - {orders_info['reason']}")
|
||||
await order_telegram_post_none(session, orders_pool)
|
||||
return { "reason": orders_info['reason'] }
|
||||
orders_info = generate_order(orders_pool)
|
||||
|
||||
orders_str = "\n".join(orders_info['orders'])
|
||||
if 'orders' not in orders_info:
|
||||
logger.info(f"{orders_pool} - {orders_info['reason']}")
|
||||
await order_telegram_post_none(session, orders_pool)
|
||||
return { "reason": orders_info['reason'] }
|
||||
|
||||
orders_str = "\n".join(orders_info['orders'])
|
||||
|
||||
created_at = datetime.datetime.now(tz=timezone())
|
||||
if orders_pool.confirm_delay is not None:
|
||||
due_at = created_at + datetime.timedelta(hours=orders_pool.confirm_delay)
|
||||
created_at = datetime.datetime.now(tz=timezone())
|
||||
due_at = None
|
||||
verify_at = None
|
||||
if orders_pool.confirm_delay is not None:
|
||||
due_at = created_at + datetime.timedelta(hours=orders_pool.confirm_delay)
|
||||
|
||||
repeats_count = orders_info.get('count', 0)
|
||||
if user.verify_mastodon_favorite:
|
||||
verify_at = due_at + datetime.timedelta(hours=user.verify_delay)
|
||||
|
||||
m_status = await order_mastodon_post(
|
||||
session,
|
||||
orders_pool,
|
||||
orders_str,
|
||||
repeats_count,
|
||||
due_at
|
||||
)
|
||||
repeats_count = orders_info.get('count', 0)
|
||||
|
||||
await order_telegram_post(
|
||||
session,
|
||||
orders_pool,
|
||||
orders_str,
|
||||
repeats_count,
|
||||
due_at,
|
||||
m_status['url']
|
||||
)
|
||||
m_status = await order_mastodon_post(
|
||||
session,
|
||||
orders_pool,
|
||||
orders_str,
|
||||
repeats_count,
|
||||
due_at,
|
||||
verify_at=verify_at
|
||||
)
|
||||
|
||||
return {
|
||||
"order_status" : order_status_put(
|
||||
orders_pool,
|
||||
orders_pool.user,
|
||||
m_status['id'],
|
||||
created_at,
|
||||
due_at,
|
||||
orders_str
|
||||
),
|
||||
"mastodon_status": m_status
|
||||
}
|
||||
await order_telegram_post(
|
||||
session,
|
||||
orders_pool,
|
||||
orders_str,
|
||||
repeats_count,
|
||||
due_at,
|
||||
m_status['url'],
|
||||
verify_at=verify_at
|
||||
)
|
||||
|
||||
return {
|
||||
"order_status" : order_status_put(
|
||||
orders_pool,
|
||||
orders_pool.user,
|
||||
m_status['id'],
|
||||
created_at,
|
||||
due_at,
|
||||
orders_str,
|
||||
verify_at=verify_at
|
||||
),
|
||||
"mastodon_status": m_status
|
||||
}
|
||||
|
||||
async def punishment_mastodon_post(session, orders_pool, punishment_str, reply_id=None):
|
||||
user = orders_pool.user
|
||||
|
|
@ -174,11 +190,30 @@ async def punishment_issue(session, order_status):
|
|||
"mastodon_status": punishment_status
|
||||
}
|
||||
|
||||
async def status_has_favorites(m, status_id, user):
|
||||
favorites = await m.statusFavorites(status_id)
|
||||
user_mastodon_account = user.mastodon_account()
|
||||
|
||||
accts = [f['acct'] for f in favorites]
|
||||
|
||||
for dom in domsubusers_doms(user):
|
||||
dom_account = dom.mastodon_account()
|
||||
if dom_account is not None and dom_account in accts:
|
||||
return True
|
||||
|
||||
for attn_user in user.mastodon_attn_list.split(" "):
|
||||
if (attn_user[1:] != user_mastodon_account and
|
||||
attn_user[1:] in accts):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
order_check_lock = asyncio.Lock()
|
||||
async def order_check(order_status_id):
|
||||
async with order_check_lock:
|
||||
async with make_session() as session:
|
||||
order_status = order_status_by_id(order_status_id)
|
||||
user = order_status.user
|
||||
|
||||
if order_status.punishment.count() > 0:
|
||||
logger.info(f'Punishment already issued for {order_status.id}')
|
||||
|
|
@ -188,12 +223,34 @@ async def order_check(order_status_id):
|
|||
context = await m.statusContext(order_status.mastodon_id)
|
||||
|
||||
confirmed_at = None
|
||||
had_replies = False
|
||||
had_reply_on_time = False
|
||||
had_media_attachment = False
|
||||
had_favorites = False
|
||||
for d in context['descendants']:
|
||||
if (
|
||||
d['in_reply_to_id'] == order_status.mastodon_id and
|
||||
d['account']['acct'] == order_status.user.mastodon_account() and
|
||||
len(d['media_attachments']) > 0
|
||||
d['account']['acct'] == order_status.user.mastodon_account()
|
||||
):
|
||||
had_replies = True
|
||||
|
||||
if (datetime.datetime.fromisoformat(d['created_at']) <
|
||||
datetime.datetime.fromisoformat(order_status.due_at)):
|
||||
had_reply_on_time = True
|
||||
else:
|
||||
continue
|
||||
|
||||
if len(d['media_attachments']) > 0:
|
||||
had_media_attachment = True
|
||||
else:
|
||||
continue
|
||||
|
||||
if user.verify_mastodon_favorite:
|
||||
if await status_has_favorites(m, d['id'], order_status.user):
|
||||
had_favorites = True
|
||||
else:
|
||||
continue
|
||||
|
||||
confirmed_at = d['created_at']
|
||||
order_status_confirm(order_status.id, confirmed_at)
|
||||
logger.info('Confirmed order %s' % (order_status.id))
|
||||
|
|
@ -214,15 +271,27 @@ async def order_check(order_status_id):
|
|||
|
||||
due_at = datetime.datetime.fromisoformat(order_status.due_at)
|
||||
if(due_at < datetime.datetime.now(datetime.UTC)):
|
||||
reason = None
|
||||
if had_replies is False:
|
||||
reason = "No replies were found"
|
||||
elif had_reply_on_time is False:
|
||||
reason = "Reply was after due date"
|
||||
elif had_media_attachment is False:
|
||||
reason = "No replies had a media attachment"
|
||||
elif had_favorites is False:
|
||||
reason = "No replies had a favorite from a dom or tagged account"
|
||||
|
||||
logger.info('Time to issue a punishment for %s' % order_status.id)
|
||||
|
||||
issue_result = await punishment_issue(session, order_status)
|
||||
|
||||
if 'order_status' in issue_result:
|
||||
punishment_status = issue_result['order_status']
|
||||
log_text = (reason + "\n") if reason is not None else ''
|
||||
log_text += punishment_status.text.split("\n")[0]
|
||||
timeline_event_put(
|
||||
TIMELINE_ORDER_PUNISHED,
|
||||
punishment_status.text.split("\n")[0],
|
||||
log_text,
|
||||
punishment_status.user,
|
||||
punishment_status.pool,
|
||||
punishment_status,
|
||||
|
|
|
|||
|
|
@ -28,8 +28,9 @@ class OrderScheduler():
|
|||
self.schedule_pool(orders_pool)
|
||||
|
||||
for order_status in order_status_outstanding():
|
||||
check_time = order_status.verify_at if order_status.verify_at is not None else order_status.due_at
|
||||
self.scheduler.once(
|
||||
datetime.datetime.fromisoformat(order_status.due_at) + GRACE_PERIOD,
|
||||
check_time + GRACE_PERIOD,
|
||||
self.scheduled_check,
|
||||
args=(order_status.id,)
|
||||
)
|
||||
|
|
@ -91,12 +92,14 @@ class OrderScheduler():
|
|||
|
||||
if 'order_status' in issue_result:
|
||||
order_status = issue_result['order_status']
|
||||
# Schedule check
|
||||
self.scheduler.once(
|
||||
order_status.due_at + GRACE_PERIOD,
|
||||
self.scheduled_check,
|
||||
args=(order_status.id,)
|
||||
)
|
||||
if order_status.due_at is not None or order_status.verify_at is not None:
|
||||
# Schedule check
|
||||
check_time = order_status.verify_at if order_status.verify_at is not None else order_status.due_at
|
||||
self.scheduler.once(
|
||||
check_time + GRACE_PERIOD,
|
||||
self.scheduled_check,
|
||||
args=(order_status.id,)
|
||||
)
|
||||
timeline_event_put(
|
||||
TIMELINE_ORDER_ISSUED,
|
||||
order_status.text.split("\n")[0],
|
||||
|
|
|
|||
44
web/api.py
44
web/api.py
|
|
@ -6,7 +6,7 @@ from flask import Blueprint, jsonify, abort, request
|
|||
from flask_login import login_required, current_user
|
||||
from db.constants import TIMELINE_ORDERS_POOL_CREATED, TIMELINE_ORDERS_POOL_DELETED, TIMELINE_ORDERS_POOL_UPDATED
|
||||
from db.models import database, OrdersPool, Order, OrderAddOn, MastodonServer
|
||||
from db.queries import timeline_event_put, timeline_event_recent, user_get, domsubusers_list, orders_pool_list, orders_pool, mastodon_server_get, mastodon_server_put, user_mastodon_server_set, user_preferences_set
|
||||
from db.queries import timeline_event_put, timeline_event_recent, user_get, domsubusers_list, orders_pool_list, orders_pool, mastodon_server_get, mastodon_server_put, user_has_doms, user_mastodon_server_set, user_preferences_set
|
||||
from settings import MASTODON_OAUTH_CLIENT_NAME, MASTODON_OAUTH_REDIRECT_URI, MASTODON_OAUTH_SCOPES, MASTODON_OAUTH_CLIENT_WEBSITE
|
||||
from util import time_sqlite
|
||||
|
||||
|
|
@ -18,15 +18,24 @@ api = Blueprint('api', __name__)
|
|||
@login_required
|
||||
def me():
|
||||
user = current_user.db_user
|
||||
has_doms = user_has_doms(user.id)
|
||||
|
||||
return jsonify({
|
||||
result = {
|
||||
"username": user.telegram_username,
|
||||
"telegram_photo_url": user.telegram_photo_url,
|
||||
"mastodon_server": user.mastodon_server.name if user.mastodon_server else None,
|
||||
"mastodon_username": user.mastodon_username,
|
||||
"mastodon_attn_list": user.mastodon_attn_list,
|
||||
"mastodon_post_public": user.mastodon_post_public
|
||||
})
|
||||
"mastodon_post_public": user.mastodon_post_public,
|
||||
"has_doms": has_doms
|
||||
}
|
||||
|
||||
|
||||
if not has_doms:
|
||||
result["verify_mastodon_favorite"] = user.verify_mastodon_favorite
|
||||
result["verify_delay"] = user.verify_delay
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
@api.route("/profile", methods=["POST",])
|
||||
@login_required
|
||||
|
|
@ -149,15 +158,30 @@ def authorized_sub(func):
|
|||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
@api.route('/subs/<username>')
|
||||
@api.route('/subs/<username>', methods=["GET", "POST"])
|
||||
@login_required
|
||||
@authorized_sub
|
||||
def sub(username, sub):
|
||||
return jsonify({
|
||||
"username": sub.telegram_username,
|
||||
"mastodon_server": sub.mastodon_server.name if sub.mastodon_server is not None else None,
|
||||
"mastodon_username": sub.mastodon_username
|
||||
})
|
||||
if user_has_doms(sub) and sub.id == current_user.db_user.id:
|
||||
abort(403)
|
||||
return
|
||||
|
||||
if request.method == "POST":
|
||||
sub.verify_mastodon_favorite = bool(request.json['verify_mastodon_favorite'])
|
||||
if request.json['verify_delay'] is not None:
|
||||
sub.verify_delay = int(request.json["verify_delay"])
|
||||
|
||||
sub.save()
|
||||
|
||||
return ('', 204)
|
||||
else:
|
||||
return jsonify({
|
||||
"username": sub.telegram_username,
|
||||
"mastodon_server": sub.mastodon_server.name if sub.mastodon_server is not None else None,
|
||||
"mastodon_username": sub.mastodon_username,
|
||||
"verify_mastodon_favorite": sub.verify_mastodon_favorite,
|
||||
"verify_delay": sub.verify_delay
|
||||
})
|
||||
|
||||
@api.route('/orders/')
|
||||
@login_required
|
||||
|
|
|
|||
|
|
@ -164,7 +164,8 @@ def login():
|
|||
db_user.save()
|
||||
|
||||
login_user(FlaskUser(db_user))
|
||||
except:
|
||||
except Exception as e:
|
||||
print(e)
|
||||
flash("Login failed. Please try again.")
|
||||
return redirect('/')
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { Link, useLoaderData } from "react-router";
|
|||
import { useForm } from "@mantine/form";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { fetchHeaders } from "./fetch";
|
||||
import { ProfileVerification } from "./ProfileVerification";
|
||||
|
||||
const RE_MASTODON_ACCOUNTS = /^(@(\w+)(@([\w\.]+))? )*(@\w+)(@[\w\.]+)?$/;
|
||||
|
||||
|
|
@ -63,10 +64,13 @@ export const profileLoader = async () =>
|
|||
export const Profile: React.FC = () => {
|
||||
const { username, telegram_photo_url, mastodon_server, mastodon_username } =
|
||||
useUserContext();
|
||||
const { mastodon_attn_list, mastodon_post_public } = useLoaderData<{
|
||||
mastodon_attn_list?: string;
|
||||
mastodon_post_public?: boolean;
|
||||
}>();
|
||||
const {
|
||||
has_doms,
|
||||
mastodon_attn_list,
|
||||
mastodon_post_public,
|
||||
verify_mastodon_favorite,
|
||||
verify_delay,
|
||||
} = useLoaderData<UserProfile>();
|
||||
const [opened, { open, close }] = useDisclosure(false);
|
||||
|
||||
const mastodon_account = React.useMemo(
|
||||
|
|
@ -155,6 +159,13 @@ export const Profile: React.FC = () => {
|
|||
</form>
|
||||
</Fieldset>
|
||||
</Paper>
|
||||
{!has_doms ? (
|
||||
<ProfileVerification
|
||||
username={username}
|
||||
verify_mastodon_favorite={verify_mastodon_favorite}
|
||||
verify_delay={verify_delay}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
103
web/vite/src/ProfileVerification.tsx
Normal file
103
web/vite/src/ProfileVerification.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
NumberInput,
|
||||
Paper,
|
||||
Title,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { useForm } from "@mantine/form";
|
||||
import { notifications } from "@mantine/notifications";
|
||||
import { fetchHeaders } from "./fetch";
|
||||
import React from "react";
|
||||
|
||||
type ProfileVerificationProps = Pick<
|
||||
UserProfile,
|
||||
"verify_mastodon_favorite" | "verify_delay"
|
||||
> & { username: string };
|
||||
|
||||
type OrderVerificationForm = Pick<
|
||||
UserProfile,
|
||||
"verify_mastodon_favorite" | "verify_delay"
|
||||
>;
|
||||
|
||||
export const ProfileVerification: React.FC<ProfileVerificationProps> = ({
|
||||
username,
|
||||
verify_mastodon_favorite,
|
||||
verify_delay,
|
||||
}) => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const form = useForm<OrderVerificationForm>({
|
||||
mode: "uncontrolled",
|
||||
initialValues: {
|
||||
verify_mastodon_favorite,
|
||||
verify_delay,
|
||||
},
|
||||
validate: {
|
||||
verify_delay: (value: number, values: OrderVerificationForm) => {
|
||||
console.log("oh boy", values.verify_mastodon_favorite, value);
|
||||
return !values.verify_mastodon_favorite || value
|
||||
? null
|
||||
: "You must set a verification delay";
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
form.onSubmit((values) => {
|
||||
setLoading(true);
|
||||
fetch(`/api/subs/${username}`, {
|
||||
method: "POST",
|
||||
headers: fetchHeaders(),
|
||||
body: JSON.stringify(values),
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
notifications.show({
|
||||
title: "Success",
|
||||
message: "Your preferences have been saved",
|
||||
color: "green",
|
||||
});
|
||||
} else {
|
||||
notifications.show({
|
||||
title: "Error",
|
||||
message: "There was a problem saving your preferences",
|
||||
color: "red",
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Paper bg="gray.1">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Title order={4} mb="md">
|
||||
Order Verification
|
||||
</Title>
|
||||
<Checkbox
|
||||
{...form.getInputProps("verify_mastodon_favorite", {
|
||||
type: "checkbox",
|
||||
})}
|
||||
label="Posts must be Favorited by a dom or tagged account"
|
||||
mb="md"
|
||||
/>
|
||||
<NumberInput
|
||||
{...form.getInputProps("verify_delay")}
|
||||
label="Verification Delay"
|
||||
description="Hours to wait after order is due to verify post"
|
||||
min={1}
|
||||
max={168}
|
||||
rightSection={<Text mr="xl">hours</Text>}
|
||||
/>
|
||||
<Button type="submit" loading={loading} mt="md">
|
||||
Save
|
||||
</Button>
|
||||
</form>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import React from "react";
|
||||
import { Params, useLoaderData, useParams, Link } from "react-router";
|
||||
import { OrderSetProps, OrderSets } from "./OrderSets";
|
||||
import { ProfileVerification } from "./ProfileVerification";
|
||||
import { Title } from "@mantine/core";
|
||||
|
||||
export const subOrderSetsLoader = async ({
|
||||
params: { username },
|
||||
|
|
@ -12,11 +14,32 @@ export const SubOrderSets: React.FC = () => {
|
|||
const { username: sub_username } = useParams();
|
||||
const orderSets = useLoaderData<OrderSetProps["orderSets"]>();
|
||||
|
||||
const [profile, setProfile] = React.useState<UserProfile | null>(null);
|
||||
React.useEffect(() => {
|
||||
fetch(`/api/subs/${sub_username}`)
|
||||
.then((response) => response.json())
|
||||
.then(setProfile);
|
||||
}, [sub_username]);
|
||||
|
||||
return (
|
||||
<OrderSets
|
||||
username={sub_username}
|
||||
orderSets={orderSets}
|
||||
linkBack={<Link to={`/dashboard/`}>Return to dashboard</Link>}
|
||||
/>
|
||||
<>
|
||||
<OrderSets
|
||||
username={sub_username}
|
||||
orderSets={orderSets}
|
||||
linkBack={<Link to={`/dashboard/`}>Return to dashboard</Link>}
|
||||
/>
|
||||
{profile ? (
|
||||
<>
|
||||
<Title order={1} mb="md">
|
||||
Sub Profile
|
||||
</Title>
|
||||
<ProfileVerification
|
||||
username={sub_username}
|
||||
verify_mastodon_favorite={profile.verify_mastodon_favorite}
|
||||
verify_delay={profile.verify_delay}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -78,7 +78,14 @@ export const TimelineList: React.FC<{
|
|||
extra,
|
||||
}) => (
|
||||
<Timeline.Item key={id} active {...(TIMELINE_TYPE[type] ?? {})}>
|
||||
<Text size="sm">{text}</Text>
|
||||
<Text size="sm">
|
||||
{text.split("\n").map((str) => (
|
||||
<>
|
||||
{str}
|
||||
<br />
|
||||
</>
|
||||
))}
|
||||
</Text>
|
||||
<Flex mt={4} gap="xs">
|
||||
{extra?.mastodon_status_url ? (
|
||||
<Text size="xs">
|
||||
|
|
|
|||
12
web/vite/src/index.d.ts
vendored
12
web/vite/src/index.d.ts
vendored
|
|
@ -1,3 +1,15 @@
|
|||
type UserProfile = {
|
||||
username: string;
|
||||
telegram_photo_url?: string;
|
||||
mastodon_server?: string;
|
||||
mastodon_username?: string;
|
||||
mastodon_attn_list?: string;
|
||||
mastodon_post_public: boolean;
|
||||
has_doms: boolean;
|
||||
verify_mastodon_favorite?: boolean;
|
||||
verify_delay?: number;
|
||||
}
|
||||
|
||||
type OrderSetOrderAddOn = {
|
||||
id: number;
|
||||
name: string;
|
||||
|
|
|
|||
Loading…
Reference in a new issue