def test_pickle_rick(dump_pickle):
    try:
        threading.Thread(target=chatcommunicate.pickle_last_messages,
                         daemon=True).start()

        chatcommunicate._pickle_run.set()

        # Yield to the pickling thread until it acquires the lock again
        while len(chatcommunicate._pickle_run._cond._waiters) == 0:
            time.sleep(0)

        assert dump_pickle.call_count == 1

        call, _ = dump_pickle.call_args_list[0]
        assert call[0] == "messageData.p"
        assert isinstance(call[1], chatcommunicate.LastMessages)
    finally:
        remove_pickle("messageData.p")
def test_blacklisted_user():
    user_url = 'https://stackoverflow.com/users/1/jeff-atwood'
    user = get_user_from_url(user_url)
    add_blacklisted_user(user, "", "")
    # Construct a "fake" post object in API-format
    post = Post(
        api_response={
            'title': '',
            'body': '',
            'owner': {
                'display_name': user,
                'reputation': 1,
                'link': user_url
            },
            'site': 'stackoverflow.com',
            'question_id': '1',
            'IsAnswer': False,
            'score': 0
        })
    is_spam, reason, _ = check_if_spam(post)
    assert is_spam is True
    # cleanup
    remove_pickle("blacklistedUsers.p")
def test_notifications():
    chatcommunicate.parse_room_config("test/test_rooms.yml")
    try:
        msg1 = Fake({
            "owner": {
                "name": "El'endia Starman",
                "id": 1,
                "is_moderator": False
            },
            "room": {
                "id": 11540,
                "_client": {
                    "host": "stackexchange.com"
                }
            },
            "_client": {
                "host": "stackexchange.com"
            }
        })

        msg2 = Fake({
            "owner": {
                "name": "angussidney",
                "id": 145827,
                "is_moderator": False
            },
            "room": {
                "id": 11540,
                "_client": {
                    "host": "stackexchange.com"
                }
            },
            "_client": {
                "host": "stackexchange.com"
            }
        })

        # User 1
        assert chatcommands.allnotificationsites("11540", original_msg=msg1) == \
            "You won't get notified for any sites in that room."
        assert chatcommands.willbenotified("11540", "gaming", original_msg=msg1) == \
            "No, you won't be notified for that site in that room."
        assert chatcommands.notify("11540", "gaming", None, original_msg=msg1) == \
            "You'll now get pings from me if I report a post on `gaming`, in room `11540` on `chat.stackexchange.com`."
        assert chatcommands.notify("11540", "codegolf.stackexchange.com", None, original_msg=msg1) == \
            "You'll now get pings from me if I report a post on `codegolf.stackexchange.com`, in room `11540` on " \
            "`chat.stackexchange.com`."
        # Check for additional text when notifying only when in room
        assert chatcommands.notify("11540", "meta.stackexchange.com", False, original_msg=msg1) == \
            "You'll now get pings from me if I report a post on `meta.stackexchange.com`, in room `11540` on " \
            "`chat.stackexchange.com`, but only when you're in that room."
        # Remove notification which is active only when in room
        assert chatcommands.unnotify("11540", "meta.stackexchange.com", original_msg=msg1) == \
            "I will no longer ping you if I report a post on `meta.stackexchange.com`, in room `11540` on " \
            "`chat.stackexchange.com`."
        assert chatcommands.willbenotified("11540", "gaming.stackexchange.com", original_msg=msg1) == \
            "Yes, you will be notified for that site in that room."
        assert chatcommands.willbenotified("11540", "codegolf", original_msg=msg1) == \
            "Yes, you will be notified for that site in that room."

        # User 2
        assert chatcommands.allnotificationsites("11540", original_msg=msg2) == \
            "You won't get notified for any sites in that room."
        assert chatcommands.willbenotified("11540", "raspberrypi", original_msg=msg2) == \
            "No, you won't be notified for that site in that room."
        assert chatcommands.notify("11540", "raspberrypi", None, original_msg=msg2) == \
            "You'll now get pings from me if I report a post on `raspberrypi`, in room `11540` on `chat.stackexchange.com`."
        assert chatcommands.notify("11540", "raspberrypi", None, original_msg=msg2) == \
            "That notification configuration is already registered."
        assert chatcommands.willbenotified("11540", "raspberrypi.stackexchange.com", original_msg=msg2) == \
            "Yes, you will be notified for that site in that room."

        # Check for no interaction
        assert chatcommands.allnotificationsites("11540", original_msg=msg1) == \
            "You will get notified for these sites:\r\ncodegolf.stackexchange.com, gaming.stackexchange.com"
        assert chatcommands.allnotificationsites("11540", original_msg=msg2) == \
            "You will get notified for these sites:\r\nraspberrypi.stackexchange.com"

        # Remove all notifications and check
        assert chatcommands.unnotify("11540", "gaming.stackexchange.com", original_msg=msg1) == \
            "I will no longer ping you if I report a post on `gaming.stackexchange.com`, in room `11540` on " \
            "`chat.stackexchange.com`."
        assert chatcommands.unnotify("11540", "codegolf", original_msg=msg1) == \
            "I will no longer ping you if I report a post on `codegolf`, in room `11540` on `chat.stackexchange.com`."
        assert chatcommands.unnotify("11540", "raspberrypi", original_msg=msg2) == \
            "I will no longer ping you if I report a post on `raspberrypi`, in room `11540` on `chat.stackexchange.com`."
        assert chatcommands.unnotify("11540", "raspberrypi", original_msg=msg2) == \
            "That configuration doesn't exist."
        assert chatcommands.allnotificationsites("11540", original_msg=msg1) == \
            "You won't get notified for any sites in that room."
        assert chatcommands.willbenotified("11540", "raspberrypi", original_msg=msg2) == \
            "No, you won't be notified for that site in that room."

        assert chatcommands.allnotificationsites(
            "asdf",
            original_msg=msg1) == "Invalid input type given for an argument"
        assert chatcommands.notify("11540", "charcoalspam.stackexchange.com", None, original_msg=msg1) == \
            "The given SE site does not exist."

        assert chatcommands.notify("11540", "codegolf", "True", original_msg=msg1) == \
            "You'll now get pings from me if I report a post on `codegolf`, in room `11540` on `chat.stackexchange.com`."
        assert chatcommands.notify("11540", "codegolf", "False", original_msg=msg1) == \
            "That notification configuration is already registered."
    finally:
        # Cleanup
        remove_pickle("notifications.p")
