def check_blacklist(string_to_test, is_username, is_watchlist): # Test the string and provide a warning message if it is already caught. if is_username: question = Post(api_response={'title': 'Valid title', 'body': 'Valid body', 'owner': {'display_name': string_to_test, 'reputation': 1, 'link': ''}, 'site': "", 'IsAnswer': False, 'score': 0}) answer = Post(api_response={'title': 'Valid title', 'body': 'Valid body', 'owner': {'display_name': string_to_test, 'reputation': 1, 'link': ''}, 'site': "", 'IsAnswer': True, 'score': 0}) else: question = Post(api_response={'title': 'Valid title', 'body': string_to_test, 'owner': {'display_name': "Valid username", 'reputation': 1, 'link': ''}, 'site': "", 'IsAnswer': False, 'score': 0}) answer = Post(api_response={'title': 'Valid title', 'body': string_to_test, 'owner': {'display_name': "Valid username", 'reputation': 1, 'link': ''}, 'site': "", 'IsAnswer': True, 'score': 0}) question_reasons, _ = FindSpam.test_post(question) answer_reasons, _ = FindSpam.test_post(answer) # Filter out duplicates reasons = list(set(question_reasons) | set(answer_reasons)) # Filter out watchlist results if not is_watchlist: reasons = list(filter(lambda reason: "potentially bad keyword" not in reason, reasons)) return reasons
def check_if_spam(title, body, user_name, user_url, post_site, post_id, is_answer, body_is_summary, owner_rep, post_score): if not body: body = "" test, why = FindSpam.test_post(title, body, user_name, post_site, is_answer, body_is_summary, owner_rep, post_score) if is_blacklisted_user(get_user_from_url(user_url)): test.append("blacklisted user") blacklisted_user_data = get_blacklisted_user_data( get_user_from_url(user_url)) if len(blacklisted_user_data) > 1: message_url = 'http:' + blacklisted_user_data[1] blacklisted_post_url = blacklisted_user_data[2] if blacklisted_post_url: rel_url = blacklisted_post_url.replace("http:", "", 1) why += u"\nBlacklisted user - blacklisted for {} (http://metasmoke.erwaysoftware.com/posts/by-url?url={}) by {}".format( blacklisted_post_url, rel_url, message_url) else: why += u"\n" + u"Blacklisted user - blacklisted by {}".format( message_url) if 0 < len(test): if has_already_been_posted(post_site, post_id, title) or is_false_positive((post_id, post_site)) \ or should_whitelist_prevent_alert(user_url, test) \ or is_ignored_post((post_id, post_site)) \ or is_auto_ignored_post((post_id, post_site)): return False, None, "" # Don't repost. Reddit will hate you. return True, test, why return False, None, ""
def check_if_spam(title, body, user_name, user_url, post_site, post_id, is_answer, body_is_summary): if not body: body = "" test, why = FindSpam.test_post(title, body, user_name, post_site, is_answer, body_is_summary) if is_blacklisted_user(get_user_from_url(user_url)): test.append("Blacklisted user") blacklisted_user_data = get_blacklisted_user_data( get_user_from_url(user_url)) if len(blacklisted_user_data) > 1: message_url = blacklisted_user_data[1] blacklisted_post_url = blacklisted_user_data[2] if blacklisted_post_url: why += u"Blacklisted user - blacklisted for {} by {}\n".format( blacklisted_post_url, message_url) else: why += u"Blacklisted user - blacklisted by {}\n".format( message_url) if 0 < len(test): if has_already_been_posted(post_site, post_id, title) or is_false_positive((post_id, post_site)) \ or should_whitelist_prevent_alert(user_url, test) \ or is_ignored_post((post_id, post_site)) \ or is_auto_ignored_post((post_id, post_site)): return False, None, "" # Don't repost. Reddit will hate you. return True, test, why return False, None, ""
def test_regexes(title, body, username, site, body_is_summary, is_answer, match): # If we want to test answers separately, this should be changed # is_answer = False post = Post( api_response={ 'title': title, 'body': body, 'owner': { 'display_name': username, 'reputation': 1, 'link': '' }, 'site': site, 'question_id': '1', 'IsAnswer': is_answer, 'BodyIsSummary': body_is_summary, 'score': 0 }) result = FindSpam.test_post(post)[0] log('info', title) log('info', "Result:", result) isspam = False if len(result) > 0: isspam = True if match != isspam: print((body, match)) assert match == isspam
def check_if_spam(post): # if not post.body: # body = "" # test, why = FindSpam.test_post(title, body, user_name, post_site, # is_answer, body_is_summary, owner_rep, post_score) test, why = FindSpam.test_post(post) if datahandling.is_blacklisted_user(parsing.get_user_from_url(post.user_url)): test.append("blacklisted user") blacklisted_user_data = datahandling.get_blacklisted_user_data(parsing.get_user_from_url(post.user_url)) if len(blacklisted_user_data) > 1: if blacklisted_user_data[1] == "metasmoke": blacklisted_by = "the metasmoke API" else: blacklisted_by = "http:" + blacklisted_user_data[1] blacklisted_post_url = blacklisted_user_data[2] if blacklisted_post_url: rel_url = blacklisted_post_url.replace("http:", "", 1) why += u"\nBlacklisted user - blacklisted for {} (" \ u"https://m.erwaysoftware.com/posts/by-url?url={}) by {}".format(blacklisted_post_url, rel_url, blacklisted_by) else: why += u"\n" + u"Blacklisted user - blacklisted by {}".format(blacklisted_by) if 0 < len(test): if datahandling.has_already_been_posted(post.post_site, post.post_id, post.title) \ or datahandling.is_false_positive((post.post_id, post.post_site)) \ or should_whitelist_prevent_alert(post.user_url, test) \ or datahandling.is_ignored_post((post.post_id, post.post_site)) \ or datahandling.is_auto_ignored_post((post.post_id, post.post_site)): return False, None, "" # Don't repost. Reddit will hate you. return True, test, why return False, None, ""
def check_if_spam(post): # if not post.body: # body = "" # test, why = FindSpam.test_post(title, body, user_name, post_site, # is_answer, body_is_summary, owner_rep, post_score) test, why = FindSpam.test_post(post) if datahandling.is_blacklisted_user( parsing.get_user_from_url(post.user_url)): test.append("blacklisted user") blacklisted_user_data = datahandling.get_blacklisted_user_data( parsing.get_user_from_url(post.user_url)) if len(blacklisted_user_data) > 1: if blacklisted_user_data[1] == "metasmoke": blacklisted_by = "the metasmoke API" else: blacklisted_by = "http:" + blacklisted_user_data[1] blacklisted_post_url = blacklisted_user_data[2] if blacklisted_post_url: rel_url = blacklisted_post_url.replace("http:", "", 1) why += u"\nBlacklisted user - blacklisted for {} (" \ u"https://m.erwaysoftware.com/posts/by-url?url={}) by {}".format(blacklisted_post_url, rel_url, blacklisted_by) else: why += u"\n" + u"Blacklisted user - blacklisted by {}".format( blacklisted_by) if 0 < len(test): if datahandling.has_already_been_posted(post.post_site, post.post_id, post.title) \ or datahandling.is_false_positive((post.post_id, post.post_site)) \ or should_whitelist_prevent_alert(post.user_url, test) \ or datahandling.is_ignored_post((post.post_id, post.post_site)) \ or datahandling.is_auto_ignored_post((post.post_id, post.post_site)): return False, None, "" # Don't repost. Reddit will hate you. return True, test, why return False, None, ""
def handlespam(data): try: d=json.loads(json.loads(data)["data"]) reason=",".join(FindSpam.testpost(d["titleEncodedFancy"],d["siteBaseHostAddress"])) s="[ [SmokeDetector](https://github.com/Charcoal-SE/SmokeDetector) ] %s: [%s](%s) on `%s`" % (reason,d["titleEncodedFancy"],d["url"],d["siteBaseHostAddress"]) print parser.unescape(s).encode('ascii',errors='replace') wrap.sendMessage("11540",s) wrapm.sendMessage("89",s) except UnboundLocalError: print "NOP"
def test_regexes(title, body, username, site, body_is_summary, match): # If we want to test answers separately, this should be changed is_answer = False result = FindSpam.test_post(title, body, username, site, is_answer, body_is_summary, 1, 0)[0] print title print result isspam = False if len(result) > 0: isspam = True assert match == isspam
def test_findspam(title, body, username, site, body_is_summary, is_answer, expected_spam): post = Post(api_response={'title': title, 'body': body, 'owner': {'display_name': username, 'reputation': 1, 'link': ''}, 'site': site, 'question_id': '1', 'IsAnswer': is_answer, 'BodyIsSummary': body_is_summary, 'score': 0}) result = FindSpam.test_post(post)[0] log('info', title) log('info', "Result:", result) scan_spam = (len(result) > 0) if scan_spam != expected_spam: print("Expected {1} on {0}".format(body, expected_spam)) assert scan_spam == expected_spam
def handlespam(data): try: d=json.loads(json.loads(data)["data"]) title = d["titleEncodedFancy"] reason=",".join(FindSpam.testpost(title,d["siteBaseHostAddress"])) escapedTitle = re.sub(r"([_*\\`\[\]])", r"\\\1", title) s="[ [SmokeDetector](https://github.com/Charcoal-SE/SmokeDetector) ] %s: [%s](%s) on `%s`" % (reason,escapedTitle,d["url"],d["siteBaseHostAddress"]) print parser.unescape(s).encode('ascii',errors='replace') room.send_message(s) roomm.send_message(s) except UnboundLocalError: print "NOP"
def check_if_spam(title, body, user_name, user_url, post_site, post_id, is_answer, body_is_summary): if not body: body = "" test, why = FindSpam.test_post(title, body, user_name, post_site, is_answer, body_is_summary) if is_blacklisted_user(get_user_from_url(user_url)): test.append("Blacklisted user") if 0 < len(test): if has_already_been_posted(post_site, post_id, title) or is_false_positive((post_id, post_site)) \ or should_whitelist_prevent_alert(user_url, test) \ or is_ignored_post((post_id, post_site)) \ or is_auto_ignored_post((post_id, post_site)): return False, None, "" # Don't repost. Reddit will hate you. return True, test, why return False, None, ""
def check_if_spam(title, body, user_name, user_url, post_site, post_id, is_answer, body_is_summary): if not body: body = "" test = FindSpam.test_post(title, body, user_name, post_site, is_answer, body_is_summary) if is_blacklisted_user(get_user_from_url(user_url)): test.append("Blacklisted user") if 0 < len(test): if has_already_been_posted(post_site, post_id, title) or is_false_positive((post_id, post_site)) \ or should_whitelist_prevent_alert(user_url, test) \ or is_ignored_post((post_id, post_site)) \ or is_auto_ignored_post((post_id, post_site)): return False, None # Don't repost. Reddit will hate you. return True, test return False, None
def test_regexes(title, body, username, site, body_is_summary, is_answer, match): # If we want to test answers separately, this should be changed # is_answer = False post = Post(api_response={'title': title, 'body': body, 'owner': {'display_name': username, 'reputation': 1, 'link': ''}, 'site': site, 'question_id': '1', 'IsAnswer': is_answer, 'BodyIsSummary': body_is_summary, 'score': 0}) result = FindSpam.test_post(post)[0] log('info', title) log('info', "Result:", result) isspam = False if len(result) > 0: isspam = True assert match == isspam
def checkifspam(data): d=json.loads(json.loads(data)["data"]) s= d["titleEncodedFancy"] print time.strftime("%Y-%m-%d %H:%M:%S"),parser.unescape(s).encode("ascii",errors="replace") site = d["siteBaseHostAddress"] site=site.encode("ascii",errors="replace") sys.stdout.flush() test=FindSpam.testpost(s,site) if (0<len(test)): post_id = d["id"] if(has_already_been_posted(site, post_id, s)): return False # Don't repost. Reddit will hate you. append_to_latest_questions(site, post_id, s) return True return False
def checkifspam(data): global lasthost,lastid d=json.loads(json.loads(data)["data"]) s= d["titleEncodedFancy"] print time.strftime("%Y-%m-%d %H:%M:%S"),parser.unescape(s).encode("ascii",errors="replace") site = d["siteBaseHostAddress"] site=site.encode("ascii",errors="replace") sys.stdout.flush() test=FindSpam.testpost(s,site) if (0<len(test)): if(lastid==d["id"] and lasthost == d["siteBaseHostAddress"]): return False # Don't repost. Reddit will hate you. lastid=d["id"] lasthost = d["siteBaseHostAddress"] return True return False
def check_if_spam(post): test, why = FindSpam.test_post(post) if datahandling.is_blacklisted_user( parsing.get_user_from_url(post.user_url)): test.append("blacklisted user") blacklisted_user_data = datahandling.get_blacklisted_user_data( parsing.get_user_from_url(post.user_url)) if len(blacklisted_user_data) > 1: if blacklisted_user_data[1] == "metasmoke": blacklisted_by = "the metasmoke API" else: blacklisted_by = blacklisted_user_data[1] blacklisted_post_url = blacklisted_user_data[2] if why and why[-1] == "\n": why = why[:-1] if blacklisted_post_url: rel_url = blacklisted_post_url.replace("http:", "", 1) why += u"\nBlacklisted user - blacklisted for {} ({}) by {}".format( blacklisted_post_url, to_metasmoke_link(rel_url), blacklisted_by) else: why += u"\n" + u"Blacklisted user - blacklisted by {}".format( blacklisted_by) if test: result = None if datahandling.has_already_been_posted(post.post_site, post.post_id, post.title): result = "post has already been reported" elif datahandling.is_false_positive((post.post_id, post.post_site)): result = "post is marked as false positive" elif should_whitelist_prevent_alert(post.user_url, test): result = "user is whitelisted" elif datahandling.is_ignored_post((post.post_id, post.post_site)): result = "post is ignored" elif datahandling.is_auto_ignored_post((post.post_id, post.post_site)): result = "post is automatically ignored" elif datahandling.has_community_bumped_post(post.post_url, post.body): result = "post is bumped by Community \u2666\uFE0F" # Dirty approach if result is None: # Post not ignored return True, test, why else: return False, (test, why), result # XXX: Return an empty string for "why" if the post isn't scanned as spam # Don't touch if unsure, you'll break !!/report return False, None, ""
def test(content, alias_used="test"): """ Test an answer to determine if it'd be automatically reported :param content: :return: A string """ result = "> " if alias_used == "test-q": kind = " question." fakepost = Post(api_response={'title': 'Valid title', 'body': content, 'owner': {'display_name': "Valid username", 'reputation': 1, 'link': ''}, 'site': "", 'IsAnswer': False, 'score': 0}) elif alias_used == "test-a": kind = "n answer." fakepost = Post(api_response={'title': 'Valid title', 'body': content, 'owner': {'display_name': "Valid username", 'reputation': 1, 'link': ''}, 'site': "", 'IsAnswer': True, 'score': 0}) elif alias_used == "test-u": kind = " username." fakepost = Post(api_response={'title': 'Valid title', 'body': "Valid question body", 'owner': {'display_name': content, 'reputation': 1, 'link': ''}, 'site': "", 'IsAnswer': False, 'score': 0}) elif alias_used == "test-t": kind = " title." fakepost = Post(api_response={'title': content, 'body': "Valid question body", 'owner': {'display_name': "Valid username", 'reputation': 1, 'link': ''}, 'site': "", 'IsAnswer': False, 'score': 0}) else: kind = " post, title or username." fakepost = Post(api_response={'title': content, 'body': content, 'owner': {'display_name': content, 'reputation': 1, 'link': ''}, 'site': "", 'IsAnswer': False, 'score': 0}) reasons, why_response = FindSpam.test_post(fakepost) if len(reasons) == 0: result += "Would not be caught as a{}".format(kind) else: result += ", ".join(reasons).capitalize() if why_response is not None and len(why_response) > 0: result += "\n----------\n" result += why_response return result
def handlespam(data): try: d=json.loads(json.loads(data)["data"]) title = d["titleEncodedFancy"] poster = d["ownerDisplayName"] reason=", ".join(FindSpam.testpost(title,poster,d["siteBaseHostAddress"])) titleToPost = GlobalVars.parser.unescape(re.sub(r"([_*\\`\[\]])", r"\\\1", title)).strip() s="[ [SmokeDetector](https://github.com/Charcoal-SE/SmokeDetector) ] %s: [%s](%s) by [%s](%s) on `%s`" % (reason,titleToPost,d["url"],poster,d["ownerUrl"],d["siteBaseHostAddress"]) print GlobalVars.parser.unescape(s).encode('ascii',errors='replace') if time.time() >= GlobalVars.blockedTime: GlobalVars.charcoal_hq.send_message(s) GlobalVars.tavern_on_the_meta.send_message(s) for specialroom in GlobalVars.specialrooms: sites = specialroom["sites"] if d["siteBaseHostAddress"] in sites and reason not in specialroom["unwantedReasons"]: specialroom["room"].send_message(s) except: print "NOP"
def command_test_username(content, content_lower, *args, **kwargs): """ Test a username to determine if it'd be automatically reported :param content_lower: :param content: :param kwargs: No additional arguments expected :return: A string """ string_to_test = content[10:] test_as_answer = False if len(string_to_test) == 0: return Response(command_status=True, message="Nothing to test") result = "> " reasons, why = FindSpam.test_post("Valid title", "Valid post body", string_to_test, "", test_as_answer, False, 1, 0) if len(reasons) == 0: result += "Would not be caught as a username." return Response(command_status=True, message=result) result += ", ".join(reasons).capitalize() if why is not None and len(why) > 0: result += "\n----------\n" result += why return Response(command_status=True, message=result)
def check_if_spam(title, body, user_name, user_url, post_site, post_id, is_answer, body_is_summary, owner_rep): if not body: body = "" test, why = FindSpam.test_post(title, body, user_name, post_site, is_answer, body_is_summary, owner_rep) if is_blacklisted_user(get_user_from_url(user_url)): test.append("Blacklisted user") blacklisted_user_data = get_blacklisted_user_data(get_user_from_url(user_url)) if len(blacklisted_user_data) > 1: message_url = 'http:' + blacklisted_user_data[1] blacklisted_post_url = blacklisted_user_data[2] if blacklisted_post_url: why += u"Blacklisted user - blacklisted for {} by {}\n".format(blacklisted_post_url, message_url) else: why += u"Blacklisted user - blacklisted by {}\n".format(message_url) if 0 < len(test): if has_already_been_posted(post_site, post_id, title) or is_false_positive((post_id, post_site)) \ or should_whitelist_prevent_alert(user_url, test) \ or is_ignored_post((post_id, post_site)) \ or is_auto_ignored_post((post_id, post_site)): return False, None, "" # Don't repost. Reddit will hate you. return True, test, why return False, None, ""
def checkifspam(data): d=json.loads(json.loads(data)["data"]) try: _ = d["ownerUrl"] except: return False # owner's account doesn't exist anymore, no need to post it in chat: http://chat.stackexchange.com/transcript/message/18380776#18380776 s= d["titleEncodedFancy"] poster = d["ownerDisplayName"] print time.strftime("%Y-%m-%d %H:%M:%S"),GlobalVars.parser.unescape(s).encode("ascii",errors="replace") quality_score = bayesian_score(s) print quality_score if(quality_score < 0.3 and d["siteBaseHostAddress"] == "stackoverflow.com"): print GlobalVars.bayesian_testroom.send_message("[ SmokeDetector | BayesianBeta ] Quality score " + str(quality_score*100) + ": [" + s + "](" + d["url"] + ")") site = d["siteBaseHostAddress"] site=site.encode("ascii",errors="replace") sys.stdout.flush() test=FindSpam.testpost(s,poster,site) if(is_blacklisted_user(get_user_from_url(d["ownerUrl"]))): if(len(test) == 0): test = "Blacklisted user" else: test += ", Blacklisted user" if (0<len(test)): post_id = d["id"] if(has_already_been_posted(site, post_id, s) or is_false_positive(post_id, site) or is_whitelisted_user(get_user_from_url(d["ownerUrl"]))): return False # Don't repost. Reddit will hate you. append_to_latest_questions(site, post_id, s) try: owner = d["ownerUrl"] users_file = open("users.txt", "a") users_file.write(site + " " + owner + " " + d["titleEncodedFancy"] + " " + d["url"] + "\n") users_file.close() except Exception as e: print e return True return False
def handle_commands(content_lower, message_parts, ev_room, ev_user_id, ev_user_name, wrap2, content, message_id): message_url = "//chat." + wrap2.host + "/transcript/message/" + str(message_id) second_part_lower = "" if len(message_parts) < 2 else message_parts[1].lower() if re.compile("^:[0-9]+$").search(message_parts[0]): msg_id = int(message_parts[0][1:]) msg = wrap2.get_message(msg_id) msg_content = msg.content_source quiet_action = ("-" in message_parts[1].lower()) if str(msg.owner.id) != GlobalVars.smokeDetector_user_id[ev_room] or msg_content is None: return post_url = fetch_post_url_from_msg_content(msg_content) post_site_id = fetch_post_id_and_site_from_msg_content(msg_content) if post_site_id is not None: post_type = post_site_id[2] else: post_type = None if (second_part_lower.startswith("false") or second_part_lower.startswith("fp")) \ and is_privileged(ev_room, ev_user_id, wrap2): if post_site_id is None: return "That message is not a report." t_metasmoke = Thread(target=Metasmoke.send_feedback_for_post, args=(post_url, second_part_lower, ev_user_name, )) t_metasmoke.start() add_false_positive((post_site_id[0], post_site_id[1])) user_added = False if message_parts[1].lower().startswith("falseu") or message_parts[1].lower().startswith("fpu"): url_from_msg = fetch_owner_url_from_msg_content(msg_content) if url_from_msg is not None: user = get_user_from_url(url_from_msg) if user is not None: add_whitelisted_user(user) user_added = True learned = False if post_type == "question": learned = bayesian_learn_title(fetch_title_from_msg_content(msg_content), "good") if learned and user_added and not quiet_action: return "Registered question as false positive, whitelisted user and added title to Bayesian doctype 'good'." elif learned and not quiet_action: return "Registered question as false positive and added title to Bayesian doctype 'good'." elif not learned: return "Registered question as false positive, but could not add title to Bayesian doctype 'good'." elif post_type == "answer": if user_added and not quiet_action: return "Registered answer as false positive and whitelisted user." elif not quiet_action: return "Registered answer as false positive." try: msg.delete() except: pass if (second_part_lower.startswith("true") or second_part_lower.startswith("tp")) \ and is_privileged(ev_room, ev_user_id, wrap2): if post_site_id is None: return "That message is not a report." t_metasmoke = Thread(target=Metasmoke.send_feedback_for_post, args=(post_url, second_part_lower, ev_user_name, )) t_metasmoke.start() learned = False user_added = False if message_parts[1].lower().startswith("trueu") or message_parts[1].lower().startswith("tpu"): url_from_msg = fetch_owner_url_from_msg_content(msg_content) if url_from_msg is not None: user = get_user_from_url(url_from_msg) if user is not None: add_blacklisted_user(user, message_url, post_url) user_added = True if post_type == "question": learned = bayesian_learn_title(fetch_title_from_msg_content(msg_content), "bad") if learned and user_added and not quiet_action: return "Blacklisted user and registered question as true positive: added title to the Bayesian doctype 'bad'." elif learned and not quiet_action: return "Registered question as true positive: added title to the Bayesian doctype 'bad'." elif not learned: return "Something went wrong when registering question as true positive." elif post_type == "answer": if user_added and not quiet_action: return "Blacklisted user." elif not user_added: return "`true`/`tp` cannot be used for answers because their job is to add the title of the *question* to the Bayesian doctype 'bad'. If you want to blacklist the poster of the answer, use `trueu` or `tpu`." if second_part_lower.startswith("ignore") and is_privileged(ev_room, ev_user_id, wrap2): if post_site_id is None: return "That message is not a report." add_ignored_post(post_site_id[0:2]) if not quiet_action: return "Post ignored; alerts about it will no longer be posted." if (second_part_lower.startswith("delete") or second_part_lower.startswith("remove") or second_part_lower.startswith("gone") or second_part_lower.startswith("poof") or second_part_lower == "del") and is_privileged(ev_room, ev_user_id, wrap2): try: msg.delete() except: pass # couldn't delete message if second_part_lower.startswith("why"): t = fetch_post_id_and_site_from_msg_content(msg_content) if t is None: return "That's not a report." post_id, site, _ = t why = get_why(site, post_id) if why is None or why == "": return "There is no `why` data for that post (anymore)." else: return why if content_lower.startswith("!!/addblu") \ and is_privileged(ev_room, ev_user_id, wrap2): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": add_blacklisted_user((uid, val), message_url, "") return "User blacklisted (`{}` on `{}`).".format(uid, val) elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/addblu profileurl` *or* `!!/addblu userid sitename`." if content_lower.startswith("!!/rmblu") \ and is_privileged(ev_room, ev_user_id, wrap2): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": if remove_blacklisted_user((uid, val)): return "User removed from blacklist (`{}` on `{}`).".format(uid, val) else: return "User is not blacklisted." elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/rmblu profileurl` *or* `!!/rmblu userid sitename`." if content_lower.startswith("!!/isblu"): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": if is_blacklisted_user((uid, val)): return "User is blacklisted. (`{}` on `{}`).".format(uid, val) else: return "User is not blacklisted. (`{}` on `{}`).".format(uid, val) elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/isblu profileurl` *or* `!!/isblu userid sitename`." if content_lower.startswith("!!/addwlu") \ and is_privileged(ev_room, ev_user_id, wrap2): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": add_whitelisted_user((uid, val)) return "User whitelisted (`{}` on `{}`).".format(uid, val) elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/addwlu profileurl` *or* `!!/addwlu userid sitename`." if content_lower.startswith("!!/rmwlu") \ and is_privileged(ev_room, ev_user_id, wrap2): uid, val = get_user_from_list_command(content_lower) if uid != -1 and val != "": if remove_whitelisted_user((uid, val)): return "User removed from whitelist (`{}` on `{}`).".format(uid, val) else: return "User is not whitelisted." elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/rmwlu profileurl` *or* `!!/rmwlu userid sitename`." if content_lower.startswith("!!/iswlu"): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": if is_whitelisted_user((uid, val)): return "User is whitelisted. (`{}` on `{}`).".format(uid, val) else: return "User is not whitelisted. (`{}` on `{}`).".format(uid, val) elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/iswlu profileurl` *or* `!!/iswlu userid sitename`." if content_lower.startswith("!!/report") \ and is_privileged(ev_room, ev_user_id, wrap2): if len(message_parts) < 2: return "Not enough arguments." url = message_parts[1] post_data = api_get_post(url) if post_data is None: return "That does not look like a valid post URL." if post_data is False: return "Could not find data for this post in the API. Check whether the post is not deleted yet." user = get_user_from_url(post_data.owner_url) if user is not None: add_blacklisted_user(user, message_url, post_data.post_url) bayesian_learn_title(post_data.title, "bad") handle_spam(post_data.title, post_data.body, post_data.owner_name, post_data.site, post_data.post_url, post_data.owner_url, post_data.post_id, ["Manually reported " + post_data.post_type], post_data.post_type == "answer") if content_lower.startswith("!!/wut"): return "Whaddya mean, 'wut'? Humans..." if content_lower.startswith("!!/lick"): return "*licks ice cream cone*" if content_lower.startswith("!!/alive"): if ev_room == GlobalVars.charcoal_room_id: return 'Of course' elif ev_room == GlobalVars.meta_tavern_room_id or ev_room == GlobalVars.socvr_room_id: return random.choice(['Yup', 'You doubt me?', 'Of course', '... did I miss something?', 'plz send teh coffee', 'Watching this endless list of new questions *never* gets boring', 'Kinda sorta']) if content_lower.startswith("!!/rev"): return '[' + \ GlobalVars.commit_with_author + \ '](https://github.com/Charcoal-SE/SmokeDetector/commit/' + \ GlobalVars.commit + \ ')' if content_lower.startswith("!!/status"): now = datetime.utcnow() diff = now - UtcDate.startup_utc_date minutes, remainder = divmod(diff.seconds, 60) minutestr = "minutes" if minutes != 1 else "minute" return 'Running since {} UTC ({} {})'.format(GlobalVars.startup_utc, minutes, minutestr) if content_lower.startswith("!!/reboot"): if is_privileged(ev_room, ev_user_id, wrap2): post_message_in_room(ev_room, "Goodbye, cruel world") os._exit(5) if content_lower.startswith("!!/stappit"): if is_privileged(ev_room, ev_user_id, wrap2): post_message_in_room(ev_room, "Goodbye, cruel world") os._exit(6) if content_lower.startswith("!!/master"): if is_privileged(ev_room, ev_user_id, wrap2): os._exit(8) if content_lower.startswith("!!/clearbl"): if is_privileged(ev_room, ev_user_id, wrap2): if os.path.isfile("blacklistedUsers.txt"): os.remove("blacklistedUsers.txt") GlobalVars.blacklisted_users = [] return "Kaboom, blacklisted users cleared." else: return "There are no blacklisted users at the moment." if content_lower.startswith("!!/block"): if is_privileged(ev_room, ev_user_id, wrap2): timeToBlock = content_lower[9:].strip() timeToBlock = int(timeToBlock) if timeToBlock else 0 if 0 < timeToBlock < 14400: GlobalVars.blockedTime = time.time() + timeToBlock else: GlobalVars.blockedTime = time.time() + 900 return "blocked" if content_lower.startswith("!!/unblock"): if is_privileged(ev_room, ev_user_id, wrap2): GlobalVars.blockedTime = time.time() return "unblocked" if content_lower.startswith("!!/errorlogs"): if is_privileged(ev_room, ev_user_id, wrap2): count = -1 if len(message_parts) != 2: return "The !!/errorlogs command requires 1 argument." try: count = int(message_parts[1]) except ValueError: pass if count == -1: return "Invalid argument." logs_part = fetch_lines_from_error_log(count) post_message_in_room(ev_room, logs_part, False) if content_lower.startswith("!!/pull"): if is_privileged(ev_room, ev_user_id, wrap2): r = requests.get('https://api.github.com/repos/Charcoal-SE/SmokeDetector/git/refs/heads/master') latest_sha = r.json()["object"]["sha"] r = requests.get('https://api.github.com/repos/Charcoal-SE/SmokeDetector/commits/' + latest_sha + '/statuses') states = [] for status in r.json(): state = status["state"] states.append(state) if "success" in states: os._exit(3) elif "error" in states or "failure" in states: return "CI build failed! :( Please check your commit." elif "pending" in states or not states: return "CI build is still pending, wait until the build has finished and then pull again." if content_lower.startswith("!!/help"): return "I'm [SmokeDetector](https://github.com/Charcoal-SE/SmokeDetector), a bot that detects spam and low-quality posts on the network and posts alerts to chat. [A command list is available here](https://github.com/Charcoal-SE/SmokeDetector/wiki/Commands)." if content_lower.startswith("!!/apiquota"): return GlobalVars.apiquota if content_lower.startswith("!!/whoami"): if (ev_room in GlobalVars.smokeDetector_user_id): return "My id for this room is {}".format(GlobalVars.smokeDetector_user_id[ev_room]) else: return "I don't know my user ID for this room. (Something is wrong, and it's apnorton's fault.)" if content_lower.startswith("!!/location"): return GlobalVars.location if content_lower.startswith("!!/queuestatus"): post_message_in_room(ev_room, GlobalVars.bodyfetcher.print_queue(), False) if content_lower.startswith("!!/blame") and (ev_room == GlobalVars.meta_tavern_room_id or ev_room == GlobalVars.socvr_room_id): GlobalVars.tavern_users_chatting = list(set(GlobalVars.tavern_users_chatting)) # Make unique user_to_blame = random.choice(GlobalVars.tavern_users_chatting) return "It's " + user_to_blame + "'s fault." if "smokedetector" in content_lower and "fault" in content_lower and ("xkcdbot" in ev_user_name.lower() or "bjb568" in ev_user_name.lower()): return "Liar" if content_lower.startswith("!!/coffee"): return "*brews coffee for @" + ev_user_name.replace(" ", "") + "*" if content_lower.startswith("!!/tea"): return "*brews a cup of " + random.choice(['earl grey', 'green', 'chamomile', 'lemon', 'darjeeling', 'mint']) + " tea for @" + ev_user_name.replace(" ", "") + "*" if content_lower.startswith("!!/brownie"): return "Brown!" if content_lower.startswith("!!/test"): string_to_test = content[8:] if len(string_to_test) == 0: return "Nothing to test" result = "> " reasons, why = FindSpam.test_post(string_to_test, string_to_test, string_to_test, "", False, False) if len(reasons) == 0: result += "Would not be caught for title, body and username." return result result += ", ".join(reasons).capitalize() if why is not None and len(why) > 0: result += "\n----------\n" result += why return result return None
def handle_commands(content_lower, message_parts, ev_room, ev_room_name, ev_user_id, ev_user_name, wrap2, content, message_id): if content_lower.startswith("!!/parse") \ and is_privileged(ev_room, ev_user_id, wrap2): string_to_parse = content[9:] print string_to_parse response = requests.get("http://*****:*****@" + ev_user_name.replace(" ", "") + "*" if content_lower.startswith("!!/tea"): return "*brews a cup of {choice} tea for @{user}*".format(choice=random.choice(['earl grey', 'green', 'chamomile', 'lemon', 'darjeeling', 'mint', 'jasmine']), user=ev_user_name.replace(" ", "")) if content_lower.startswith("!!/brownie"): return "Brown!" if content_lower.startswith("!!/hats"): wb_end = datetime(2016, 1, 4, 0, 0, 0) now = datetime.utcnow() if wb_end > now: diff = wb_end - now hours, remainder = divmod(diff.seconds, 3600) minutes, seconds = divmod(remainder, 60) daystr = "days" if diff.days != 1 else "day" hourstr = "hours" if hours != 1 else "hour" minutestr = "minutes" if minutes != 1 else "minute" secondstr = "seconds" if seconds != 1 else "second" return "HURRY UP AND EARN MORE HATS! Winterbash will be over in {} {}, {} {}, {} {}, and {} {}. :(".format(diff.days, daystr, hours, hourstr, minutes, minutestr, seconds, secondstr) return "Winterbash is over. :(" if content_lower.startswith("!!/test"): string_to_test = content[8:] test_as_answer = False if content_lower.startswith("!!/test-a"): string_to_test = content[10:] test_as_answer = True if len(string_to_test) == 0: return "Nothing to test" result = "> " reasons, why = FindSpam.test_post(string_to_test, string_to_test, string_to_test, "", test_as_answer, False, 1, 0) if len(reasons) == 0: result += "Would not be caught for title, {}, and username.".format("answer" if test_as_answer else "body") return result result += ", ".join(reasons).capitalize() if why is not None and len(why) > 0: result += "\n----------\n" result += why return result if content_lower.startswith("!!/amiprivileged"): if is_privileged(ev_room, ev_user_id, wrap2): return "Yes, you are a privileged user." return "No, you are not a privileged user." if content_lower.startswith("!!/notify"): if len(message_parts) != 3: return False, "2 arguments expected" user_id = int(ev_user_id) chat_site = wrap2.host room_id = message_parts[1] if not room_id.isdigit(): return False, "Room ID is invalid." room_id = int(room_id) quiet_action = ("-" in message_parts[2]) se_site = message_parts[2].replace('-', '') response, full_site = add_to_notification_list(user_id, chat_site, room_id, se_site) if response == 0: if quiet_action: return None return "You'll now get pings from me if I report a post on `{site_name}`, in room `{room_id}` on `chat.{chat_domain}`".format(site_name=se_site, room_id=room_id, chat_domain=chat_site) elif response == -1: return "That notification configuration is already registered." elif response == -2: return False, "The given SE site does not exist." if content_lower.startswith("!!/unnotify"): if len(message_parts) != 3: return False, "2 arguments expected" user_id = int(ev_user_id) chat_site = wrap2.host room_id = message_parts[1] if not room_id.isdigit(): return False, "Room ID is invalid." room_id = int(room_id) quiet_action = ("-" in message_parts[2]) se_site = message_parts[2].replace('-', '') response = remove_from_notification_list(user_id, chat_site, room_id, se_site) if response: if quiet_action: return None return "I will no longer ping you if I report a post on `{site_name}`, in room `{room_id}` on `chat.{chat_domain}`".format(site_name=se_site, room_id=room_id, chat_domain=chat_site) return "That configuration doesn't exist." if content_lower.startswith("!!/willibenotified"): if len(message_parts) != 3: return False, "2 arguments expected" user_id = int(ev_user_id) chat_site = wrap2.host room_id = message_parts[1] if not room_id.isdigit(): return False, "Room ID is invalid" room_id = int(room_id) se_site = message_parts[2] will_be_notified = will_i_be_notified(user_id, chat_site, room_id, se_site) if will_be_notified: return "Yes, you will be notified for that site in that room." return "No, you won't be notified for that site in that room." if content_lower.startswith("!!/allnotificationsites"): if len(message_parts) != 2: return False, "1 argument expected" user_id = int(ev_user_id) chat_site = wrap2.host room_id = message_parts[1] if not room_id.isdigit(): return False, "Room ID is invalid." sites = get_all_notification_sites(user_id, chat_site, room_id) if len(sites) == 0: return "You won't get notified for any sites in that room." return "You will get notified for these sites:\r\n" + ", ".join(sites) return False, None # Unrecognized command, can be edited later.
def test_number_lists(): errors = {} no_exacts = [] maybe_north_american_already = [] all_errors = [] def clear_errors(): errors['fails_number_regex'] = [] errors['no_unique'] = [] errors['blacklist_dup_with_no_unique'] = [] errors['blacklist_dup_with_unique'] = [] errors['duplicate_normializations'] = [] def get_sorted_current_errors_and_clear_errors(): current_errors = [ error for sub in [errors[error_group] for error_group in errors] for error in sub ] clear_errors() return current_errors def test_a_number_list(list_type, number_list, blacklist_normalized=None): line_number = 0 all_processed = set() this_list_all_normalized = set() lines_no_unique = [] lines_duplicate_blacklist_with_no_unique = [] lines_duplicate_blacklist_with_unique = [] for pattern in number_list: line_number += 1 entry_description = "{} number ({})::{}:: ".format( list_type, line_number, pattern) (processed_pattern, this_normalized) = number_list[pattern] unique_normalized = this_normalized - this_list_all_normalized duplicate_normalized = this_normalized - unique_normalized maybe_north_american_not_in_all = get_maybe_north_american_not_in_normalized_but_in_all( processed_pattern, this_normalized, this_list_all_normalized) if maybe_north_american_not_in_all: maybe_north_american_already.append( entry_description + "has maybe North American form already in all normalized: {}" .format(maybe_north_american_not_in_all)) this_list_all_normalized |= unique_normalized digit_count = len(regex.findall(r'\d', processed_pattern)) digit_count_text = " ({} digits is OK)".format(digit_count) if not is_digit_count_in_number_regex_range(digit_count): digit_count_text = ": {} digits is not >= {} and <= {}".format( digit_count, NUMBER_REGEX_MINIMUM_DIGITS, NUMBER_REGEX_MAXIMUM_DIGITS) if not matches_number_regex(processed_pattern): errors['fails_number_regex'].append( entry_description + "fails NUMBER_REGEX{}::{}".format( digit_count_text, processed_pattern)) else: this_no_exacts = [] if not matches_number_regex_start(processed_pattern): this_no_exacts.append("Does not match NUMBER_REGEX_START.") if not matches_number_regex_end(processed_pattern): this_no_exacts.append("Does not match NUMBER_REGEX_END.") if len(this_no_exacts) > 0: no_exacts.append(entry_description + " ".join(this_no_exacts) + digit_count_text + "::" + pattern) if not unique_normalized: errors['no_unique'].append( entry_description + "has no unique normalized entries. Duplicate normalized entries: {}" .format(duplicate_normalized)) lines_no_unique.append(str(line_number)) if blacklist_normalized: this_normalized_in_blacklist = this_normalized & blacklist_normalized this_normalized_not_in_blacklist = this_normalized - blacklist_normalized if this_normalized_in_blacklist: not_in_blacklist_text = ":: normalized not in blacklist: {}".format( this_normalized_not_in_blacklist) error_text = entry_description + "has duplicate normalized entries on the blacklist: {}".format( this_normalized_in_blacklist) + not_in_blacklist_text if this_normalized_not_in_blacklist: lines_duplicate_blacklist_with_unique.append( str(line_number)) errors['blacklist_dup_with_unique'].append(error_text) else: lines_duplicate_blacklist_with_no_unique.append( str(line_number)) errors['blacklist_dup_with_no_unique'].append( error_text) if duplicate_normalized: errors['duplicate_normializations'].append( entry_description + "Has duplicate normalized entries: {}".format( duplicate_normalized)) lines_no_unique.reverse() deletion_list = [] for error_group in errors: if errors[error_group]: has_errors = True # The following produces a sequence of commands which can be used in vi/vim to delete the entries in the appropriate file which have errors. # It's intended use is in the transition from not checking normalizations to applying homoglyphs and checking normalizations. line_list = [ error.split('(')[1].split(')')[0] for error in errors[error_group] ] line_list.reverse() deletion_list.append('{}: to remove {}: {}'.format( list_type, error_group, 'Gdd'.join(line_list) + 'Gdd')) if (deletion_list): print('\n') print( 'USE ONLY ONE OF THE FOLLOWING PER RUN OF THESE TESTS. Using more than one will result in the wrong lines being deleted:' ) print('\n'.join(deletion_list)) print('\n\n') return all_processed, this_list_all_normalized clear_errors() FindSpam.reload_blacklists() blacklist_processed, blacklist_normalized = test_a_number_list( "blacklisted", GlobalVars.blacklisted_numbers_full) all_errors.extend(get_sorted_current_errors_and_clear_errors()) test_a_number_list("watched", GlobalVars.watched_numbers_full, blacklist_normalized=blacklist_normalized) all_errors.extend(get_sorted_current_errors_and_clear_errors()) no_exacts_count = len(no_exacts) if (no_exacts_count > 0): pluralize = "" if no_exacts_count == 1 else "s" print("\n\t".join([ "{} pattern{} can't match exactly:".format(no_exacts_count, pluralize) ] + no_exacts)) maybe_north_american_already_count = len(maybe_north_american_already) if (maybe_north_american_already_count > 0): pluralize = "" if maybe_north_american_already_count == 1 else "s" print("\n\t".join([ "{} pattern{} are maybe North American with the alternate version already in all normalized:" .format(maybe_north_american_already_count, pluralize) ] + maybe_north_american_already)) error_count = len(all_errors) if error_count > 0: pluralize = "" if error_count == 1 else "s" # Failure is, currently, disabled. In order for these tests to pass CI testing, they require substantial changes to the # blacklisted_numbers.txt and watched_numbers.txt files in order to remove effectively duplicate entries, # correct entries which overlap, etc. Given the dynamic nature of those lists, it's highly likely that changes to # those lists will result in merge conflicts, potentially significant merge conflicts. As such, my plan is to # wait until after this is merged to enable the potential for failures here and perform the needed changes to those # files. # The output which is provided here and above should make it substantially easier to make the needed changes. pytest.fail("\n\t".join([ "{} error{} have occurred (NOTE: you should find the normalized duplicate entry and decide which is better to keep):" .format(error_count, pluralize) ] + all_errors))
def handle_commands(content_lower, message_parts, ev_room, ev_user_id, ev_user_name, wrap2, content): second_part_lower = "" if len(message_parts) < 2 else message_parts[1].lower() if re.compile(":[0-9]+").search(message_parts[0]): msg_id = int(message_parts[0][1:]) msg = wrap2.get_message(msg_id) msg_content = msg.content_source quiet_action = ("-" in message_parts[1].lower()) if str(msg.owner.id) != GlobalVars.smokeDetector_user_id[ev_room] or msg_content is None: return post_url = fetch_post_url_from_msg_content(msg_content) post_site_id = fetch_post_id_and_site_from_msg_content(msg_content) if post_site_id is not None: post_type = post_site_id[2] else: post_type = None if (second_part_lower.startswith("false") or second_part_lower.startswith("fp")) \ and is_privileged(ev_room, ev_user_id, wrap2): if post_site_id is None: return "That message is not a report." t_metasmoke = Thread(target=Metasmoke.send_feedback_for_post, args=(post_url, second_part_lower, ev_user_name, )) t_metasmoke.start() add_false_positive((post_site_id[0], post_site_id[1])) user_added = False if message_parts[1].lower().startswith("falseu") or message_parts[1].lower().startswith("fpu"): url_from_msg = fetch_owner_url_from_msg_content(msg_content) if url_from_msg is not None: user = get_user_from_url(url_from_msg) if user is not None: add_whitelisted_user(user) user_added = True learned = False if post_type == "question": learned = bayesian_learn_title(fetch_title_from_msg_content(msg_content), "good") if learned and user_added and not quiet_action: return "Registered question as false positive, whitelisted user and added title to Bayesian doctype 'good'." elif learned and not quiet_action: return "Registered question as false positive and added title to Bayesian doctype 'good'." elif not learned: return "Registered question as false positive, but could not add title to Bayesian doctype 'good'." elif post_type == "answer": if user_added and not quiet_action: return "Registered answer as false positive and whitelisted user." elif not quiet_action: return "Registered answer as false positive." try: msg.delete() except: pass if (second_part_lower.startswith("true") or second_part_lower.startswith("tp")) \ and is_privileged(ev_room, ev_user_id, wrap2): if post_site_id is None: return "That message is not a report." t_metasmoke = Thread(target=Metasmoke.send_feedback_for_post, args=(post_url, second_part_lower, ev_user_name, )) t_metasmoke.start() learned = False user_added = False if message_parts[1].lower().startswith("trueu") or message_parts[1].lower().startswith("tpu"): url_from_msg = fetch_owner_url_from_msg_content(msg_content) if url_from_msg is not None: user = get_user_from_url(url_from_msg) if user is not None: add_blacklisted_user(user) user_added = True if post_type == "question": learned = bayesian_learn_title(fetch_title_from_msg_content(msg_content), "bad") if learned and user_added and not quiet_action: return "Blacklisted user and registered question as true positive: added title to the Bayesian doctype 'bad'." elif learned and not quiet_action: return "Registered question as true positive: added title to the Bayesian doctype 'bad'." elif not learned: return "Something went wrong when registering question as true positive." elif post_type == "answer": if user_added and not quiet_action: return "Blacklisted user." elif not user_added: return "`true`/`tp` cannot be used for answers because their job is to add the title of the *question* to the Bayesian doctype 'bad'. If you want to blacklist the poster of the answer, use `trueu` or `tpu`." if second_part_lower.startswith("ignore") and is_privileged(ev_room, ev_user_id, wrap2): if post_site_id is None: return "That message is not a report." add_ignored_post(post_site_id[0:2]) if not quiet_action: return "Post ignored; alerts about it will no longer be posted." if (second_part_lower.startswith("delete") or second_part_lower.startswith("remove") or second_part_lower.startswith("gone") or second_part_lower == "del") and is_privileged(ev_room, ev_user_id, wrap2): try: msg.delete() except: pass # couldn't delete message if second_part_lower.startswith("why"): t = fetch_post_id_and_site_from_msg_content(msg_content) if t is None: return "That's not a report." post_id, site, _ = t why = get_why(site, post_id) if why is None or why == "": return "There is no `why` data for that post (anymore)." else: return why if content_lower.startswith("!!/addblu") \ and is_privileged(ev_room, ev_user_id, wrap2): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": add_blacklisted_user((uid, val)) return "User blacklisted (`{}` on `{}`).".format(uid, val) elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/addblu profileurl` *or* `!!/addblu userid sitename`." if content_lower.startswith("!!/rmblu") \ and is_privileged(ev_room, ev_user_id, wrap2): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": if remove_blacklisted_user((uid, val)): return "User removed from blacklist (`{}` on `{}`).".format(uid, val) else: return "User is not blacklisted." elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/rmblu profileurl` *or* `!!/rmblu userid sitename`." if content_lower.startswith("!!/isblu"): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": if is_blacklisted_user((uid, val)): return "User is blacklisted. (`{}` on `{}`).".format(uid, val) else: return "User is not blacklisted. (`{}` on `{}`).".format(uid, val) elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/isblu profileurl` *or* `!!/isblu userid sitename`." if content_lower.startswith("!!/addwlu") \ and is_privileged(ev_room, ev_user_id, wrap2): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": add_whitelisted_user((uid, val)) return "User whitelisted (`{}` on `{}`).".format(uid, val) elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/addwlu profileurl` *or* `!!/addwlu userid sitename`." if content_lower.startswith("!!/rmwlu") \ and is_privileged(ev_room, ev_user_id, wrap2): uid, val = get_user_from_list_command(content_lower) if uid != -1 and val != "": if remove_whitelisted_user((uid, val)): return "User removed from whitelist (`{}` on `{}`).".format(uid, val) else: return "User is not whitelisted." elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/rmwlu profileurl` *or* `!!/rmwlu userid sitename`." if content_lower.startswith("!!/iswlu"): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": if is_whitelisted_user((uid, val)): return "User is whitelisted. (`{}` on `{}`).".format(uid, val) else: return "User is not whitelisted. (`{}` on `{}`).".format(uid, val) elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/iswlu profileurl` *or* `!!/iswlu userid sitename`." if content_lower.startswith("!!/report") \ and is_privileged(ev_room, ev_user_id, wrap2): if len(message_parts) < 2: return "Not enough arguments." url = message_parts[1] post_data = api_get_post(url) if post_data is None: return "That does not look like a valid post URL." if post_data is False: return "Could not find data for this post in the API. Check whether the post is not deleted yet." user = get_user_from_url(post_data.owner_url) if user is not None: add_blacklisted_user(user) bayesian_learn_title(post_data.title, "bad") handle_spam(post_data.title, post_data.body, post_data.owner_name, post_data.site, post_data.post_url, post_data.owner_url, post_data.post_id, ["Manually reported " + post_data.post_type], post_data.post_type == "answer") if content_lower.startswith("!!/wut"): return "Whaddya mean, 'wut'? Humans..." if content_lower.startswith("!!/lick"): return "*licks ice cream cone*" if content_lower.startswith("!!/alive"): if ev_room == GlobalVars.charcoal_room_id: return 'Of course' elif ev_room == GlobalVars.meta_tavern_room_id or ev_room == GlobalVars.socvr_room_id: return random.choice(['Yup', 'You doubt me?', 'Of course', '... did I miss something?', 'plz send teh coffee', 'Watching this endless list of new questions *never* gets boring', 'Kinda sorta']) if content_lower.startswith("!!/rev"): return '[' + \ GlobalVars.commit_with_author + \ '](https://github.com/Charcoal-SE/SmokeDetector/commit/' + \ GlobalVars.commit + \ ')' if content_lower.startswith("!!/status"): now = datetime.utcnow() diff = now - UtcDate.startup_utc_date minutes, remainder = divmod(diff.seconds, 60) minutestr = "minutes" if minutes != 1 else "minute" return 'Running since {} UTC ({} {})'.format(GlobalVars.startup_utc, minutes, minutestr) if content_lower.startswith("!!/reboot"): if is_privileged(ev_room, ev_user_id, wrap2): post_message_in_room(ev_room, "Goodbye, cruel world") os._exit(5) if content_lower.startswith("!!/stappit"): if is_privileged(ev_room, ev_user_id, wrap2): post_message_in_room(ev_room, "Goodbye, cruel world") os._exit(6) if content_lower.startswith("!!/master"): if is_privileged(ev_room, ev_user_id, wrap2): os._exit(8) if content_lower.startswith("!!/clearbl"): if is_privileged(ev_room, ev_user_id, wrap2): if os.path.isfile("blacklistedUsers.txt"): os.remove("blacklistedUsers.txt") GlobalVars.blacklisted_users = [] return "Kaboom, blacklisted users cleared." else: return "There are no blacklisted users at the moment." if content_lower.startswith("!!/block"): if is_privileged(ev_room, ev_user_id, wrap2): timeToBlock = content_lower[9:].strip() timeToBlock = int(timeToBlock) if timeToBlock else 0 if 0 < timeToBlock < 14400: GlobalVars.blockedTime = time.time() + timeToBlock else: GlobalVars.blockedTime = time.time() + 900 return "blocked" if content_lower.startswith("!!/unblock"): if is_privileged(ev_room, ev_user_id, wrap2): GlobalVars.blockedTime = time.time() return "unblocked" if content_lower.startswith("!!/errorlogs"): if is_privileged(ev_room, ev_user_id, wrap2): count = -1 if len(message_parts) != 2: return "The !!/errorlogs command requires 1 argument." try: count = int(message_parts[1]) except ValueError: pass if count == -1: return "Invalid argument." logs_part = fetch_lines_from_error_log(count) post_message_in_room(ev_room, logs_part, False) if content_lower.startswith("!!/pull"): if is_privileged(ev_room, ev_user_id, wrap2): r = requests.get('https://api.github.com/repos/Charcoal-SE/SmokeDetector/git/refs/heads/master') latest_sha = r.json()["object"]["sha"] r = requests.get('https://api.github.com/repos/Charcoal-SE/SmokeDetector/commits/' + latest_sha + '/statuses') states = [] for status in r.json(): state = status["state"] states.append(state) if "success" in states: os._exit(3) elif "error" in states or "failure" in states: return "CI build failed! :( Please check your commit." elif "pending" in states or not states: return "CI build is still pending, wait until the build has finished and then pull again." if content_lower.startswith("!!/help"): return "I'm [SmokeDetector](https://github.com/Charcoal-SE/SmokeDetector), a bot that detects spam and low-quality posts on the network and posts alerts to chat. [A command list is available here](https://github.com/Charcoal-SE/SmokeDetector/wiki/Commands)." if content_lower.startswith("!!/apiquota"): return GlobalVars.apiquota if content_lower.startswith("!!/whoami"): if (ev_room in GlobalVars.smokeDetector_user_id): return "My id for this room is {}".format(GlobalVars.smokeDetector_user_id[ev_room]) else: return "I don't know my user ID for this room. (Something is wrong, and it's apnorton's fault.)" if content_lower.startswith("!!/location"): return GlobalVars.location if content_lower.startswith("!!/queuestatus"): post_message_in_room(ev_room, GlobalVars.bodyfetcher.print_queue(), False) if content_lower.startswith("!!/blame") and ev_room == GlobalVars.meta_tavern_room_id: GlobalVars.tavern_users_chatting = list(set(GlobalVars.tavern_users_chatting)) # Make unique user_to_blame = random.choice(GlobalVars.tavern_users_chatting) return "It's " + user_to_blame + "'s fault." if "smokedetector" in content_lower and "fault" in content_lower and ("xkcdbot" in ev_user_name.lower() or "bjb568" in ev_user_name.lower()): return "Liar" if content_lower.startswith("!!/coffee"): return "*brews coffee for @" + ev_user_name.replace(" ", "") + "*" if content_lower.startswith("!!/tea"): return "*brews a cup of " + random.choice(['earl grey', 'green', 'chamomile', 'lemon', 'darjeeling', 'mint']) + " tea for @" + ev_user_name.replace(" ", "") + "*" if content_lower.startswith("!!/test"): string_to_test = content[8:] if len(string_to_test) == 0: return "Nothing to test" result = "> " reasons, why = FindSpam.test_post(string_to_test, string_to_test, string_to_test, "", False, False) if len(reasons) == 0: result += "Would not be caught for title, body and username." return result result += ", ".join(reasons).capitalize() if why is not None and len(why) > 0: result += "\n----------\n" result += why return result return None
def handle_commands(content_lower, message_parts, ev_room, ev_room_name, ev_user_id, ev_user_name, wrap2, content, message_id): message_url = "//chat." + wrap2.host + "/transcript/message/" + str(message_id) second_part_lower = "" if len(message_parts) < 2 else message_parts[1].lower() if second_part_lower == "f": second_part_lower = "fp-" if second_part_lower == "k": second_part_lower = "tpu-" if re.compile("^:[0-9]+$").search(message_parts[0]): msg_id = int(message_parts[0][1:]) msg = wrap2.get_message(msg_id) msg_content = msg.content_source quiet_action = ("-" in second_part_lower) if str(msg.owner.id) != GlobalVars.smokeDetector_user_id[ev_room] or msg_content is None: return post_url = fetch_post_url_from_msg_content(msg_content) post_site_id = fetch_post_id_and_site_from_msg_content(msg_content) if post_site_id is not None: post_type = post_site_id[2] else: post_type = None if (second_part_lower.startswith("false") or second_part_lower.startswith("fp")) \ and is_privileged(ev_room, ev_user_id, wrap2): if post_site_id is None: return "That message is not a report." t_metasmoke = Thread(target=Metasmoke.send_feedback_for_post, args=(post_url, second_part_lower, ev_user_name, )) t_metasmoke.start() add_false_positive((post_site_id[0], post_site_id[1])) user_added = False if second_part_lower.startswith("falseu") or second_part_lower.startswith("fpu"): url_from_msg = fetch_owner_url_from_msg_content(msg_content) if url_from_msg is not None: user = get_user_from_url(url_from_msg) if user is not None: add_whitelisted_user(user) user_added = True if post_type == "question": if user_added and not quiet_action: return "Registered question as false positive and whitelisted user." elif not quiet_action: return "Registered question as false positive." elif post_type == "answer": if user_added and not quiet_action: return "Registered answer as false positive and whitelisted user." elif not quiet_action: return "Registered answer as false positive." try: msg.delete() except: pass if (second_part_lower.startswith("true") or second_part_lower.startswith("tp")) \ and is_privileged(ev_room, ev_user_id, wrap2): if post_site_id is None: return "That message is not a report." t_metasmoke = Thread(target=Metasmoke.send_feedback_for_post, args=(post_url, second_part_lower, ev_user_name, )) t_metasmoke.start() user_added = False if second_part_lower.startswith("trueu") or second_part_lower.startswith("tpu"): url_from_msg = fetch_owner_url_from_msg_content(msg_content) if url_from_msg is not None: user = get_user_from_url(url_from_msg) if user is not None: add_blacklisted_user(user, message_url, "http:" + post_url) user_added = True if post_type == "question": if not quiet_action: if user_added: return "Blacklisted user and registered question as true positive." return "Recorded question as true positive in metasmoke. Use `tpu` or `trueu` if you want to blacklist a user." else: return None elif post_type == "answer": if not quiet_action: if user_added: return "Blacklisted user." return "Recorded answer as true positive in metasmoke. If you want to blacklist the poster of the answer, use `trueu` or `tpu`." else: return None if second_part_lower.startswith("ignore") and is_privileged(ev_room, ev_user_id, wrap2): if post_site_id is None: return "That message is not a report." t_metasmoke = Thread(target=Metasmoke.send_feedback_for_post, args=(post_url, second_part_lower, ev_user_name, )) t_metasmoke.start() add_ignored_post(post_site_id[0:2]) if not quiet_action: return "Post ignored; alerts about it will no longer be posted." else: return None if (second_part_lower.startswith("delete") or second_part_lower.startswith("remove") or second_part_lower.startswith("gone") or second_part_lower.startswith("poof") or second_part_lower == "del") and is_privileged(ev_room, ev_user_id, wrap2): try: msg.delete() except: pass # couldn't delete message if second_part_lower.startswith("postgone") and is_privileged(ev_room, ev_user_id, wrap2): edited = edited_message_after_postgone_command(msg_content) if edited is None: return "That's not a report." msg.edit(edited) return None if second_part_lower.startswith("why"): t = fetch_post_id_and_site_from_msg_content(msg_content) if t is None: t = fetch_user_from_allspam_report(msg_content) if t is None: return "That's not a report." why = get_why_allspam(t) if why is None or why == "": return "There is no `why` data for that user (anymore)." else: return why post_id, site, _ = t why = get_why(site, post_id) if why is None or why == "": return "There is no `why` data for that post (anymore)." else: return why if content_lower.startswith("!!/addblu") \ and is_privileged(ev_room, ev_user_id, wrap2): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": add_blacklisted_user((uid, val), message_url, "") return "User blacklisted (`{}` on `{}`).".format(uid, val) elif uid == -2: return "Error: {}".format(val) else: return "Invalid format. Valid format: `!!/addblu profileurl` *or* `!!/addblu userid sitename`." if content_lower.startswith("!!/rmblu") \ and is_privileged(ev_room, ev_user_id, wrap2): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": if remove_blacklisted_user((uid, val)): return "User removed from blacklist (`{}` on `{}`).".format(uid, val) else: return "User is not blacklisted." elif uid == -2: return "Error: {}".format(val) else: return False, "Invalid format. Valid format: `!!/rmblu profileurl` *or* `!!/rmblu userid sitename`." if content_lower.startswith("!!/isblu"): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": if is_blacklisted_user((uid, val)): return "User is blacklisted (`{}` on `{}`).".format(uid, val) else: return "User is not blacklisted (`{}` on `{}`).".format(uid, val) elif uid == -2: return "Error: {}".format(val) else: return False, "Invalid format. Valid format: `!!/isblu profileurl` *or* `!!/isblu userid sitename`." if content_lower.startswith("!!/addwlu") \ and is_privileged(ev_room, ev_user_id, wrap2): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": add_whitelisted_user((uid, val)) return "User whitelisted (`{}` on `{}`).".format(uid, val) elif uid == -2: return "Error: {}".format(val) else: return False, "Invalid format. Valid format: `!!/addwlu profileurl` *or* `!!/addwlu userid sitename`." if content_lower.startswith("!!/rmwlu") \ and is_privileged(ev_room, ev_user_id, wrap2): uid, val = get_user_from_list_command(content_lower) if uid != -1 and val != "": if remove_whitelisted_user((uid, val)): return "User removed from whitelist (`{}` on `{}`).".format(uid, val) else: return "User is not whitelisted." elif uid == -2: return "Error: {}".format(val) else: return False, "Invalid format. Valid format: `!!/rmwlu profileurl` *or* `!!/rmwlu userid sitename`." if content_lower.startswith("!!/iswlu"): uid, val = get_user_from_list_command(content_lower) if uid > -1 and val != "": if is_whitelisted_user((uid, val)): return "User is whitelisted (`{}` on `{}`).".format(uid, val) else: return "User is not whitelisted (`{}` on `{}`).".format(uid, val) elif uid == -2: return "Error: {}".format(val) else: return False, "Invalid format. Valid format: `!!/iswlu profileurl` *or* `!!/iswlu userid sitename`." if content_lower.startswith("!!/report") \ and is_privileged(ev_room, ev_user_id, wrap2): crn, wait = can_report_now(ev_user_id, wrap2.host) if not crn: return "You can execute the !!/report command again in {} seconds. " \ "To avoid one user sending lots of reports in a few commands and slowing SmokeDetector down due to rate-limiting, " \ "you have to wait 30 seconds after you've reported multiple posts using !!/report, even if your current command just has one URL. " \ "(Note that this timeout won't be applied if you only used !!/report for one post)".format(wait) if len(message_parts) < 2: return False, "Not enough arguments." output = [] index = 0 urls = list(set(message_parts[1:])) if len(urls) > 5: return False, "To avoid SmokeDetector reporting posts too slowly, " \ "you can report at most 5 posts at a time. " \ "This is to avoid SmokeDetector's chat messages getting rate-limited too much, " \ "which would slow down reports." for url in urls: index += 1 post_data = api_get_post(url) if post_data is None: output.append("Post {}: That does not look like a valid post URL.".format(index)) continue if post_data is False: output.append("Post {}: Could not find data for this post in the API. It may already have been deleted.".format(index)) continue user = get_user_from_url(post_data.owner_url) if user is not None: add_blacklisted_user(user, message_url, post_data.post_url) why = u"Post manually reported by user *{}* in room *{}*.\n".format(ev_user_name, ev_room_name.decode('utf-8')) batch = "" if len(urls) > 1: batch = " (batch report: post {} out of {})".format(index, len(urls)) handle_spam(post_data.title, post_data.body, post_data.owner_name, post_data.site, post_data.post_url, post_data.owner_url, post_data.post_id, ["Manually reported " + post_data.post_type + batch], post_data.post_type == "answer", why, post_data.owner_rep, post_data.score, post_data.up_vote_count, post_data.down_vote_count) if 1 < len(urls) > len(output): add_or_update_multiple_reporter(ev_user_id, wrap2.host, time.time()) if len(output) > 0: return os.linesep.join(output) else: return None if content_lower.startswith("!!/allspam") and is_privileged(ev_room, ev_user_id, wrap2): if len(message_parts) != 2: return False, "1 argument expected" url = message_parts[1] user = get_user_from_url(url) if user is None: return "That doesn't look like a valid user URL." why = u"User manually reported by *{}* in room *{}*.\n".format(ev_user_name, ev_room_name.decode('utf-8')) handle_user_with_all_spam(user, why) if content_lower.startswith("!!/wut"): return "Whaddya mean, 'wut'? Humans..." if content_lower.startswith("!!/lick"): return "*licks ice cream cone*" if content_lower.startswith("!!/alive"): if ev_room == GlobalVars.charcoal_room_id: return 'Of course' elif ev_room == GlobalVars.meta_tavern_room_id or ev_room == GlobalVars.socvr_room_id: return random.choice(['Yup', 'You doubt me?', 'Of course', '... did I miss something?', 'plz send teh coffee', 'Watching this endless list of new questions *never* gets boring', 'Kinda sorta']) if content_lower.startswith("!!/rev") or content_lower.startswith("!!/ver"): return '[' + \ GlobalVars.commit_with_author + \ '](https://github.com/Charcoal-SE/SmokeDetector/commit/' + \ GlobalVars.commit + \ ')' if content_lower.startswith("!!/status"): now = datetime.utcnow() diff = now - UtcDate.startup_utc_date minutes, remainder = divmod(diff.seconds, 60) minutestr = "minutes" if minutes != 1 else "minute" return 'Running since {} UTC ({} {})'.format(GlobalVars.startup_utc, minutes, minutestr) if content_lower.startswith("!!/reboot"): if is_privileged(ev_room, ev_user_id, wrap2): post_message_in_room(ev_room, "Goodbye, cruel world") os._exit(5) if content_lower.startswith("!!/stappit"): if is_privileged(ev_room, ev_user_id, wrap2): post_message_in_room(ev_room, "Goodbye, cruel world") os._exit(6) if content_lower.startswith("!!/master"): if is_privileged(ev_room, ev_user_id, wrap2): os._exit(8) if content_lower.startswith("!!/clearbl"): if is_privileged(ev_room, ev_user_id, wrap2): if os.path.isfile("blacklistedUsers.txt"): os.remove("blacklistedUsers.txt") GlobalVars.blacklisted_users = [] return "Kaboom, blacklisted users cleared." else: return "There are no blacklisted users at the moment." if content_lower.startswith("!!/block"): if is_privileged(ev_room, ev_user_id, wrap2): timeToBlock = content_lower[9:].strip() timeToBlock = int(timeToBlock) if timeToBlock else 0 if 0 < timeToBlock < 14400: GlobalVars.blockedTime = time.time() + timeToBlock else: GlobalVars.blockedTime = time.time() + 900 GlobalVars.charcoal_hq.send_message("Reports blocked for {} seconds.".format(GlobalVars.blockedTime - time.time())) return "blocked" if content_lower.startswith("!!/unblock"): if is_privileged(ev_room, ev_user_id, wrap2): GlobalVars.blockedTime = time.time() GlobalVars.charcoal_hq.send_message("Reports unblocked.") return "unblocked" if content_lower.startswith("!!/errorlogs"): if is_privileged(ev_room, ev_user_id, wrap2): count = -1 if len(message_parts) != 2: return "The !!/errorlogs command requires 1 argument." try: count = int(message_parts[1]) except ValueError: pass if count == -1: return "Invalid argument." logs_part = fetch_lines_from_error_log(count) post_message_in_room(ev_room, logs_part, False) if content_lower.startswith("!!/pull"): if is_privileged(ev_room, ev_user_id, wrap2): r = requests.get('https://api.github.com/repos/Charcoal-SE/SmokeDetector/git/refs/heads/master') latest_sha = r.json()["object"]["sha"] r = requests.get('https://api.github.com/repos/Charcoal-SE/SmokeDetector/commits/' + latest_sha + '/statuses') states = [] for status in r.json(): state = status["state"] states.append(state) if "success" in states: os._exit(3) elif "error" in states or "failure" in states: return "CI build failed! :( Please check your commit." elif "pending" in states or not states: return "CI build is still pending, wait until the build has finished and then pull again." if content_lower.startswith("!!/help") or content_lower.startswith("!!/info"): return "I'm [SmokeDetector](https://github.com/Charcoal-SE/SmokeDetector), a bot that detects spam and offensive posts on the network and posts alerts to chat. [A command list is available here](https://github.com/Charcoal-SE/SmokeDetector/wiki/Commands)." if content_lower.startswith("!!/apiquota"): return "The current API quota remaining is {}.".format(GlobalVars.apiquota) if content_lower.startswith("!!/whoami"): if (ev_room in GlobalVars.smokeDetector_user_id): return "My id for this room is {}.".format(GlobalVars.smokeDetector_user_id[ev_room]) else: return "I don't know my user ID for this room. (Something is wrong, and it's apnorton's fault.)" if content_lower.startswith("!!/location"): return GlobalVars.location if content_lower.startswith("!!/queuestatus"): post_message_in_room(ev_room, GlobalVars.bodyfetcher.print_queue(), False) if content_lower.startswith("!!/blame"): GlobalVars.users_chatting[ev_room] = list(set(GlobalVars.users_chatting[ev_room])) # Make unique user_to_blame = random.choice(GlobalVars.users_chatting[ev_room]) return u"It's [{}]({})'s fault.".format(user_to_blame[0], user_to_blame[1]) if "smokedetector" in content_lower and "fault" in content_lower and ("xkcdbot" in ev_user_name.lower() or "bjb568" in ev_user_name.lower()): return "Liar" if content_lower.startswith("!!/coffee"): return "*brews coffee for @" + ev_user_name.replace(" ", "") + "*" if content_lower.startswith("!!/tea"): return "*brews a cup of " + random.choice(['earl grey', 'green', 'chamomile', 'lemon', 'darjeeling', 'mint']) + " tea for @" + ev_user_name.replace(" ", "") + "*" if content_lower.startswith("!!/brownie"): return "Brown!" if content_lower.startswith("!!/hats"): wb_end = datetime(2016, 1, 4, 0, 0, 0) now = datetime.utcnow() if wb_end > now: diff = wb_end - now hours, remainder = divmod(diff.seconds, 3600) minutes, seconds = divmod(remainder, 60) daystr = "days" if diff.days != 1 else "day" hourstr = "hours" if hours != 1 else "hour" minutestr = "minutes" if minutes != 1 else "minute" secondstr = "seconds" if seconds != 1 else "second" return "HURRY UP AND EARN MORE HATS! Winterbash will be over in {} {}, {} {}, {} {}, and {} {}. :(".format(diff.days, daystr, hours, hourstr, minutes, minutestr, seconds, secondstr) else: return "Winterbash is over. :(" if content_lower.startswith("!!/test"): string_to_test = content[8:] if len(string_to_test) == 0: return "Nothing to test" result = "> " reasons, why = FindSpam.test_post(string_to_test, string_to_test, string_to_test, "", False, False, 1, 0) if len(reasons) == 0: result += "Would not be caught for title, body, and username." return result result += ", ".join(reasons).capitalize() if why is not None and len(why) > 0: result += "\n----------\n" result += why return result if content_lower.startswith("!!/amiprivileged"): if is_privileged(ev_room, ev_user_id, wrap2): return "Yes, you are a privileged user." else: return "No, you are not a privileged user." if content_lower.startswith("!!/notify"): if len(message_parts) != 3: return False, "2 arguments expected" user_id = int(ev_user_id) chat_site = wrap2.host room_id = message_parts[1] if not room_id.isdigit(): return False, "Room ID is invalid." else: room_id = int(room_id) quiet_action = ("-" in message_parts[2]) se_site = message_parts[2].replace('-', '') r, full_site = add_to_notification_list(user_id, chat_site, room_id, se_site) if r == 0: if not quiet_action: return "You'll now get pings from me if I report a post on `%s`, in room `%s` on `chat.%s`" % (full_site, room_id, chat_site) else: return None elif r == -1: return "That notification configuration is already registered." elif r == -2: return False, "The given SE site does not exist." if content_lower.startswith("!!/unnotify"): if len(message_parts) != 3: return False, "2 arguments expected" user_id = int(ev_user_id) chat_site = wrap2.host room_id = message_parts[1] if not room_id.isdigit(): return False, "Room ID is invalid." else: room_id = int(room_id) quiet_action = ("-" in message_parts[2]) se_site = message_parts[2].replace('-', '') r = remove_from_notification_list(user_id, chat_site, room_id, se_site) if r: if not quiet_action: return "I will no longer ping you if I report a post on `%s`, in room `%s` on `chat.%s`" % (se_site, room_id, chat_site) else: return None else: return "That configuration doesn't exist." if content_lower.startswith("!!/willibenotified"): if len(message_parts) != 3: return False, "2 arguments expected" user_id = int(ev_user_id) chat_site = wrap2.host room_id = message_parts[1] if not room_id.isdigit(): return False, "Room ID is invalid" else: room_id = int(room_id) se_site = message_parts[2] will_be_notified = will_i_be_notified(user_id, chat_site, room_id, se_site) if will_be_notified: return "Yes, you will be notified for that site in that room." else: return "No, you won't be notified for that site in that room." if content_lower.startswith("!!/allnotificationsites"): if len(message_parts) != 2: return False, "1 argument expected" user_id = int(ev_user_id) chat_site = wrap2.host room_id = message_parts[1] if not room_id.isdigit(): return False, "Room ID is invalid." sites = get_all_notification_sites(user_id, chat_site, room_id) if len(sites) == 0: return "You won't get notified for any sites in that room." else: return "You will get notified for these sites:\r\n" + ", ".join(sites) return False, None # Unrecognized command, can be edited later.