Scheduling
This commit is contained in:
parent
a728a75245
commit
8e8a85e822
10 changed files with 185 additions and 88 deletions
2
Pipfile
2
Pipfile
|
|
@ -6,6 +6,8 @@ name = "pypi"
|
|||
[packages]
|
||||
pyyaml = "*"
|
||||
aiohttp = "*"
|
||||
scheduler = "*"
|
||||
pytz = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
|
|||
27
Pipfile.lock
generated
27
Pipfile.lock
generated
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "184f1a8e3109bf0a0a96236437c7bfe1bddfc114455b6cdf36bedaa491aed03f"
|
||||
"sha256": "97e23fc67642a95bffea8782f93d340d26a92a440feae5e4b7d10dedbec5cccb"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
|
@ -591,6 +591,14 @@
|
|||
"markers": "python_version >= '3.9'",
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3",
|
||||
"sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2025.2"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c",
|
||||
|
|
@ -671,6 +679,23 @@
|
|||
"markers": "python_version >= '3.8'",
|
||||
"version": "==6.0.3"
|
||||
},
|
||||
"scheduler": {
|
||||
"hashes": [
|
||||
"sha256:4575a12cd269e4e4896409836fd911560cb63fb7634360ee62aa2fa4ec495ffd",
|
||||
"sha256:8f52ea6390757e4f42a8becbcb474e7744a3082ea5e1cdb0c972ea8b2c5d1891"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==0.8.8"
|
||||
},
|
||||
"typeguard": {
|
||||
"hashes": [
|
||||
"sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74",
|
||||
"sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e"
|
||||
],
|
||||
"markers": "python_version >= '3.9'",
|
||||
"version": "==4.4.4"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466",
|
||||
|
|
|
|||
2
db.py
2
db.py
|
|
@ -2,7 +2,7 @@ import os
|
|||
import sqlite3
|
||||
import datetime
|
||||
|
||||
SQLITE_DB = os.environ.get('SQLITE_DB', 'db.sqlite3')
|
||||
from settings import SQLITE_DB
|
||||
|
||||
TABLE_REPEATS = '''
|
||||
CREATE TABLE IF NOT EXISTS repeats (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import yaml
|
|||
import json
|
||||
import logging
|
||||
import random
|
||||
|
||||
from db import Database
|
||||
from settings import ORDERS_YML
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -33,8 +35,8 @@ def pick_order(orders):
|
|||
|
||||
return (result, repeat)
|
||||
|
||||
def read_config(filename="orders.yml"):
|
||||
with open(filename) as stream:
|
||||
def read_config():
|
||||
with open(ORDERS_YML) as stream:
|
||||
try:
|
||||
orders = yaml.safe_load(stream)
|
||||
except yaml.YAMLError as exc:
|
||||
|
|
|
|||
90
main.py
90
main.py
|
|
@ -3,90 +3,12 @@ import sys
|
|||
import logging
|
||||
import argparse
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import datetime
|
||||
|
||||
from generate import generate_order, generate_punishment
|
||||
from mastodon import Mastodon
|
||||
from db import Database
|
||||
from scheduling import OrderScheduler
|
||||
from orders import order_issue, order_check
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ORDER_TIMEOUT = datetime.timedelta(
|
||||
hours=os.getenv('ORDER_TIMEOUT', default=3)
|
||||
)
|
||||
MASTODON_USERNAME = os.getenv('MASTODON_USERNAME')
|
||||
|
||||
def make_session():
|
||||
return aiohttp.ClientSession()
|
||||
|
||||
async def immediate():
|
||||
orders_info = generate_order()
|
||||
|
||||
if 'orders' not in orders_info:
|
||||
logger.info("No orders for today")
|
||||
return
|
||||
|
||||
post = "Here are today's orders for @%s - \n\n" % MASTODON_USERNAME
|
||||
if "count" in orders_info and orders_info['count'] > 1:
|
||||
post += f"These are the same orders from the last {orders_info['count']} days\n\n"
|
||||
post += "\n".join(orders_info['orders'])
|
||||
|
||||
async with make_session() as session:
|
||||
m = Mastodon(session)
|
||||
status = await m.statusPost(post)
|
||||
db = Database()
|
||||
|
||||
db.order_status_put(
|
||||
status['id'],
|
||||
status['created_at'],
|
||||
post
|
||||
)
|
||||
|
||||
async def check():
|
||||
async with make_session() as session:
|
||||
m = Mastodon(session)
|
||||
db = Database()
|
||||
|
||||
outstanding_orders = db.order_status_outstanding()
|
||||
for outstanding_order in outstanding_orders:
|
||||
context = await m.statusContext(outstanding_order['mastodon_id'])
|
||||
|
||||
confirmed_at = None
|
||||
for d in context['descendants']:
|
||||
if (
|
||||
d['in_reply_to_id'] == outstanding_order['mastodon_id'] and
|
||||
d['account']['username'] == MASTODON_USERNAME and
|
||||
len(d['media_attachments']) > 0
|
||||
):
|
||||
confirmed_at = d['created_at']
|
||||
db.order_status_confirm(outstanding_order['id'], confirmed_at)
|
||||
logger.info('Confirmed order %s' % (outstanding_order['created_at']))
|
||||
break
|
||||
|
||||
if confirmed_at is None:
|
||||
logger.info('Order %s remains unconfirmed' % (outstanding_order['created_at']))
|
||||
|
||||
oo_created_at = datetime.datetime.fromisoformat(outstanding_order['created_at'])
|
||||
if(oo_created_at + ORDER_TIMEOUT < datetime.datetime.now(datetime.UTC)):
|
||||
logger.info('Time to issue a punishment for %s' % outstanding_order['created_at'])
|
||||
|
||||
punishment = generate_punishment()
|
||||
|
||||
post = "@%s has failed to post proof of compliance. Punishment is as follows -\n\n" % MASTODON_USERNAME
|
||||
post += "\n".join(punishment)
|
||||
|
||||
punishment_status = await m.statusPost(
|
||||
post,
|
||||
in_reply_to_id=outstanding_order['mastodon_id']
|
||||
)
|
||||
db.punishment_status_put(
|
||||
outstanding_order['id'],
|
||||
punishment_status['id'],
|
||||
punishment_status['created_at'],
|
||||
post
|
||||
)
|
||||
|
||||
if __name__=='__main__':
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s %(module)s [%(levelname)-4.4s] %(message)s",
|
||||
|
|
@ -106,9 +28,13 @@ if __name__=='__main__':
|
|||
|
||||
if args.command == 'immediate':
|
||||
loop = asyncio.new_event_loop()
|
||||
loop.run_until_complete(immediate())
|
||||
loop.run_until_complete(order_issue())
|
||||
loop.close()
|
||||
elif args.command == 'check':
|
||||
loop = asyncio.new_event_loop()
|
||||
loop.run_until_complete(check())
|
||||
loop.run_until_complete(order_check())
|
||||
loop.close()
|
||||
else:
|
||||
loop = asyncio.new_event_loop()
|
||||
s = OrderScheduler(loop)
|
||||
loop.run_forever()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import os
|
||||
import logging
|
||||
|
||||
MASTODON_INSTANCE = os.environ.get("MASTODON_INSTANCE")
|
||||
MASTODON_ACCESS_TOKEN = os.environ.get('MASTODON_ACCESS_TOKEN')
|
||||
from settings import MASTODON_INSTANCE, MASTODON_ACCESS_TOKEN
|
||||
|
||||
API_STATUSES = '/api/v1/statuses'
|
||||
API_STATUS_CONTEXT = '/api/v1/statuses/%(id)s/context'
|
||||
|
|
|
|||
77
orders.py
Normal file
77
orders.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import logging
|
||||
import datetime
|
||||
|
||||
from util import make_session
|
||||
from generate import generate_order, generate_punishment
|
||||
from db import Database
|
||||
from mastodon import Mastodon
|
||||
from settings import MASTODON_USERNAME, ORDER_TIMEOUT
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def order_issue():
|
||||
orders_info = generate_order()
|
||||
|
||||
if 'orders' not in orders_info:
|
||||
logger.info("No orders for today")
|
||||
return
|
||||
|
||||
post = "Here are today's orders for @%s - \n\n" % MASTODON_USERNAME
|
||||
if "count" in orders_info and orders_info['count'] > 1:
|
||||
post += f"These are the same orders from the last {orders_info['count']} days\n\n"
|
||||
post += "\n".join(orders_info['orders'])
|
||||
|
||||
async with make_session() as session:
|
||||
m = Mastodon(session)
|
||||
status = await m.statusPost(post)
|
||||
db = Database()
|
||||
|
||||
db.order_status_put(
|
||||
status['id'],
|
||||
status['created_at'],
|
||||
post
|
||||
)
|
||||
|
||||
async def order_check():
|
||||
async with make_session() as session:
|
||||
m = Mastodon(session)
|
||||
db = Database()
|
||||
|
||||
outstanding_orders = db.order_status_outstanding()
|
||||
for outstanding_order in outstanding_orders:
|
||||
context = await m.statusContext(outstanding_order['mastodon_id'])
|
||||
|
||||
confirmed_at = None
|
||||
for d in context['descendants']:
|
||||
if (
|
||||
d['in_reply_to_id'] == outstanding_order['mastodon_id'] and
|
||||
d['account']['username'] == MASTODON_USERNAME and
|
||||
len(d['media_attachments']) > 0
|
||||
):
|
||||
confirmed_at = d['created_at']
|
||||
db.order_status_confirm(outstanding_order['id'], confirmed_at)
|
||||
logger.info('Confirmed order %s' % (outstanding_order['created_at']))
|
||||
break
|
||||
|
||||
if confirmed_at is None:
|
||||
logger.info('Order %s remains unconfirmed' % (outstanding_order['created_at']))
|
||||
|
||||
oo_created_at = datetime.datetime.fromisoformat(outstanding_order['created_at'])
|
||||
if(oo_created_at + ORDER_TIMEOUT < datetime.datetime.now(datetime.UTC)):
|
||||
logger.info('Time to issue a punishment for %s' % outstanding_order['created_at'])
|
||||
|
||||
punishment = generate_punishment()
|
||||
|
||||
post = "@%s has failed to post proof of compliance. Punishment is as follows -\n\n" % MASTODON_USERNAME
|
||||
post += "\n".join(punishment)
|
||||
|
||||
punishment_status = await m.statusPost(
|
||||
post,
|
||||
in_reply_to_id=outstanding_order['mastodon_id']
|
||||
)
|
||||
db.punishment_status_put(
|
||||
outstanding_order['id'],
|
||||
punishment_status['id'],
|
||||
punishment_status['created_at'],
|
||||
post
|
||||
)
|
||||
45
scheduling.py
Normal file
45
scheduling.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import logging
|
||||
import datetime
|
||||
import pytz
|
||||
|
||||
from scheduler.asyncio import Scheduler
|
||||
|
||||
from settings import TIMEZONE, ORDER_TIME, ORDER_TIMEOUT
|
||||
from orders import order_issue, order_check
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SATURDAY = 5
|
||||
SUNDAY = 6
|
||||
|
||||
class OrderScheduler():
|
||||
def __init__(self, loop):
|
||||
self.tz = pytz.timezone(TIMEZONE)
|
||||
|
||||
self.scheduler = Scheduler(loop=loop, tzinfo=self.tz)
|
||||
|
||||
order_time_arr = list(map(int, ORDER_TIME.split(':')))
|
||||
order_time = datetime.time(hour=order_time_arr[0], minute=order_time_arr[1], tzinfo=self.tz)
|
||||
self.scheduler.daily(order_time, self.scheduled_order)
|
||||
|
||||
logger.info(self.scheduler)
|
||||
|
||||
async def scheduled_order(self):
|
||||
day_of_week = datetime.datetime.now(tz=self.tz).weekday()
|
||||
if (day_of_week in [SATURDAY, SUNDAY]):
|
||||
return
|
||||
|
||||
await order_issue()
|
||||
|
||||
# Schedule check
|
||||
self.scheduler.once(
|
||||
datetime.datetime.now(tz=self.tz) +
|
||||
ORDER_TIMEOUT +
|
||||
datetime.timedelta(seconds=10),
|
||||
self.scheduled_check
|
||||
)
|
||||
|
||||
logger.info(self.scheduler)
|
||||
|
||||
async def scheduled_check(self):
|
||||
await order_check()
|
||||
17
settings.py
Normal file
17
settings.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import os
|
||||
import datetime
|
||||
|
||||
ORDER_TIME = os.environ.get('ORDER_TIME', '9:00')
|
||||
ORDER_TIMEOUT = datetime.timedelta(
|
||||
hours=os.environ.get('ORDER_TIMEOUT', 3)
|
||||
)
|
||||
MASTODON_USERNAME = os.environ.get('MASTODON_USERNAME')
|
||||
|
||||
MASTODON_INSTANCE = os.environ.get("MASTODON_INSTANCE")
|
||||
MASTODON_ACCESS_TOKEN = os.environ.get('MASTODON_ACCESS_TOKEN')
|
||||
|
||||
SQLITE_DB = os.environ.get('SQLITE_DB', 'db.sqlite3')
|
||||
|
||||
ORDERS_YML = os.environ.get('ORDERS_YML', 'orders.yml')
|
||||
|
||||
TIMEZONE = os.environ.get('TIMEZONE', 'America/Chicago')
|
||||
4
util.py
Normal file
4
util.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import aiohttp
|
||||
|
||||
def make_session():
|
||||
return aiohttp.ClientSession()
|
||||
Loading…
Reference in a new issue