def wechat_shared_image_msg(self, msg: wxpy.Message, source: str, text: str = "", mode: str = "image") -> Message: efb_msg = Message() efb_msg.type = MsgType.Image efb_msg.text = self._("Via {source}").format(source=source) if text: efb_msg.text = "%s\n%s" % (text, efb_msg.text) efb_msg.path, efb_msg.mime, efb_msg.file = self.save_file(msg, app_message=mode) return efb_msg
def qq_music_wrapper(self, data, chat: Chat = None): efb_msg = Message() if data['type'] == '163': # Netease Cloud Music efb_msg.type = MsgType.Text efb_msg.text = 'https://music.163.com/#/song?id=' + data['id'] else: efb_msg.type = MsgType.Text efb_msg.text = data['text'] return [efb_msg] # todo Port for other music platform
def wechat_file_msg(self, msg: wxpy.Message) -> Message: efb_msg = Message(type=MsgType.File) try: file_name = msg.file_name efb_msg.text = file_name or "" app_name = msg.app_name if app_name: efb_msg.text = self._("{file_name} sent via {app_name}").format(file_name=file_name, app_name=app_name) efb_msg.filename = file_name or "" efb_msg.path, efb_msg.mime, efb_msg.file = self.save_file(msg) except EOFError: efb_msg.type = MsgType.Text efb_msg.text += self._("[Failed to download the file, please check your phone.]") return efb_msg
def wechat_location_msg(self, msg: wxpy.Message) -> Message: efb_msg = Message() efb_msg.text = msg.text.split('\n')[0][:-1] efb_msg.attributes = LocationAttribute(latitude=float(msg.location['x']), longitude=float(msg.location['y'])) efb_msg.type = MsgType.Location return efb_msg
def qq_text_simple_wrapper(self, text: str, ats: dict): # This cute function only accepts string! efb_msg = Message() efb_msg.type = MsgType.Text efb_msg.text = text if ats: # This is used to replace specific text with @blahblah # And Milkice really requires a brain check efb_msg.substitutions = Substitutions(ats) return efb_msg
def process_audio(self, message: Message, audio: NamedTemporaryFile): try: # reply_text: str = '\n'.join(self.recognize(audio.name, self.lang)) reply_text: str = '\n'.join(self.recognize(audio.name)) except Exception: reply_text = 'Failed to recognize voice content.' if getattr(message, 'text', None) is None: message.text = "" message.text += reply_text message.text = message.text[:4000] # message.file = None message.edit = True message.edit_media = False coordinator.send_message(message) audio.close()
def wechat_voice_msg(self, msg: wxpy.Message) -> Message: efb_msg = Message(type=MsgType.Voice) try: efb_msg.path, efb_msg.mime, efb_msg.file = self.save_file(msg) efb_msg.text = "" except EOFError: efb_msg.type = MsgType.Text efb_msg.text += self._("[Failed to download the voice message, please check your phone.]") return efb_msg
def make_status_message(self, msg_base: Message = None, mid: MessageID = None) -> Message: if mid is not None: msg = self.message_cache[mid] elif msg_base is not None: msg = msg_base else: raise ValueError reply = Message( type=MsgType.Text, chat=msg.chat, author=msg.chat.make_system_member(uid=ChatID("filter_info"), name="Filter middleware", middleware=self), deliver_to=coordinator.master, ) if mid: reply.uid = mid else: reply.uid = str(uuid.uuid4()) status = self.filter_reason(msg) if not status: # Blue circle emoji status = "\U0001F535 This chat is not filtered." else: # Red circle emoji status = "\U0001F534 " + status reply.text = "Filter status for chat {chat_id} from {module_id}:\n" \ "\n" \ "{status}\n".format( module_id=msg.chat.module_id, chat_id=msg.chat.id, status=status ) command = MessageCommand(name="%COMMAND_NAME%", callable_name="toggle_filter_by_chat_id", kwargs={ "mid": reply.uid, "module_id": msg.chat.module_id, "chat_id": msg.chat.id }) if self.is_chat_filtered_by_id(msg.chat): command.name = "Unfilter by chat ID" command.kwargs['value'] = False else: command.name = "Filter by chat ID" command.kwargs['value'] = True reply.commands = MessageCommands([command]) return reply
def wechat_video_msg(self, msg: wxpy.Message) -> Message: efb_msg = Message(type=MsgType.Video) try: if msg.file_size == 0: raise EOFError efb_msg.path, efb_msg.mime, efb_msg.file = self.save_file(msg) efb_msg.filename = msg.file_name efb_msg.text = "" except EOFError: efb_msg.type = MsgType.Text efb_msg.text += self._("[Failed to download the video message, please check your phone.]") return efb_msg
def edit_text_message(self, message: Message, reactions: bool = False, commands: bool = False, substitution: bool = False) -> Message: message.edit = True message.text = f"Edited {message.type.name} message {message.uid} @ {time.time_ns()}" message = self.attach_message_properties(message, reactions, commands, substitution) self.messages_sent[message.uid] = message coordinator.send_message(message) return message
def process_message(self, message: Message) -> Optional[Message]: if message.type == MsgType.Text and \ message.author.module_id.startswith("blueset.wechat") and \ type(message.chat) == GroupChat and \ self.match_list(message.text): x: int = 0 message.text = "@ME " + message.text message.substitutions = Substitutions({}) message.substitutions[(x, x + 3)] = message.chat return message
def master_qr_code(self, uuid, status, qrcode=None): status = int(status) if self.qr_uuid == (uuid, status): return self.qr_uuid = (uuid, status) msg = Message( uid=f"ews_auth_{uuid}_{status}_{uuid4()}", type=MsgType.Text, chat=self.user_auth_chat, author=self.user_auth_chat.other, deliver_to=coordinator.master, ) if status == 201: msg.type = MsgType.Text msg.text = self._('Confirm on your phone.') self.master_qr_picture_id = None elif status == 200: msg.type = MsgType.Text msg.text = self._("Successfully logged in.") self.master_qr_picture_id = None elif uuid != self.qr_uuid: msg.type = MsgType.Image file = NamedTemporaryFile(suffix=".png") qr_url = "https://login.weixin.qq.com/l/" + uuid QRCode(qr_url).png(file, scale=10) msg.text = self._("QR code expired, please scan the new one.") msg.path = Path(file.name) msg.file = file msg.mime = 'image/png' if self.master_qr_picture_id is not None: msg.edit = True msg.edit_media = True msg.uid = self.master_qr_picture_id else: self.master_qr_picture_id = msg.uid if status in (200, 201) or uuid != self.qr_uuid: coordinator.send_message(msg)
def edit_file_like_message_text(self, message: Message, reactions: bool = False, commands: bool = False, substitution: bool = False) -> Message: message.text = f"Content of edited {message.type.name} message with ID {message.uid}" message.edit = True message.edit_media = False message = self.attach_message_properties(message, reactions, commands, substitution) self.messages_sent[message.uid] = message coordinator.send_message(message) return message
def qq_image_wrapper(self, data, chat: Chat = None): efb_msg = Message() if 'url' not in data: efb_msg.type = MsgType.Text efb_msg.text = self._('[Image Source missing]') return [efb_msg] efb_msg.file = cq_get_image(data['url']) if efb_msg.file is None: efb_msg.type = MsgType.Text efb_msg.text = self._('[Download image failed, please check on your QQ client]') return [efb_msg] efb_msg.type = MsgType.Image mime = magic.from_file(efb_msg.file.name, mime=True) if isinstance(mime, bytes): mime = mime.decode() efb_msg.filename = data['file'] if 'file' in data else efb_msg.file.name efb_msg.filename += '.' + str(mime).split('/')[1] efb_msg.path = efb_msg.file.name efb_msg.mime = mime if "gif" in mime: efb_msg.type = MsgType.Animation return [efb_msg]
def edit_location_message(self, message: Message, reactions: bool = False, commands: bool = False, substitution: bool = False) -> Message: message.text = f"Content of edited location message with ID {message.uid}" message.edit = True message.attributes = LocationAttribute( latitude=random.uniform(0.0, 90.0), longitude=random.uniform(0.0, 90.0)) message = self.attach_message_properties(message, reactions, commands, substitution) self.messages_sent[message.uid] = message coordinator.send_message(message) return message
def wechat_picture_msg(self, msg: wxpy.Message) -> Message: efb_msg = Message(type=MsgType.Image) try: if msg.raw['MsgType'] == 47 and not msg.raw['Content']: raise EOFError if msg.file_size == 0: raise EOFError efb_msg.path, efb_msg.mime, efb_msg.file = self.save_file(msg) efb_msg.filename = msg.file_name # ^ Also throws EOFError efb_msg.text = "" except EOFError: efb_msg.text += self._("[Failed to download the picture, please check your phone.]") efb_msg.type = MsgType.Unsupported return efb_msg
def edit_link_message(self, message: Message, reactions: bool = False, commands: bool = False, substitution: bool = False) -> Message: message.text = f"Content of edited link message with ID {message.uid}" message.edit = True message.attributes = LinkAttribute( title="EH Forwarder Bot (edited)", description="EH Forwarder Bot project site. (edited)", url="https://efb.1a23.studio/#edited") message = self.attach_message_properties(message, reactions, commands, substitution) self.messages_sent[message.uid] = message coordinator.send_message(message) return message
def reply_message(self, message: Message, text: str): reply = Message() reply.text = text # reply.chat = coordinator.slaves[message.chat.channel_id].get_chat(message.chat.chat_uid) reply.chat = coordinator.slaves[message.chat.module_id].get_chat( message.chat.uid) reply.author = message.chat.make_system_member( uid=self.middleware_id, name=self.middleware_name, middleware=self) reply.type = MsgType.Text # reply.deliver_to = coordinator.master reply.deliver_to = coordinator.slaves[message.chat.module_id] # reply.target = message reply.uid = str(uuid.uuid4()) r2 = reply coordinator.send_message(reply) r2.deliver_to = coordinator.master coordinator.send_message(r2)
def qq_record_wrapper(self, data, chat: Chat = None): # Experimental! efb_msg = Message() try: transformed_file = self.inst.coolq_api_query("get_record", file=data['file'], out_format='mp3') efb_msg.type = MsgType.Audio efb_msg.file = download_voice(transformed_file['file'], self.inst.client_config['api_root'].rstrip("/"), self.inst.client_config['access_token']) mime = magic.from_file(efb_msg.file.name, mime=True) if isinstance(mime, bytes): mime = mime.decode() efb_msg.path = efb_msg.file.name efb_msg.mime = mime except Exception: efb_msg.type = MsgType.Unsupported efb_msg.text = self._('[Voice Message] Please check it on your QQ') logging.getLogger(__name__).exception("Failed to download voice") return [efb_msg]
def wechat_sticker_msg(self, msg: wxpy.Message) -> Message: efb_msg = Message(type=MsgType.Sticker) try: if msg.raw['MsgType'] == 47 and not msg.raw['Content']: raise EOFError if msg.file_size == 0: raise EOFError efb_msg.path, efb_msg.mime, efb_msg.file = self.save_file(msg) efb_msg.filename = msg.file_name # ^ Also throws EOFError if 'gif' in efb_msg.mime and Image.open(efb_msg.path).is_animated: efb_msg.type = MsgType.Animation efb_msg.text = "" except EOFError: efb_msg.text += self._("[Failed to download the sticker, please check your phone.]") efb_msg.type = MsgType.Unsupported return efb_msg
def edit_file_like_message(self, message: Message, file_path: Path, mime: str, reactions: bool = False, commands: bool = False, substitution: bool = False) -> Message: message.text = f"Content of edited {message.type.name} media with ID {message.uid}" message.edit = True message.edit_media = True message.file = file_path.open('rb') message.filename = file_path.name message.path = file_path message.mime = mime message = self.attach_message_properties(message, reactions, commands, substitution) self.messages_sent[message.uid] = message coordinator.send_message(message) return message
def process_message(self, message: Message) -> Optional[Message]: """ Process a message with middleware Args: message (:obj:`.Message`): Message object to process Returns: Optional[:obj:`.Message`]: Processed message or None if discarded. """ if not self.sent_by_master(message): return message msg_text = message.text if msg_text.startswith('\\np '): message.text = msg_text[3:] return message # re_url = r'https?:\/\/\S+' # taken from django https://github.com/django/django/blob/master/django/core/validators.py#L68 valid_url = re.compile( r'(https?://)?' # scheme is validated separately r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}(?<!-)\.?)|' # domain... r'localhost|' # localhost... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4 r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6 r'(?::\d+)?' # optional port r'(?:[/?]\S+|[/?])?', re.IGNORECASE) url = valid_url.search(msg_text) if not url: return message url = url.group(0) try: lp = LinkPreview(url) title = lp.title desc = lp.desc except Exception as e: self.logger.error("Failed to get link preview: {}".format(e)) return message text = msg_text if title or desc: text = '\n'.join([ msg_text, 'preview'.center(23, '='), title.center(23), '-' * 27, str(desc), ]) if lp.type.startswith('image') and lp.image: suffix = os.path.splitext(lp.image_url)[1] message.file = NamedTemporaryFile(suffix=suffix) message.filename = os.path.basename(message.file.name) message.file.write(lp.image) message.file.file.seek(0) message.type = MsgType.Image message.mime = lp.type message.path = message.file.name message.text = text return message
def send_message(self, msg: Message) -> Message: """Send a message to WeChat. Supports text, image, sticker, and file. Args: msg (channel.Message): Message Object to be sent. Returns: This method returns nothing. Raises: EFBChatNotFound: Raised when a chat required is not found. EFBMessageTypeNotSupported: Raised when the message type sent is not supported by the channel. EFBOperationNotSupported: Raised when an message edit request is sent, but not supported by the channel. EFBMessageNotFound: Raised when an existing message indicated is not found. E.g.: The message to be edited, the message referred in the :attr:`msg.target <.Message.target>` attribute. EFBMessageError: Raised when other error occurred while sending or editing the message. """ if msg.chat == self.user_auth_chat: raise EFBChatNotFound chat: wxpy.Chat = self.chats.get_wxpy_chat_by_uid(msg.chat.uid) # List of "SentMessage" response for all messages sent r: List[wxpy.SentMessage] = [] self.logger.info("[%s] Sending message to WeChat:\n" "uid: %s\n" "UserName: %s\n" "NickName: %s\n" "Type: %s\n" "Text: %s", msg.uid, msg.chat.uid, chat.user_name, chat.name, msg.type, msg.text) try: chat.mark_as_read() except wxpy.ResponseError as e: self.logger.exception( "[%s] Error occurred while marking chat as read. (%s)", msg.uid, e) send_text_only = False self.logger.debug('[%s] Is edited: %s', msg.uid, msg.edit) if msg.edit and msg.uid: if self.flag('delete_on_edit'): msg_ids = json.loads(msg.uid) if msg.type in self.MEDIA_MSG_TYPES and not msg.edit_media: # Treat message as text message to prevent resend of media msg_ids = msg_ids[1:] send_text_only = True failed = 0 for i in msg_ids: try: ews_utils.message_id_to_dummy_message(i, self).recall() except wxpy.ResponseError as e: self.logger.error( "[%s] Trying to recall message but failed: %s", msg.uid, e) failed += 1 if failed: raise EFBMessageError( self.ngettext('Failed to recall {failed} out of {total} message, edited message was not sent.', 'Failed to recall {failed} out of {total} messages, edited message was not sent.', len(msg_ids)).format( failed=failed, total=len(msg_ids) )) # Not caching message ID as message recall feedback is not needed in edit mode else: raise EFBOperationNotSupported() if send_text_only or msg.type in [MsgType.Text, MsgType.Link]: if isinstance(msg.target, Message): max_length = self.flag("max_quote_length") qt_txt = msg.target.text or msg.target.type.name if max_length > 0: if len(qt_txt) >= max_length: tgt_text = qt_txt[:max_length] tgt_text += "…" else: tgt_text = qt_txt elif max_length < 0: tgt_text = qt_txt else: tgt_text = "" if isinstance(chat, wxpy.Group) and not isinstance(msg.target.author, SelfChatMember): tgt_alias = "@%s\u2005:" % msg.target.author.display_name else: tgt_alias = "" msg.text = f"「{tgt_alias}{tgt_text}」\n- - - - - - - - - - - - - - -\n{msg.text}" r.append(self._bot_send_msg(chat, msg.text)) self.logger.debug( '[%s] Sent as a text message. %s', msg.uid, msg.text) elif msg.type in (MsgType.Image, MsgType.Sticker, MsgType.Animation): self.logger.info("[%s] Image/GIF/Sticker %s", msg.uid, msg.type) convert_to = None file = msg.file assert file is not None if self.flag('send_stickers_and_gif_as_jpeg'): if msg.type == MsgType.Sticker or msg.mime == "image/gif": convert_to = "image/jpeg" else: if msg.type == MsgType.Sticker: convert_to = "image/gif" if convert_to == "image/gif": with NamedTemporaryFile(suffix=".gif") as f: try: img = Image.open(file) try: alpha = img.split()[3] mask = Image.eval( alpha, lambda a: 255 if a <= 128 else 0) except IndexError: mask = Image.eval(img.split()[0], lambda a: 0) img = img.convert('RGB').convert( 'P', palette=Image.ADAPTIVE, colors=255) img.paste(255, mask) img.save(f, transparency=255) msg.path = Path(f.name) self.logger.debug( '[%s] Image converted from %s to GIF', msg.uid, msg.mime) file.close() if f.seek(0, 2) > self.MAX_FILE_SIZE: raise EFBMessageError( self._("Image size is too large. (IS02)")) f.seek(0) r.append(self._bot_send_image(chat, f.name, f)) finally: if not file.closed: file.close() elif convert_to == "image/jpeg": with NamedTemporaryFile(suffix=".jpg") as f: try: img = Image.open(file).convert('RGBA') out = Image.new("RGBA", img.size, (255, 255, 255, 255)) out.paste(img, img) out.convert('RGB').save(f) msg.path = Path(f.name) self.logger.debug( '[%s] Image converted from %s to JPEG', msg.uid, msg.mime) file.close() if f.seek(0, 2) > self.MAX_FILE_SIZE: raise EFBMessageError( self._("Image size is too large. (IS02)")) f.seek(0) r.append(self._bot_send_image(chat, f.name, f)) finally: if not file.closed: file.close() else: try: if file.seek(0, 2) > self.MAX_FILE_SIZE: raise EFBMessageError( self._("Image size is too large. (IS01)")) file.seek(0) self.logger.debug( "[%s] Sending %s (image) to WeChat.", msg.uid, msg.path) filename = msg.filename or (msg.path and msg.path.name) assert filename r.append(self._bot_send_image(chat, filename, file)) finally: if not file.closed: file.close() if msg.text: r.append(self._bot_send_msg(chat, msg.text)) elif msg.type in (MsgType.File, MsgType.Audio): self.logger.info("[%s] Sending %s to WeChat\nFileName: %s\nPath: %s\nFilename: %s", msg.uid, msg.type, msg.text, msg.path, msg.filename) filename = msg.filename or (msg.path and msg.path.name) assert filename and msg.file r.append(self._bot_send_file(chat, filename, file=msg.file)) if msg.text: self._bot_send_msg(chat, msg.text) if not msg.file.closed: msg.file.close() elif msg.type == MsgType.Video: self.logger.info( "[%s] Sending video to WeChat\nFileName: %s\nPath: %s", msg.uid, msg.text, msg.path) filename = msg.filename or (msg.path and msg.path.name) assert filename and msg.file r.append(self._bot_send_video(chat, filename, file=msg.file)) if msg.text: r.append(self._bot_send_msg(chat, msg.text)) if not msg.file.closed: msg.file.close() else: raise EFBMessageTypeNotSupported() msg.uid = ews_utils.generate_message_uid(r) self.logger.debug( 'WeChat message is assigned with unique ID: %s', msg.uid) return msg