コード例 #1
0
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
コード例 #2
0
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, ""
コード例 #3
0
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, ""
コード例 #4
0
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
コード例 #5
0
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, ""
コード例 #6
0
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, ""
コード例 #7
0
ファイル: ws.py プロジェクト: TheGuywithTheHat/SmokeDetector
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"
コード例 #8
0
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
コード例 #9
0
ファイル: test_regexes.py プロジェクト: ioffl/SmokeDetector
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
コード例 #10
0
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
コード例 #11
0
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
コード例 #12
0
ファイル: ws.py プロジェクト: braiam/SmokeDetector
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"
コード例 #13
0
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, ""
コード例 #14
0
ファイル: spamhandling.py プロジェクト: JC3/SmokeDetector
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
コード例 #15
0
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
コード例 #16
0
ファイル: ws.py プロジェクト: ndrewh/SmokeDetector
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
コード例 #17
0
ファイル: ws.py プロジェクト: TheGuywithTheHat/SmokeDetector
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
コード例 #18
0
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, ""
コード例 #19
0
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
コード例 #20
0
ファイル: ws.py プロジェクト: ProgramFOX/SmokeDetector
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"
コード例 #21
0
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)
コード例 #22
0
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, ""
コード例 #23
0
ファイル: ws.py プロジェクト: ProgramFOX/SmokeDetector
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
コード例 #24
0
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
コード例 #25
0
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.
コード例 #26
0
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))
コード例 #27
0
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
コード例 #28
0
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.