async def create(cls, contacts: List[Contact], topic: str) -> Room: """ create room instance """ if not hasattr(contacts, '__len__'): raise WechatyOperationError('contacts should be list type') if len(contacts) < 2: raise WechatyOperationError( 'contactList need at least 2 contact to create a new room' ) log.info( 'Room create <%s - %s>', ','.join([contact.contact_id for contact in contacts]), topic ) try: contact_ids = list(map(lambda x: x.contact_id, contacts)) room_id = await cls.get_puppet(). \ room_create(contact_ids=contact_ids, topic=topic) room = cls.load(room_id=room_id) await room.ready() return room except Exception as exception: message = 'Room create error <%s>' % str(exception.args) log.error(message) raise WechatyOperationError(message)
async def qr_code(self) -> str: """ Return the qrcode of ContactSelf Examples: >>> contact_self = bot.contact_self() >>> qr_code = await contact_self.qr_code() Raises: WechatyOperationError: if there is login exception, it will not get the qrcode WechatyOperationError: if the contact is not self, it will not get the qrcode Returns: str: the content of qrcode """ try: contact_id: str = self.puppet.self_id() except Exception: raise WechatyOperationError( 'Can not get qr_code, user might be either not logged in or already logged out' ) if self.contact_id != contact_id: raise WechatyOperationError( 'only can get qr_code for the login user self') qr_code_value = await self.puppet.contact_self_qr_code() return qr_code_value
async def to_recalled(self) -> Message: """ Get the recalled message Args: None Examples: >>> msg.to_recalled() Returns: Message: the recalled message """ if self.message_type() != MessageType.MESSAGE_TYPE_RECALLED: raise WechatyOperationError( 'Can not call toRecalled() on message which is not' ' recalled type.') origin_message_id = self.text() if origin_message_id is None: raise WechatyPayloadError('Can not find recalled message') log.info('get recall message <%s>', origin_message_id) try: message = self.wechaty.Message.load(origin_message_id) await message.ready() return message except Exception as exception: error_info = 'can"t load or ready message payload {}'.format( str(exception.args)) log.error(error_info) raise WechatyOperationError(error_info)
async def qr_code(self) -> str: """ :return: """ try: puppet_id: str = self.puppet.self_id() except Exception: raise WechatyOperationError( 'Can not get qr_code, user might be either not logged in or already logged out') if self.contact_id != puppet_id: raise WechatyOperationError('only can get qr_code for the login user self') qr_code_value = await self.puppet.contact_self_qr_code() return qr_code_value
async def forward(self, to: Union[Room, Contact]) -> None: """ Forward a message to a room or a contact. Args: to: the room or contact to forward to Examples: >>> msg.forward(room) >>> msg.forward(contact) """ log.info('forward() <%s>', to) if to is None: raise WechatyPayloadError('to param not found') try: if isinstance(to, Room): to_id = to.room_id elif isinstance(to, Contact): to_id = to.contact_id else: raise WechatyPayloadError( 'expected type is <Room, Contact>, but get <%s>' % to.__class__) print(to_id) await self.puppet.message_forward(to_id, self.message_id) # pylint:disable=W0703 except Exception as exception: message = 'Message forward error <%s>' % exception.args log.error(message) raise WechatyOperationError(message)
async def to_file_box(self) -> FileBox: """ Extract the Media File from the Message, and put it into the FileBox. Notes: ``` File MessageType is : { MESSAGE_TYPE_ATTACHMENT, MESSAGE_TYPE_EMOTICON, MESSAGE_TYPE_IMAGE, MESSAGE_TYPE_VIDEO } ``` Examples: >>> msg.to_file_box() Returns: FileBox: file box """ log.info('Message to FileBox') if self.type() not in SUPPORTED_MESSAGE_FILE_TYPES: raise WechatyOperationError( f'this type <{self.type().name}> message can"t be converted to ' f'FileBox') msg_type: MessageType = self.type() if msg_type == MessageType.MESSAGE_TYPE_IMAGE: file_box = await self.puppet.message_image(self.message_id) else: file_box = await self.puppet.message_file(self.message_id) return file_box
async def forward(self, to: Union[Room, Contact]): """ doc :param to: :return: """ log.info('forward() <%s>', to) if to is None: raise WechatyPayloadError('to param not found') try: if isinstance(to, Room): to_id = to.room_id elif isinstance(to, Contact): to_id = to.contact_id else: raise WechatyPayloadError( 'expected type is <Room, Contact>, but get <%s>' % to.__class__) print(to_id) await self.puppet.message_forward(to_id, self.message_id) # pylint:disable=W0703 except Exception as exception: message = 'Message forward error <%s>' % exception.args log.error(message) raise WechatyOperationError(message)
async def avatar(self, file_box: Optional[FileBox] = None) -> FileBox: """get avatar of ContactSelf Args: file_box (Optional[FileBox], optional): i. Defaults to None. Examples: >>> contact_self = bot.contact_self() >>> file_box = await contact_self.avatar() Raises: WechatyOperationError: _description_ Returns: FileBox: _description_ """ log.info('avatar(%s)' % file_box.name if file_box else '') if not file_box: file_box = await super().avatar(None) return file_box if self.contact_id != self.puppet.self_id(): raise WechatyOperationError( 'set avatar only available for user self') await self.puppet.contact_avatar(self.contact_id, file_box)
async def accept(self) -> None: """ accept friendship Args: None Examples: >>> await friendship.accept() Returns: None """ log.info('accept friendship, friendship_id: <%s>', self.friendship_id) if self.type() != FriendshipType.FRIENDSHIP_TYPE_RECEIVE: raise WechatyOperationError( 'accept() need type to be FriendshipType.' 'FRIENDSHIP_TYPE_RECEIVE, but it got a " + FriendshipType : ' '<{0}>'.format(self.type().name)) log.info('friendship accept to %s', self.payload.contact_id) await self.puppet.friendship_accept(friendship_id=self.friendship_id) contact = self.contact() # reset contact data try: # TODO -> some other logical code await contact.ready() # pylint:disable=W0703 except Exception as e: log.info("can't reload contact data %s", str(e.args))
async def avatar(self, file_box: Optional[FileBox] = None) -> FileBox: """ Get or set avatar of ContactSelf. Args: file_box: FileBox object, if not provided, it will return a FileBox object Examples: >>> contact_self = bot.contact_self() >>> file_box = await contact_self.avatar() >>> file_box = await contact_self.avatar(file_box) Raises: WechatyOperationError: if the contact is not self, it will not get the avatar Returns: FileBox: file_box """ log.info('avatar(%s)' % file_box.name if file_box else '') if not file_box: file_box = await super().avatar(None) return file_box if self.contact_id != self.puppet.self_id(): raise WechatyOperationError( 'set avatar only available for user self') await self.puppet.contact_avatar(self.contact_id, file_box)
async def signature(self, signature: str) -> Any: """ Set the signature of login contact. Args: signature: new signature Examples: >>> contact_self = bot.contact_self() >>> await contact_self.signature('new signature') Raises: WechatyOperationError: if there is login exception, it will not set the signature WechatyOperationError: if the contact is not self, it will not set the signature Returns: Any: the signature of login user """ puppet_id = self.puppet.self_id() if self.contact_id != puppet_id: raise WechatyOperationError( 'only can get qr_code for the login user self') return self.puppet.contact_signature(signature)
async def say( self, some_thing: Union[str, Contact, FileBox, MiniProgram, UrlLink], mention_ids: Optional[List[str]] = None) -> Union[None, Message]: """ Room Say(%s, %s) """ log.info('Room say <%s, %s>', some_thing, mention_ids) if not some_thing: log.error('can"t say nothing') return None # we should import UrlLink type locally because of circular dependency from wechaty.user.url_link import UrlLink from wechaty.user.mini_program import MiniProgram from wechaty.user.contact import Contact if isinstance(some_thing, str): if mention_ids: mention_info = [] for mention_id in mention_ids: mention_contact: Contact = self.wechaty.Contact.load( mention_id) await mention_contact.ready() alias = await mention_contact.alias() name = mention_contact.name mention_info.append('@' + (alias or name)) mention_text = AT_SEPARATOR.join(mention_info) some_thing = mention_text + ' ' + some_thing msg_id = await self.puppet.message_send_text( conversation_id=self.room_id, message=some_thing, mention_ids=mention_ids) elif isinstance(some_thing, FileBox): msg_id = await self.puppet.message_send_file( conversation_id=self.room_id, file=some_thing) elif isinstance(some_thing, Contact): msg_id = await self.puppet.message_send_contact( conversation_id=self.room_id, contact_id=some_thing.contact_id) elif isinstance(some_thing, UrlLink): msg_id = await self.puppet.message_send_url( conversation_id=self.room_id, url=json.dumps(dataclasses.asdict(some_thing.payload))) elif isinstance(some_thing, MiniProgram): # TODO -> mini_program key is not clear assert some_thing.payload is not None msg_id = await self.puppet.message_send_mini_program( conversation_id=self.room_id, mini_program=some_thing.payload) else: raise WechatyOperationError('arg unsupported: ', some_thing) if msg_id is not None: msg = self.wechaty.Message.load(msg_id) await msg.ready() return msg return None
def name(self, name: Optional[str]): puppet_id: str = self.puppet.self_id() if self.contact_id != puppet_id: raise WechatyOperationError( 'only can get qr_code for the login user self') asyncio.run(self.puppet.contact_self_name(name))
async def to_file_box(self) -> FileBox: """ Extract the Media File from the Message, and put it into the FileBox. """ log.info('Message to FileBox') if self.type() == MessageType.MESSAGE_TYPE_TEXT: raise WechatyOperationError( 'text message can"t convert to FileBox') file_box = await self.puppet.message_file(self.message_id) return file_box
def to_image(self) -> Image: """ Extract the Image File from the Message, so that we can use different image sizes. :return: """ log.info('Message to Image() for message %s', self.message_id) if self.type() != MessageType.MESSAGE_TYPE_IMAGE: raise WechatyOperationError( 'current message type: %s, not image type' % self.type()) return self.wechaty.Image.create(self.message_id)
async def delete(self, contact: Contact) -> None: """ Delete contact in a room """ log.info('Room delete<%s>', contact) if contact is None or contact.contact_id is None: raise WechatyOperationError('Contact is none or contact_id not found') await self.puppet.room_delete(self.room_id, contact.contact_id) # reload the payload await self.ready(force_sync=True)
async def signature(self, signature: str) -> Any: """ :param signature: :return: """ puppet_id = self.puppet.self_id() if self.contact_id != puppet_id: raise WechatyOperationError('only can get qr_code for the login user self') return self.puppet.contact_signature(signature)
def _filter_contacts( cls, contacts: List[Contact], query: Union[str, ContactQueryFilter, Callable[[Contact], bool]] ) -> List[Contact]: func: Callable[[Contact], bool] if isinstance(query, str): def filter_func(contact: Contact) -> bool: payload = contact.payload if not payload: return False if query in payload.alias or query in payload.name: return True if query == payload.id or quit == payload.weixin: return True return False func = filter_func elif isinstance(query, ContactQueryFilter): def filter_func(contact: Contact) -> bool: payload = contact.payload # to pass the type checking assert isinstance(query, ContactQueryFilter) if not payload: return False if query.alias and query.alias in payload.alias: return True if query.name and query.name in payload.name: return True if query.id and query.id == payload.id: return True if query.weixin and query.weixin == payload.weixin: return True return False func = filter_func elif isinstance(types, types.FunctionType): func = query else: raise WechatyOperationError( f'Query Argument<{query}> is not correct') assert not not func contacts = list(filter(func, contacts)) return contacts
async def to_mini_program(self) -> MiniProgram: """ get message mini_program :return: """ log.info('Message to MiniProgram <%s>', self.message_id) if self.type() != MessageType.MESSAGE_TYPE_MINI_PROGRAM: raise WechatyOperationError('not a mini_program type message') payload = await self.puppet.message_mini_program(self.message_id) if payload is None: raise WechatyPayloadError('no miniProgram payload for message %s' % self.message_id) return MiniProgram(payload)
async def to_url_link(self) -> UrlLink: """ get url_link from message :return: """ log.info('Message to UrlLink') if self.type() != MessageType.MESSAGE_TYPE_URL: raise WechatyOperationError( 'current message type: %s, not url type' % self.type()) payload = await self.puppet.message_url(self.message_id) if payload is None: raise WechatyPayloadError( 'can not get url_link_payload by message: %s' % self.message_id) return UrlLink(payload)
async def say( self, message: Union[str, Message, FileBox, Contact, UrlLink] ) -> Optional[Message]: """ say something :param message: message content """ if not message: log.error('can"t say nothing') return None if not self.is_ready(): await self.ready() # import some class because circular dependency from wechaty.user.url_link import UrlLink if isinstance(message, str): # say text msg_id = await self.puppet.message_send_text( conversation_id=self.contact_id, message=message) elif isinstance(message, Contact): msg_id = await self.puppet.message_send_contact( contact_id=message.contact_id, conversation_id=self.contact_id) elif isinstance(message, FileBox): msg_id = await self.puppet.message_send_file( conversation_id=self.contact_id, file=message) elif isinstance(message, UrlLink): # use this way to resolve circulation dependency import msg_id = await self.puppet.message_send_url( conversation_id=self.contact_id, url=json.dumps(dataclasses.asdict(message.payload))) # elif isinstance(message, MiniProgram): # msg_id = await self.puppet.message_send_mini_program( # self.contact_id, message.payload) else: log.info('unsupported tags %s', message) raise WechatyOperationError('unsupported tags') if msg_id is not None: msg = self.wechaty.Message.load(msg_id) await msg.ready() return msg return None
def _filter_rooms( cls, rooms: List[Room], query: Union[str, RoomQueryFilter, Callable[[Room], bool]]) -> List[Room]: """ filter rooms with query which can be string, RoomQueryFilter, or callable<filter> function Args: rooms: list of room query: the query message Returns: the filtered contacts """ func: Callable[[Room], bool] if isinstance(query, str): def filter_func(room: Room) -> bool: payload = room.payload if not payload: return False if query == payload.id or query in payload.topic: return True return False func = filter_func elif isinstance(query, RoomQueryFilter): def filter_func(room: Room) -> bool: # to pass the type checking assert isinstance(query, RoomQueryFilter) payload = room.payload if not payload: return False if query.id == payload.id or query.topic in payload.topic: return True return False func = filter_func elif isinstance(query, types.FunctionType): func = query else: raise WechatyOperationError( f'Query Argument<{query}> is not correct') assert not not func rooms = list(filter(func, rooms)) return rooms
async def avatar(self, file: Optional[FileBox] = None) -> FileBox: """ :param file: :return: """ log.info('Contact', 'avatar(%s)' % file.name if file else '') if not file: file_box = await super().avatar(None) return file_box if self.contact_id != self.puppet.self_id(): raise WechatyOperationError( 'set avatar only available for user self') await self.puppet.contact_avatar(self.contact_id, file)
async def to_contact(self) -> Contact: """ Get Share Card of the Message Extract the Contact Card from the Message, and encapsulate it into Contact class :return: """ log.info('Message to Contact') if self.type() != MessageType.MESSAGE_TYPE_CONTACT: raise WechatyOperationError( 'current message type: %s, not contact type' % self.type()) contact_id = await self.puppet.message_contact(self.message_id) contact = self.wechaty.Contact.load(contact_id) await contact.ready() return contact
async def delete(self, target: Union[Contact, Favorite]): """ remove tag from contact or favorite :param target: :return: """ log.info('delete tag %s', self.tag_id) if target is Contact: await self.puppet.tag_contact_delete(tag_id=self.tag_id) elif target is Favorite: # TODO -> tag_favorite_delete not implement pass # await self.puppet.tag_contact_delete() else: raise WechatyOperationError( 'target param is required to be Contact or Favorite object')
async def alias(self, member: Contact) -> Optional[str]: """ Return contact's roomAlias in the room TODO -> 'Wechaty Puppet Unsupported API Error. Learn More At https://github.com/wechaty/wechaty-puppet/wiki/Compatibility', None)> the result of the function will always return an empty string """ if member is None: raise WechatyOperationError('member can"t be none') room_member_payload = await self.puppet.room_member_payload( room_id=self.room_id, contact_id=member.contact_id) if room_member_payload is not None \ and room_member_payload.room_alias is not None: return room_member_payload.room_alias return None
async def accept(self) -> None: """ accept the room invitation """ log.info('accept() <%s>', self) await self.puppet.room_invitation_accept( room_invitation_id=self.invitation_id) inviter = await self.inviter() topic = await self.topic() try: await inviter.ready() log.info('accept() with room(%s) & inviter(%s) ready())', topic, inviter) except Exception as exception: message = 'accept() with room(%s) & inviter(%s) error' % (topic, inviter) log.error(message) raise WechatyOperationError(message)
async def to_file_box(self) -> FileBox: """ Extract the Media File from the Message, and put it into the FileBox. File MessageType is : { MESSAGE_TYPE_ATTACHMENT, MESSAGE_TYPE_EMOTICON, MESSAGE_TYPE_IMAGE, MESSAGE_TYPE_VIDEO } """ log.info('Message to FileBox') if self.type() not in SUPPORTED_MESSAGE_FILE_TYPES: raise WechatyOperationError( f'this type <{self.type().name}> message can"t be converted to ' f'FileBox') file_box = await self.puppet.message_file(self.message_id) return file_box
def remove(self, source: Union[Contact, Favorite]): """ Remove this tag from Contact/Favorite tips : This function is depending on the Puppet Implementation, see [puppet-compatible-table](https://github.com/wechaty/ wechaty/wiki/Puppet#3-puppet-compatible-table) :param source: :return: """ log.info('remove tag for %s with %s', self.tag_id, str(source)) try: if isinstance(source, Contact): self.puppet.tag_contact_remove(tag_id=self.tag_id, contact_id=source.contact_id) elif isinstance(source, Favorite): # TODO -> tag_favorite_remove not implement pass except Exception as e: log.info('remove exception %s', str(e.args)) raise WechatyOperationError('remove error')
async def say(self, message: Union[str, Message, FileBox, Contact, UrlLink, MiniProgram] ) -> Optional[Message]: """say something to contact, which can be text, image, file, contact, url Note: Its implementation depends on the puppet, so if you want to use this method, please check [Puppet](https://github.com/wechaty/wechaty/wiki/Puppet#3-puppet-compatible-table). Args: message: the message object to be sended to contact Examples: >>> contact = Contact.load('contact-id') >>> await contact.say('hello') >>> await contact.say(FileBox.from_file('/path/to/file')) >>> await contact.say(contact) >>> await contact.say(UrlLink('https://wechaty.js.org')) >>> # the data format of mini program should pre-stored >>> await contact.say(MiniProgram('username', 'appid')) Returns: Message: if the message is send successfully, return the message object, otherwise return None """ if not message: log.error('can"t say nothing') return None if not self.is_ready(): await self.ready() # import some class because circular dependency from wechaty.user.url_link import UrlLink # pylint: disable=import-outside-toplevel if isinstance(message, str): # say text msg_id = await self.puppet.message_send_text( conversation_id=self.contact_id, message=message ) elif isinstance(message, Contact): msg_id = await self.puppet.message_send_contact( contact_id=message.contact_id, conversation_id=self.contact_id ) elif isinstance(message, FileBox): msg_id = await self.puppet.message_send_file( conversation_id=self.contact_id, file=message ) elif isinstance(message, UrlLink): # use this way to resolve circulation dependency import msg_id = await self.puppet.message_send_url( conversation_id=self.contact_id, url=json.dumps(dataclasses.asdict(message.payload), ensure_ascii=False) ) elif isinstance(message, MiniProgram): msg_id = await self.puppet.message_send_mini_program( conversation_id=self.contact_id, mini_program=message.payload ) else: log.info('unsupported tags %s', message) raise WechatyOperationError('unsupported tags') if msg_id is not None: msg = self.wechaty.Message.load(msg_id) await msg.ready() return msg return None