async def _run(self, owner_id): if owner_id and owner_id.__class__ is not int: raise TypeError( 'Owner_id must be positive integer, not {0.__class__.__name__}' .format(owner_id)) if owner_id and owner_id < 0: raise VKApiError('Owner_id must be positive integer') user = await self.get_own_page() if isinstance(user, Group): self.is_group = True self.group = user if self.is_group and owner_id: raise VKApiError( 'Owner_id passed together with group access_token') ts = await self.get_longpoll_server() await self.print_warnings() self.dispatch('ready') updates = [] while True: try: lp = self.loop.create_task(self.longpoll(ts)) for update in updates: self.handle_update(update) ts, updates = await lp except Exception as e: print( 'Ignoring exception in longpoll cycle:\n{}'.format(e), file=sys.stderr) ts = await self.get_longpoll_server() raise LoginError('User token passed to group client')
async def get_groups(self, *gids): """|coro| Alias for VK Api 'groups.get' method call Parameters ---------- gids: List[:class:`int`] List of group ids to request Raises -------- vk_botting.VKApiError When error is returned by VK API. Returns ------- List[:class:`.Group`] List of :class:`.Group` instances for requested groups """ groups = await self.vk_request('groups.getById', group_ids=','.join(map(str, gids))) if 'error' in groups.keys(): raise VKApiError( '[{error_code}] {error_msg}'.format(**groups['error'])) groups = groups.get('response') return [Group(group) for group in groups]
async def get_users(self, *uids, fields=None, name_case=None): """|coro| Alias for VK Api 'users.get' method call Parameters ---------- uids: List[:class:`int`] List of user ids to request fields: List[:class:`str`] Optional. Fields that should be requested from VK Api. None by default name_case: :class:`str` Optional. Name case for users' names to be returned in. 'nom' by default. Can be 'nom', 'gen', 'dat', 'acc', 'ins' or 'abl' Raises -------- vk_botting.VKApiError When error is returned by VK API. Returns ------- List[:class:`.User`] List of :class:`.User` instances for requested users """ if name_case is None: name_case = 'nom' users = await self.vk_request('users.get', user_ids=uids, fields=fields, name_case=name_case) if 'error' in users.keys(): raise VKApiError( '[{error_code}] {error_msg}'.format(**users['error'])) users = users.get('response') return [User(self, user) for user in users]
def add_vkpay_button(self, _hash, payload=None): """Adds button to current line Parameters ---------- _hash: :class:`str` Hash for a VKPay button payload: :class:`dict` Payload for a VKPay button Raises ------ vk_botting.VKApiError When there are already too many buttons on one line """ current_line = self.lines[-1] if len(current_line) != 0: raise VKApiError( 'This type of button takes the entire width of the line') if payload is not None and not isinstance(payload, str): payload = to_json(payload) button_type = KeyboardButton.VKPAY.value current_line.append({ 'action': { 'type': button_type, 'payload': payload, 'hash': _hash } })
async def _run(self, owner_id): if owner_id and owner_id.__class__ is not int: raise TypeError( 'Owner_id must be positive integer, not {0.__class__.__name__}' .format(owner_id)) if owner_id and owner_id < 0: raise VKApiError('Owner_id must be positive integer') user = await self.get_own_page() if isinstance(user, User): self.is_group = False self.group = Group({}) self.user = user ts = await self.get_user_longpoll() self.dispatch('ready') updates = [] while True: try: lp = self.loop.create_task(self.longpoll(ts)) for update in updates: self.loop.create_task(self.handle_user_update(update)) ts, updates = await lp except Exception as e: print( 'Ignoring exception in longpoll cycle:\n{}'.format(e), file=sys.stderr) ts = await self.get_user_longpoll() raise LoginError('Group token passed to user client')
async def delete(self, delete_for_all=1): """|coro| Coroutine to delete the message. Parameters ---------- delete_for_all: :class:`bool` ``True`` if message should be deleted for all users. Defaults to ``True`` Raises -------- vk_botting.VKApiError When error is returned by VK API. """ params = { 'group_id': self.bot.group.id, 'delete_for_all': delete_for_all, 'peer_id': self.peer_id, 'conversation_message_ids': self.conversation_message_id, } res = await self.bot.vk_request('messages.delete', **params) if 'error' in res.keys(): raise VKApiError( '[{error_code}] {error_msg}'.format(**res['error']))
async def get_longpoll_server(self): res = await self.vk_request('groups.getLongPollServer', group_id=self.group.id) error = res.get('error', None) if error and error['error_code'] == 100: if self.force: await self.enable_longpoll() return await self.get_longpoll_server() raise VKApiError( 'Longpoll is disabled for this group. Enable longpoll or try force mode' ) elif error: raise VKApiError('[{error_code}]{error_msg}'.format(**error)) self.key = res['response']['key'] self.server = res['response']['server'].replace(r'\/', '/') ts = res['response']['ts'] return ts
async def _answer(self, event_data): res = await self.bot.vk_request('messages.sendMessageEventAnswer', event_id=self.event_id, user_id=self.user_id, peer_id=self.peer_id, event_data=event_data) if 'error' in res.keys(): raise VKApiError( '[{error_code}] {error_msg}'.format(**res['error'])) return res
def add_line(self): """Adds new line to the keyboard Raises ------ vk_botting.VKApiError When there are already too many lines in a keyboard """ if (len(self.lines) > 5 and self.inline) or len(self.lines) > 9: num = 6 if self.inline else 10 raise VKApiError('Max {} lines'.format(num)) self.lines.append([])
def _add_button(self, button_type, label, color, payload): current_line = self.lines[-1] if len(current_line) > 4: raise VKApiError('Max 5 buttons on a line') color_value = color if isinstance(color, KeyboardColor): color_value = color_value.value current_line.append({ 'color': color_value, 'action': { 'type': button_type, 'payload': payload, 'label': label, } })
async def get_user_longpoll(self): res = await self.vk_request('messages.getLongPollServer', group_id=self.group.id, lp_version=3) error = res.get('error', None) if error and error['error_code'] == 15: raise LoginError( 'User has no access to messages API. Try generating token with vk_botting.auth methods' ) elif error: raise VKApiError( '[{error_code}] {error_msg}'.format(**res['error'])) self.key = res['response']['key'] server = res['response']['server'].replace(r'\/', '/') self.server = 'https://{}'.format(server) ts = res['response']['ts'] return ts
async def edit(self, message=None, *, attachment=None, keep_forward_messages=1, keep_snippets=1): """|coro| Coroutine to edit the message. Parameters ---------- message: :class:`str` New text of the message. attachment: Union[List[:class:`str`], :class:`str`, List[:class:`.Attachment`], :class:`.Attachment`] New attachment to the message. keep_forward_messages: :class:`bool` ``True`` if fwd_messages should be kept. Defaults to ``True`` keep_snippets: :class:`bool` ``True`` if snippets should be kept. Defaults to ``True`` Raises -------- vk_botting.VKApiError When error is returned by VK API. Returns --------- :class:`.Message` The message that was sent. """ params = { 'group_id': self.bot.group.id, 'peer_id': self.peer_id, 'message': message, 'attachment': attachment, 'keep_forward_messages': keep_forward_messages, 'keep_snippets': keep_snippets, 'conversation_message_id': self.conversation_message_id } res = await self.bot.vk_request('messages.edit', **params) if 'error' in res.keys(): raise VKApiError( '[{error_code}] {error_msg}'.format(**res['error'])) return res
async def edit(self, message=None, *, attachment=None, keep_forward_messages='true', keep_snippets='true'): params = { 'peer_id': self.peer_id, 'message': message, 'attachment': attachment, 'message_id': self.id, 'keep_forward_messages': keep_forward_messages, 'keep_snippets': keep_snippets } res = await self.bot.vk_request('messages.edit', **params) if 'error' in res.keys(): raise VKApiError( '[{error_code}] {error_msg}'.format(**res['error'])) return res
def add_vkapps_button(self, app_id, owner_id, label, _hash, payload=None): """Adds button to current line Parameters ---------- app_id: :class:`int` Id of VK App owner_id: :class:`int` Id of VK App owner label: :class:`str` Button label to be displayed _hash: :class:`str` Hash for a VK App button payload: :class:`dict` Optional. Should be used for some button types Raises ------ vk_botting.VKApiError When there are already too many buttons on one line """ current_line = self.lines[-1] if len(current_line) != 0: raise VKApiError( 'This type of button takes the entire width of the line') if payload is not None and not isinstance(payload, str): payload = to_json(payload) button_type = KeyboardButton.VKAPPS.value current_line.append({ 'action': { 'type': button_type, 'app_id': app_id, 'owner_id': owner_id, 'label': label, 'payload': payload, 'hash': _hash } })
async def reply(self, message=None, *, attachment=None, sticker_id=None, keyboard=None): peer_id = await self._get_conversation() params = { 'random_id': randint(-2**63, 2**63 - 1), 'peer_id': peer_id, 'message': message, 'attachment': attachment, 'reply_to': self.id, 'sticker_id': sticker_id, 'keyboard': keyboard } res = await self.bot.vk_request('messages.send', **params) if 'error' in res.keys(): raise VKApiError( '[{error_code}] {error_msg}'.format(**res['error'])) params['id'] = res['response'] return self.bot.build_msg(params)
async def _vk_request(self, method, post, calln=1, **kwargs): if calln > 10: raise VKApiError('VK API call failed after 10 retries') for param in kwargs: if isinstance(kwargs[param], (list, tuple)): kwargs[param] = ','.join(map(str, kwargs[param])) elif isinstance(kwargs[param], dict): kwargs[param] = to_json(kwargs[param]) res = await self.general_request( 'https://api.vk.com/method/{}'.format(method), post=post, **kwargs) if isinstance(res, str): await asyncio.sleep(0.1) return await self._vk_request(method, post, calln + 1, **kwargs) error = res.get('error', None) if error and error.get('error_code', None) == 6: await asyncio.sleep(1) return await self._vk_request(method, post, calln + 1, **kwargs) elif error and error.get( 'error_code', None ) == 10 and 'could not check access_token now' in error.get( 'error_msg', ''): await asyncio.sleep(0.1) return await self._vk_request(method, post, calln + 1, **kwargs) return res
def add_callback_button(self, label, color=KeyboardColor.PRIMARY, payload=None): """Adds a callback button (https://vk.com/dev/bots_docs_5) to current line Parameters ---------- label: :class:`str` Button label to be displayed color: :class:Union[`str`, `KeyboardColor`] Button color. Can be value from :class:`.KeyboardColor` enum payload: :class:`dict` Optional. Should be used for some buttons Raises ------ vk_botting.VKApiError When there are already too many buttons on one line """ if not self.inline: raise VKApiError( 'Cannot add a callback button to non-inline keyboard') self._add_button(KeyboardButton.CALLBACK.value, label, color, payload)
async def send_message(self, peer_id=None, message=None, attachment=None, sticker_id=None, keyboard=None, reply_to=None, forward_messages=None, forward=None, **kwargs): """|coro| Sends a message to the given destination with the text given. The content must be a type that can convert to a string through ``str(message)``. If the content is set to ``None`` (the default), then the ``attachment`` or ``sticker_id`` parameter must be provided. If the ``attachment`` parameter is provided, it must be :class:`str`, List[:class:`str`], :class:`.Attachment` or List[:class:`.Attachment`] If the ``keyboard`` parameter is provided, it must be :class:`str` or :class:`.Keyboard` (recommended) Parameters ------------ peer_id: :class:`int` Id of conversation to send message to message: :class:`str` The text of the message to send. attachment: Union[List[:class:`str`], :class:`str`, List[:class:`.Attachment`], :class:`.Attachment`] The attachment to the message sent. sticker_id: Union[:class:`str`, :class:`int`] Sticker_id to be sent. keyboard: :class:`.Keyboard` The keyboard to send along message. reply_to: Union[:class:`str`, :class:`int`] A message id to reply to. forward_messages: Union[List[:class:`int`], List[:class:`str`]] Message ids to be forwarded along with message. forward: :class:`dict` Check docs for more details on this one. as_user: :class:`bool` If message should be sent as user (using attached user token). Raises -------- vk_botting.VKApiError When error is returned by VK API. Returns --------- :class:`.Message` The message that was sent. """ as_user = kwargs.pop('as_user', False) if kwargs: print('Unknown parameters passed to send_message: {}'.format( ', '.join(kwargs.keys())), file=sys.stderr) if isinstance(attachment, str) or attachment is None: pass elif isinstance(attachment, Iterable): attachment = ','.join(map(str, attachment)) else: attachment = str(attachment) if message: message = str(message) if len(message) > 4096: w = textwrap.TextWrapper(width=4096, replace_whitespace=False) messages = w.wrap(message) for message in messages[:-1]: await self.send_message(peer_id, message, **kwargs) return await self.send_message( peer_id, messages[-1], attachment=attachment, sticker_id=sticker_id, keyboard=keyboard, reply_to=reply_to, forward_messages=forward_messages, **kwargs) params = { 'random_id': getrandbits(64), 'message': message, 'attachment': attachment, 'reply_to': reply_to, 'forward_messages': forward_messages, 'sticker_id': sticker_id, 'keyboard': keyboard, 'forward': forward } if self.is_group and not as_user: params['group_id'] = self.group.id params['peer_ids'] = peer_id else: params['peer_id'] = peer_id res = await self.vk_request( 'messages.send', ** params) if not as_user else await self.user_vk_request( 'messages.send', **params) if 'error' in res.keys(): if res['error'].get('error_code') == 9: await asyncio.sleep(1) return await self.send_message( peer_id, message, attachment=attachment, sticker_id=sticker_id, keyboard=keyboard, reply_to=reply_to, forward_messages=forward_messages, **kwargs) raise VKApiError( '[{error_code}] {error_msg}'.format(**res['error'])) if self.is_group and not as_user: params['from_id'] = -self.group.id params['conversation_message_id'] = res['response'][0][ 'conversation_message_id'] params['id'] = res['response'][0]['message_id'] params['peer_id'] = peer_id params.pop('peer_ids') else: params['from_id'] = self.user.id params['id'] = res['response'] return self.build_msg(params)