fulvia/modules/watcher.py

197 lines
4.8 KiB
Python
Raw Normal View History

2018-03-16 03:13:43 -04:00
#!/usr/bin/env python3
"""
A thread watcher module for 4chan.
"""
import time
import threading
import requests
from module import commands, example
def setup(bot):
"""
Establishes the bot's dictionary of watched threads.
"""
if not bot.memory.get("watcher"):
bot.memory["watcher"] = {}
con = bot.db.connect()
cur = con.cursor()
try:
watching = cur.execute("SELECT * FROM watcher").fetchall()
except:
cur.execute("CREATE TABLE watcher("
"api_url TEXT PRIMARY KEY,"
"name TEXT DEFAULT 'Anonymous',"
"last_post INTEGER,"
2019-01-17 11:18:15 -05:00
"time_since TEXT,"
"channel TEXT)")
2018-03-16 03:13:43 -04:00
con.commit()
else:
for thread in watching:
if get_thread_url(thread[0]) in bot.memory["watcher"].keys():
continue
2019-01-17 11:18:15 -05:00
t = WatcherThread(bot, *thread)
2018-03-16 03:13:43 -04:00
t.start()
bot.memory["watcher"][get_thread_url(thread[0])] = t
con.close()
def get_time():
"""
Returns the current time formatted in If-Modified-Since notation.
"""
return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
def get_num_posts(thread, name):
"""
Gets the number of OP posts from the thread JSON.
"""
num = 0
for post in thread["posts"]:
if post.get("name") == name:
num += 1
return num
def get_last_post(thread, name):
"""
Gets the last post made by name.
"""
for post in reversed(thread["posts"]):
if post.get("name") == name:
return post.get("no")
def get_api_url(url):
"""
Returns the API url for the provided thread url.
"""
return url.replace("boards.4chan", "a.4cdn") + ".json"
def get_thread_url(api_url):
"""
Returns the normal thread url for the provided API url.
"""
return api_url.replace("a.4cdn", "boards.4chan").replace(".json", "")
@commands("watch")
2019-01-17 11:18:15 -05:00
@example(".watch https://boards.4chan.org/qst/thread/2 OPName")
2018-03-16 03:13:43 -04:00
def watch(bot, trigger):
"""
A thread watcher for 4chan.
"""
2020-01-07 18:58:19 -05:00
if len(trigger.args) < 2:
boy.reply("What thread?")
url = trigger.args[1]
name = trigger.args[2] if len(trigger.args) >= 3 else "Anonymous"
2018-03-16 03:13:43 -04:00
if url in bot.memory["watcher"].keys():
2018-05-25 15:21:18 -04:00
return bot.msg("Error: I'm already watching that thread.")
2018-03-16 03:13:43 -04:00
api_url = get_api_url(url)
res = requests.get(api_url, verify=True)
if res.status_code == 404:
2018-05-25 15:21:18 -04:00
return bot.msg("404: thread not found")
2018-03-16 03:13:43 -04:00
thread = res.json()
last_post = get_last_post(thread, name)
time_since = get_time()
2019-01-17 11:18:15 -05:00
t = WatcherThread(bot, api_url, name,last_post, time_since,trigger.channel)
2018-03-16 03:13:43 -04:00
t.start()
bot.memory["watcher"][url] = t
2019-01-17 11:18:15 -05:00
bot.db.execute(
"INSERT INTO watcher(api_url, name, last_post, time_since, channel) "
"VALUES(?,?,?,?,?)",
(api_url, name, last_post, time_since, trigger.channel))
2018-03-16 03:13:43 -04:00
2018-05-25 15:21:18 -04:00
bot.msg("[\x0304Watcher\x03] Watching thread: \x0307" + url)
2018-03-16 03:13:43 -04:00
@commands("unwatch")
@example(".unwatch https://boards.4chan.org/qst/thread/2")
def unwatch(bot, trigger):
"""
Stops the thread watcher thread for that thread.
"""
2020-01-07 18:58:19 -05:00
if len(trigger.args) < 2:
boy.reply("What thread?")
url = trigger.args[1]
2018-03-16 03:13:43 -04:00
try:
bot.memory["watcher"][url].stop.set()
bot.memory["watcher"].pop(url)
except KeyError:
2018-05-25 15:21:18 -04:00
return bot.msg("Error: I'm not watching that thread.")
2018-03-16 03:13:43 -04:00
removeThread(bot, get_api_url(url),)
2018-05-25 15:21:18 -04:00
bot.msg("[\x0304Watcher\x03] No longer watching: \x0307" + url)
2018-03-16 03:13:43 -04:00
def removeThread(bot, url):
"""
Removes the provided thread from the database. This should be the API url.
"""
bot.db.execute("DELETE FROM watcher WHERE api_url = ?", (url,))
class WatcherThread(threading.Thread):
2019-01-17 11:18:15 -05:00
def __init__(self, bot, api_url, name, last_post, time_since, channel):
2018-03-16 03:13:43 -04:00
threading.Thread.__init__(self)
self.stop = threading.Event()
self.period = 20
2019-01-17 11:18:15 -05:00
self.bot = bot
self.channel = channel
2018-03-16 03:13:43 -04:00
self.api_url = api_url
self.name = name
self.last_post = last_post
self.time_since = time_since
def run(self):
while not self.stop.is_set():
self.stop.wait(self.period)
headers = {"If-Modified-Since": self.time_since}
try:
res = requests.get(self.api_url, headers=headers, verify=True)
self.time_since = get_time()
except urllib3.exceptions.NewConnectionError:
print(f"Watcher: Thread {self.api_url}: Connection error")
continue
if res.status_code == 404:
2019-01-17 11:18:15 -05:00
msg = "[\x0304Watcher\x03] Thread deleted: "
msg += f"\x0307{get_thread_url(self.api_url)}"
self.bot.msg(self.channel, msg)
removeThread(self.bot, self.api_url)
2018-03-16 03:13:43 -04:00
self.stop.set()
continue
if res.status_code == 304:
continue
thread = res.json()
if thread["posts"][0].get("closed"):
2019-01-17 11:18:15 -05:00
msg = "[\x0304Watcher\x03] Thread closed: "
msg += f"\x0307{get_thread_url(self.api_url)}"
self.bot.msg(self.channel, msg)
removeThread(self.bot, self.api_url)
2018-03-16 03:13:43 -04:00
self.stop.set()
continue
new_last_post = get_last_post(thread, self.name)
if new_last_post > self.last_post:
self.last_post = new_last_post
2019-01-17 11:18:15 -05:00
print(self.last_post)
msg = "[\x0304Watcher\x03] New post from \x0308"
msg += f"{self.name}\x03: \x0307{get_thread_url(self.api_url)}"
msg += f"#{self.last_post}"
self.bot.msg(self.channel, msg)