Merge branch 'dev'

master
Thomas Lynch 2 years ago
commit e61e075593
  1. 4
      components/evaluators.py
  2. 18
      components/notifiers.py
  3. 27
      components/watchers.py
  4. 40
      notification_button.py
  5. 27
      session.py

@ -25,8 +25,6 @@ class PostEvaluator(Evaluator):
trigger_urls = [
*filter(self.url_blacklist_re.match, self._url_extractor.find_urls(text, only_unique=True))
] if self.url_blacklist_re and text else []
trigger_entries = [
_format_match(entry) for entry in re.finditer(self.blacklist_re, text)
] if self.blacklist_re and text else []
trigger_entries = re.findall(self.blacklist_re, text) if self.blacklist_re and text else []
logging.debug(f'Evaluated text:{text}\ntrigger urls:{trigger_urls}\ntrigger entries:{trigger_entries}')
return trigger_urls, trigger_entries

@ -1,17 +1,29 @@
import subprocess
from os import getcwd
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def notify(self, title, content, *args, **kwargs):
raise NotImplementedError
class TermuxNotifier(Notifier):
def notify(self, title, content, *args, **kwargs):
subprocess.call(['termux-notification', '--title', title, '--content', content])
args = ['termux-notification', '--title', title,
'--content', content or 'No Message']
#open link when clicking on notification
if 'link' in kwargs:
args = args + ['--action', f'termux-open-url {kwargs["link"]}']
#add buttons to the notification
if 'buttons' in kwargs:
post = kwargs["post"]
for i, button in enumerate(kwargs["buttons"], start=1):
args = args + [f'--button{i}', button["text"],
f'--button{i}-action', f'python3 {getcwd()}/notification_button.py -b {post["board"]} -p {post["postId"]} -a {button["actions"]}']
subprocess.call(args)
class NotifySendNotifier(Notifier):
def notify(self, title, content, *args, **kwargs):

@ -5,9 +5,9 @@ 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_path(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):
@ -35,18 +35,22 @@ class RecentWatcher(Watcher):
def connect():
logging.debug(f'Live posts client connected')
client.emit('room', f'{board}-manage-recent-hashed' if board else 'globalmanage-recent-hashed')
notify(f'Connected', f'Watching live posts')
@client.event
def disconnect():
logging.error(f'Live posts client disconnected')
notify(f'Lost live posts connection', f'Retrying in {reconnection_delay} seconds')
@client.on('newPost')
def on_new_post(post):
urls, entries = evaluate(post["nomarkup"])
if urls or entries:
notify(f'Alert! {get_path(post)}', '\n'.join(urls) + '\n'.join(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()
@ -66,6 +70,7 @@ class ReportsWatcher(Watcher):
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
@ -80,15 +85,19 @@ class ReportsWatcher(Watcher):
try:
reported_posts, num_reported_posts = self.fetch_reports()
if 0 < num_reported_posts != self.known_reports:
self.notify(f'New reports!', "\n".join([
f'{get_path(p)} {[r["reason"] for r in (p["globalreports"] if "globalreports" in p else p["reports"])]}'
for p in reported_posts]))
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')
self.notify(f'Error while fetching reports', f'Trying to reconnect')
if self._stp.wait(self.fetch_interval):
logging.info("Exiting reports watcher")

@ -0,0 +1,40 @@
import logging
import subprocess
import sys, getopt
from config import config
from session import ModSession
def main(argv):
logging.basicConfig(level=logging.DEBUG,
format='[%(asctime)s %(funcName)s] %(message)s (%(name)s)')
try:
opts, args = getopt.getopt(argv, 'b:p:a:', ['board=', 'postid=', 'actions='])
optdict = dict(opts)
except getopt.GetoptError:
logging.error('invalid arguments')
sys.exit(1)
session = ModSession(imageboard=config.IMAGEBOARD, username=config.ACCOUNT_USERNAME,
password=config.ACCOUNT_PASSWORD, retries=config.REQUEST_RETRIES,
timeout=config.REQUEST_TIMEOUT, backoff_factor=config.RETRIES_BACKOFF_FACTOR)
session.update_csrf()
res = session.post_actions(board=optdict['-b'], postid=optdict['-p'], actions=optdict['-a'])
toast_message = None
if 'message' in res:
toast_message = res['message']
elif 'messages' in res:
toast_message = "\n".join(res['messages'])
elif 'error' in res:
toast_message = res['error']
elif 'errors' in res:
toast_message = "\n".join(res['errors'])
if toast_message:
subprocess.call(['termux-toast', toast_message])
if __name__ == '__main__':
main(sys.argv[1:])

@ -12,6 +12,7 @@ class ModSession(Session):
self.imageboard = imageboard
self.imageboard_url = f"https://{imageboard}"
self.auth_params = {'username': username, 'password': password}
self.csrf_token = None
# overwrites session default behaviour
self.mount(self.imageboard_url, HTTPAdapter(max_retries=Retry(total=retries, backoff_factor=backoff_factor)))
@ -32,3 +33,29 @@ class ModSession(Session):
except requests.RequestException as e: # ambiguous catch but atm nothing can be done in more specific cases
logging.error(f'Exception {e} occurred while authenticating moderator')
raise Exception('Unable to authenticate moderator')
def update_csrf(self):
try:
res = self.get(url=f'{self.imageboard_url}/csrf.json',
headers={'Referer': f'{self.imageboard_url}/csrf.json'}).json()
if 'token' in res:
self.csrf_token = res['token']
else:
raise Exception('Unable to update csrf token')
except requests.RequestException as e:
logging.error(f'Exception {e} occurred while updating csrf token')
raise Exception('Unable to update csrf token')
def post_actions(self, **kwargs):
try:
actions = kwargs['actions'].split(',')
body = {'checkedposts':kwargs["postid"],'_csrf':self.csrf_token,'log_message':'globalafk'}
for action in actions:
body[action] = '1'
res = self.post(url=f'{self.imageboard_url}/forms/board/{kwargs["board"]}/modactions',
headers={'Referer': f'{self.imageboard_url}/forms/board/{kwargs["board"]}/modactions','x-using-xhr': 'true'},
data=body).json()
return res
except requests.RequestException as e:
logging.error(f'Exception {e} occurred while posting action')
raise Exception('Failed to submit post actions')

Loading…
Cancel
Save