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
28 lib
= modlib
.modlib(__name__
)
29 def modstart(parent
, *args
, **kwargs
):
31 return lib
.modstart(parent
, *args
, **kwargs
)
32 def modstop(*args
, **kwargs
):
33 global wants_to_stop
, runner
35 debug("Stopping runner")
38 debug("runner stopped")
40 return lib
.modstop(*args
, **kwargs
)
43 from modules
.contrib
.sseclient
import SSEClient
45 from bs4
import BeautifulSoup
46 import threading
, json
, time
, collections
, re
49 return lib
.parent
.channel(lib
.parent
.cfg
.get('reddark', 'channel', default
="##.test"))
54 def debug(message
, send_to_owner
=True):
55 if lib
.parent
is None:
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
)
63 def getText(subreddit
):
67 r
= requests
.get('https://old.reddit.com/' + subreddit
, headers
={'User-Agent': 'better-see-reason-bot/1.0'}
)
68 except Exception as e2
:
71 debug("Error getting text: " + repr(e
))
73 if r
.status_code
!= 403:
74 debug("Error getting text: " + str(r
.status_code
) + " (403 expected)")
76 soup
= BeautifulSoup(r
.text
, 'html.parser')
77 elements
= soup
.find_all(class_
='interstitial-subreddit-description')
79 text
= elements
[0].get_text()
80 text
= re
.sub(re
.escape(subreddit
), '', text
, re
.IGNORECASE
).replace("\n", " ")
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'])
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)
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)
95 def handleState(message
):
96 global last_update
, update_interval
, last_topic
97 if time
.time() < last_update
+update_interval
:
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}
)
121 def gotParent(parent
):
125 messages
= SSEClient(parent
.cfg
.get('reddark', 'sse', default
="https://reddark.rewby.archivete.am/sse"), timeout
=60)
126 debug("Connected to SSE", False)
130 if len(msg
.data
) == 0:
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'])
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
144 runner
= threading
.Thread(target
=loop_messages
)
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
154 lib
.parent
.cfg
.set('reddark', 'topicsuffix', ' '.join(args
))
156 bot
.msg(replyto
, "Updated topic suffix")