-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathPageBot.py
273 lines (254 loc) · 9.66 KB
/
PageBot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
import praw
from praw.exceptions import APIException
from prawcore.exceptions import RequestException, NotFound, Forbidden
from praw.models import Comment, Message
import re, math, logging, time
import sys, os, traceback, signal
class PageBot():
user_agent = "User-Agent:Parliament Pager:v2.3.2 (by /u/Zagorath)"
def __init__(self):
reload(sys)
sys.setdefaultencoding('utf-8')
self.r = praw.Reddit('PageBot', user_agent=self.user_agent)
logging.basicConfig(
filename='pagebot.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
self.me = self.r.user.me().name
while True:
#if exiting:
# sys.exit(0)
try:
self.run()
time.sleep(30)
except RequestException as f:
logging.warning("Failed to connect or other RequestException, "
"outside of writing.")
time.sleep(30)
except Exception as e:
# If there's an error that isn't otherwise caught
# Make sure it is logged.
logging.critical(e)
exc_type, exc_obj, exc_tb = sys.exc_info()
msg = str(exc_type) + str(exc_tb.tb_lineno)
logging.critical(msg)
# Wait 15 minutes before trying again
time.sleep(15*60)
logging.debug("Starting again")
continue
def run(self):
"""Runs through all unread messages in the inbox
Passes valid mentions to the pager
Accepts mod invites
"""
for self.message in self.r.inbox.unread():
# Handle PMs. Mostly dealing with mod & approved submitter status
if isinstance(self.message, Message):
# If the message is a mod invitation, accept it
if self.message.body.startswith('**ggadzooks!'):
self.accept_mod_invite()
# If it's not a mod invite (mod acceptance, removal, approved submitter)
else:
logging.info("Message with id {} received titled {}".format(
self.message.id, self.message.subject))
# If the message is a comment
elif isinstance(self.message, Comment):
# If it mentions this bot and prefixes it with +
if "+/u/{}".format(self.me).lower() in self.message.body.lower():
self._log(logging.INFO, "Valid page order received")
self.page()
# If it's a comment that mentions but doesn't prefix with +
elif "u/{}".format(self.me).lower() in self.message.body.lower():
self._log(logging.INFO, "Bot mentioned, no page order")
# If it's a comment reply
else:
stop_messages = set(['stop', 'fuck', 'unsubscribe'])
stopped = False
# If indicates user wants to be removed from paging list contact mods.
for word in stop_messages:
if word in self.message.body:
unsub = ("This bot replies to all users on the subreddit's "
"paging list. Contact mods of the subreddit "
"being paged if you wish to be removed from the list.")
self.reply(unsub)
stopped = True
self._log(logging.CRITICAL, "Request to stop replied to")
# If the comment has an unknown cause
if not stopped:
self._log(logging.INFO, "Unknown comment received")
self._log(logging.DEBUG, "Message dealt with")
self.message.mark_read()
self._log(logging.DEBUG, "Message marked unread")
def page(self):
"""From the current self.message, parse out the page order
(demarked by "+/u/ParliamentPageBot" in this bot's main incarnation)
and page each of the users represented by the message.
"""
try:
self._log(logging.DEBUG, "Comment with body '{}' of type '{}'".format(
self.message.body, type(self.message.body)))
page_orders = re.split(
"\+\/u\/{}".format(self.me),
self.message.body,
flags=re.IGNORECASE
)
except UnicodeEncodeError as e:
logging.ERROR('Problem parsing the message because of Unicode.')
self.message.mark_read()
return
logging.debug("page_orders are '{}' of type '{}'".format(
page_orders, type(page_orders)))
# If exactly one page order is found
if len(page_orders) == 2:
self.parse_order(page_orders[1])
# If too many page orders are found
elif len(page_orders) > 2:
self.reply("More than one paging order found. "
"Please only use one page order per comment. "
"You can, however, page multiple subreddits' pagelists "
"by including more than one in the command.")
self._log(logging.INFO, "More than one page order found")
# This code should never be reached
else:
self._log(
logging.ERROR,
"Paging syntax was used without valid order "
)
def parse_order(self, page_order):
"""Takes a page order and adds all valid entries to the to_page list
page_order is the string representing list of subreddits to page,
and an instruction to be delivered with the command (in square brackets)
"""
self.reason = ''
self.to_page = set()
logging.debug(
"Page order in {} is {}".format(self.message.id, page_order))
try:
self.reason = page_order.split('[')[1].split(']')[0]
logging.debug("Reason for {} is {}".format(self.message.id, self.reason))
except IndexError as e:
self._log(logging.INFO, "No message contained in comment")
# Remove the reason and any newlines from the command
paging_commands = page_order.replace(self.reason, '')
paging_commands = paging_commands.replace('\n', ' ')
paging_commands = paging_commands.replace('\r', ' ')
here = set(['here', 'this', 'self'])
self._log(logging.DEBUG,
"Paging list (page order without reason): '{}'".format(paging_commands))
paging_list = paging_commands.split(' ')
logging.debug("Paging list: '{}'".format(paging_list))
# Remove punctuation from commands
paging_list = [l.rstrip("'\",.:;?!#&*()@") for l in paging_list]
# "item" should either be a subreddit (prefixed with /r/)
# or should be one of "here", "this", or "self"
for item in paging_list:
logging.debug("Item to be paged: {} of type {}".format(item, type(item)))
# if the item is a specified subreddit
if item.startswith("/r/") or item.startswith("r/"):
logging.debug("{} starts with /r/ or r/".format(item))
sub_name = item.replace('r/', '', 1).replace('/', '')
logging.debug("Subreddit's name is {}".format(sub_name))
s = self.r.subreddit(sub_name)
self.add_users(s)
# TODO: work out specific username paging, put it here
elif item.lower() in here:
logging.debug("The item is in the 'here' list")
self.add_users(self.message.subreddit)
logging.debug("Users being paged: ".format(self.to_page))
self.page_users()
logging.debug("All users have been paged")
def add_users(self, subreddit):
"""Adds users from the specified subreddit's "pagelist"
to the to_page list
"""
try:
wiki = subreddit.wiki['pagelist']
contents = wiki.content_md.split('\r\n\r\n')
logging.debug(
"Adding users to the to_page list from {}".format(subreddit))
for user in contents:
self.to_page.add(user)
if not isinstance(user, basestring):
logging.critical(
"Attempting to add '{}' to page list, not a str".format(user))
# If there is no pagelist page created
except NotFound as e:
error_message = "No paging list found at /r/{}/wiki/pagelist".format(
subreddit.display_name)
self._log(logging.INFO, "No wiki was found for {}".format(
subreddit.display_name))
self.reply(error_message)
except Forbidden as f:
error_message = ("Sorry, I don't have permission to read the wiki page "
"/r/{}/wiki/pagelist. Make sure the bot has permission to "
"read the wiki, either by adding it to the modlist with "
"wiki permission, or by making the wiki public.".format(
subreddit.display_name))
self._log(logging.INFO, "Was unable to view /r/{} wiki".format(
subreddit.display_name))
self.reply(error_message)
def page_users(self):
l = list(self.to_page)
num_replies = int(math.ceil(len(self.to_page)/3.0))
if num_replies < 1:
self._log(logging.WARNING, "No users were found to page")
self.reply("Sorry, but no users to page were found")
for i in range(num_replies):
# Page each of the members
if i*3 + 2 < len(self.to_page):
# If there are at least 3 members left to page, page the next three
self.reply("Paging {0}, {1}, and {2} {3}".format(
l[i*3], l[i*3 + 1], l[i*3 + 2], self.reason))
elif i*3 + 1 < len(self.to_page):
# If there are two more to be paged, page them
self.reply("Paging {0} and {1} {2}".format(
l[i*3], l[i*3 + 1], self.reason))
else:
# If only one member left to be paged, page them
self.reply("Paging {0} {1}".format(l[i*3], self.reason))
def reply(self, message):
"""Replies to self.message with the given message
If it to connect, it waits 1 minute before trying again,
doubling the time interval until it works.
"""
success = False
minutes = 1
while not success:
try:
self.message.reply(message)
success = True
except RequestException as e:
logging.warning("Failed to connect or other RequestException, "
"while writing.")
time.sleep(minutes * 60)
minutes *= 2
if minutes > 32:
self._log(
logging.ERROR,
"Have tried for over an hour to reply to message"
)
return
def accept_mod_invite(self):
logging.info("Received mod invite to {}".format(self.message.subreddit))
sr = self.message.subreddit
try:
sr.mod.accept_invite()
except APIException as e:
logging.warning(
"APIException raised in attempting to accept mod invite."
)
def _log(self, level, message):
message += " -- by {} with id {}".format(
self.message.author,
self.message.id)
logging.log(level, message)
#def signal_handler(signal, frame):
# exiting = True
# logging.warning("SIGINT detected, exiting gracefully.")
# print "SIGINT detected, exiting gracefully."
##############################################################
#exiting = False
if __name__ == '__main__':
#signal.signal(signal.SIGINT, signal_handler)
PageBot()