def test_whitelisted_users():
    chatcommunicate.parse_room_config("test/test_rooms.yml")
    try:
        msg = Fake({
            "owner": {
                "name": "El'endia Starman",
                "id": 1,
                "is_moderator": False
            },
            "room": {
                "id": 11540,
                "_client": {
                    "host": "stackexchange.com"
                }
            },
            "_client": {
                "host": "stackexchange.com"
            }
        })

        # Format: !!/*wlu profileurl
        assert chatcommands.iswlu("https://stackoverflow.com/users/4622463/angussidney", original_msg=msg) == \
            "User is not whitelisted (`4622463` on `stackoverflow.com`)."
        assert chatcommands.addwlu("https://stackoverflow.com/users/4622463/angussidney", original_msg=msg) == \
            "User whitelisted (`4622463` on `stackoverflow.com`)."
        # TODO: Add test here as well
        assert chatcommands.iswlu("https://stackoverflow.com/users/4622463/angussidney", original_msg=msg) == \
            "User is whitelisted (`4622463` on `stackoverflow.com`)."
        assert chatcommands.rmwlu("https://stackoverflow.com/users/4622463/angussidney", original_msg=msg) == \
            "User removed from whitelist (`4622463` on `stackoverflow.com`)."
        assert chatcommands.iswlu("https://stackoverflow.com/users/4622463/angussidney", original_msg=msg) == \
            "User is not whitelisted (`4622463` on `stackoverflow.com`)."
        assert chatcommands.rmwlu("https://stackoverflow.com/users/4622463/angussidney", original_msg=msg) == \
            "User is not whitelisted."

        # Format: !!/*wlu userid sitename
        assert chatcommands.iswlu("4622463 stackoverflow", original_msg=msg) == \
            "User is not whitelisted (`4622463` on `stackoverflow.com`)."
        assert chatcommands.addwlu("4622463 stackoverflow", original_msg=msg) == \
            "User whitelisted (`4622463` on `stackoverflow.com`)."
        # TODO: Add test here as well
        assert chatcommands.iswlu("4622463 stackoverflow", original_msg=msg) == \
            "User is whitelisted (`4622463` on `stackoverflow.com`)."
        assert chatcommands.rmwlu("4622463 stackoverflow", original_msg=msg) == \
            "User removed from whitelist (`4622463` on `stackoverflow.com`)."
        assert chatcommands.iswlu("4622463 stackoverflow", original_msg=msg) == \
            "User is not whitelisted (`4622463` on `stackoverflow.com`)."
        assert chatcommands.rmwlu("4622463 stackoverflow", original_msg=msg) == \
            "User is not whitelisted."

        # Invalid input
        assert chatcommands.addwlu("https://meta.stackexchange.com/users", original_msg=msg) == \
            "Invalid format. Valid format: `!!/addwlu profileurl` *or* `!!/addwlu userid sitename`."
        assert chatcommands.rmwlu("https://meta.stackexchange.com/", original_msg=msg) == \
            "Invalid format. Valid format: `!!/rmwlu profileurl` *or* `!!/rmwlu userid sitename`."
        assert chatcommands.iswlu("msklkldsklaskd", original_msg=msg) == \
            "Invalid format. Valid format: `!!/iswlu profileurl` *or* `!!/iswlu userid sitename`."

        # Invalid sitename
        assert chatcommands.addwlu("1 completelyfakesite", original_msg=msg) == \
            "Error: Could not find the given site."
        assert chatcommands.iswlu("1 completelyfakesite", original_msg=msg) == \
            "Error: Could not find the given site."
    except:
        # Cleanup
        remove_pickle("whitelistedUsers.p")
