1 # Reddark streaming module
2 # vim: fileencoding=utf-8
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
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.
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
27 lib
= modlib
.modlib(__name__
)
28 def modstart(parent
, *args
, **kwargs
):
30 return lib
.modstart(parent
, *args
, **kwargs
)
31 def modstop(*args
, **kwargs
):
32 global wants_to_stop
, runner
34 debug("Stopping runner")
35 wants_to_stop
= True # tell the SSE thread to stop the next time it wakes up
36 runner
.join() # blocks the main thread til the next chunk received (or timeout)! oh well.
37 debug("runner stopped")
39 return lib
.modstop(*args
, **kwargs
)
42 from modules
.contrib
.sseclient
import SSEClient
44 from bs4
import BeautifulSoup
45 import threading
, json
, time
, collections
, re
48 return lib
.parent
.channel(lib
.parent
.cfg
.get('reddark', 'channel', default
="##.test"))
54 return chan().msg(message
, truncate
=True, msgtype
="NOTICE")
56 def debug(message
, send_to_owner
=True):
57 if lib
.parent
is None:
60 if lib
.parent
.cfg
.getboolean('reddark', 'debug', True):
61 lib
.parent
.log('Reddark', 'R', message
)
62 if send_to_owner
and lib
.parent
.cfg
.get('debug', 'owner'):
63 bot().fastmsg(lib
.parent
.cfg
.get('debug', 'owner'), message
)
65 def getText(subreddit
):
69 r
= requests
.get('https://old.reddit.com/' + subreddit
, headers
={'User-Agent': 'better-see-reason-bot/1.0'}
)
70 except Exception as e2
:
73 debug("Error getting text: " + repr(e
), False)
75 if r
.status_code
!= 403:
76 debug("Error getting text: " + str(r
.status_code
) + " (403 expected)", False)
78 soup
= BeautifulSoup(r
.text
, 'html.parser')
79 elements
= soup
.find_all(class_
='interstitial-subreddit-description')
81 text
= elements
[0].get_text()
82 text
= re
.sub(re
.escape(subreddit
), '', text
, re
.IGNORECASE
).replace("\n", " ")
86 def handleDelta(message
):
87 message
['state'] = message
['state'].lower()
88 message
['previous_state'] = message
['previous_state'].lower()
89 if message
['state'] == 'private':
90 message
['text'] = getText(message
['name'])
92 chanmsg('[%(section)s] %(name)s went %(state)s (was: %(previous_state)s) (https://old.reddit.com/%(name)s)%(text)s' % message
)
93 elif message
['state'] == 'public':
94 chanmsg('[%(section)s] %(name)s went \x02%(state)s\x02 (was: %(previous_state)s) (https://old.reddit.com/%(name)s)' % message
)
96 chanmsg('[%(section)s] %(name)s went %(state)s (was: %(previous_state)s) (https://old.reddit.com/%(name)s)' % message
)
98 def handleState(message
):
99 global last_update
, last_topic
100 if time
.time() < last_update
+ lib
.parent
.cfg
.getint('reddark', 'update_interval', 600):
102 output
= collections
.defaultdict(int)
103 output
['totalSubs'] = len(message
['subreddits'])
104 for sub
in message
['subreddits']:
105 if sub
['state'] == 'PRIVATE':
106 output
['privateSubs'] += 1
107 output
['protestingSubs'] += 1
108 elif sub
['state'] == 'PUBLIC':
109 output
['publicSubs'] += 1
111 output
['restrictedSubs'] += 1
112 output
['protestingSubs'] += 1
113 output
['pct'] = round(output
['protestingSubs']/output
['totalSubs']*100, 2)
114 output
['pctPublic'] = round(output
['publicSubs']/output
['totalSubs']*100, 2)
115 output
['pctPrivate'] = round(output
['privateSubs']/output
['totalSubs']*100, 2)
116 output
['pctRestricted'] = round(output
['restrictedSubs']/output
['totalSubs']*100, 2)
117 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
)
118 debug(newTopic
, False)
119 if last_topic
!= newTopic
:
120 last_topic
= newTopic
121 last_update
= time
.time()
122 bot().conn
.send("TOPIC %(chan)s :%(topic)s %(suffix)s" % {'chan': chan(), 'topic': newTopic, 'suffix': lib.parent.cfg.get('reddark', 'topicsuffix', '| Reddark That Works™ https://reddark.rewby.archivete.am/ (https://github.com/reddark-remix/reddark-remix) | https://www.youtube.com/watch?v=xMaE6toi4mk')}
)
124 def gotParent(parent
):
128 messages
= SSEClient(parent
.cfg
.get('reddark', 'sse', default
="https://reddark.rewby.archivete.am/sse"), timeout
=60)
129 debug("Connected to SSE", False)
134 if len(msg
.data
) == 0:
136 data
= json
.loads(msg
.data
)
137 debug(repr(data
)[0:500], False)
138 if data
['type'] == 'Delta':
139 handleDelta(data
['content'])
140 elif data
['type'] == 'CurrentStateUpdate':
141 handleState(data
['content'])
143 debug("Unknown event: " + msg
, False)
144 except Exception as e
:
145 debug("Failed to parse an event: " + repr(e
))
146 #{"type":"Delta","content":{"name":"r/TrainCrashSeries","section":"1k+","previous_state":"PRIVATE","state":"RESTRICTED"}}
147 #{"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
149 runner
= threading
.Thread(target
=loop_messages
)
153 @lib.hook(needchan
=False, glevel
=50)
154 @lib.help('[<suffix>]', 'sets reddark topic suffix')
155 def topicsuffix(bot
, user
, chan
, realtarget
, *args
):
156 if chan
is not None: replyto
= chan
159 lib
.parent
.cfg
.set('reddark', 'topicsuffix', ' '.join(args
))
161 bot
.msg(replyto
, "Updated topic suffix")
163 @lib.hook(needchan
=False, glevel
=50)
164 @lib.help('<seconds>', 'sets reddark topic max update interval')
166 def updateinterval(bot
, user
, chan
, realtarget
, *args
):
167 if chan
is not None: replyto
= chan
170 lib
.parent
.cfg
.set('reddark', 'update_interval', int(' '.join(args
)))
172 bot
.msg(replyto
, "Updated topic interval")