A simple python script that sends ugly notifications when something happens on a jschan imageboard that you moderate.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

104 lines
4.3 KiB

import logging
from abc import ABC
from threading import Thread, Event
import socketio
from requests import RequestException
def get_quote(post): return f'>>>/{post["board"]}/{post["thread"] or post["postId"]} ({post["postId"]})'
def get_manage_path(post): return f'/{post["board"]}/manage/thread/{post["thread"] or post["postId"]}.html#{post["postId"]}'
class Watcher(ABC, Thread):
def __init__(self, session):
Thread.__init__(self)
self.daemon = True
self._stp = Event() # _stop is reserved
self.session = session
def stop(self):
logging.debug(f'Killing thread, goodbye')
self._stp.set()
class RecentWatcher(Watcher):
def __init__(self, session, notify, evaluate, board=None, reconnection_delay=15):
super().__init__(session)
client = socketio.Client( # cannot use class variable to make the annotations
http_session=session,
reconnection_delay=reconnection_delay,
reconnection_delay_max=reconnection_delay
)
@client.event
def connect():
logging.debug(f'Live posts client connected')
client.emit('room', f'{board}-manage-recent-hashed' if board else 'globalmanage-recent-hashed')
@client.event
def disconnect():
logging.error(f'Live posts client disconnected')
@client.on('newPost')
def on_new_post(post):
urls, entries = evaluate(post["nomarkup"])
if urls or entries:
post_url=f'{session.imageboard_url}{get_manage_path(post)}'
buttons=[{"text":"Delete","actions":"delete"},
{"text":"Delete+Ban" if board else "Delete+Global Ban","actions":"delete,ban" if board else "delete,global_ban"}]
#todo: add this last button even if a board recents, because global staff can still global ban. but need a way to
#check if the account is global staff, which we dont have a json endpoint for in jschan yet.
#{"text":"Delete+Global Ban","actions":"dismiss" if board else "global_dismiss"}]
notify(f'Alert! {get_quote(post)}\n', post['nomarkup'], link=post_url, post=post, buttons=buttons)
self.client = client
self.start()
def run(self):
self.client.connect(f'wss://{self.session.imageboard}/', transports=['websocket'])
self.client.wait() # blocks the thread until something happens
if self._stp.wait():
logging.info("Exiting recent watcher")
self.client.disconnect()
class ReportsWatcher(Watcher):
def __init__(self, session, notify, board=None, fetch_interval=60 * 2):
super().__init__(session)
self.notify = notify
self.fetch_interval = fetch_interval
self.board = board
self._endpoint = f'{session.imageboard_url}/{f"{board}/manage" if board else "globalmanage"}/reports.json'
self.known_reports = 0
self.start()
def fetch_reports(self):
reports = self.session.get(url=self._endpoint).json()["reports"]
return reports, len(reports)
def run(self):
while True: # main loop, do while bootleg
try:
reported_posts, num_reported_posts = self.fetch_reports()
if 0 < num_reported_posts != self.known_reports:
for p in reported_posts:
post_url=f'{self.session.imageboard_url}{get_manage_path(p)}'
#todo: allow to customise these buttons somewhere
buttons=[{"text":"Delete","actions":"delete"},
{"text":"Delete+Ban" if self.board else "Delete+Global Ban","actions":"delete,ban" if self.board else "delete,global_ban"},
{"text":"Dismiss","actions":"dismiss" if self.board else "global_dismiss"}]
self.notify(f'New reports!', "\n".join([f'{get_quote(p)} {[r["reason"] for r in (p["globalreports"] if "globalreports" in p else p["reports"])]}']),
link=post_url, post=p, buttons=buttons)
self.known_reports = num_reported_posts
except RequestException as e:
logging.error(f'Exception {e} occurred while fetching reports')
if self._stp.wait(self.fetch_interval):
logging.info("Exiting reports watcher")
break