def init(username, password, try_cookies=True):
    global _clients
    global _rooms
    global _room_data
    global _last_messages

    for site in _clients.keys():
        client = Client(site)
        logged_in = False

        if try_cookies:
            if GlobalVars.cookies is None:
                datahandling.remove_pickle("cookies.p")
                GlobalVars.cookies = {}
            else:
                cookies = GlobalVars.cookies
                try:
                    if site in cookies and cookies[site] is not None:
                        client.login_with_cookie(cookies[site])
                        logged_in = True
                        log(
                            'debug',
                            'chat.{}: Logged in using cached cookies'.format(
                                site))
                except LoginError as e:
                    exc_type, exc_obj, exc_tb = sys.exc_info()
                    log(
                        'debug', 'chat.{}: Login error {}: {}'.format(
                            site, exc_type.__name__, exc_obj))
                    log(
                        'debug',
                        'chat.{}: Falling back to credential-based login'.
                        format(site))
                    del cookies[site]
                    datahandling.dump_cookies()

        if not logged_in:
            for retry in range(3):
                try:
                    GlobalVars.cookies[site] = client.login(username, password)
                    break
                except Exception as e:
                    exc_type, exc_obj, exc_tb = sys.exc_info()
                    log(
                        'debug', 'chat.{}: Login error {}: {}'.format(
                            site, exc_type.__name__, exc_obj))
            else:
                raise Exception("Failed to log into " + site +
                                ", max retries exceeded")

        _clients[site] = client

    if os.path.exists("rooms_custom.yml"):
        parse_room_config("rooms_custom.yml")
    else:
        parse_room_config("rooms.yml")

    if not GlobalVars.standby_mode:
        join_command_rooms()

    if datahandling.has_pickle("messageData.p"):
        try:
            _last_messages = datahandling.load_pickle("messageData.p")
        except EOFError:
            pass

    threading.Thread(name="pickle ---rick--- runner",
                     target=pickle_last_messages,
                     daemon=True).start()
    threading.Thread(name="message sender", target=send_messages,
                     daemon=True).start()

    if try_cookies:
        datahandling.dump_cookies()
