def logout(api) -> None: cmd_list = [] cmd_list.append(command.GoMainMenu) cmd_list.append('g') cmd_list.append(command.Enter) cmd_list.append('y') cmd_list.append(command.Enter) cmd = ''.join(cmd_list) target_list = [ connect_core.TargetUnit( [ i18n.logout, i18n.Success, ], '任意鍵', break_detect=True, ), ] log.log(api.config, log.level.INFO, [i18n.Start, i18n.logout]) try: api.connect_core.send(cmd, target_list) api.connect_core.close() except exceptions.ConnectionClosed: pass except RuntimeError: pass api._login_status = False log.show_value(api.config, log.level.INFO, i18n.logout, i18n.Done)
def _one_thread(self) -> None: current_thread_id = threading.get_ident() if current_thread_id == self._ThreadID: return log.show_value(self.config, log.level.DEBUG, 'ThreadID', self._ThreadID) log.show_value(self.config, log.level.DEBUG, 'Current thread id', current_thread_id) raise exceptions.MultiThreadOperated()
def _wait(): for i in range(self.config.retry_wait_time): log.show_value(self.config, log.level.INFO, [ i18n.Prepare, i18n.Again, i18n.Connect, i18n.PTT, ], str(self.config.retry_wait_time - i)) time.sleep(1)
def __init__(self, config): self.config = config self._RDQ = ReceiveDataQueue() self._UseTooManyResources = TargetUnit( [ i18n.UseTooManyResources, ], screens.Target.UseTooManyResources, exceptions_=exceptions.UseTooManyResources()) log.show_value(self.config, log.level.INFO, [ i18n.connect_core, ], i18n.Init)
def pairing(ptt_bot, pool, msg_type): while len(pool) >= 2: target_0 = pool[0] target_1 = pool[1] remove_from_pool(target_0) remove_from_pool(target_1) log.show_value('pairing', log.level.INFO, f'{msg_type} 配對成功', f'{target_0} vs {target_1}') ptt_bot.mail(target_0, 'Ptt Talk 配對結果', f'成功配對 {target_1} 請交換 {msg_type}', 0) ptt_bot.mail(target_1, 'Ptt Talk 配對結果', f'成功配對 {target_0} 請交換 {msg_type}', 0)
def _get_newest_index(api) -> int: last_screen = api.connect_core.get_screen_queue()[-1] # print(last_screen) last_screen_list = last_screen.split('\n') last_screen_list = last_screen_list[3:] last_screen_list = '\n'.join([x[:9] for x in last_screen_list]) # print(last_screen_list) all_index = re.findall(r'\d+', last_screen_list) if len(all_index) == 0: # print(last_screen) # raise exceptions.UnknownError(i18n.UnknownError) return 0 all_index = list(map(int, all_index)) all_index.sort(reverse=True) # print(all_index) max_check_range = 6 newest_index = 0 for IndexTemp in all_index: need_continue = True if IndexTemp > max_check_range: check_range = max_check_range else: check_range = IndexTemp for i in range(1, check_range): if str(IndexTemp - i) not in last_screen: need_continue = False break if need_continue: log.show_value( api.config, log.level.DEBUG, i18n.FindNewestIndex, IndexTemp ) newest_index = IndexTemp break if newest_index == 0: last_screen = api.connect_core.get_screen_queue()[-1] print(last_screen) raise exceptions.UnknownError(i18n.UnknownError) return newest_index
def check_index(config, index_name, index, max_value=None) -> None: check(config, int, index_name, index) if index < 1: raise ValueError( log.merge(config, [ index_name, str(index), i18n.ErrorParameter, i18n.OutOfRange, f'{index} must bigger than 0' ])) if max_value is not None: if index > max_value: log.show_value(config, log.level.INFO, 'Index', index) log.show_value(config, log.level.INFO, 'max_value', max_value) raise ValueError( log.merge(config, [ index_name, str(index), i18n.ErrorParameter, i18n.OutOfRange, f'must between 0 ~ {max_value} but get {index}' ]))
def get_mailbox_capacity(api): last_screen = api.connect_core.get_screen_queue()[-1] capacity_line = last_screen.split('\n')[2] log.show_value( api.config, log.level.DEBUG, 'capacity_line', capacity_line ) pattern_result = re.compile('(\d+)/(\d+)').search(capacity_line) if pattern_result is not None: # print(pattern_result.group(0)) current_capacity = int(pattern_result.group(0).split('/')[0]) max_capacity = int(pattern_result.group(0).split('/')[1]) log.show_value( api.config, log.level.DEBUG, 'current_capacity', current_capacity ) log.show_value( api.config, log.level.DEBUG, 'max_capacity', max_capacity ) return current_capacity, max_capacity return 0, 0
def check_index(config, index_name, index, max_value=None) -> None: check(config, int, index_name, index) if index < 1: raise ValueError( log.merge(config, [ index_name, str(index), i18n.ErrorParameter, i18n.OutOfRange, ])) if max_value is not None: if index > max_value: log.show_value(config, log.level.INFO, 'Index', index) log.show_value(config, log.level.INFO, 'max_value', max_value) raise ValueError( log.merge(config, [ index_name, str(index), i18n.ErrorParameter, i18n.OutOfRange, ]))
def add_pool(ptt_bot, author, msg): exist = remove_from_pool(author) response = '' if '站內信' in msg: if author not in mail_pool: log.show_value('add_pool', log.level.INFO, '加入站內信配對池', author) mail_pool.append(author) response += '站內信' if '賴' in msg or 'line' in msg.lower(): if author not in line_pool: log.show_value('add_pool', log.level.INFO, '加入賴配對池', author) line_pool.append(author) if len(response) == 0: response += '賴' else: response += ', 賴' if '水球' in msg: if author not in waterball_pool: log.show_value('add_pool', log.level.INFO, '加入水球配對池', author) waterball_pool.append(author) if len(response) == 0: response += '水球' else: response += ', 水球' if len(response) == 0: if exist: ptt_bot.mail(author, 'Ptt Talk 取消配對成功', f'已經成功取消配對,感謝您的使用', 0) else: content = '''請詳閱使用說明 https://github.com/PttCodingMan/PttTalk ''' content = content.replace('\n', '\r\n') ptt_bot.mail(author, 'Ptt Talk 註冊失敗', content, 0) else: ptt_bot.mail(author, 'Ptt Talk 註冊成功', f'已經成功註冊 {response} 通信方式', 0)
def send(self, msg: str, target_list: list, screen_timeout: int = 0, refresh: bool = True, secret: bool = False) -> int: def clean_screen(recv_screen: str, NoColor: bool = True) -> str: if not recv_screen: return recv_screen # http://asf.atmel.com/docs/latest/uc3l/html/group__group__avr32__lib_utils__print__funcs.html#ga024c3e2852fe509450ebc363df52ae73 # screen = re.sub('\[[\d+;]*m', '', screen) recv_screen = re.sub(r'[\r]', '', recv_screen) # recv_screen = re.sub(r'[\x00-\x08]', '', recv_screen) recv_screen = re.sub(r'[\x00-\x07]', '', recv_screen) # print(recv_screen) recv_screen = re.sub(r'[\x0b\x0c]', '', recv_screen) # screen = re.sub(r'[\x0e-\x1f]', '', screen) recv_screen = re.sub(r'[\x0e-\x1A]', '', recv_screen) recv_screen = re.sub(r'[\x1C-\x1F]', '', recv_screen) recv_screen = re.sub(r'[\x7f-\xff]', '', recv_screen) recv_screen = screens.vt100(recv_screen) return recv_screen if not all(isinstance(T, TargetUnit) for T in target_list): raise ValueError('Item of TargetList must be TargetUnit') if self._UseTooManyResources not in target_list: target_list.append(self._UseTooManyResources) if screen_timeout == 0: current_screen_timeout = self.config.screen_timeout else: current_screen_timeout = screen_timeout break_detect_after_send = False break_index = -1 is_secret = secret use_too_many_res = False while True: if refresh and not msg.endswith(command.Refresh): msg = msg + command.Refresh try: msg = msg.encode('big5uao', 'replace') except AttributeError: pass except Exception as e: traceback.print_tb(e.__traceback__) print(e) msg = msg.encode('big5', 'replace') if is_secret: log.show_value(self.config, log.level.DEBUG, [i18n.SendMsg], i18n.HideSensitiveInfor) else: log.show_value(self.config, log.level.DEBUG, [i18n.SendMsg], msg) if self.config.connect_mode == connect_mode.TELNET: try: self._core.read_very_eager() self._core.write(msg) except EOFError: raise exceptions.ConnectionClosed() else: try: asyncio.get_event_loop().run_until_complete( self._core.send(msg)) except websockets.exceptions.ConnectionClosedError: raise exceptions.ConnectionClosed() except RuntimeError: raise exceptions.ConnectionClosed() except websockets.exceptions.ConnectionClosedOK: raise exceptions.ConnectionClosed() if break_detect_after_send: return break_index msg = '' receive_data_buffer = bytes() # print(f'0 {use_too_many_res}') start_time = time.time() mid_time = time.time() while mid_time - start_time < current_screen_timeout: recv_data_obj = RecvData() if self.config.connect_mode == connect_mode.TELNET: try: recv_data_obj.data = self._core.read_very_eager() except EOFError: return -1 else: try: asyncio.get_event_loop().run_until_complete( websocket_receiver(self._core, current_screen_timeout, recv_data_obj)) except websockets.exceptions.ConnectionClosed: # print(f'0.1 {use_too_many_res}') if use_too_many_res: # print(f'0.2 {use_too_many_res}') raise exceptions.UseTooManyResources() # print(f'0.3 {use_too_many_res}') raise exceptions.ConnectionClosed() except websockets.exceptions.ConnectionClosedOK: raise exceptions.ConnectionClosed() except asyncio.TimeoutError: return -1 except RuntimeError: raise exceptions.ConnectionClosed() receive_data_buffer += recv_data_obj.data receive_data_temp = receive_data_buffer.decode( 'big5uao', errors='replace') screen = clean_screen(receive_data_temp) # qq = receive_data_temp.encode('utf-8') # b = None # for q in qq: # if not b: # b = f'bytes([{q}' # else: # b += f', {q}' # b += f'])' # print(b) find_target = False for Target in target_list: condition = Target.is_match(screen) if condition: if Target._Handler is not None: Target._Handler(screen) if len(screen) > 0: screens.show(self.config, screen) self._RDQ.add(screen) # self._ReceiveDataQueue.append(screen) if Target == self._UseTooManyResources: # print('!!!!!!!!!!!!!!!') use_too_many_res = True # print(f'1 {use_too_many_res}') break Target.raise_exception() find_target = True log.show_value(self.config, Target.get_log_level(), [i18n.PTT, i18n.Msg], Target.get_display_msg()) end_time = time.time() log.show_value(self.config, log.level.DEBUG, [ i18n.SpendTime, ], round(end_time - start_time, 3)) if Target.is_break(): return target_list.index(Target) msg = Target.get_response(screen) add_refresh = False if Target.is_refresh(): add_refresh = True elif refresh: add_refresh = True if add_refresh: if not msg.endswith(command.Refresh): msg = msg + command.Refresh is_secret = Target.is_secret() if Target.is_break_after_send(): break_index = target_list.index(Target) break_detect_after_send = True break # print(f'2 {use_too_many_res}') if use_too_many_res: # print(f'3 {use_too_many_res}') continue # print(f'4 {use_too_many_res}') if find_target: break if len(screen) > 0: screens.show(self.config, screen) self._RDQ.add(screen) # self._ReceiveDataQueue.append(screen) mid_time = time.time() if not find_target: # raise exceptions.NoMatchTargetError(self._RDQ) return -1 # raise exceptions.NoMatchTargetError(self._RDQ) return -1
def get_mail(api, index) -> data_type.MailInfo: cmd_list = [] cmd_list.append(command.GoMainMenu) cmd_list.append(command.Ctrl_Z) cmd_list.append('m') cmd_list.append(str(index)) cmd_list.append(command.Enter) # cmd_list.append(command.Enter) cmd = ''.join(cmd_list) fast_target = '' for i in range(0, 5): space = ' ' * i fast_target = f'{api.cursor}{space}{index}' if api.cursor == data_type.Cursor.NEW: if len(fast_target) == 6: break else: if len(fast_target) == 5: break target_list = [ connect_core.TargetUnit(i18n.MailBox, screens.Target.InMailBox, break_detect=True, log_level=log.level.DEBUG), connect_core.TargetUnit(i18n.MailBox, fast_target, break_detect=True, log_level=log.level.DEBUG) ] api.connect_core.send( cmd, target_list, ) # last_screen = api.connect_core.get_screen_queue()[-1] # print(last_screen) origin_mail, _ = _api_util.get_content(api, post_mode=False) # print(origin_mail) mail_author_pattern = re.compile('作者 (.+)') pattern_result = mail_author_pattern.search(origin_mail) mail_author = pattern_result.group(0)[2:].strip() log.show_value(api.config, log.level.DEBUG, i18n.Author, mail_author) mail_title_pattern = re.compile('標題 (.+)') pattern_result = mail_title_pattern.search(origin_mail) mail_title = pattern_result.group(0)[2:].strip() log.show_value(api.config, log.level.DEBUG, i18n.Title, mail_title) mail_date_pattern = re.compile('時間 (.+)') pattern_result = mail_date_pattern.search(origin_mail) mail_date = pattern_result.group(0)[2:].strip() log.show_value(api.config, log.level.DEBUG, i18n.Date, mail_date) content_start = '───────────────────────────────────────' content_end = '--\n※ 發信站: 批踢踢實業坊(ptt.cc)' mail_content = origin_mail[origin_mail.find(content_start) + len(content_start) + 1:] mail_content = mail_content[:mail_content.rfind(content_end) + 3] log.show_value(api.config, log.level.DEBUG, i18n.Content, mail_content) # ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 111.242.182.114 # ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 59.104.127.126 (臺灣) ip_line = origin_mail.split('\n') ip_line = [x for x in ip_line if x.startswith(content_end[3:])][0] # print(ip_line) pattern = re.compile('[\d]+\.[\d]+\.[\d]+\.[\d]+') result = pattern.search(ip_line) mail_ip = result.group(0) log.show_value(api.config, log.level.DEBUG, [ i18n.MailBox, 'IP', ], mail_ip) location = ip_line[ip_line.find(mail_ip) + len(mail_ip):].strip() if len(location) == 0: mail_location = None else: # print(location) mail_location = location[1:-1] log.show_value(api.config, log.level.DEBUG, [ i18n.MailBox, 'location', ], mail_location) mail_result = data_type.MailInfo(origin_mail=origin_mail, author=mail_author, title=mail_title, date=mail_date, content=mail_content, ip=mail_ip, location=mail_location) return mail_result
def connect(self) -> None: def _wait(): for i in range(self.config.retry_wait_time): log.show_value(self.config, log.level.INFO, [ i18n.Prepare, i18n.Again, i18n.Connect, i18n.PTT, ], str(self.config.retry_wait_time - i)) time.sleep(1) log.show_value(self.config, log.level.INFO, [ i18n.connect_core, ], i18n.Active) if self.config.host == data_type.host_type.PTT1: telnet_host = 'ptt.cc' websocket_host = 'wss://ws.ptt.cc/bbs/' websocket_origin = 'https://term.ptt.cc' elif self.config.host == data_type.host_type.PTT2: telnet_host = 'ptt2.cc' websocket_host = 'wss://ws.ptt2.cc/bbs/' websocket_origin = 'https://term.ptt2.cc' elif self.config.host == data_type.host_type.LOCALHOST: telnet_host = 'localhost' websocket_host = 'wss://localhost' websocket_origin = 'https://term.ptt.cc' else: telnet_host = self.config.host websocket_host = f'wss://{self.config.host}' websocket_origin = 'https://term.ptt.cc' if self.config.connect_mode == connect_mode.TELNET: log.show_value(self.config, log.level.INFO, i18n.ConnectMode, i18n.ConnectMode_Telnet) elif self.config.connect_mode == connect_mode.WEBSOCKET: log.show_value(self.config, log.level.INFO, i18n.ConnectMode, i18n.ConnectMode_WebSocket) connect_success = False for _ in range(2): try: if self.config.connect_mode == connect_mode.TELNET: self._core = telnetlib.Telnet(telnet_host, self.config.port) else: if not threading.current_thread() is threading.main_thread( ): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) log.show_value(self.config, log.level.DEBUG, 'USER_AGENT', websockets.http.USER_AGENT) self._core = asyncio.get_event_loop().run_until_complete( websockets.connect(websocket_host, origin=websocket_origin)) connect_success = True except Exception as e: traceback.print_tb(e.__traceback__) print(e) if self.config.host == data_type.host_type.PTT1: log.show_value(self.config, log.level.INFO, [ i18n.Connect, i18n.PTT, ], i18n.Fail) else: log.show_value(self.config, log.level.INFO, [ i18n.Connect, i18n.PTT2, ], i18n.Fail) _wait() continue break if not connect_success: raise exceptions.ConnectError(self.config)
def push(api, board: str, push_type: int, push_content: str, post_aid: str, post_index: int) -> None: api._goto_board(board) cmd_list = list() if post_aid is not None: cmd_list.append('#' + post_aid) elif post_index != 0: cmd_list.append(str(post_index)) cmd_list.append(command.Enter) cmd_list.append(command.Push) cmd = ''.join(cmd_list) target_list = [ connect_core.TargetUnit(i18n.HasPushPermission, '您覺得這篇', log_level=log.level.DEBUG, break_detect=True), connect_core.TargetUnit(i18n.OnlyArrow, '加註方式', log_level=log.level.DEBUG, break_detect=True), connect_core.TargetUnit(i18n.NoFastPush, '禁止快速連續推文', log_level=log.level.INFO, break_detect=True, exceptions_=exceptions.NoFastPush()), connect_core.TargetUnit(i18n.NoFastPush, '禁止短時間內大量推文', log_level=log.level.INFO, break_detect=True, exceptions_=exceptions.NoFastPush()), connect_core.TargetUnit(i18n.NoPermission, '使用者不可發言', log_level=log.level.INFO, break_detect=True, exceptions_=exceptions.NoPermission( i18n.NoPermission)), connect_core.TargetUnit(i18n.NoPush, '◆ 抱歉, 禁止推薦', log_level=log.level.INFO, break_detect=True, exceptions_=exceptions.NoPush()), ] index = api.connect_core.send(cmd, target_list) if index == -1: if post_aid is not None: raise exceptions.NoSuchPost(board, post_aid) else: raise exceptions.NoSuchPost(board, post_index) cmd_list = list() if index == 0: push_option_line = api.connect_core.get_screen_queue()[-1] push_option_line = push_option_line.split('\n')[-1] log.show_value(api.config, log.level.DEBUG, 'Push option line', push_option_line) enable_push = '值得推薦' in push_option_line enable_boo = '給它噓聲' in push_option_line enable_arrow = '只加→註解' in push_option_line log.show_value(api.config, log.level.DEBUG, 'Push', enable_push) log.show_value(api.config, log.level.DEBUG, 'Boo', enable_boo) log.show_value(api.config, log.level.DEBUG, 'Arrow', enable_arrow) if push_type == data_type.push_type.PUSH and not enable_push: push_type = data_type.push_type.ARROW elif push_type == data_type.push_type.BOO and not enable_boo: push_type = data_type.push_type.ARROW elif push_type == data_type.push_type.ARROW and not enable_arrow: push_type = data_type.push_type.PUSH cmd_list.append(str(push_type)) # elif index == 1: # push_type = data_type.push_type.ARROW cmd_list.append(push_content) cmd_list.append(command.Enter) cmd_list.append('y') cmd_list.append(command.Enter) cmd = ''.join(cmd_list) target_list = [ connect_core.TargetUnit([ i18n.Push, i18n.Success, ], screens.Target.InBoard, break_detect=True, log_level=log.level.DEBUG), ] api.connect_core.send(cmd, target_list)
def connect(self) -> None: def _wait(): for i in range(self.config.retry_wait_time): log.show_value( self.config, log.level.INFO, [ i18n.Prepare, i18n.Again, i18n.Connect, i18n.PTT, ], str(self.config.retry_wait_time - i) ) time.sleep(1) log.show_value( self.config, log.level.INFO, [ i18n.connect_core, ], i18n.Active ) if self.config.connect_mode == connect_mode.TELNET: log.show_value( self.config, log.level.INFO, i18n.ConnectMode, i18n.ConnectMode_Telnet ) elif self.config.connect_mode == connect_mode.WEBSOCKET: log.show_value( self.config, log.level.INFO, i18n.ConnectMode, i18n.ConnectMode_WebSocket ) connect_success = False global new_event_loop thread_id = threading.get_ident() for _ in range(2): try: if self.config.connect_mode == connect_mode.TELNET: if self.config.host == data_type.host_type.PTT1: self._core = telnetlib.Telnet('ptt.cc', self.config.port) elif self.config.host == data_type.host_type.PTT2: self._core = telnetlib.Telnet('ptt2.cc', self.config.port) else: self._core = telnetlib.Telnet('localhost', self.config.port) else: if thread_id not in new_event_loop: new_event_loop.append(thread_id) try: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) except Exception as e: pass if self.config.host == data_type.host_type.PTT1: self._core = asyncio.get_event_loop().run_until_complete( websockets.connect( 'wss://ws.ptt.cc/bbs/', origin='https://term.ptt.cc' ) ) elif self.config.host == data_type.host_type.PTT2: self._core = asyncio.get_event_loop().run_until_complete( websockets.connect( 'wss://ws.ptt2.cc/bbs', origin='https://term.ptt2.cc' ) ) else: self._core = asyncio.get_event_loop().run_until_complete( websockets.connect( 'wss://localhost', origin='https://term.ptt.cc' ) ) connect_success = True except Exception as e: traceback.print_tb(e.__traceback__) print(e) if self.config.host == data_type.host_type.PTT1: log.show_value( self.config, log.level.INFO, [ i18n.Connect, i18n.PTT, ], i18n.Fail ) else: log.show_value( self.config, log.level.INFO, [ i18n.Connect, i18n.PTT2, ], i18n.Fail ) _wait() continue break if not connect_success: raise exceptions.ConnectError(self.config)
def get_content(api, post_mode: bool = True): api.Unconfirmed = False def is_unconfirmed_handler(): api.Unconfirmed = True if post_mode: cmd = command.Enter * 2 else: cmd = command.Enter target_list = [ # 待證實文章 connect_core.TargetUnit(i18n.UnconfirmedPost, '本篇文章內容經站方授權之板務管理人員判斷有尚待證實之處', response=' ', handler=is_unconfirmed_handler), connect_core.TargetUnit([ i18n.BrowsePost, i18n.Done, ], screens.Target.PostEnd, break_detect=True, log_level=log.level.DEBUG), connect_core.TargetUnit([ i18n.BrowsePost, ], screens.Target.InPost, break_detect=True, log_level=log.level.DEBUG), connect_core.TargetUnit([ i18n.PostNoContent, ], screens.Target.PostNoContent, break_detect=True, log_level=log.level.DEBUG), # 動畫文章 connect_core.TargetUnit([ i18n.AnimationPost, ], screens.Target.Animation, response=command.GoMainMenu_TypeQ, break_detect_after_send=True), ] line_from_pattern = re.compile('[\d]+~[\d]+') content_start = '───────────────────────────────────────' content_end = [] content_end.append('--\n※ 發信站: 批踢踢實業坊(ptt.cc)') content_end.append('--\n※ 發信站: 批踢踢兔(ptt2.cc)') content_end.append('--\n※ 發信站: 新批踢踢(ptt2.twbbs.org.tw)') has_control_code = False control_code_mode = False push_start = False content_start_exist = False content_start_jump = False content_start_jump_set = False first_page = True origin_post = [] stop_dict = dict() while True: index = api.connect_core.send(cmd, target_list) if index == 3 or index == 4: return None, False last_screen = api.connect_core.get_screen_queue()[-1] lines = last_screen.split('\n') last_line = lines[-1] lines.pop() last_screen = '\n'.join(lines) if content_start in last_screen and not content_start_exist: content_start_exist = True if content_start_exist: if not content_start_jump_set: if content_start not in last_screen: content_start_jump = True content_start_jump_set = True else: content_start_jump = False pattern_result = line_from_pattern.search(last_line) if pattern_result is None: control_code_mode = True has_control_code = True else: last_read_line_list = pattern_result.group(0).split('~') last_read_line_a_temp = int(last_read_line_list[0]) last_read_line_b_temp = int(last_read_line_list[1]) if control_code_mode: last_read_line_a = last_read_line_a_temp - 1 last_read_line_b = last_read_line_b_temp - 1 control_code_mode = False if first_page: first_page = False origin_post.append(last_screen) else: # print(LastScreen) # print(f'LastReadLineATemp [{LastReadLineATemp}]') # print(f'LastReadLineBTemp [{LastReadLineBTemp}]') # print(f'Dis [{23 - (LastReadLineBTemp - LastReadLineATemp)}]') # print(f'ContentStartJump {ContentStartJump}') # print(f'GetLineB {LastReadLineBTemp - LastReadLineB}') # print(f'GetLineA {LastReadLineATemp - LastReadLineA}') if not control_code_mode: if last_read_line_a_temp in stop_dict: new_content_part = '\n'.join( lines[-stop_dict[last_read_line_a_temp]:]) else: get_line_b = last_read_line_b_temp - last_read_line_b if get_line_b > 0: # print('Type 1') # print(f'GetLineB [{GetLineB}]') new_content_part = '\n'.join(lines[-get_line_b:]) else: # 駐足現象,LastReadLineB跟上一次相比並沒有改變 if (last_read_line_b_temp + 1) not in stop_dict: stop_dict[last_read_line_b_temp + 1] = 1 stop_dict[last_read_line_b_temp + 1] += 1 get_line_a = last_read_line_a_temp - last_read_line_a if get_line_a > 0: new_content_part = '\n'.join(lines[-get_line_a:]) else: new_content_part = '\n'.join(lines) else: new_content_part = lines[-1] origin_post.append(new_content_part) log.show_value(api.config, log.level.DEBUG, 'NewContentPart', new_content_part) if index == 1: if content_start_jump and len(new_content_part) == 0: # print(f'!!!GetLineB {GetLineB}') get_line_b += 1 new_content_part = '\n'.join(lines[-get_line_b:]) # print(f'!!!NewContentPart {NewContentPart}') origin_post.pop() origin_post.append(new_content_part) break if not control_code_mode: last_read_line_a = last_read_line_a_temp last_read_line_b = last_read_line_b_temp for EC in content_end: if EC in last_screen: push_start = True break if not push_start: cmd = command.Down else: cmd = command.Right # print(api.Unconfirmed) origin_post = '\n'.join(origin_post) # OriginPost = [line.strip() for line in OriginPost.split('\n')] # OriginPost = '\n'.join(OriginPost) log.show_value(api.config, log.level.DEBUG, 'OriginPost', origin_post) return origin_post, has_control_code
def login( api, ptt_id, password, kick_other_login): if api._login_status: api.logout() api.config.kick_other_login = kick_other_login def kick_other_loginDisplayMsg(): if api.config.kick_other_login: return i18n.kick_other_login return i18n.Notkick_other_login def kick_other_loginResponse(Screen): if api.config.kick_other_login: return 'y' + command.Enter return 'n' + command.Enter if len(password) > 8: password = password[:8] ptt_id = ptt_id.strip() password = password.strip() api._ID = ptt_id api._Password = password api.config.kick_other_login = kick_other_login api.connect_core.connect() log.show_value( api.config, log.level.INFO, [ i18n.login, i18n.ID ], ptt_id ) target_list = [ connect_core.TargetUnit( i18n.loginSuccess, screens.Target.MainMenu, break_detect=True ), connect_core.TargetUnit( i18n.HasNewMailGotoMainMenu, '你有新信件', # 加個進去 A 選單再出來的動作,讓畫面更新最底下一行 response=command.GoMainMenu + 'A' + command.Right + command.Left, ), connect_core.TargetUnit( i18n.GoMainMenu, '【看板列表】', response=command.GoMainMenu, ), connect_core.TargetUnit( i18n.ErrorIDPW, '密碼不對', break_detect=True, exceptions_=exceptions.WrongIDorPassword() ), connect_core.TargetUnit( i18n.LoginTooOften, '登入太頻繁', break_detect=True, exceptions_=exceptions.LoginTooOften() ), connect_core.TargetUnit( i18n.SystemBusyTryLater, '系統過載', break_detect=True, ), connect_core.TargetUnit( i18n.DelWrongPWRecord, '您要刪除以上錯誤嘗試的記錄嗎', response='y' + command.Enter, ), connect_core.TargetUnit( i18n.MailBoxFull, '您保存信件數目', response=command.GoMainMenu, ), connect_core.TargetUnit( i18n.PostNotFinish, '請選擇暫存檔 (0-9)[0]', response=command.Enter, ), connect_core.TargetUnit( i18n.PostNotFinish, '有一篇文章尚未完成', response='Q' + command.Enter, ), connect_core.TargetUnit( i18n.SigningUnPleaseWait, '登入中,請稍候', ), connect_core.TargetUnit( kick_other_loginDisplayMsg, '您想刪除其他重複登入的連線嗎', response=kick_other_loginResponse, ), connect_core.TargetUnit( i18n.AnyKeyContinue, '任意鍵', response=command.Enter ), connect_core.TargetUnit( i18n.SigningUpdate, '正在更新與同步線上使用者及好友名單', ), ] cmd_list = [] cmd_list.append(ptt_id) cmd_list.append(command.Enter) cmd_list.append(password) cmd_list.append(command.Enter) cmd = ''.join(cmd_list) index = api.connect_core.send( cmd, target_list, screen_timeout=api.config.screen_long_timeout, refresh=False, secret=True ) if target_list[index].get_display_msg() != i18n.loginSuccess: ori_screen = api.connect_core.get_screen_queue()[-1] print(ori_screen) raise exceptions.LoginError() ori_screen = api.connect_core.get_screen_queue()[-1] if '> (' in ori_screen: api.cursor = data_type.Cursor.NEW log.log( api.config, log.level.DEBUG, i18n.NewCursor ) else: api.cursor = data_type.Cursor.OLD log.log( api.config, log.level.DEBUG, i18n.OldCursor ) if api.cursor not in screens.Target.InBoardWithCursor: screens.Target.InBoardWithCursor.append('\n' + api.cursor) api._unregistered_user = True if '(T)alk' in ori_screen: api._unregistered_user = False if '(P)lay' in ori_screen: api._unregistered_user = False if '(N)amelist' in ori_screen: api._unregistered_user = False if api._unregistered_user: # print(ori_screen) log.log( api.config, log.level.INFO, i18n.UnregisteredUserCantUseAllAPI ) api._login_status = True
def get_user(api, ptt_id: str) -> data_type.UserInfo: cmd_list = list() cmd_list.append(command.GoMainMenu) cmd_list.append('T') cmd_list.append(command.Enter) cmd_list.append('Q') cmd_list.append(command.Enter) cmd_list.append(ptt_id) cmd_list.append(command.Enter) cmd = ''.join(cmd_list) target_list = [ connect_core.TargetUnit( [ i18n.GetUser, i18n.Success, ], screens.Target.AnyKey, break_detect=True), connect_core.TargetUnit( [ i18n.GetUser, i18n.Fail, ], screens.Target.InTalk, break_detect=True), ] index = api.connect_core.send( cmd, target_list) ori_screen = api.connect_core.get_screen_queue()[-1] if index == 1: raise exceptions.NoSuchUser(ptt_id) # PTT1 # 《ID暱稱》CodingMan (專業程式 BUG 製造機)《經濟狀況》小康 ($73866) # 《登入次數》1118 次 (同天內只計一次) 《有效文章》15 篇 (退:0) # 《目前動態》閱讀文章 《私人信箱》最近無新信件 # 《上次上站》10/06/2019 17:29:49 Sun 《上次故鄉》111.251.231.184 # 《 五子棋 》 0 勝 0 敗 0 和 《象棋戰績》 0 勝 0 敗 0 和 # https://github.com/Truth0906/PTTLibrary # 強大的 PTT 函式庫 # 提供您 快速 穩定 完整 的 PTT API # 提供專業的 PTT 機器人諮詢服務 # PTT2 # 《ID暱稱》CodingMan (專業程式 BUG 製造機)《經濟狀況》家徒四壁 ($0) # 《登入次數》8 次 (同天內只計一次) 《有效文章》0 篇 # 《目前動態》看板列表 《私人信箱》最近無新信件 # 《上次上站》10/06/2019 17:27:55 Sun 《上次故鄉》111.251.231.184 # 《 五子棋 》 0 勝 0 敗 0 和 《象棋戰績》 0 勝 0 敗 0 和 # 《個人名片》CodingMan 目前沒有名片 # print(ori_screen) # data = lib_util.get_sub_string_list(ori_screen, '》', ['《', '\n']) data = parse_user_page(ori_screen) if len(data) < 10: print('\n'.join(data)) print(len(data)) raise exceptions.ParseError(ori_screen) # print('\n=> '.join(data)) ptt_id = data[0] money = data[1] login_time = int(data[2]) temp = re.findall(r'\d+', data[3]) legal_post = int(temp[0]) # PTT2 沒有退文 if api.config.host == data_type.host_type.PTT1: illegal_post = int(temp[1]) else: illegal_post = -1 status = data[4] mail = data[5] last_login = data[6] last_ip = data[7] five_chess = data[8] chess = data[9] signature_file = '\n'.join(ori_screen.split('\n')[6:-1]) log.show_value(api.config, log.level.DEBUG, 'ptt_id', ptt_id) log.show_value(api.config, log.level.DEBUG, 'money', money) log.show_value(api.config, log.level.DEBUG, 'login_time', login_time) log.show_value(api.config, log.level.DEBUG, 'legal_post', legal_post) log.show_value(api.config, log.level.DEBUG, 'illegal_post', illegal_post) log.show_value(api.config, log.level.DEBUG, 'status', status) log.show_value(api.config, log.level.DEBUG, 'mail', mail) log.show_value(api.config, log.level.DEBUG, 'last_login', last_login) log.show_value(api.config, log.level.DEBUG, 'last_ip', last_ip) log.show_value(api.config, log.level.DEBUG, 'five_chess', five_chess) log.show_value(api.config, log.level.DEBUG, 'chess', chess) log.show_value(api.config, log.level.DEBUG, 'signature_file', signature_file) user = data_type.UserInfo( ptt_id, money, login_time, legal_post, illegal_post, status, mail, last_login, last_ip, five_chess, chess, signature_file) return user
def get_board_info(api, board: str, call_by_others: bool) -> None: cmd_list = [] cmd_list.append(command.GoMainMenu) cmd_list.append('qs') cmd_list.append(board) cmd_list.append(command.Enter) cmd_list.append(command.Ctrl_C * 2) cmd_list.append(command.Space) cmd = ''.join(cmd_list) if call_by_others: log_level = log.level.DEBUG else: log_level = log.level.INFO target_list = [ connect_core.TargetUnit(i18n.IntoBoard, ['文章選讀', '進板畫面'], break_detect=True, log_level=log_level), ] api.connect_core.send(cmd, target_list) ori_screen = api.connect_core.get_screen_queue()[-1] # print(OriScreen) nuser = ori_screen.split('\n')[2] # print(Nuser) if '[靜]' in nuser: online_user = 0 else: if '編號' not in nuser or '人氣' not in nuser: raise exceptions.NoSuchBoard(api.config, board) pattern = re.compile('[\d]+') r = pattern.search(nuser) if r is None: raise exceptions.NoSuchBoard(api.config, board) # 減一是把自己本身拿掉 online_user = int(r.group(0)) - 1 log.show_value(api.config, log.level.DEBUG, '人氣', online_user) target_list = [ connect_core.TargetUnit(i18n.ReadingBoardInfo, '任意鍵繼續', break_detect=True, log_level=log_level), ] api.connect_core.send('i', target_list) ori_screen = api.connect_core.get_screen_queue()[-1] # print(OriScreen) p = re.compile('《(.+)》看板設定') r = p.search(ori_screen) if r is not None: boardname = r.group(0)[1:-5].strip() log.show_value(api.config, log.level.DEBUG, '看板名稱', boardname) if boardname != board: raise exceptions.NoSuchBoard(api.config, board) p = re.compile('中文敘述: (.+)') r = p.search(ori_screen) if r is not None: chinese_des = r.group(0)[5:].strip() log.show_value(api.config, log.level.DEBUG, '中文敘述', chinese_des) p = re.compile('板主名單: (.+)') r = p.search(ori_screen) if r is not None: moderator_line = r.group(0)[5:].strip() moderators = moderator_line.split('/') log.show_value(api.config, log.level.DEBUG, '板主名單', moderators) open_status = ('公開狀態(是否隱形): 公開' in ori_screen) log.show_value(api.config, log.level.DEBUG, '公開狀態', open_status) into_top_ten_when_hide = ('隱板時 可以 進入十大排行榜' in ori_screen) log.show_value(api.config, log.level.DEBUG, '隱板時可以進入十大排行榜', into_top_ten_when_hide) non_board_members_post = ('開放 非看板會員發文' in ori_screen) log.show_value(api.config, log.level.DEBUG, '非看板會員發文', non_board_members_post) reply_post = ('開放 回應文章' in ori_screen) log.show_value(api.config, log.level.DEBUG, '回應文章', reply_post) self_del_post = ('開放 自刪文章' in ori_screen) log.show_value(api.config, log.level.DEBUG, '自刪文章', self_del_post) push_post = ('開放 推薦文章' in ori_screen) log.show_value(api.config, log.level.DEBUG, '推薦文章', push_post) boo_post = ('開放 噓文' in ori_screen) log.show_value(api.config, log.level.DEBUG, '噓文', boo_post) # 限制 快速連推文章, 最低間隔時間: 5 秒 # 開放 快速連推文章 fast_push = ('開放 快速連推文章' in ori_screen) log.show_value(api.config, log.level.DEBUG, '快速連推文章', fast_push) if not fast_push: p = re.compile('最低間隔時間: [\d]+') r = p.search(ori_screen) if r is not None: min_interval = r.group(0)[7:].strip() min_interval = int(min_interval) else: min_interval = 0 log.show_value(api.config, log.level.DEBUG, '最低間隔時間', min_interval) else: min_interval = 0 # 推文時 自動 記錄來源 IP # 推文時 不會 記錄來源 IP push_record_ip = ('推文時 自動 記錄來源 IP' in ori_screen) log.show_value(api.config, log.level.DEBUG, '記錄來源 IP', push_record_ip) # 推文時 對齊 開頭 # 推文時 不用對齊 開頭 push_aligned = ('推文時 對齊 開頭' in ori_screen) log.show_value(api.config, log.level.DEBUG, '對齊開頭', push_aligned) # 板主 可 刪除部份違規文字 moderator_can_del_illegal_content = ('板主 可 刪除部份違規文字' in ori_screen) log.show_value(api.config, log.level.DEBUG, '板主可刪除部份違規文字', moderator_can_del_illegal_content) # 轉錄文章 會 自動記錄,且 需要 發文權限 tran_post_auto_recorded_and_require_post_permissions = ( '轉錄文章 會 自動記錄,且 需要 發文權限' in ori_screen) log.show_value(api.config, log.level.DEBUG, '轉錄文章 會 自動記錄,且 需要 發文權限', tran_post_auto_recorded_and_require_post_permissions) cool_mode = ('未 設為冷靜模式' not in ori_screen) log.show_value(api.config, log.level.DEBUG, '冷靜模式', cool_mode) require18 = ('禁止 未滿十八歲進入' in ori_screen) log.show_value(api.config, log.level.DEBUG, '禁止未滿十八歲進入', require18) p = re.compile('登入次數 [\d]+ 次以上') r = p.search(ori_screen) if r is not None: require_login_time = r.group(0).split(' ')[1] require_login_time = int(require_login_time) else: require_login_time = 0 log.show_value(api.config, log.level.DEBUG, '發文限制登入次數', require_login_time) p = re.compile('退文篇數 [\d]+ 篇以下') r = p.search(ori_screen) if r is not None: require_illegal_post = r.group(0).split(' ')[1] require_illegal_post = int(require_illegal_post) else: require_illegal_post = 0 log.show_value(api.config, log.level.DEBUG, '發文限制退文篇數', require_illegal_post) board_info = data_type.BoardInfo( boardname, online_user, chinese_des, moderators, open_status, into_top_ten_when_hide, non_board_members_post, reply_post, self_del_post, push_post, boo_post, fast_push, min_interval, push_record_ip, push_aligned, moderator_can_del_illegal_content, tran_post_auto_recorded_and_require_post_permissions, cool_mode, require18, require_login_time, require_illegal_post, ) return board_info
def push(self, board: str, push_type: int, push_content: str, post_aid: str = None, post_index: int = 0) -> None: self._one_thread() if not self._login_status: raise exceptions.Requirelogin(i18n.Requirelogin) self.config.log_last_value = None check_value.check(self.config, str, 'Board', board) check_value.check(self.config, int, 'push_type', push_type, value_class=data_type.push_type) check_value.check(self.config, str, 'PushContent', push_content) if post_aid is not None: check_value.check(self.config, str, 'PostAID', post_aid) check_value.check(self.config, int, 'PostIndex', post_index) if len(board) == 0: raise ValueError( log.merge(self.config, [i18n.Board, i18n.ErrorParameter, board])) if post_index != 0 and isinstance(post_aid, str): raise ValueError( log.merge(self.config, [ 'PostIndex', 'PostAID', i18n.ErrorParameter, i18n.BothInput ])) if post_index == 0 and post_aid is None: raise ValueError( log.merge(self.config, [ 'PostIndex', 'PostAID', i18n.ErrorParameter, i18n.NoInput ])) if post_index != 0: newest_index = self._get_newest_index(data_type.index_type.BBS, board=board) check_value.check_index(self.config, 'PostIndex', post_index, newest_index) self._check_board(board) max_push_length = 33 push_list = [] temp_start_index = 0 temp_end_index = temp_start_index + 1 while temp_end_index <= len(push_content): temp = '' last_temp = None while len(temp.encode('big5-uao', 'replace')) < max_push_length: temp = push_content[temp_start_index:temp_end_index] if not len(temp.encode('big5-uao', 'replace')) < max_push_length: break elif push_content.endswith(temp): break elif temp.endswith('\n'): break elif last_temp == temp: break temp_end_index += 1 last_temp = temp push_list.append(temp.strip()) temp_start_index = temp_end_index temp_end_index = temp_start_index + 1 push_list = filter(None, push_list) for push in push_list: log.show_value(self.config, log.level.INFO, i18n.Push, push) for _ in range(2): try: self._push(board, push_type, push, post_aid=post_aid, post_index=post_index) break except exceptions.NoFastPush: # screens.show(self.config, self.connect_core.getScreenQueue()) log.log(self.config, log.level.INFO, '等待快速推文') time.sleep(5.2)
def crawl_board( self, crawl_type: int, post_handler, board: str, # BBS版本 start_index: int = 0, end_index: int = 0, start_aid: str = None, end_aid: str = None, search_type: int = 0, search_condition: str = None, query: bool = False, # 網頁版本 start_page: int = 0, end_page: int = 0) -> list: self._one_thread() self.config.log_last_value = None check_value.check(self.config, int, 'crawl_type', crawl_type, value_class=data_type.crawl_type) check_value.check(self.config, str, 'Board', board) if len(board) == 0: raise ValueError( log.merge(self.config, [i18n.Board, i18n.ErrorParameter, board])) if crawl_type == data_type.crawl_type.BBS: if not self._login_status: raise exceptions.Requirelogin(i18n.Requirelogin) check_value.check(self.config, int, 'SearchType', search_type) if search_condition is not None: check_value.check(self.config, str, 'SearchCondition', search_condition) if start_aid is not None: check_value.check(self.config, str, 'StartAID', start_aid) if end_aid is not None: check_value.check(self.config, str, 'EndAID', end_aid) if (start_aid is not None or end_aid is not None) and \ (start_index != 0 or end_index != 0): raise ValueError( log.merge( self.config, ['AID', 'Index', i18n.ErrorParameter, i18n.BothInput])) if (start_aid is not None or end_aid is not None) and \ (search_condition is not None): raise ValueError( log.merge(self.config, [ 'AID', 'SearchCondition', i18n.ErrorParameter, i18n.BothInput ])) if search_type == data_type.post_search_type.PUSH: try: S = int(search_condition) except ValueError: raise ValueError( log.merge(self.config, [ 'SearchCondition', i18n.ErrorParameter, ])) if not (-100 <= S <= 110): raise ValueError( log.merge(self.config, [ 'SearchCondition', i18n.ErrorParameter, ])) if start_index != 0: newest_index = self._get_newest_index( data_type.index_type.BBS, board=board, search_type=search_type, search_condition=search_condition) check_value.check_index_range(self.config, 'start_index', start_index, 'end_index', end_index, max_value=newest_index) elif start_aid is not None and end_aid is not None: start_index = self.get_post(board, post_aid=start_aid, query=True).index end_index = self.get_post(board, post_aid=end_aid, query=True).index check_value.check_index_range(self.config, 'start_index', start_index, 'end_index', end_index) else: raise ValueError( log.merge(self.config, [i18n.ErrorParameter, i18n.NoInput])) log.show_value(self.config, log.level.DEBUG, 'StartIndex', start_index) log.show_value(self.config, log.level.DEBUG, 'EndIndex', end_index) error_post_list = [] del_post_list = [] if self.config.log_level == log.level.INFO: PB = progressbar.ProgressBar(max_value=end_index - start_index + 1, redirect_stdout=True) for index in range(start_index, end_index + 1): for i in range(2): need_continue = False post = None try: post = self._get_post( board, post_index=index, search_type=search_type, search_condition=search_condition, query=query) except exceptions.ParseError as e: if i == 1: raise e need_continue = True except exceptions.UnknownError as e: if i == 1: raise e need_continue = True except exceptions.NoSuchBoard as e: if i == 1: raise e need_continue = True except exceptions.NoMatchTargetError as e: if i == 1: raise e need_continue = True except exceptions.ConnectionClosed as e: if i == 1: raise e log.log(self.config, log.level.INFO, i18n.RestoreConnection) self._login(self._ID, self._Password, self.config.kick_other_login) need_continue = True except exceptions.UseTooManyResources as e: if i == 1: raise e log.log(self.config, log.level.INFO, i18n.RestoreConnection) self._login(self._ID, self._Password, self.config.kick_other_login) need_continue = True if post is None: need_continue = True elif not post.pass_format_check: need_continue = True if need_continue: log.log(self.config, log.level.DEBUG, 'Wait for retry repost') time.sleep(0.1) continue break if self.config.log_level == log.level.INFO: PB.update(index - start_index) if post is None: error_post_list.append(index) continue if not post.pass_format_check: if post.aid is not None: error_post_list.append(post.aid) else: error_post_list.append(index) continue if post.delete_status != data_type.post_delete_status.NOT_DELETED: del_post_list.append(index) post_handler(post) if self.config.log_level == log.level.INFO: PB.finish() return error_post_list, del_post_list else: if self.config.host == data_type.host_type.PTT2: raise exceptions.HostNotSupport( lib_util.get_current_func_name()) # 網頁版本爬蟲 # https://www.ptt.cc/bbs/index.html # 1. 取得總共有幾頁 MaxPage newest_index = self._get_newest_index(data_type.index_type.WEB, board=board) # 2. 檢查 StartPage 跟 EndPage 有沒有在 1 ~ MaxPage 之間 check_value.check_index_range(self.config, 'StartPage', start_page, 'EndPage', end_page, max_value=newest_index) # 3. 把每篇文章(包括被刪除文章)欄位解析出來組合成 data_type.PostInfo error_post_list = [] del_post_list = [] # PostAID = "" _url = 'https://www.ptt.cc/bbs/' index = str(newest_index) if self.config.log_level == log.level.INFO: PB = progressbar.ProgressBar(max_value=end_page - start_page + 1, redirect_stdout=True) def deleted_post(post_title): if post_title.startswith('('): if '本文' in post_title: return data_type.post_delete_status.AUTHOR elif post_title.startswith('(已被'): return data_type.post_delete_status.MODERATOR else: return data_type.post_delete_status.UNKNOWN else: return data_type.post_delete_status.NOT_DELETED for index in range(start_page, newest_index + 1): log.show_value(self.config, log.level.DEBUG, 'CurrentPage', index) url = _url + board + '/index' + str(index) + '.html' r = requests.get(url, cookies={'over18': '1'}) if r.status_code != requests.codes.ok: raise exceptions.NoSuchBoard(self.config, board) soup = BeautifulSoup(r.text, 'html.parser') for div in soup.select('div.r-ent'): web = div.select('div.title a') post = { 'author': div.select('div.author')[0].text, 'title': div.select('div.title')[0].text.strip('\n').strip(), 'web': web[0].get('href') if web else '' } if post['title'].startswith('('): del_post_list.append(post['title']) if post['title'].startswith('(本文'): if '[' in post['title']: post['author'] = post['title'].split( '[')[1].split(']')[0] else: post['author'] = post['title'].split( '<')[1].split('>')[0] else: post['author'] = post['title'].split('<')[1].split( '>')[0] post = data_type.PostInfo( board=board, author=post['author'], title=post['title'], web_url='https://www.ptt.cc' + post['web'], delete_status=deleted_post(post['title'])) post_handler(post) if self.config.log_level == log.level.INFO: PB.update(index - start_page) log.show_value(self.config, log.level.DEBUG, 'DelPostList', del_post_list) # 4. 把組合出來的 Post 塞給 handler # 5. 顯示 progress bar if self.config.log_level == log.level.INFO: PB.finish() return error_post_list, del_post_list
def __init__(self, language: int = 0, log_level: int = 0, screen_time_out: int = 0, screen_long_time_out: int = 0, screen_post_timeout: int = 0, connect_mode: int = 0, port: int = 0, log_handler=None, host: int = 0): self._ID = None if log_handler is not None and not callable(log_handler): raise TypeError('[PyPtt] log_handler is must callable!!') if log_handler is not None: has_log_handler = True set_log_handler_result = True try: log_handler(f'PyPtt v {version.V}') log_handler('Developed by CodingMan') except Exception: log_handler = None set_log_handler_result = False else: has_log_handler = False print(f'PyPtt v {version.V}') print('Developed by CodingMan') self._login_status = False self._unregistered_user = True self.config = config.Config() if not isinstance(language, int): raise TypeError('[PyPtt] language must be integer') if not isinstance(log_level, int): raise TypeError('[PyPtt] log_level must be integer') if not isinstance(screen_time_out, int): raise TypeError('[PyPtt] screen_timeout must be integer') if not isinstance(screen_long_time_out, int): raise TypeError('[PyPtt] screen_long_timeout must be integer') if not isinstance(host, int): raise TypeError('[PyPtt] host must be integer') if screen_time_out != 0: self.config.screen_timeout = screen_time_out if screen_long_time_out != 0: self.config.screen_long_timeout = screen_long_time_out if screen_post_timeout != 0: self.config.screen_post_timeout = screen_post_timeout if log_level == 0: log_level = self.config.log_level elif not lib_util.check_range(log.level, log_level): raise ValueError('[PyPtt] Unknown log_level', log_level) else: self.config.log_level = log_level if language == 0: language = self.config.language elif not lib_util.check_range(i18n.language, language): raise ValueError('[PyPtt] Unknown language', language) else: self.config.language = language i18n.load(self.config.language) if log_handler is not None: self.config.log_handler = log_handler log.show_value(self.config, log.level.INFO, i18n.log_handler, i18n.Init) elif has_log_handler and not set_log_handler_result: log.show_value(self.config, log.level.INFO, i18n.log_handler, [i18n.Init, i18n.Fail]) if self.config.language == i18n.language.CHINESE: log.show_value(self.config, log.level.INFO, [i18n.ChineseTranditional, i18n.languageModule], i18n.Init) elif self.config.language == i18n.language.ENGLISH: log.show_value(self.config, log.level.INFO, [i18n.English, i18n.languageModule], i18n.Init) if connect_mode == 0: connect_mode = self.config.connect_mode elif not lib_util.check_range(connect_core.connect_mode, connect_mode): raise ValueError('[PyPtt] Unknown connect_mode', connect_mode) else: self.config.connect_mode = connect_mode if port == 0: port = self.config.port elif not 0 < port < 65535: raise ValueError('[PyPtt] Unknown port', port) else: self.config.port = port if host == 0: host = self.config.host elif not lib_util.check_range(data_type.host_type, host): raise ValueError('[PyPtt] Unknown host', host) self.config.host = host if self.config.host == data_type.host_type.PTT1: log.show_value(self.config, log.level.INFO, [i18n.Connect, i18n.host], i18n.PTT) elif self.config.host == data_type.host_type.PTT2: log.show_value(self.config, log.level.INFO, [i18n.Connect, i18n.host], i18n.PTT2) elif self.config.host == data_type.host_type.LOCALHOST: log.show_value(self.config, log.level.INFO, [i18n.Connect, i18n.host], i18n.Localhost) self.connect_core = connect_core.API(self.config) self._ExistBoardList = [] self._ModeratorList = dict() self._LastThrowWaterBallTime = 0 self._ThreadID = threading.get_ident() log.show_value(self.config, log.level.DEBUG, 'ThreadID', self._ThreadID) log.show_value(self.config, log.level.INFO, [ i18n.Library, ' v ' + version.V, ], i18n.Init)
sys.exit() except PTT.exceptions.WrongIDorPassword: ptt_bot.log('帳號密碼錯誤') sys.exit() except PTT.exceptions.LoginTooOften: ptt_bot.log('請稍等一下再登入') sys.exit() ptt_bot.set_call_status(PTT.data_type.call_status.OFF) return ptt_bot if __name__ == '__main__': log.show_value('main', log.level.INFO, 'Ptt Talk', version) ptt_id, password = get_password('account.txt') try: ptt_bot = login() line_pool = [] waterball_pool = [] mail_pool = [] max_pool_count = 2 file_to_list() while True:
def get_waterball(api, operate_type: int) -> list: if operate_type == data_type.waterball_operate_type.NOTHING: water_ball_operate_type = 'R' elif operate_type == data_type.waterball_operate_type.CLEAR: water_ball_operate_type = 'C' + command.Enter + 'Y' elif operate_type == data_type.waterball_operate_type.MAIL: water_ball_operate_type = 'M' target_list = [ connect_core.TargetUnit(i18n.NoWaterball, '◆ 暫無訊息記錄', break_detect=True, log_level=log.level.DEBUG), connect_core.TargetUnit([ i18n.BrowseWaterball, i18n.Done, ], screens.Target.WaterBallListEnd, response=command.Left + water_ball_operate_type + command.Enter + command.GoMainMenu, break_detect_after_send=True, log_level=log.level.DEBUG), connect_core.TargetUnit([ i18n.BrowseWaterball, ], screens.Target.InWaterBallList, break_detect=True, log_level=log.level.DEBUG), ] cmd_list = [command.GoMainMenu, 'T', command.Enter, 'D', command.Enter] cmd = ''.join(cmd_list) line_from_pattern = re.compile('[\d]+~[\d]+') to_water_ball_target_pattern = re.compile('To [\w]+:') from_water_ball_target_pattern = re.compile('★[\w]+ ') water_ball_date_pattern = re.compile( '\[[\d]+/[\d]+/[\d]+ [\d]+:[\d]+:[\d]+\]') all_waterball = list() first_page = True while True: index = api.connect_core.send(cmd, target_list) log.show_value(api.config, log.level.DEBUG, 'index', index) if index == 0: return list() ori_screen = api.connect_core.get_screen_queue()[-1] lines = ori_screen.split('\n') last_line = lines[-1] lines.pop() lines = list(filter(None, lines)) ori_screen = '\n'.join(lines) # print('=' * 50) # print(OriScreen) # print('=' * 50) # ScreenTemp = OriScreen log.show_value(api.config, log.level.DEBUG, 'OriScreen', ori_screen) log.show_value(api.config, log.level.DEBUG, 'LastLine', last_line) if last_line.startswith('★'): continue # 整理水球換行格式 # ScreenTemp = ScreenTemp.replace( # ']\n', ']==PTTWaterBallNewLine==') # ScreenTemp = ScreenTemp.replace('\\\n', '') # ScreenTemp = ScreenTemp.replace('\n', '') # ScreenTemp = ScreenTemp.replace( # ']==PTTWaterBallNewLine==', ']\n') # print('=' * 50) # print(LastLine) # print('=' * 50) pattern_result = line_from_pattern.search(last_line) try: last_read_line_list = pattern_result.group(0).split('~') except Exception as e: # Error result # 'NoneType' object has no attribute 'group' # ori_screen 【聊天說話】 批踢踢實業坊 # ) │ \ / /▎ ‧ ▉ ▁__C \ \ # ) (. │ / ∕/◤ . .▉ ▔▔ ◣ ﹨| # ( ( :. │ 7/ ◤▁▁▁▁▁▉▁▁▁▁ ◣\ # ╰╮╮ ▁▂▃▄▃▂▁ │ 〢∕▄▄▄▄▄▄█▄▄▄▄▄▄\〢 # ╰ ▊▃▄▅▆▅▄▃\▆◣ │ ==} █ {== # last_line ▄▆▉▇▅▃▃▃▅▇◤ ◥▍ │ 〣\ ▅▆ █ █ # import traceback # traceback.print_tb(e.__traceback__) # print(e) # print(f'ori_screen {ori_screen}') # print(f'last_line {last_line}') continue last_read_line_a_temp = int(last_read_line_list[0]) last_read_line_b_temp = int(last_read_line_list[1]) # last_read_line_a = last_read_line_a_temp - 1 # last_read_line_b = last_read_line_b_temp - 1 if first_page: first_page = False all_waterball.append(ori_screen) last_read_line_a = last_read_line_a_temp - 1 last_read_line_b = last_read_line_b_temp - 1 else: get_line_a = last_read_line_a_temp - last_read_line_a get_line_b = last_read_line_b_temp - last_read_line_b # print(f'last_read_line_a [{last_read_line_a}]') # print(f'last_read_line_b [{last_read_line_b}]') # print(f'last_read_line_a_temp [{last_read_line_a_temp}]') # print(f'last_read_line_b_temp [{last_read_line_b_temp}]') # print(f'get_line_a [{get_line_a}]') # print(f'get_line_b [{get_line_b}]') if get_line_b > 0: # print('Type 1') if not all_waterball[-1].endswith(']'): get_line_b += 1 new_content_part = '\n'.join(lines[-get_line_b:]) else: # print('Type 2') if get_line_a > 0: # print('Type 2 - 1') if len(lines[-get_line_a]) == 0: # print('!!!!!!!!!') get_line_a += 1 new_content_part = '\n'.join(lines[-get_line_a:]) else: # print('Type 2 - 2') new_content_part = '\n'.join(lines) all_waterball.append(new_content_part) log.show_value(api.config, log.level.DEBUG, 'NewContentPart', new_content_part) if index == 1: break last_read_line_a = last_read_line_a_temp last_read_line_b = last_read_line_b_temp cmd = command.Down all_waterball = '\n'.join(all_waterball) if api.config.host == data_type.host_type.PTT1: all_waterball = all_waterball.replace(']\n', ']==PTTWaterBallNewLine==') all_waterball = all_waterball.replace('\n', '') all_waterball = all_waterball.replace(']==PTTWaterBallNewLine==', ']\n') else: all_waterball = all_waterball.replace('\\\n', '') log.show_value(api.config, log.level.DEBUG, 'AllWaterball', all_waterball) # print('=' * 20) # print(AllWaterball) # print('=' * 20) water_ball_list = list() for line in all_waterball.split('\n'): if (not line.startswith('To')) and (not line.startswith('★')): log.show_value(api.config, log.level.DEBUG, 'Discard waterball', line) continue log.show_value(api.config, log.level.DEBUG, 'Ready to parse waterball', line) if line.startswith('To'): log.show_value(api.config, log.level.DEBUG, 'Waterball Type', 'Send') waterball_type = data_type.waterball_type.SEND pattern_result = to_water_ball_target_pattern.search(line) target = pattern_result.group(0)[3:-1] pattern_result = water_ball_date_pattern.search(line) date = pattern_result.group(0)[1:-1] content = line content = content[content.find(target + ':') + len(target + ':'):] content = content[:content.rfind(date) - 1] content = content.strip() elif line.startswith('★'): log.show_value(api.config, log.level.DEBUG, 'Waterball Type', 'Catch') waterball_type = data_type.waterball_type.CATCH pattern_result = from_water_ball_target_pattern.search(line) target = pattern_result.group(0)[1:-1] pattern_result = water_ball_date_pattern.search(line) date = pattern_result.group(0)[1:-1] content = line content = content[content.find(target + ' ') + len(target + ' '):] content = content[:content.rfind(date) - 1] content = content.strip() log.show_value(api.config, log.level.DEBUG, 'Waterball target', target) log.show_value(api.config, log.level.DEBUG, 'Waterball content', content) log.show_value(api.config, log.level.DEBUG, 'Waterball date', date) current_waterball = data_type.WaterballInfo(waterball_type, target, content, date) water_ball_list.append(current_waterball) return water_ball_list
def mail(api, ptt_id: str, title: str, content: str, sign_file) -> None: # log.showValue( # api.config, # log.level.INFO, # [ # i18n.PTT, # i18n.Msg # ], # i18n.MarkPost # ) CmdList = [] CmdList.append(command.GoMainMenu) CmdList.append('M') CmdList.append(command.Enter) CmdList.append('S') CmdList.append(command.Enter) CmdList.append(ptt_id) CmdList.append(command.Enter) Cmd = ''.join(CmdList) TargetList = [ connect_core.TargetUnit([i18n.Start, i18n.SendMail], '主題:', break_detect=True), connect_core.TargetUnit(i18n.NoSuchUser, '【電子郵件】', exceptions_=exceptions.NoSuchUser(ptt_id)), ] api.connect_core.send(Cmd, TargetList, screen_timeout=api.config.screen_long_timeout) CmdList = [] CmdList.append(title) CmdList.append(command.Enter) CmdList.append(content) CmdList.append(command.Ctrl_X) Cmd = ''.join(CmdList) if sign_file == 0: SingFileSelection = i18n.NoSignatureFile else: SingFileSelection = i18n.Select + ' ' + \ str(sign_file) + 'th ' + i18n.SignatureFile TargetList = [ connect_core.TargetUnit(i18n.AnyKeyContinue, '任意鍵', break_detect=True), connect_core.TargetUnit( i18n.SaveFile, '確定要儲存檔案嗎', response='s' + command.Enter, # Refresh=False, ), connect_core.TargetUnit(i18n.SelfSaveDraft, '是否自存底稿', response='y' + command.Enter), connect_core.TargetUnit(SingFileSelection, '選擇簽名檔', response=str(sign_file) + command.Enter), connect_core.TargetUnit(SingFileSelection, 'x=隨機', response=str(sign_file) + command.Enter), ] api.connect_core.send(Cmd, TargetList, screen_timeout=api.config.screen_post_timeout) log.show_value(api.config, log.level.INFO, i18n.SendMail, i18n.Success)
def throw_waterball(api: object, target_id: str, content: str) -> None: max_length = 50 water_ball_list = list() temp_start_index = 0 temp_end_index = temp_start_index + 1 while temp_end_index <= len(content): temp = '' last_temp = None while len(temp.encode('big5uao', 'ignore')) < max_length: temp = content[temp_start_index:temp_end_index] if not len(temp.encode('big5uao', 'ignore')) < max_length: break elif content.endswith(temp) and temp_start_index != 0: break elif temp.endswith('\n'): break elif last_temp == temp: break temp_end_index += 1 last_temp = temp water_ball_list.append(temp.strip()) temp_start_index = temp_end_index temp_end_index = temp_start_index + 1 water_ball_list = filter(None, water_ball_list) for waterball in water_ball_list: if api._LastThrowWaterBallTime != 0: current_time = time.time() while (current_time - api._LastThrowWaterBallTime) < 3.2: time.sleep(0.1) current_time = time.time() log.show_value(api.config, log.level.INFO, i18n.WaterBall, waterball) target_list = [ connect_core.TargetUnit(i18n.SetCallStatus, '您的呼叫器目前設定為關閉', response='y' + command.Enter), # 對方已落跑了 connect_core.TargetUnit( i18n.SetCallStatus, '◆ 糟糕! 對方已落跑了', exceptions_=exceptions.UserOffline(target_id)), connect_core.TargetUnit([i18n.Throw, target_id, i18n.WaterBall], '丟 ' + target_id + ' 水球:', response=waterball + command.Enter * 2 + command.GoMainMenu), connect_core.TargetUnit([i18n.Throw, i18n.WaterBall, i18n.Success], screens.Target.MainMenu, break_detect=True) ] cmd_list = list() cmd_list.append(command.GoMainMenu) cmd_list.append('T') cmd_list.append(command.Enter) cmd_list.append('U') cmd_list.append(command.Enter) if '【好友列表】' in api.connect_core.get_screen_queue()[-1]: cmd_list.append('f') cmd_list.append('s') cmd_list.append(target_id) cmd_list.append(command.Enter) cmd_list.append('w') cmd = ''.join(cmd_list) api.connect_core.send(cmd, target_list, screen_timeout=api.config.screen_long_timeout) api._LastThrowWaterBallTime = time.time()
def get_board_list(api) -> list: # log.showValue( # api.config, # log.level.INFO, # [ # i18n.PTT, # i18n.Msg # ], # i18n.MarkPost # ) cmd_list = [] cmd_list.append(command.GoMainMenu) cmd_list.append('F') cmd_list.append(command.Enter) cmd_list.append('y') cmd_list.append('$') cmd = ''.join(cmd_list) target_list = [ connect_core.TargetUnit(i18n.BoardList, screens.Target.InBoardList, break_detect=True) ] api.connect_core.send(cmd, target_list, screen_timeout=api.config.screen_long_timeout) ori_screen = api.connect_core.get_screen_queue()[-1] max_no = 0 for line in ori_screen.split('\n'): if '◎' not in line and '●' not in line: continue if line.startswith(api.cursor): line = line[len(api.cursor):] # print(f'->{line}<') if '◎' in line: front_part = line[:line.find('◎')] else: front_part = line[:line.find('●')] front_part_list = [x for x in front_part.split(' ')] front_part_list = list(filter(None, front_part_list)) # print(f'FrontPartList =>{FrontPartList}<=') max_no = int(front_part_list[0]) log.show_value(api.config, log.level.DEBUG, 'MaxNo', max_no) if api.config.log_level == log.level.INFO: pb = progressbar.ProgressBar(max_value=max_no, redirect_stdout=True) cmd_list = [] cmd_list.append(command.GoMainMenu) cmd_list.append('F') cmd_list.append(command.Enter) cmd_list.append('y') cmd_list.append('0') cmd = ''.join(cmd_list) board_list = [] while True: api.connect_core.send(cmd, target_list, screen_timeout=api.config.screen_long_timeout) ori_screen = api.connect_core.get_screen_queue()[-1] # print(OriScreen) for line in ori_screen.split('\n'): if '◎' not in line and '●' not in line: continue if line.startswith(api.cursor): line = line[len(api.cursor):] # print(f'->{line}<') if '◎' in line: front_part = line[:line.find('◎')] else: front_part = line[:line.find('●')] front_part_list = [x for x in front_part.split(' ')] front_part_list = list(filter(None, front_part_list)) # print(f'FrontPartList =>{FrontPartList}<=') no = int(front_part_list[0]) # print(f'No =>{No}<=') # print(f'LastNo =>{LastNo}<=') log.show_value(api.config, log.level.DEBUG, 'Board NO', no) board_name = front_part_list[1] if board_name.startswith('ˇ'): board_name = board_name[1:] log.show_value(api.config, log.level.DEBUG, 'Board Name', board_name) board_list.append(board_name) if api.config.log_level == log.level.INFO: pb.update(no) if no >= max_no: break cmd = command.Ctrl_F if api.config.log_level == log.level.INFO: pb.finish() return board_list
def get_post( api, board: str, post_aid: str = None, post_index: int = 0, search_type: int = 0, search_condition: str = None, query: bool = False) -> data_type.PostInfo: cmd_list = [] cmd_list.append(command.GoMainMenu) cmd_list.append('qs') cmd_list.append(board) cmd_list.append(command.Enter) cmd_list.append(command.Ctrl_C * 2) cmd_list.append(command.Space) if post_aid is not None: cmd_list.append('#' + post_aid) elif post_index != 0: if search_condition is not None: if search_type == data_type.post_search_type.KEYWORD: cmd_list.append('/') elif search_type == data_type.post_search_type.AUTHOR: cmd_list.append('a') elif search_type == data_type.post_search_type.PUSH: cmd_list.append('Z') elif search_type == data_type.post_search_type.MARK: cmd_list.append('G') elif search_type == data_type.post_search_type.MONEY: cmd_list.append('A') cmd_list.append(search_condition) cmd_list.append(command.Enter) cmd_list.append(str(post_index)) cmd_list.append(command.Enter) cmd_list.append(command.QueryPost) cmd = ''.join(cmd_list) target_list = [ connect_core.TargetUnit( [ i18n.CatchPost, i18n.Success, ], screens.Target.QueryPost, break_detect=True, refresh=False, log_level=log.level.DEBUG ), connect_core.TargetUnit( [ i18n.PostDeleted, i18n.Success, ], screens.Target.InBoard, break_detect=True, log_level=log.level.DEBUG ), connect_core.TargetUnit( i18n.NoSuchBoard, screens.Target.MainMenu_Exiting, exceptions_=exceptions.NoSuchBoard(api.config, board) ), ] index = api.connect_core.send(cmd, target_list) ori_screen = api.connect_core.get_screen_queue()[-1] post_author = None post_title = None if index < 0 or index == 1: # 文章被刪除 log.log(api.config, log.level.DEBUG, i18n.PostDeleted) log.show_value( api.config, log.level.DEBUG, 'OriScreen', ori_screen ) cursor_line = [line for line in ori_screen.split( '\n') if line.startswith(api.cursor)] if len(cursor_line) != 1: raise exceptions.UnknownError(ori_screen) cursor_line = cursor_line[0] log.show_value( api.config, log.level.DEBUG, 'CursorLine', cursor_line ) pattern = re.compile('[\d]+\/[\d]+') pattern_result = pattern.search(cursor_line) if pattern_result is None: list_date = None else: list_date = pattern_result.group(0) list_date = list_date[-5:] pattern = re.compile('\[[\w]+\]') pattern_result = pattern.search(cursor_line) if pattern_result is not None: post_del_status = data_type.post_delete_status.AUTHOR else: pattern = re.compile('<[\w]+>') pattern_result = pattern.search(cursor_line) post_del_status = data_type.post_delete_status.MODERATOR # > 79843 9/11 - □ (本文已被吃掉)< # > 76060 8/28 - □ (本文已被刪除) [weida7332] # print(f'O=>{CursorLine}<') if pattern_result is not None: post_author = pattern_result.group(0)[1:-1] else: post_author = None post_del_status = data_type.post_delete_status.UNKNOWN log.show_value(api.config, log.level.DEBUG, 'ListDate', list_date) log.show_value(api.config, log.level.DEBUG, 'PostAuthor', post_author) log.show_value(api.config, log.level.DEBUG, 'post_del_status', post_del_status) return data_type.PostInfo( board=board, author=post_author, list_date=list_date, delete_status=post_del_status, format_check=True ) elif index == 0: lock_post = False try: cursor_line = [line for line in ori_screen.split( '\n') if line.strip().startswith(api.cursor)][0] except Exception as e: print(api.cursor) print(ori_screen) raise e post_author = cursor_line if '□' in post_author: post_author = post_author[:post_author.find('□')].strip() elif 'R:' in post_author: post_author = post_author[:post_author.find('R:')].strip() elif ' 轉 ' in post_author: post_author = post_author[:post_author.find('轉')].strip() elif ' 鎖 ' in post_author: post_author = post_author[:post_author.find('鎖')].strip() lock_post = True post_author = post_author[post_author.rfind(' '):].strip() post_title = cursor_line if ' □ ' in post_title: post_title = post_title[post_title.find('□') + 1:].strip() elif ' R:' in post_title: post_title = post_title[post_title.find('R:'):].strip() elif ' 轉 ' in post_title: # print(f'[{PostTitle}]=========>') post_title = post_title[post_title.find('轉') + 1:].strip() post_title = f'Fw: {post_title}' # print(f'=========>[{PostTitle}]') elif ' 鎖 ' in post_title: post_title = post_title[post_title.find('鎖') + 1:].strip() ori_screen_temp = ori_screen[ori_screen.find('┌──────────'):] ori_screen_temp = ori_screen_temp[:ori_screen_temp.find( '└─────────────') ] aid_line = [line for line in ori_screen.split( '\n') if line.startswith('│ 文章代碼(AID)')] if len(aid_line) == 1: aid_line = aid_line[0] pattern = re.compile('#[\w|-]+') pattern_result = pattern.search(aid_line) post_aid = pattern_result.group(0)[1:] pattern = re.compile('文章網址: https:[\S]+html') pattern_result = pattern.search(ori_screen_temp) if pattern_result is None: post_web = None else: post_web = pattern_result.group(0)[6:] pattern = re.compile('這一篇文章值 [\d]+ Ptt幣') pattern_result = pattern.search(ori_screen_temp) if pattern_result is None: # 特殊文章無價格 post_money = -1 else: post_money = pattern_result.group(0)[7:] post_money = post_money[:post_money.find(' ')] post_money = int(post_money) pattern = re.compile('[\d]+\/[\d]+') pattern_result = pattern.search(cursor_line) if pattern_result is None: list_date = None else: list_date = pattern_result.group(0) list_date = list_date[-5:] # print(list_date) # > 7485 9 8/09 CodingMan □ [閒聊] PTT Library 更新 # > 79189 M 1 9/17 LittleCalf □ [公告] 禁言退文公告 # >781508 +爆 9/17 jodojeda □ [新聞] 國人吃魚少 學者:應把吃魚當成輕鬆愉快 # >781406 +X1 9/17 kingofage111 R: [申請] ReDmango 請辭Gossiping板主職務 if post_index == 0: pattern = re.compile('[\d]+') pattern_result = pattern.search(cursor_line) if pattern_result is not None: post_index = int(pattern_result.group(0)) push_number = cursor_line # print(f'2>{push_number}<') push_number = push_number[7:11] # print(PushNumber) push_number = push_number.split(' ') # print(PushNumber) push_number = list(filter(None, push_number)) # print(PushNumber) if len(push_number) == 0: push_number = None else: push_number = push_number[-1] # print(PushNumber) if push_number.startswith('+') or push_number.startswith('~'): push_number = push_number[1:] # print(PushNumber) if push_number.lower().startswith('m'): push_number = push_number[1:] # print(PushNumber) if push_number.lower().startswith('!'): push_number = push_number[1:] if push_number.lower().startswith('s'): push_number = push_number[1:] if push_number.lower().startswith('='): push_number = push_number[1:] if len(push_number) == 0: push_number = None # print(PushNumber) log.show_value(api.config, log.level.DEBUG, 'PostAuthor', post_author) log.show_value(api.config, log.level.DEBUG, 'PostTitle', post_title) log.show_value(api.config, log.level.DEBUG, 'PostAID', post_aid) log.show_value(api.config, log.level.DEBUG, 'PostWeb', post_web) log.show_value(api.config, log.level.DEBUG, 'PostMoney', post_money) log.show_value(api.config, log.level.DEBUG, 'ListDate', list_date) log.show_value(api.config, log.level.DEBUG, 'PushNumber', push_number) if lock_post: post = data_type.PostInfo( board=board, aid=post_aid, index=post_index, author=post_author, title=post_title, web_url=post_web, money=post_money, list_date=list_date, format_check=True, push_number=push_number, lock=True, ) return post if query: post = data_type.PostInfo( board=board, aid=post_aid, index=post_index, author=post_author, title=post_title, web_url=post_web, money=post_money, list_date=list_date, format_check=True, push_number=push_number, ) return post origin_post, has_control_code = _api_util.get_content(api) if origin_post is None: post = data_type.PostInfo( board=board, aid=post_aid, index=post_index, author=post_author, title=post_title, web_url=post_web, money=post_money, list_date=list_date, control_code=has_control_code, format_check=False, push_number=push_number, unconfirmed=api.Unconfirmed, ) return post # print('=' * 20) # print() # print('=' * 20) content_start = '───────────────────────────────────────' content_end = [] content_end.append('--\n※ 發信站: 批踢踢實業坊(ptt.cc)') content_end.append('--\n※ 發信站: 批踢踢兔(ptt2.cc)') content_end.append('--\n※ 發信站: 新批踢踢(ptt2.twbbs.org.tw)') post_author_pattern_new = re.compile('作者 (.+) 看板') post_author_pattern_old = re.compile('作者 (.+)') board_pattern = re.compile('看板 (.+)') post_date = None post_content = [] ip = None location = None push_list = [] # 格式確認,亂改的我也沒辦法Q_Q origin_post_lines = origin_post.split('\n') author_line = origin_post_lines[0] if board.lower() == 'allpost': board_line = author_line[author_line.find(')') + 1:] pattern_result = board_pattern.search(board_line) if pattern_result is not None: board_temp = post_author = pattern_result.group(0) board_temp = board_temp[2:].strip() if len(board_temp) > 0: board = board_temp log.show_value( api.config, log.level.DEBUG, i18n.Board, board ) pattern_result = post_author_pattern_new.search(author_line) if pattern_result is not None: post_author = pattern_result.group(0) post_author = post_author[:post_author.rfind(')') + 1] else: pattern_result = post_author_pattern_old.search(author_line) if pattern_result is None: log.show_value( api.config, log.level.DEBUG, i18n.SubstandardPost, i18n.Author ) post = data_type.PostInfo( board=board, aid=post_aid, index=post_index, author=post_author, date=post_date, title=post_title, web_url=post_web, money=post_money, content=post_content, ip=ip, push_list=push_list, list_date=list_date, control_code=has_control_code, format_check=False, location=location, push_number=push_number, origin_post=origin_post, unconfirmed=api.Unconfirmed, ) return post post_author = pattern_result.group(0) post_author = post_author[:post_author.rfind(')') + 1] post_author = post_author[4:].strip() log.show_value( api.config, log.level.DEBUG, i18n.Author, post_author ) post_title_pattern = re.compile('標題 (.+)') title_line = origin_post_lines[1] pattern_result = post_title_pattern.search(title_line) if pattern_result is None: log.show_value( api.config, log.level.DEBUG, i18n.SubstandardPost, i18n.Title ) post = data_type.PostInfo( board=board, aid=post_aid, index=post_index, author=post_author, date=post_date, title=post_title, web_url=post_web, money=post_money, content=post_content, ip=ip, push_list=push_list, list_date=list_date, control_code=has_control_code, format_check=False, location=location, push_number=push_number, origin_post=origin_post, unconfirmed=api.Unconfirmed, ) return post post_title = pattern_result.group(0) post_title = post_title[4:].strip() log.show_value( api.config, log.level.DEBUG, i18n.Title, post_title ) post_date_pattern = re.compile('時間 (.+)') date_line = origin_post_lines[2] pattern_result = post_date_pattern.search(date_line) if pattern_result is None: log.show_value( api.config, log.level.DEBUG, i18n.SubstandardPost, i18n.Date ) post = data_type.PostInfo( board=board, aid=post_aid, index=post_index, author=post_author, date=post_date, title=post_title, web_url=post_web, money=post_money, content=post_content, ip=ip, push_list=push_list, list_date=list_date, control_code=has_control_code, format_check=False, location=location, push_number=push_number, origin_post=origin_post, unconfirmed=api.Unconfirmed, ) return post post_date = pattern_result.group(0) post_date = post_date[4:].strip() log.show_value( api.config, log.level.DEBUG, i18n.Date, post_date ) content_fail = True if content_start not in origin_post: # print('Type 1') content_fail = True else: post_content = origin_post post_content = post_content[ post_content.find(content_start) + len(content_start) + 1: ] # print('Type 2') # print(f'PostContent [{PostContent}]') for EC in content_end: # + 3 = 把 --\n 拿掉 # print(f'EC [{EC}]') if EC in post_content: content_fail = False post_content = post_content[ :post_content.rfind(EC) + 3 ] origin_post_lines = origin_post[origin_post.find(EC):] # post_content = post_content.strip() origin_post_lines = origin_post_lines.split('\n') break if content_fail: log.show_value( api.config, log.level.DEBUG, i18n.SubstandardPost, i18n.Content ) post = data_type.PostInfo( board=board, aid=post_aid, index=post_index, author=post_author, date=post_date, title=post_title, web_url=post_web, money=post_money, content=post_content, ip=ip, push_list=push_list, list_date=list_date, control_code=has_control_code, format_check=False, location=location, push_number=push_number, origin_post=origin_post, unconfirmed=api.Unconfirmed, ) return post log.show_value( api.config, log.level.DEBUG, i18n.Content, post_content ) info_lines = [ line for line in origin_post_lines if line.startswith('※') or line.startswith('◆') ] pattern = re.compile('[\d]+\.[\d]+\.[\d]+\.[\d]+') pattern_p2 = re.compile('[\d]+-[\d]+-[\d]+-[\d]+') for line in reversed(info_lines): log.show_value( api.config, log.level.DEBUG, 'IP Line', line ) # type 1 # ※ 編輯: CodingMan (111.243.146.98 臺灣) # ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 111.243.146.98 (臺灣) # type 2 # ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 116.241.32.178 # ※ 編輯: kill77845 (114.136.55.237), 12/08/2018 16:47:59 # type 3 # ※ 發信站: 批踢踢實業坊(ptt.cc) # ◆ From: 211.20.78.69 # ※ 編輯: JCC 來自: 211.20.78.69 (06/20 10:22) # ※ 編輯: JCC (118.163.28.150), 12/03/2015 14:25:35 pattern_result = pattern.search(line) if pattern_result is not None: ip = pattern_result.group(0) location_temp = line[line.find(ip) + len(ip):].strip() location_temp = location_temp.replace('(', '') location_temp = location_temp[:location_temp.rfind(')')] location_temp = location_temp.strip() # print(f'=>[{LocationTemp}]') if ' ' not in location_temp and len(location_temp) > 0: location = location_temp log.show_value(api.config, log.level.DEBUG, 'Location', location) break pattern_result = pattern_p2.search(line) if pattern_result is not None: ip = pattern_result.group(0) ip = ip.replace('-', '.') # print(f'IP -> [{IP}]') break if api.config.host == data_type.host_type.PTT1: if ip is None: log.show_value( api.config, log.level.DEBUG, i18n.SubstandardPost, 'IP' ) post = data_type.PostInfo( board=board, aid=post_aid, index=post_index, author=post_author, date=post_date, title=post_title, web_url=post_web, money=post_money, content=post_content, ip=ip, push_list=push_list, list_date=list_date, control_code=has_control_code, format_check=False, location=location, push_number=push_number, origin_post=origin_post, unconfirmed=api.Unconfirmed, ) return post log.show_value(api.config, log.level.DEBUG, 'IP', ip) push_author_pattern = re.compile('[推|噓|→] [\w| ]+:') push_date_pattern = re.compile('[\d]+/[\d]+ [\d]+:[\d]+') push_ip_pattern = re.compile('[\d]+\.[\d]+\.[\d]+\.[\d]+') push_list = [] for line in origin_post_lines: if line.startswith('推'): push_type = data_type.push_type.PUSH elif line.startswith('噓 '): push_type = data_type.push_type.BOO elif line.startswith('→ '): push_type = data_type.push_type.ARROW else: continue result = push_author_pattern.search(line) if result is None: # 不符合推文格式 continue push_author = result.group(0)[2:-1].strip() log.show_value(api.config, log.level.DEBUG, [ i18n.Push, i18n.ID, ], push_author ) result = push_date_pattern.search(line) if result is None: continue push_date = result.group(0) log.show_value(api.config, log.level.DEBUG, [ i18n.Push, i18n.Date, ], push_date ) push_ip = None result = push_ip_pattern.search(line) if result is not None: push_ip = result.group(0) log.show_value( api.config, log.level.DEBUG, [ i18n.Push, 'IP', ], push_ip ) push_content = line[ line.find(push_author) + len(push_author): ] # PushContent = PushContent.replace(PushDate, '') if api.config.host == data_type.host_type.PTT1: push_content = push_content[ :push_content.rfind(push_date) ] else: # → CodingMan:What is Ptt? 推 10/04 13:25 push_content = push_content[ :push_content.rfind(push_date) - 2 ] if push_ip is not None: push_content = push_content.replace(push_ip, '') push_content = push_content[ push_content.find(':') + 1: ].strip() log.show_value( api.config, log.level.DEBUG, [ i18n.Push, i18n.Content, ], push_content ) current_push = data_type.PushInfo( push_type, push_author, push_content, push_ip, push_date ) push_list.append(current_push) post = data_type.PostInfo( board=board, aid=post_aid, index=post_index, author=post_author, date=post_date, title=post_title, web_url=post_web, money=post_money, content=post_content, ip=ip, push_list=push_list, list_date=list_date, control_code=has_control_code, format_check=True, location=location, push_number=push_number, origin_post=origin_post, unconfirmed=api.Unconfirmed, ) return post
def markPost(api, mark_type: int, board: str, post_aid: str, post_index: int, search_type: int, search_condition: str) -> None: log.show_value(api.config, log.level.INFO, [i18n.PTT, i18n.Msg], i18n.MarkPost) check_value.check(api.config, int, 'mark_type', mark_type, value_class=data_type.mark_type) check_value.check(api.config, str, 'Board', board) if post_aid is not None: check_value.check(api.config, str, 'PostAID', post_aid) check_value.check(api.config, int, 'PostIndex', post_index) check_value.check(api.config, int, 'SearchType', search_type, value_class=data_type.post_search_type) if search_condition is not None: check_value.check(api.config, str, 'SearchCondition', search_condition) if len(board) == 0: raise ValueError(log.merge([i18n.Board, i18n.ErrorParameter, board])) if mark_type != data_type.mark_type.DeleteD: if post_index != 0 and isinstance(post_aid, str): raise ValueError( log.merge(api.config, [ 'PostIndex', 'PostAID', i18n.ErrorParameter, i18n.BothInput ])) if post_index == 0 and post_aid is None: raise ValueError( log.merge(api.config, ['PostIndex', 'PostAID', i18n.ErrorParameter])) if search_condition is not None and search_type == 0: raise ValueError( log.merge(api.config, [ 'SearchType', i18n.ErrorParameter, ])) if search_type == data_type.post_search_type.PUSH: try: S = int(search_condition) except ValueError: raise ValueError( log.merge(api.config, [ 'SearchCondition', i18n.ErrorParameter, ])) if not (-100 <= S <= 110): raise ValueError( log.merge(api.config, [ 'SearchCondition', i18n.ErrorParameter, ])) if post_aid is not None and search_condition is not None: raise ValueError( log.merge(api.config, [ 'PostAID', 'SearchCondition', i18n.ErrorParameter, i18n.BothInput, ])) if post_index != 0: newest_index = api._get_newest_index(data_type.index_type.BBS, board=board, search_type=search_type, search_condition=search_condition) check_value.check_index(api.config, 'PostIndex', post_index, max_value=newest_index) if mark_type == data_type.mark_type.UNCONFIRMED: # 批踢踢兔沒有待證文章功能 QQ if api.config.host == data_type.host_type.PTT2: raise exceptions.HostNotSupport(lib_util.get_current_func_name()) api._check_board(board, check_moderator=True) cmd_list = [] cmd_list.append(command.GoMainMenu) cmd_list.append('qs') cmd_list.append(board) cmd_list.append(command.Enter) cmd = ''.join(cmd_list) target_list = [ connect_core.TargetUnit( i18n.AnyKeyContinue, '任意鍵', response=' ', ), connect_core.TargetUnit([ '動畫播放中', ], '互動式動畫播放中', response=command.Ctrl_C, log_level=log.level.DEBUG), connect_core.TargetUnit([ '進板成功', ], screens.Target.InBoard, break_detect=True, log_level=log.level.DEBUG), ] index = api.connect_core.send(cmd, target_list) cmd_list = [] if post_aid is not None: cmd_list.append('#' + post_aid) cmd_list.append(command.Enter) elif post_index != 0: if search_condition is not None: if search_type == data_type.post_search_type.KEYWORD: cmd_list.append('/') elif search_type == data_type.post_search_type.AUTHOR: cmd_list.append('a') elif search_type == data_type.post_search_type.PUSH: cmd_list.append('Z') elif search_type == data_type.post_search_type.MARK: cmd_list.append('G') elif search_type == data_type.post_search_type.MONEY: cmd_list.append('A') cmd_list.append(search_condition) cmd_list.append(command.Enter) cmd_list.append(str(post_index)) cmd_list.append(command.Enter) if mark_type == data_type.mark_type.S: cmd_list.append('L') elif mark_type == data_type.mark_type.D: cmd_list.append('t') elif mark_type == data_type.mark_type.DeleteD: cmd_list.append(command.Ctrl_D) elif mark_type == data_type.mark_type.M: cmd_list.append('m') elif mark_type == data_type.mark_type.UNCONFIRMED: cmd_list.append(command.Ctrl_E + 'S') cmd = ''.join(cmd_list) target_list = [ connect_core.TargetUnit([i18n.DelAllMarkPost], '刪除所有標記', response='y' + command.Enter, log_level=log.level.INFO), connect_core.TargetUnit([ i18n.Mark, i18n.Success, ], screens.Target.InBoard, break_detect=True, log_level=log.level.INFO), ] index = api.connect_core.send(cmd, target_list)
def login(api, ptt_id, password, kick_other_login): if api._login_status: api.logout() api.config.kick_other_login = kick_other_login def kick_other_loginDisplayMsg(): if api.config.kick_other_login: return i18n.kick_other_login return i18n.Notkick_other_login def kick_other_loginResponse(Screen): if api.config.kick_other_login: return 'y' + command.Enter return 'n' + command.Enter api._mailbox_full = False def mailbox_full(): log.log(api.config, log.level.INFO, i18n.MailBoxFull) api._mailbox_full = True def register_processing(screen): pattern = re.compile('[\d]+') api.process_picks = int(pattern.search(screen).group(0)) if len(password) > 8: password = password[:8] ptt_id = ptt_id.strip() password = password.strip() api._ID = ptt_id api._Password = password api.config.kick_other_login = kick_other_login api.connect_core.connect() log.show_value(api.config, log.level.INFO, [i18n.login, i18n.ID], ptt_id) target_list = [ connect_core.TargetUnit( # i18n.HasNewMailGotoMainMenu, i18n.MailBox, screens.Target.InMailBox, # 加個進去 A 選單再出來的動作,讓畫面更新最底下一行 response=command.GoMainMenu + 'A' + command.Right + command.Left, break_detect=True), connect_core.TargetUnit(i18n.loginSuccess, screens.Target.MainMenu, break_detect=True), connect_core.TargetUnit( i18n.GoMainMenu, '【看板列表】', response=command.GoMainMenu, ), connect_core.TargetUnit(i18n.ErrorIDPW, '密碼不對', break_detect=True, exceptions_=exceptions.WrongIDorPassword()), connect_core.TargetUnit(i18n.LoginTooOften, '登入太頻繁', break_detect=True, exceptions_=exceptions.LoginTooOften()), connect_core.TargetUnit( i18n.SystemBusyTryLater, '系統過載', break_detect=True, ), connect_core.TargetUnit( i18n.DelWrongPWRecord, '您要刪除以上錯誤嘗試的記錄嗎', response='y' + command.Enter, ), connect_core.TargetUnit( i18n.PostNotFinish, '請選擇暫存檔 (0-9)[0]', response=command.Enter, ), connect_core.TargetUnit( i18n.PostNotFinish, '有一篇文章尚未完成', response='Q' + command.Enter, ), connect_core.TargetUnit( i18n.SigningUnPleaseWait, '登入中,請稍候', ), connect_core.TargetUnit( kick_other_loginDisplayMsg, '您想刪除其他重複登入的連線嗎', response=kick_other_loginResponse, ), connect_core.TargetUnit(i18n.AnyKeyContinue, '◆ 您的註冊申請單尚在處理中', response=command.Enter, handler=register_processing), connect_core.TargetUnit(i18n.AnyKeyContinue, '任意鍵', response=' '), connect_core.TargetUnit( i18n.SigningUpdate, '正在更新與同步線上使用者及好友名單', ), connect_core.TargetUnit( i18n.GoMainMenu, '【分類看板】', response=command.GoMainMenu, ), connect_core.TargetUnit( i18n.ErrorLoginRichPeopleGoMainMenu, ['大富翁', '排行榜', '名次', '代號', '暱稱', '數目'], response=command.GoMainMenu, ), connect_core.TargetUnit(i18n.SkipRegistrationForm, '您確定要填寫註冊單嗎', response=command.Enter * 3), connect_core.TargetUnit(i18n.SkipRegistrationForm, '以上資料是否正確', response='y' + command.Enter), connect_core.TargetUnit(i18n.SkipRegistrationForm, '另外若輸入後發生認證碼錯誤請先確認輸入是否為最後一封', response='x' + command.Enter), connect_core.TargetUnit(i18n.SkipRegistrationForm, '此帳號已設定為只能使用安全連線', exceptions_=exceptions.OnlySecureConnection()) ] cmd_list = [] cmd_list.append(ptt_id) cmd_list.append(command.Enter) cmd_list.append(password) cmd_list.append(command.Enter) cmd = ''.join(cmd_list) index = api.connect_core.send( cmd, target_list, screen_timeout=api.config.screen_long_timeout, refresh=False, secret=True) ori_screen = api.connect_core.get_screen_queue()[-1] if index == 0: current_capacity, max_capacity = _api_util.get_mailbox_capacity(api) log.log(api.config, log.level.INFO, i18n.HasNewMailGotoMainMenu) if current_capacity > max_capacity: api._mailbox_full = True log.log(api.config, log.level.INFO, i18n.MailBoxFull) if api._mailbox_full: log.log(api.config, log.level.INFO, i18n.UseMailboxAPIWillLogoutAfterExecution) target_list = [ connect_core.TargetUnit(i18n.loginSuccess, screens.Target.MainMenu, break_detect=True) ] cmd = command.GoMainMenu + 'A' + command.Right + command.Left index = api.connect_core.send( cmd, target_list, screen_timeout=api.config.screen_long_timeout, secret=True) ori_screen = api.connect_core.get_screen_queue()[-1] if target_list[index].get_display_msg() != i18n.loginSuccess: print(ori_screen) raise exceptions.LoginError() if '> (' in ori_screen: api.cursor = data_type.Cursor.NEW log.log(api.config, log.level.DEBUG, i18n.NewCursor) else: api.cursor = data_type.Cursor.OLD log.log(api.config, log.level.DEBUG, i18n.OldCursor) if api.cursor not in screens.Target.InBoardWithCursor: screens.Target.InBoardWithCursor.append('\n' + api.cursor) if len(screens.Target.MainMenu) == len(screens.Target.CursorToGoodbye): if api.cursor == '>': screens.Target.CursorToGoodbye.append('> (G)oodbye') else: screens.Target.CursorToGoodbye.append('●(G)oodbye') api.unregistered_user = True if '(T)alk' in ori_screen: api.unregistered_user = False if '(P)lay' in ori_screen: api.unregistered_user = False if '(N)amelist' in ori_screen: api.unregistered_user = False if api.unregistered_user: # print(ori_screen) log.log(api.config, log.level.INFO, i18n.UnregisteredUserCantUseAllAPI) api.registered_user = not api.unregistered_user if api.process_picks != 0: log.show_value(api.config, log.level.INFO, i18n.PicksInRegister, api.process_picks) api._login_status = True