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()
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
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()
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()