Example #6
0
def init(username, password, try_cookies=True):
    global _clients
    global _rooms
    global _room_data
    global _last_messages

    for site in _clients.keys():
        client = Client(site)
        logged_in = False

        if try_cookies:
            if GlobalVars.cookies is None:
                datahandling.remove_pickle("cookies.p")
                GlobalVars.cookies = {}
            else:
                cookies = GlobalVars.cookies
                try:
                    if site in cookies and cookies[site] is not None:
                        try:
                            # This implements a quick login to only chat using the existing cookies. It doesn't
                            # require accessing main SE sites, so should be available when SE is in read-only mode.
                            # Ideally, we'll update ChatExchange with something similar.
                            client._br.session.cookies.update(cookies[site])
                            # client.get_me() will raise an exception if the cookies don't work.
                            me = client.get_me()
                            if me.id > 0:
                                client.logged_in = True
                                logged_in = True
                                log(
                                    'debug',
                                    'chat.{}: Logged in to chat only using cached cookies'
                                    .format(site))
                        except Exception:
                            # This is a fallback using the ChatExchange functionality we've been using for a long time.
                            log(
                                'debug',
                                'chat.{}: chat-only login failed. Falling back to normal cookies'
                                .format(site))
                            client.login_with_cookie(cookies[site])
                            logged_in = True
                            log(
                                'debug',
                                'chat.{}: Logged in using cached cookies'.
                                format(site))
                except LoginError as e:
                    exc_type, exc_obj, exc_tb = sys.exc_info()
                    log(
                        'debug', 'chat.{}: Login error {}: {}'.format(
                            site, exc_type.__name__, exc_obj))
                    log(
                        'debug',
                        'chat.{}: Falling back to credential-based login'.
                        format(site))
                    # Instead of deleting the cookies, start with a clean slate of a new client.
                    client = Client(site)

        if not logged_in:
            for retry in range(3):
                try:
                    GlobalVars.cookies[site] = client.login(username, password)
                    break
                except Exception as e:
                    exc_type, exc_obj, exc_tb = sys.exc_info()
                    log(
                        'debug', 'chat.{}: Login error {}: {}'.format(
                            site, exc_type.__name__, exc_obj))
                    if exc_type.__name__ == 'LoginError' and str(
                            exc_obj) == 'fkey input not found':
                        # ChatExchange didn't find the `fkey` <input> in the SE login page. Under most operating
                        # conditions, this means that we've either lost connectivity to SE entirely or SE
                        # is in read-only mode and isn't accepting login attempts. Under those conditions,
                        # there's nothing which we or SD can do other than wait for SE to resolve the issue.
                        # So, instead of spinning the CPU hard in order to retry at the maximum rate, we delay a bit.
                        # The situations where the problem is on our end rather than on SE's end tend
                        sleep_time = 30 * (retry + 1)
                        log(
                            'warning',
                            'Login to SE appears unavailable. Can be caused by: SD config issue,'
                            +
                            ' bad network connection, or Stack Exchange is down/read-only.'
                            + ' Sleeping for {} seconds.'.format(sleep_time))
                        time.sleep(sleep_time)
            else:
                raise Exception("Failed to log into " + site +
                                ", max retries exceeded")

        _clients[site] = client

    if os.path.exists("rooms_custom.yml"):
        parse_room_config("rooms_custom.yml")
    else:
        parse_room_config("rooms.yml")

    if not GlobalVars.standby_mode:
        join_command_rooms()

    if datahandling.has_pickle("messageData.p"):
        try:
            _last_messages = datahandling.load_pickle("messageData.p")
        except EOFError:
            pass

    threading.Thread(name="pickle ---rick--- runner",
                     target=pickle_last_messages,
                     daemon=True).start()
    threading.Thread(name="message sender", target=send_messages,
                     daemon=True).start()

    if try_cookies:
        datahandling.dump_cookies()
def test_whitelisted_user():
    user_url = 'https://stackoverflow.com/users/2/geoff-dalgas'
    user = get_user_from_url(user_url)
    add_whitelisted_user(user)
    user_url2 = 'https://stackoverflow.com/users/0/test'
    user2 = get_user_from_url(user_url2)
    add_whitelisted_user(user2)
    post = Post(
        api_response={
            'title': '',
            'body': '',
            'owner': {
                'display_name': 'bagprada',
                'reputation': 1,
                'link': user_url
            },
            'site': 'stackoverflow.com',
            'question_id': '1',
            'IsAnswer': False,
            'score': 0
        })
    is_spam, reason, _ = check_if_spam(post)
    assert is_spam is False
    post = Post(
        api_response={
            'title': 'baba ji',
            'body': '',
            'owner': {
                'display_name': '',
                'reputation': 1,
                'link': user_url
            },
            'site': 'stackoverflow.com',
            'question_id': '2',
            'IsAnswer': False,
            'score': 0
        })
    is_spam, reason, _ = check_if_spam(post)
    assert is_spam is True
    post = Post(
        api_response={
            'title': 'baba ji',
            'body': '',
            'owner': {
                'display_name': 'bagprada',
                'reputation': 1,
                'link': user_url
            },
            'site': 'stackoverflow.com',
            'question_id': '3',
            'IsAnswer': False,
            'score': 0
        })
    is_spam, reason, _ = check_if_spam(post)
    assert is_spam is True
    post = Post(
        api_response={
            'title': 'test',
            'body': '',
            'owner': {
                'display_name': 'baba ji - muscle building',
                'reputation': 1,
                'link': user_url2
            },
            'site': 'stackoverflow.com',
            'question_id': '0',
            'IsAnswer': False,
            'score': 0
        })
    is_spam, reason, _ = check_if_spam(post)
    assert is_spam is False
    # cleanup
    remove_pickle("whitelistedUsers.p")