]> jfr.im git - erebus.git/blob - modules/reddark.py
implement handleState and getText
[erebus.git] / modules / reddark.py
1 # Reddark streaming module
2 # vim: fileencoding=utf-8
3
4 # module info
5 modinfo = {
6 'author': 'Multiple',
7 'license': 'unknown',
8 'compatible': [0], # compatible module API versions
9 'depends': [], # other modules required to work properly?
10 'softdeps': ['help'], # modules which are preferred but not required
11 }
12 # note: softdeps will be loaded before this module, IF not disabled in the configuration (autoload.module = 0) (and if it exists)
13 # however, if it is disabled it will be silently ignored, and if it is unloaded at runtime it won't cause this one to unload.
14 #
15 # basically, softdeps are things this module will use if available, but does not require (no errors will occur if it's not loaded)
16 # for example, @lib.help() will attempt to use the help module, but swallow errors if it is not loaded
17
18
19 # global variables
20 wants_to_stop = False
21 runner = None
22 last_update = 0
23 update_interval = 600
24 last_topic = ""
25
26 # preamble
27 import modlib
28 lib = modlib.modlib(__name__)
29 def modstart(parent, *args, **kwargs):
30 gotParent(parent)
31 return lib.modstart(parent, *args, **kwargs)
32 def modstop(*args, **kwargs):
33 global wants_to_stop, runner
34 if runner:
35 debug("Stopping runner")
36 wants_to_stop = True
37 runner.join()
38 debug("runner stopped")
39 wants_to_stop = False
40 return lib.modstop(*args, **kwargs)
41
42 # module code
43 from modules.contrib.sseclient import SSEClient
44 import requests
45 from bs4 import BeautifulSoup
46 import threading, json, time, collections, re
47
48 def chan():
49 return lib.parent.channel(lib.parent.cfg.get('reddark', 'channel', default="##.test"))
50
51 def bot():
52 return chan().bot
53
54 def debug(message, send_to_owner=True):
55 if lib.parent is None:
56 print(message)
57 return
58 if lib.parent.cfg.getboolean('reddark', 'debug', True):
59 lib.parent.log('Reddark', 'R', message)
60 if send_to_owner and lib.parent.cfg.get('debug', 'owner'):
61 bot().fastmsg(lib.parent.cfg.get('debug', 'owner'), message)
62
63 def getText(subreddit):
64 r = None
65 e = None
66 try:
67 r = requests.get('https://old.reddit.com/' + subreddit, headers={'User-Agent': 'better-see-reason-bot/1.0'})
68 except Exception as e2:
69 e = e2
70 if r is None:
71 debug("Error getting text: " + repr(e))
72 return
73 if r.status_code != 403:
74 debug("Error getting text: " + str(r.status_code) + " (403 expected)")
75 return
76 soup = BeautifulSoup(r.text, 'html.parser')
77 elements = soup.find_all(class_='interstitial-subreddit-description')
78 if elements[0]:
79 text = elements[0].get_text()
80 text = re.sub(re.escape(subreddit), '', text, re.IGNORECASE).replace("\n", " ")
81 return text
82
83 def handleDelta(message):
84 message['state'] = message['state'].lower()
85 message['previous_state'] = message['previous_state'].lower()
86 if message['state'] == 'private':
87 message['text'] = getText(message['name'])
88 print(repr(message))
89 chan().msg('[%(section)s] %(name)s went %(state)s (was: %(previous_state)s) (https://old.reddit.com/%(name)s) - %(text)s' % message, truncate=True)
90 elif message['state'] == 'restricted':
91 chan().msg('[%(section)s] %(name)s went %(state)s (was: %(previous_state)s) (https://old.reddit.com/%(name)s)' % message, truncate=True)
92 else:
93 chan().msg('[%(section)s] %(name)s went \x02%(state)s\x02 (was: %(previous_state)s) (https://old.reddit.com/%(name)s)' % message, truncate=True)
94
95 def handleState(message):
96 global last_update, update_interval, last_topic
97 if time.time() < last_update+update_interval:
98 return
99 output = collections.defaultdict(int)
100 output['totalSubs'] = len(message['subreddits'])
101 for sub in message['subreddits']:
102 if sub['state'] == 'PRIVATE':
103 output['privateSubs'] += 1
104 output['protestingSubs'] += 1
105 elif sub['state'] == 'RESTRICTED':
106 output['restrictedSubs'] += 1
107 output['protestingSubs'] += 1
108 elif sub['state'] == 'PUBLIC':
109 output['publicSubs'] += 1
110 output['pct'] = round(output['protestingSubs']/output['totalSubs']*100, 2)
111 output['pctPublic'] = round(output['publicSubs']/output['totalSubs']*100, 2)
112 output['pctPrivate'] = round(output['privateSubs']/output['totalSubs']*100, 2)
113 output['pctRestricted'] = round(output['restrictedSubs']/output['totalSubs']*100, 2)
114 newTopic = 'subreddits protesting: %(protestingSubs)s out of %(totalSubs)s pledged (%(pct)s%%) | private: %(privateSubs)s (%(pctPrivate)s%%), restricted: %(restrictedSubs)s (%(pctRestricted)s%%), public: %(publicSubs)s (%(pctPublic)s%%)' % dict(output)
115 debug(newTopic, False)
116 if last_topic != newTopic:
117 last_topic = newTopic
118 last_update = time.time()
119 bot().conn.send("TOPIC %(chan)s :%(topic)s" % {'chan': chan(), 'topic': newTopic})
120
121 def gotParent(parent):
122 global runner
123 def loop_messages():
124 global wants_to_stop
125 messages = SSEClient(parent.cfg.get('reddark', 'sse', default="https://reddark.rewby.archivete.am/sse"), timeout=60)
126 debug("Connected to SSE", False)
127 for msg in messages:
128 if wants_to_stop:
129 return
130 if len(msg.data) == 0:
131 continue
132 data = json.loads(msg.data)
133 if data['type'] == 'Delta':
134 debug(repr(data), False)
135 handleDelta(data['content'])
136 elif data['type'] == 'CurrentStateUpdate':
137 debug(repr(data)[0:500], False)
138 handleState(data['content'])
139 else:
140 debug("Unknown event: " + msg, False)
141 #{"type":"Delta","content":{"name":"r/TrainCrashSeries","section":"1k+","previous_state":"PRIVATE","state":"RESTRICTED"}}
142 #{"type":"CurrentStateUpdate","content":{"sections":["40+ million","30+ million","20+ million","10+ million","5+ million","1+ million","500k+","250k+","100k+","50k+","5k+","1k+","1k and below"],"subreddits":[{"name":"r/032r4r","section":"1k+","state":"PRIVATE"},{"name":"r/0sanitymemes","section":"5k+","state":"PRIVATE"},{"name":"r/1022","section":"5k+","state":"PRIVATE"},{"name":"r/11foot8","section":"100k+","state":"PRIVATE"},{"name":"r/1200isjerky","section":"50k+","state":"PRIVATE"},{"name":"r
143
144 runner = threading.Thread(target=loop_messages)
145 runner.daemon = True
146 runner.start()
147
148 @lib.hook(needchan=False, glevel=50)
149 @lib.help(None, 'sets reddark topic suffix')
150 def topicsuffix(bot, user, chan, realtarget, *args):
151 if chan is not None: replyto = chan
152 else: replyto = user
153
154 lib.parent.cfg.set('reddark', 'topicsuffix', ' '.join(args))
155
156 bot.msg(replyto, "Updated topic suffix")