예제 #1
0
    def __init__(self):
        if GlobalVars.no_edit_watcher:
            self.socket = None
            return
        # posts is a dict with the WebSocket action as keys {site_id}-question-{question_id} as keys
        # with each value being: (site_id, hostname, question_id, max_time)
        self.posts = {}
        self.posts_lock = threading.Lock()
        self.save_handle = None
        self.save_handle_lock = threading.Lock()

        try:
            self.socket = websocket.create_connection(
                "wss://qa.sockets.stackexchange.com/")
        except websocket.WebSocketException:
            self.socket = None
            log('error', 'EditWatcher failed to create a websocket connection')

        if datahandling.has_pickle(PICKLE_FILENAME):
            pickle_data = datahandling.load_pickle(PICKLE_FILENAME)
            now = time.time()
            new_posts = {
                action: value
                for action, value in pickle_data if value[-1] > now
            }
            with self.posts_lock:
                self.posts = new_posts
            for action in new_posts.keys():
                Tasks.do(self._subscribe, action)
            self._schedule_save()

        threading.Thread(name="edit watcher", target=self._start,
                         daemon=True).start()
    def __init__(self):
        if GlobalVars.no_deletion_watcher:
            return
        self.posts = {}
        self.posts_lock = threading.Lock()
        self.save_handle = None
        self.save_handle_lock = threading.Lock()

        try:
            self.socket = websocket.create_connection(
                "wss://qa.sockets.stackexchange.com/")
        except websocket.WebSocketException:
            self.socket = None
            log('error',
                'DeletionWatcher failed to create a websocket connection')
            return

        if datahandling.has_pickle(PICKLE_FILENAME):
            pickle_data = datahandling.load_pickle(PICKLE_FILENAME)
            for post in DeletionWatcher._check_batch(pickle_data):
                self.subscribe(post, pickle=False)
            self._schedule_save()

        threading.Thread(name="deletion watcher",
                         target=self._start,
                         daemon=True).start()
예제 #3
0
    def __init__(self):
        DeletionWatcher.update_site_id_list()
        self.posts = {}

        try:
            self.socket = websocket.create_connection(
                "wss://qa.sockets.stackexchange.com/")
        except websocket.WebSocketException:
            log('error',
                'DeletionWatcher failed to create a websocket connection')
            return

        if datahandling.has_pickle(PICKLE_FILENAME):
            pickle_data = datahandling.load_pickle(PICKLE_FILENAME)
            for post in DeletionWatcher._check_batch(pickle_data):
                self.subscribe(post, pickle=False)
            self._save()

        threading.Thread(name="deletion watcher",
                         target=self._start,
                         daemon=True).start()
                           target=chatcommunicate.pickle_last_messages,
                           daemon=True)
    thread.assert_any_call(name="message sender",
                           target=chatcommunicate.send_messages,
                           daemon=True)

    assert len(chatcommunicate._rooms) == 3
    assert chatcommunicate._rooms[("stackexchange.com",
                                   11540)].deletion_watcher is True
    assert chatcommunicate._rooms[("stackexchange.com",
                                   30332)].deletion_watcher is False
    assert chatcommunicate._rooms[("stackoverflow.com",
                                   111347)].deletion_watcher is False


@pytest.mark.skipif(has_pickle("messageData.p"),
                    reason="shouldn't overwrite file")
@patch("datahandling.dump_pickle")
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
예제 #5
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:
                        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()
예제 #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()