├── .gitignore ├── LICENSE ├── README.md ├── examples ├── default_api.py ├── getting_flags.py ├── login_via_data.py ├── login_via_token.py ├── polling.py └── upload_media.py ├── setup.py └── vk_advanced_api ├── API.py ├── Auth.py ├── Pool.py ├── Request.py ├── Response.py ├── __init__.py ├── version └── vkapi.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nikita 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VK Advanced API 2 | 3 | ### Поддержка приостановлена! 4 | > Весь контент, изложенный в этом репозитории, не является актуальным, автор прекращает поддержку работы и обновления функционала, а также более не несет ответственности за корректность работы приложения. 5 | --- 6 | 7 | Интересная штука то получается. Вроде всех этих open-source либ много, но все они 8 | какие-то недоработанные :rage: 9 | 10 | Именно так я решил. 11 | Решил и сделал :relaxed: 12 | 13 | VK Advanced API предоставляет возможность в полной мере насладиться всеми 14 | фичами API ВК, которое оно само предоставить не может. 15 | 16 | Здесь вы увидите: 17 | - Авторизация с помощью Логина и Пароля пользователя, а не только токена 18 | - Улучшение работы некоторых методов и их группирование в один полноценно рабочий метод 19 | - Улучшенный polling эвентов 20 | - Новый тип эвентов - Notifications Events (название авторское) 21 | - Обработку капчи с помощью сервиса RuCaptcha 22 | - Кастомную обработку ошибок 23 | - Качественную и быструю работу 24 | - Гибкую настройку под все нужды 25 | 26 | Это и не только вы сможете увидеть тут! 27 | 28 | --- 29 | ### Установка 30 | 31 | Очень простая и удобная установка! 32 | 33 | **Возможны некоторые ошибки при установке (так уж собрался пакет, что необходимые модули ставяться в конце)** 34 | 35 | **Чтобы их избежать, следуйте инструкции ниже** 36 | 37 | Выполните следующие команды в вашей консоли 38 | ```shell 39 | pip install requests 40 | pip install parsel 41 | pip install captcha_solver 42 | pip install pymitter 43 | pip install lxml 44 | ``` 45 | 46 | И установите сам модуль 47 | ```shell 48 | pip install vk_advanced_api 49 | ``` 50 | 51 | Если хотите установить определенную версию, используйте `==version`, где 52 | version - желаемая версия 53 | ```shell 54 | pip install vk_advanced_api==1.3.1 55 | ``` 56 | Чтобы обновить модуль, напишите параметр `--upgrade`, установится последнаяя стабильная версия 57 | ```shell 58 | pip install --upgrade vk_advanced_api 59 | ``` 60 | 61 | --- 62 | ### Авторизация 63 | 64 | (Все представленное здесь хорошо описано в директории examples) 65 | 66 | Через данные пользователя 67 | ```python 68 | # Импорт модуля 69 | import vk_advanced_api 70 | 71 | # Экземпляр класса VKAPI 72 | api = vk_advanced_api.VKAPI( 73 | app_id='your-app-id', 74 | login='help@ar4ikov.ru', 75 | password='qwerty', 76 | captcha_key='your-captcha-key', 77 | version=5.71, 78 | warn_level=1 79 | ) 80 | ``` 81 | где: 82 | - app_id - ID Вашего Standalone-приложения ([тут прочтите](http://vk.com/editapp?act=create)) 83 | - login - Логин/Email/Телефон юзера 84 | - password - Пароль юзера 85 | - captcha_key - API Ключ к вашему аккаунту на RuCaptcha 86 | - version - Желаемая версия API VK 87 | - warn_level - Уровень лога ошибок, где 1 - вывод в консоль, 2 - вызов ошибок (raise) 88 | 89 | При данном типе авторизации будет получен Access Token юзера, так что вы всегда 90 | сможете получить его 91 | ```python 92 | access_token = api.access_token 93 | ``` 94 | 95 | Через токен 96 | ```python 97 | # Импортируем модуль 98 | import vk_advanced_api 99 | 100 | # Создаем экземпляр класса VKAPI 101 | api = vk_advanced_api.VKAPI( 102 | access_token='Your-Access-Token', 103 | captcha_key='your-captcha-key', 104 | version=5.71, 105 | warn_level=1 106 | ) 107 | ``` 108 | где: 109 | - access_token - Access Token юзера 110 | 111 | --- 112 | ### Вызов методов 113 | 114 | (Все представленное здесь хорошо описано в директории examples) 115 | 116 | Всего в модифицированной версии есть пару методов, основную часть которых, 117 | конечно же, составляет само API Вконтактика. 118 | 119 | Один из таких - `sendMessage` 120 | (В коде там поищите его описание, мне тут лень описывать это, ну серьезно) 121 | ```python 122 | 123 | # Тут всё вроде понятно, кроме attachments 124 | # Он описан в examples/upload_media.py 125 | 126 | api.sendMessage( 127 | user_id=1, 128 | message='Привет, Дуров', 129 | attachments=[] 130 | ) 131 | ``` 132 | 133 | Чтобы получить класс API, обратимся к ***utils*** 134 | ```python 135 | >>> utils = api.utils 136 | ``` 137 | После попытаемся вызвать метод. 138 | Т.к. я постарался поиграться с "магией" Питона ( ***__getattr__*** и ***__call__*** ), 139 | то все методы можно получить просто как методы самой библиотеки. Те, кто знают, что 140 | может ***__getattr__*** и ***__call__*** поймут меня. 141 | 142 | ```python 143 | >>> utils.messages.send(user_id=1, message='Привет, Дуров!') 144 | ``` 145 | 146 | (а это типо ответ API на ваш запрос) 147 | ```shell 148 | 364582 149 | ``` 150 | 151 | За подобную идею хочу отблагодарить человека [dimka665](https://github.com/dimka665) и его проект 152 | [vk](https://github.com/dimka665/vk) 153 | 154 | --- 155 | ### LongPolling и обработка эвентов 156 | 157 | (Все представленное здесь **полностью** описано в директории examples 158 | Сказал же, что **ПОЛНОСТЬЮ**!) 159 | 160 | Более подробно о технологии `LongPolling VK` читайте [тут](https://vk.com/dev/using_longpoll) 161 | 162 | Моя либа предоставляет возможность работать с обработанными эвентами, в частности 163 | направленные на помощь в создании чат-ботов 164 | 165 | Например, вот вам эвент ***new_message*** 166 | 167 | Ключ | Что означает 168 | -------------|-------------------------------------------------------------- 169 | type | Тип сообщения (public или private) 170 | message_id | ID сообщения 171 | date | Дата события по UNIX 172 | is_out | Определяет, явзяется ли сообщение исходящим (True, если да) 173 | args | Аргументы (просто всё, что разделено пробелом), помогают при создании команд для чат-бота 174 | is_command | Определяет, является ли сообщение командой (True, если да) 175 | peer_id | ID диалога (диалогом может быть беседа или личный чат) 176 | from_id | (только для бесед, вернет None, если личное сообщение) - ID пользователя, который отправил сообщение 177 | body | Тело эвента в нетронутом виде, которое поступило при запросе на Polling сервер VK 178 | is_acted | (только для бесед, вернет False, если личное сообщение) - Позволяет определить, является ли этот эвент действием в беседе 179 | attachments | Вложения 180 | 181 | 182 | Или вот вам описание ключа ***act*** в эвенте ***new_action*** 183 | 184 | Ключ | Что означает 185 | -------------|-------------------------------------------------------------- 186 | act | ID действия 187 | act_mid | ID юзера, над которым совершили действие 188 | act_from | ID юзера, который совершил действие 189 | act_text | Текст, который был передан в действии (обычно новое название беседы) 190 | 191 | Также существует особый тип эвентов - `Notifications Events`. 192 | 193 | По своей сути данный тип эвентов является тем самым случаем, когда обычный метод становится возможностью получать 194 | все более свежую информацию. Данная функция - `уникальна` и встречается `только` в данной библиотеке. 195 | (За идею спасибо [ему](https://vk.com/enotakin)) 196 | 197 | **Примечание** 198 | 199 | ***Данный функционал находится в тестировании и возможно нуждается в правках.*** 200 | 201 | Ниже представлено описание ключей данного эвента -> ***new_notification*** 202 | 203 | Ключ | Что означает 204 | -------------|-------------------------------------------------------------- 205 | user_id | ID пользователя, который совершил действие. Параметр `user_ids` не передается, если был передан текущий 206 | user_ids | Список, содержащий ID пользователей, совершивших данное действие в один промежуток времени или там, где это необходимо. Параметр `user_id` в данном случае не передается 207 | type | Тип действия, подробнее на https://vk.com/dev/notifications.get 208 | date | Дата события по UNIX 209 | body | Тело события (Для разных типов событий содержатся разные поля) 210 | parent_id | ID материала, к которому появился ответ 211 | parent | Информация о материале, к которому появился ответ 212 | 213 | (На самом деле я простот игрался с таблицами, красиво выглядят...) 214 | 215 | --- 216 | ### Заключение 217 | 218 | Не поймите меня неправильно, но я просто уже устал от того, что для многих понимания 219 | `простота` и `удобство` утратило свой смысл. Большинство существующих либ для работы 220 | с API Вконтакта либо слишком простые и засчёт этого мало чем отличаются от сырых 221 | запросов, либо настолько накрученные, что рядовой юзер может потеряться во всем разнообразии вещей. 222 | 223 | Я не поливаю их говном, это моё личное мнение. 224 | НО именно это мнение подтолкнуло сделать меня нечто подобное. :computer: 225 | 226 | Естественно, либа будет обновляться, улучшаться. 227 | На этом пока всё. 228 | -------------------------------------------------------------------------------- /examples/default_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Пример #5. Использование VK API напрямую. 4 | Действия всё те же. Но расскажу поподробнее об одном из них. 5 | 6 | 7 | """ 8 | 9 | # Импортируем модуль 10 | import vk_advanced_api 11 | 12 | # Создаем экземпляр класса VKAPI 13 | api = vk_advanced_api.VKAPI( 14 | access_token='Your-Access-Token', 15 | captcha_key='your-captcha-key', 16 | version=5.71, 17 | warn_level=1, 18 | command_prefix='/' 19 | ) 20 | 21 | # Utils - прямое взаимодействие с API VK 22 | utils = api.utils 23 | 24 | # Utils по сути возвращает уже настроенный класс API 25 | # Для обращения к этому классу используйте методы VK 26 | # 27 | # >>> utils.messages.send(user_id=1, message='Test') 28 | # 29 | # Мы вызываем метод messages.send с параметрами user_id=1 и message=Test 30 | # При попытке указать заведомо неверный метод, класс вернет ошибку, которая 31 | # уже взависимости от warn_level будет либо выведена на экран, либо вызвана, как ошибка. 32 | # 33 | # >>> utils.messsssages.sandWITHComg(usr_ed=1, messssssage='TOOOSTER') 34 | 35 | # Верный метод 36 | utils.messages.send(user_id=1, message='Test') 37 | 38 | # Неверный метод 39 | utils.messsssages.sandWITHComg(usr_ed=1, messssssage='TOOOSTER') -------------------------------------------------------------------------------- /examples/getting_flags.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Пример #6. Работа с флагами эвента. 4 | Стало возможным после добавления в патче 1.1.0 метода `getFlags` 5 | 6 | """ 7 | 8 | # Импортируем модуль 9 | import vk_advanced_api 10 | 11 | # Создаем экземпляр класса VKAPI 12 | api = vk_advanced_api.VKAPI( 13 | access_token='Your-Access-Token', 14 | captcha_key='your-captcha-key', 15 | version=5.71, 16 | warn_level=1, 17 | command_prefix='/' 18 | ) 19 | 20 | api.polling() 21 | 22 | @api.poll.on('new_message') 23 | def new_message(event, command): 24 | """ 25 | 26 | Получим флаги сообщения и переведем их в человеческий вид 27 | 28 | :param event: 29 | :param command: 30 | :return: 31 | """ 32 | 33 | # Обрабится к оригинальному эвенту и получим третий элемент списка с флагами 34 | flags = event['body'][2] 35 | 36 | # Вызовем метод `getFlags`, который принимает INTEGER-значение ID флагов из эвента 37 | # Вернет список с названиями флагов. Более подробно на https://vk.com/dev/using_longpoll_2 38 | flags = api.getFlags(flags) 39 | 40 | print('Флаги сообщения с ID {id} -> {flags}'.format(id=event['message_id'], flags=flags)) 41 | -------------------------------------------------------------------------------- /examples/login_via_data.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Пример #2. Авторизация с помощью личных данных пользователя. 4 | От прошлого отличается лишь тем, что тут указываются 5 | логин и пароль пользователя вместо токена. 6 | 7 | На выходе вы всегда можете получить Access-Token пользователя 8 | 9 | ATTENTION! Для данного вида авторизации требуется создать 10 | Standalone-приложение VK 11 | 12 | Создать можно тут -> https://vk.com/editapp?act=create 13 | От вас потребуется указать лишь ID вашего приложения, указанного 14 | в поле app_id 15 | 16 | """ 17 | 18 | # Импорт модуля 19 | import vk_advanced_api 20 | 21 | # Экземпляр класса VKAPI 22 | api = vk_advanced_api.VKAPI( 23 | app_id='your-app-id', 24 | login='help@ar4ikov.ru', 25 | password='qwerty', 26 | captcha_key='your-captcha-key', 27 | version=5.71, 28 | warn_level=1, 29 | command_prefix='/' 30 | ) 31 | 32 | # Utils - прямое взаимодействие с API VK 33 | utils = api.utils 34 | 35 | # Получаем информацию о пользователе, токен которого был указан 36 | print(utils.users.get()) -------------------------------------------------------------------------------- /examples/login_via_token.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Пример #1. Авторизация через access-token пользователя 4 | Ничего сложного, так ведь?) 5 | 6 | ATTENTION! Проблемой может стать Анти-капча 7 | Почему? Ну просто может вы не знаете как его получить) 8 | 9 | Моя либа использует RuCaptcha сервис 10 | Подробно -> https://rucaptcha.com 11 | 12 | Ключ нужно указать в поле captcha_key 13 | 14 | Ещё один необязательный параметр - версия API VK 15 | По умолчанию стоит 5.71 (В моей либе) 16 | Свою версию можно указать в поле version 17 | 18 | Следующий необязательный параметр - warn_level (пока имеет значения 1 или 2) 19 | > 1 - Выводит информацию в консоль 20 | > 2 - Выводит посредством вызова исключений (raise) 21 | По умолчанию выставлен режим 1 22 | 23 | Последний необязательный параметр - command_prefix 24 | По строке, которую вы укажете библиотека сможет определить, 25 | является ли отправленное сообщение командой, которую может обработать бот. 26 | В случае, если первый симвл сообщения был равен `command_prefix`, то 27 | `is_command` вернет True 28 | 29 | """ 30 | 31 | # Импортируем модуль 32 | import vk_advanced_api 33 | 34 | # Создаем экземпляр класса VKAPI 35 | api = vk_advanced_api.VKAPI( 36 | access_token='Your-Access-Token', 37 | captcha_key='your-captcha-key', 38 | version=5.71, 39 | warn_level=1, 40 | command_prefix='/' 41 | ) 42 | 43 | # Utils - прямое взаимодействие с API VK 44 | utils = api.utils 45 | 46 | # Получаем информацию о пользователе, токен которого был указан 47 | print(utils.users.get()) -------------------------------------------------------------------------------- /examples/polling.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Пример #3. Работа с эвентами или LongPolling-сервером. 4 | Подробнее о LongPolling VK -> https://vk.com/dev/using_longpoll 5 | 6 | Наше API подразумивает собой обработку многих эвентов. 7 | Но пока доступны (и сделаны xD) только 2. 8 | 9 | - new_message - Новое сообщение в беседе или личных сообщениях 10 | -------------------------------- 11 | :type | Тип сообщения: 12 | | - public - если сообщение из беседы 13 | | - private - если сообщение носит личный характер 14 | :is_out | Определяет, явзяется ли сообщение исходящим (True, если да) 15 | :args | Аргументы (просто всё, что разделено пробелом), помогают при создании команд для чат-бота 16 | :is_command | Определяет, является ли сообщение командой (True, если да) 17 | :peer_id | ID диалога (диалогом может быть беседа или личный чат) 18 | :from_id | (только для бесед, вернет None, если личное сообщение) - ID пользователя, который отправил сообщение 19 | :body | Тело эвента в нетронутом виде, которое поступило при запросе на Polling сервер VK 20 | :is_acted | (только для бесед, вернет False, если личное сообщение) - Позволяет определить, является ли этот эвент действием в беседе (True, если является) 21 | :attachments | Вложения вида: 22 | | [owner_id + "_" + id] 23 | 24 | - new_action - Эвент, привязанный к беседе, позволяет узнать, кто кого кикнул или пригласил, 25 | кто сменил фотографию или название беседы 26 | --------------------------------- 27 | :attachments | Вложения вида: 28 | | [owner_id + "_" + id] 29 | :peer_id | ID диалога (диалогом может быть беседа или личный чат) 30 | :type | Тип сообщения: 31 | | - public - если сообщение из беседы 32 | | - private - если сообщение носит личный характер 33 | :from_id | (только для бесед, вернет None, если личное сообщение) - ID пользователя, который отправил сообщение 34 | :is_acted | (только для бесед, вернет False, если личное сообщение) - Позволяет определить, является ли этот эвент действием в беседе (True, если является) 35 | :is_out | Определяет, явзяется ли сообщение исходящим (True, если да) 36 | :acts | Сами действия, которые произошли в беседе. Вид: 37 | | { 38 | | act | ID действия 39 | | act_mid | ID юзера, над которым совершили действие 40 | | act_from | ID юзера, который совершил действие 41 | | act_text | Текст, который был передан в действии (обычно новое название беседы) 42 | | } 43 | 44 | - new_notification - Эвент, который был создан на основе метода notifications.get(), но в виде эвент-системы 45 | ----------------------------- 46 | :user_id | ID пользователя, который совершил действие 47 | :type | Тип действия, подробнее на https://vk.com/dev/notifications.get 48 | :date | Дата события по UNIX 49 | :body | Тело события (Для разных типов событий содержатся разные поля) 50 | :parent_id | ID материала, к которому появился ответ 51 | :parent | Информация о материале, к которому появился ответ 52 | 53 | - error - Технический эвент, позволяет обработать ошибки класса VKAPI 54 | Ошибки класса API (или utils) обрабатываются внутри класса 55 | 56 | Больше эвентов в обновлениях! 57 | 58 | """ 59 | 60 | # Импортируем модуль 61 | import vk_advanced_api 62 | 63 | # Создаем экземпляр класса VKAPI 64 | api = vk_advanced_api.VKAPI( 65 | captcha_key='your-captcha-key', 66 | access_token='Your-Access-Token', 67 | version=5.71, 68 | warn_level=1, 69 | command_prefix='/' 70 | ) 71 | 72 | # Utils - прямое взаимодействие с API VK 73 | utils = api.utils 74 | 75 | # Включаем Polling 76 | # 77 | # `enable_notifications` - включает Notifications Events 78 | # По умолчанию стоит `False`, является необязательным параметром и добавлено временно 79 | # 80 | api.polling(enable_notifications=False) 81 | 82 | # Прослушиваем эвент new_messages 83 | @api.poll.on('new_message') 84 | def bot(event, command): 85 | """ 86 | 87 | Функция, которая выполняется при прослушивании эвента 88 | 89 | :param event: - Тело эвента 90 | :param command: - Команда, которую указал пользователь (если сообщение 91 | носит обычный характер, вернет None) 92 | :return: - None или True или False 93 | """ 94 | print("Новое сообщение -> {}".format(event)) 95 | if command: 96 | print('Требуется выполнить команду -> {}'.format(command)) 97 | 98 | # Определяем, что за команда была указана 99 | if command == '/about': 100 | api.sendMessage(user_id=event['peer_id'], message='Я - новый бот VK!\nЯ использую новую open-source библиотеку vk_advanced_api (https://github.com/Ar4ikov/vk_advanced_api)') 101 | 102 | @api.poll.on('new_action') 103 | def onAction(event): 104 | """ 105 | 106 | Функция обработки актов в беседах 107 | 108 | :param event: - Тело эвента 109 | :return: - None или True или False 110 | """ 111 | 112 | # Тело события 113 | print("Новое событие в беседе -> {}".format(event)) 114 | 115 | # Определяем само событие и обрабатываем его 116 | # Более подробно -> https://vk.com/dev/using_longpoll_2 (6. Вложения и дополнительные данные) 117 | if event['acts']['act'] == 'chat_create': 118 | print('Создан чат с названием {}'.format(event['acts']['act_text'])) 119 | 120 | elif event['acts']['act'] == 'chat_title_update': 121 | print('{user} сменил название чата на {title}'.format(user=event['acts']['act_from'], title=event['acts']['act_text'])) 122 | 123 | elif event['acts']['act'] == 'chat_photo_update': 124 | print('{} сменил фотографию чата'.format(event['acts']['act_from'])) 125 | 126 | elif event['acts']['act'] == 'chat_invite_user': 127 | print('{user} пригласил в чат {mid}'.format(user=event['acts']['act_from'], mid=event['acts']['act_mid'])) 128 | 129 | elif event['acts']['act'] == 'chat_kick_user': 130 | print('{user} кикнул из чата {mid}'.format(user=event['acts']['act_from'], mid=event['acts']['act_mid'])) 131 | 132 | elif event['acts']['act'] == 'chat_invite_user_by_link': 133 | print('{user} присоединился в чат по ссылке'.format(user=event['acts']['act_from'])) 134 | 135 | @api.poll.on('new_notification') 136 | def handleNotification(event): 137 | if event.get('user_id'): 138 | print('Пользователь {user} совершил `{type}` в {time} по UNIX'.format(user=event['user_id'], type=event['type'], time=event['date'])) 139 | else: 140 | print('В {time} по UNIX пользователи {users} совершили `{type}`'.format(time=event['date'], users=event['user_ids'], type=event['type'])) 141 | 142 | @api.poll.on('error') 143 | def errorHandler(event): 144 | """ 145 | 146 | Обработка ошибок класса VKAPI 147 | 148 | :param event: - Тело ошибки 149 | :return: 150 | """ 151 | print('Новая ошибка -> {}'.format(event)) -------------------------------------------------------------------------------- /examples/upload_media.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Пример #4. Загрузка фото/аудио/видео на сервера VK для отправки в сообщения 4 | 5 | ATTENTION! Из-за политики авторского права VK запретила общественности 6 | метод audio в своём API, из-за чего аудио будет загружена в виде 7 | голосового сообщения. 8 | 9 | """ 10 | 11 | # Импортируем модуль 12 | import vk_advanced_api 13 | 14 | # Создаем экземпляр класса VKAPI 15 | api = vk_advanced_api.VKAPI( 16 | access_token='Your-Access-Token', 17 | captcha_key='your-captcha-key', 18 | version=5.71, 19 | warn_level=1, 20 | command_prefix='/' 21 | ) 22 | 23 | # Список -> Файлы для загрузки на сервер 24 | files = ['captcha.png', 'test.jpg'] 25 | 26 | uploaded_files = api.sendPhoto(files=files) 27 | 28 | # На выходе вы получите список со следующей структурой: 29 | # { 30 | # - owner_id -> ID владельца файла VK 31 | # - id -> ID загруженного медиа 32 | # } 33 | # 34 | # Такую же процедуру можно проделать абсолютно для всех типов файлов, как видео, так аудио 35 | 36 | files_1 = ['Blue_Chair.mp4', 'Do_not_go_on_bear_fucking_bitch.mp4'] 37 | uploaded_files_1 = api.sendVideo(files=files_1) 38 | 39 | files_2 = ['voice.mp3'] 40 | uploaded_files_2 = api.sendAudioMessage(files=files_2) 41 | 42 | # А затем это можно отправить сообщение с ними, предварительно обоаботав данные 43 | 44 | attachments = [] 45 | 46 | for item in uploaded_files: 47 | attachments.append(item['owner_id'] + "_" + item['id']) 48 | 49 | api.sendMessage( 50 | user_id=1, 51 | message='Держи, друг! Я отправил тебе фотграфии с багами Телеграма!', 52 | attachments=attachments 53 | ) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | import os 6 | 7 | _version_file_path = os.path.join(os.path.dirname(__file__), 'vk_advanced_api', 'version') 8 | 9 | with open(_version_file_path) as f: 10 | __version__ = f.readline().strip() 11 | 12 | setup( 13 | name='vk_advanced_api', 14 | version=__version__, 15 | install_requires=['requests', 'captcha_solver', 'lxml', 'parsel', 'pymitter'], 16 | packages=['vk_advanced_api'], 17 | package_data={'vk_advanced_api': ['version']}, 18 | url='https://github.com/Ar4ikov/vk_advanced_api', 19 | license='MIT License', 20 | author='Nikita Archikov', 21 | author_email='bizy18588@gmail.com', 22 | description='Simple to use OpenSource Lib for API VK', 23 | keywords='opensource vk api wrappper ar4ikov vkadvancedapi vk_advanced_api' 24 | ) 25 | -------------------------------------------------------------------------------- /vk_advanced_api/API.py: -------------------------------------------------------------------------------- 1 | # ---------------------------- 2 | # | vk_advanced_api 3 | # | Класс: API 4 | # | Автор: https://vk.com/Ar4ikov 5 | # | Создан 07.03.2018 - 8:19 6 | # ---------------------------- 7 | 8 | import requests 9 | from captcha_solver import CaptchaSolver 10 | import re 11 | import sys 12 | from time import sleep 13 | 14 | from vk_advanced_api.Request import Request 15 | from vk_advanced_api import Pool as RequestPool 16 | from uuid import uuid4 as UUID 17 | 18 | class API_Constructor(): 19 | def __init__(self, warn_level=None, api_source=None, access_token=None, session=requests.session(), proxy=None, rucaptcha_key=None, version=None): 20 | """ 21 | 22 | Название класса довольно странное. Ведь оно 23 | символизирует не само тело запросов и обработку ошибок, 24 | а предназначено для определения отдельной структуры для аккаунта. 25 | 26 | :param api_source: - Ресурс API VK 27 | :param access_token: - Токен аккаунта VK 28 | :param version: - Версия API VK 29 | :param session: - Сессия для запросов (т.к. VK ввели привязку токена по IP, с которого был получен) 30 | :param proxy: - Прокси, с которого был получен токен 31 | :param rucaptcha_key: - RuCaptcha-Ключ для разгадки капч 32 | :param warn_level: - Уровень вывода ошибок и логов: 33 | > 1 - Выводит информацию в консоль 34 | > 2 - Выводит посредством вызова исключений (raise) 35 | """ 36 | 37 | self.headers = { 38 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36', 39 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 40 | 'Accept-Language': 'ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3', 41 | 'Accept-Encoding': 'gzip, deflate', 42 | 'Connection': 'keep-alive', 43 | 'DNT': '1' 44 | } 45 | self.access_token = access_token or None 46 | self.version = version or None 47 | self.session = session or None 48 | self.proxy = proxy or None 49 | self.rucaptcha_key = rucaptcha_key or None 50 | self.api_source = api_source or 'https://api.vk.com/method/' 51 | self.warn_level = warn_level or 1 52 | 53 | @staticmethod 54 | def getRequestingBody(): 55 | return API_Constructor.getRequest 56 | 57 | def getRequest(self, method, **data): 58 | """ 59 | 60 | Главный метод для запросов к API VK 61 | 62 | :param version: - Версия API VK 63 | :param session: - Текущая сессия сп ользователем 64 | :param method: - Необходимый метод 65 | :param proxy: - Прокси, с которого был получен токен 66 | :param data: - Собственно, все необходимые параметры 67 | :return: - Тело ответа, если не было ошибок 68 | """ 69 | 70 | captcha_error = True 71 | captcha_key = None 72 | captcha_sid = None 73 | 74 | while captcha_error: 75 | # Добавляем к запросу параметры для разгадки капчи 76 | data['captcha_key'] = captcha_key 77 | data['captcha_sid'] = captcha_sid 78 | data['access_token'] = self.access_token 79 | data['v'] = self.version 80 | 81 | # Делаем запрос 82 | response = self.session.post(self.api_source + method, params=data, proxies={'https': self.proxy}, headers=self.headers) 83 | response_text = re.sub('true', 'True', response.text) 84 | response_text = re.sub('false', 'False', response_text) 85 | response_text = re.sub('null', 'None', response_text) 86 | 87 | # Проверяем на ошибки 88 | if eval(response_text).get('error'): 89 | 90 | error = eval(response.text)['error'] 91 | if error['error_code'] == 14: 92 | if self.rucaptcha_key: 93 | captcha_img = eval(response.text)['error']['captcha_img'] 94 | captcha_img = re.sub(r'\\/', '/', captcha_img) 95 | error['captcha_img'] = captcha_img 96 | 97 | captcha_body = self.errorHandler(error=error) 98 | captcha_sid = captcha_body['captcha_sid'] 99 | captcha_key = captcha_body['captcha_key'] 100 | print(captcha_key) 101 | elif error['error_code'] in [6, 900, 901, 902]: 102 | self.errorHandler(error=error) 103 | else: 104 | if self.warn_level == 1: 105 | return error 106 | elif self.warn_level == 2: 107 | raise VKAPIError(error) 108 | else: 109 | raise VKAPITechnicalError('Неверный уровень логирования. Возможные уровни: 1, 2.') 110 | else: 111 | return eval(response_text)['response'] 112 | 113 | def errorHandler(self, error): 114 | sender = self.getRequest(method='users.get')[0]['id'] 115 | if error['error_code'] == 6: 116 | sleep(1) 117 | print("Запросы отправляются слишком быстро") 118 | elif error['error_code'] == 900: 119 | for item in error['request_params']: 120 | if item['key'] == 'user_id': 121 | user_id = item['value'] 122 | print('Пользователь id{user_id} добавил аккаунт id{sender} в черный список'.format(user_id=user_id, sender=sender)) 123 | break 124 | elif error['error_code'] == 901: 125 | for item in error['request_params']: 126 | if item['key'] == 'user_id': 127 | user_id = item['value'] 128 | print('Пользователь id{user_id} запретил отправку сообщений от имени сообщества'.format(user_id=user_id, sender=sender)) 129 | break 130 | elif error['error_code'] == 902: 131 | for item in error['request_params']: 132 | if item['key'] == 'user_id': 133 | user_id = item['value'] 134 | print('Пользователь id{user_id} запретил отправлять ему сообщения в настройках приватности'.format(user_id=user_id)) 135 | break 136 | elif error['error_code'] == 14: 137 | print('Аккаунт id{} словил капчу! -> {}'.format(sender, error['captcha_img'])) 138 | return {'captcha_key': self.getRuCaptchaSolver(self.savePhotoFrom(error['captcha_img'], 'captcha.png')), 'captcha_sid': error['captcha_sid']} 139 | 140 | def getRuCaptchaSolver(self, filename): 141 | """ 142 | 143 | :param filename: - Имя файла с фотографией капчи 144 | :return: - Вернет саму отгадку капчи 145 | """ 146 | 147 | while True: 148 | try: 149 | solver = CaptchaSolver('rucaptcha', api_key=self.rucaptcha_key) 150 | raw_data = open(filename, 'rb').read() 151 | 152 | return solver.solve_captcha(raw_data) 153 | 154 | except: 155 | pass 156 | 157 | def savePhotoFrom(self, server, filename): 158 | """ 159 | Метод поможет сохранить фотографии прямо на оборудование 160 | 161 | :param server: - Сервер с фотографией 162 | :param filename: - Имя файла, куда нужно сохранить фотографию 163 | :return: - Вернет имя файла при удачном сохранении, или tuple(False, ошибка) 164 | """ 165 | 166 | try: 167 | response = requests.get(server) 168 | out = open(filename, "wb") 169 | out.write(response.content) 170 | out.close() 171 | return filename 172 | 173 | except: 174 | return False, str(sys.exc_info()) 175 | 176 | class API_Object(): 177 | def __init__(self, warn_level=None, method=None, api_source=None, access_token=None, proxy=None, rucaptcha_key=None, version=None): 178 | self.method = method or None 179 | self.access_token = access_token or None 180 | self.version = version or None 181 | self.proxy = proxy or None 182 | self.rucaptcha_key = rucaptcha_key or None 183 | self.api_source = api_source or None 184 | self.warn_level = warn_level or 1 185 | 186 | def __getattr__(self, method): 187 | return API_Object( 188 | method=self.method + "." + method, 189 | access_token=self.access_token, 190 | version=self.version, 191 | proxy=self.proxy, 192 | rucaptcha_key=self.rucaptcha_key, 193 | api_source=self.api_source, 194 | warn_level=self.warn_level or 1 195 | ) 196 | 197 | def __call__(self, **kwargs): 198 | API = API_Constructor( 199 | access_token=self.access_token, 200 | version=self.version, 201 | proxy=self.proxy, 202 | rucaptcha_key=self.rucaptcha_key, 203 | api_source=self.api_source, 204 | warn_level=self.warn_level or 1 205 | ) 206 | RequestPool.Pool.startPool() 207 | request = Request(method=self.method, cls=API, id=UUID(), **kwargs) 208 | 209 | RequestPool.Pool.addRequest(request) 210 | 211 | while True: 212 | for response in RequestPool.Pool.getProcessed(): 213 | if response.getId() == request.getId(): 214 | 215 | RequestPool.Pool.processed.remove(response) 216 | return response.body 217 | class API(): 218 | def __init__(self, warn_level=None, api_source=None, access_token=None, proxy=None, rucaptcha_key=None, version=None): 219 | self.access_token = access_token or None 220 | self.version = version or None 221 | self.proxy = proxy or None 222 | self.rucaptcha_key = rucaptcha_key or None 223 | self.api_source = api_source or None 224 | self.warn_level = warn_level or 1 225 | 226 | def __getattr__(self, method): 227 | return API_Object( 228 | method=method, 229 | access_token=self.access_token, 230 | version=self.version, 231 | proxy=self.proxy, 232 | rucaptcha_key=self.rucaptcha_key, 233 | api_source=self.api_source, 234 | warn_level = self.warn_level 235 | ) 236 | 237 | class VKAPIError(Exception): 238 | pass 239 | 240 | class VKAPITechnicalError(Exception): 241 | pass -------------------------------------------------------------------------------- /vk_advanced_api/Auth.py: -------------------------------------------------------------------------------- 1 | # --------------------------- 2 | # | vk_advanced_api 3 | # | Класс: Auth 4 | # | Автор: https://vk.com/Ar4ikov 5 | # | Создан 12.03.2018 - 18:56 6 | # --------------------------- 7 | 8 | from parsel import Selector 9 | import requests 10 | import lxml.html 11 | 12 | class Auth(): 13 | def __init__(self, access_token=None, login=None, password=None, proxy=None, app_id=None, scopes_list=None, version=None): 14 | """ 15 | 16 | Класс аунтификации юзера через кастомное Standalone-приложение VK 17 | 18 | :param access_token: - Access-Token юзера, если есть, класс становиться бесполезен o_0 19 | :param login: - Логин/Email/Телефон пользователя 20 | :param password: - Пароль пользователя 21 | :param proxy: - Прокси, с которого нужно получить токен, в данном случае, не используется 22 | :param app_id: - APP_ID Standalone-приложения VK 23 | :param scopes_list: - Список прав доступа токена 24 | :param version: - Версия API VK 25 | """ 26 | 27 | self.access_token = None 28 | self.proxy = proxy or None 29 | self.app_id = app_id or None 30 | self.version = version or None 31 | self.headers = { 32 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36', 33 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 34 | 'Accept-Language': 'ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3', 35 | 'Accept-Encoding': 'gzip, deflate', 36 | 'Connection': 'keep-alive', 37 | 'DNT': '1' 38 | } 39 | if not access_token: 40 | self.login = login or None 41 | self.password = password or None 42 | self.access_token = self.getToken(scopes_list=scopes_list) 43 | else: 44 | self.access_token = access_token or None 45 | 46 | def pasre(self, response, tag): 47 | responseTags = [] 48 | for tags in Selector(response).css(tag).extract(): 49 | responseTags.append(tags) 50 | 51 | response = [] 52 | for tags in responseTags: 53 | response.append({ 54 | 'grant-access': Selector(tags).css('form::attr(action)').extract_first(), 55 | }) 56 | 57 | return response 58 | 59 | def doLogin(self): 60 | """ 61 | 62 | Позволяет выполнить вход в аккаунт VK 63 | 64 | :return: - Сессию, которая потребуется для получения токена 65 | """ 66 | if self.access_token: 67 | return self.access_token 68 | 69 | request = 'https://vk.com' 70 | session = requests.Session() 71 | data = session.post(request, headers=self.headers, proxies={'https': self.proxy}) 72 | page = lxml.html.fromstring(data.content) 73 | 74 | form = page.forms[0] 75 | form.fields['email'] = self.login 76 | form.fields['pass'] = self.password 77 | 78 | response = session.post(form.action, data=form.form_values(), proxies={'https': self.proxy}) 79 | if 'onLoginDone' in response.text: 80 | self.session = session 81 | return session 82 | else: 83 | raise VKAuthError("Не удалось авторизироваться: неправильный логин или пароль.\n" 84 | "Тажке это может быть связано с двухфакторной аунтификацией.\n" 85 | "Попробуйте отключить её в настройках безопасности и повторить попытку снова.") 86 | 87 | def getToken(self, scopes_list=None): 88 | """ 89 | 90 | Метод по получению токена с сессии пользователя 91 | 92 | :param session: - Сессия, с которой был выполнен вход в аккаунт 93 | :param scopes_list: - Список прав, которые следует получить 94 | :return: Токен для работы с API VK 95 | """ 96 | self.session = self.doLogin() 97 | 98 | if self.access_token: 99 | return self.access_token 100 | 101 | # Полный легальный список возможных методов 102 | scopes_list = scopes_list or [ 103 | 'audio', 104 | 'notify', 105 | 'friends', 106 | 'photos', 107 | 'video', 108 | 'stories', 109 | 'pages', 110 | 'status', 111 | 'notes', 112 | 'messages', 113 | 'wall', 114 | 'ads', 115 | 'offline', 116 | 'docs', 117 | 'groups', 118 | 'notifications', 119 | 'stats', 120 | 'email', 121 | 'market' 122 | ] 123 | scopes = "" 124 | app_id = self.app_id 125 | 126 | i = 0 127 | for scope in scopes_list: 128 | if i == len(scopes_list)-1: 129 | scopes = scopes + scope 130 | else: 131 | scopes = scopes + scope + "%2C" 132 | i += 1 133 | 134 | # Далее - костыльный и некрасивый код :( 135 | # Он не достоин вашего внимания! 136 | 137 | request = 'https://oauth.vk.com/authorize?client_id={app_id}&scope={scopes}&redirect_uri=https://oauth.vk.com/blank.html&display=page&v=5.69&response_type=token'.format(app_id=app_id, scopes=scopes) 138 | 139 | response = self.session.post(request, proxies={'https': self.proxy}) 140 | 141 | try: 142 | body = eval(response.text) 143 | if body.get('error'): 144 | raise VKAuthError(body) 145 | except VKAuthError: 146 | body = eval(response.text) 147 | raise VKAuthError(body) 148 | except: 149 | pass 150 | 151 | if not response.url.count('authorize'): 152 | token = response.url[45:130] 153 | return token 154 | 155 | response = response.text 156 | 157 | try: 158 | body = eval(response) 159 | if body.get('error'): 160 | raise VKAuthError(body) 161 | except VKAuthError: 162 | body = eval(response) 163 | raise VKAuthError(body) 164 | except: 165 | pass 166 | 167 | link = self.pasre(response=response, tag='form')[0]['grant-access'] 168 | token = self.session.post(link, proxies={'https': self.proxy}).url[45:130] 169 | return token 170 | 171 | class VKAuthError(Exception): 172 | pass 173 | -------------------------------------------------------------------------------- /vk_advanced_api/Pool.py: -------------------------------------------------------------------------------- 1 | from vk_advanced_api.Response import Response 2 | from threading import Thread 3 | import time 4 | 5 | class Pool(): 6 | 7 | pool = [] 8 | processed = [] 9 | started = False 10 | id = 1 11 | 12 | @staticmethod 13 | def PoolBody(): 14 | while True: 15 | i = 0 16 | for request in Pool.pool: 17 | i += 1 18 | Pool.id += 1 19 | 20 | MakingRequest = request.getCls().getRequestingBody() 21 | 22 | time.sleep(0.34) 23 | 24 | response = MakingRequest(request.getCls(), method=request.getMethod(), **request.getParams()) 25 | Pool.processed.append(Response(body=response, id=request.getId())) 26 | 27 | if len(Pool.pool) > 0: 28 | Pool.pool.remove(request) 29 | @staticmethod 30 | def startPool(): 31 | if not Pool.started: 32 | Pool.started = True 33 | Thread(target=Pool.PoolBody, name="RequestPool", args=()).start() 34 | 35 | @staticmethod 36 | def getActualId(): 37 | return Pool.id 38 | 39 | @staticmethod 40 | def addTask(Pool, request): 41 | Pool.pool.append(request) 42 | 43 | @staticmethod 44 | def addRequest(request): 45 | Pool.pool.append(request) 46 | 47 | @staticmethod 48 | def getProcessed(): 49 | return Pool.processed -------------------------------------------------------------------------------- /vk_advanced_api/Request.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 as UUID 2 | 3 | class Request(): 4 | def __init__(self, method, cls, id, **params): 5 | """ 6 | 7 | Request class, который является прямым телом запроса 8 | 9 | :param method: - Метод API VK 10 | :param cls: - class API (API.py -> API_Constructor) 11 | :param id: - ID запроса 12 | :param uuid: - UUID запроса 13 | :param params: - Параметры запроса к API VK 14 | """ 15 | self.method = method 16 | self.cls = cls 17 | self.id = id 18 | self.params = params 19 | 20 | def getCls(self): 21 | return self.cls 22 | 23 | def getId(self): 24 | return self.id 25 | 26 | def getMethod(self): 27 | return self.method 28 | 29 | def getParams(self): 30 | return self.params -------------------------------------------------------------------------------- /vk_advanced_api/Response.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 as UUID 2 | class Response(): 3 | def __init__(self, body, id): 4 | """ 5 | 6 | Класс ответа Response 7 | 8 | :param body: - Тело ответа 9 | :param uuid: - UUID ответа 10 | :param id: - Id ответа 11 | """ 12 | self.body = body 13 | self.id = id 14 | 15 | def getBody(self): 16 | return self.body 17 | 18 | def getId(self): 19 | return self.id -------------------------------------------------------------------------------- /vk_advanced_api/__init__.py: -------------------------------------------------------------------------------- 1 | from vk_advanced_api.vkapi import VKAPI 2 | from vk_advanced_api.Auth import Auth -------------------------------------------------------------------------------- /vk_advanced_api/version: -------------------------------------------------------------------------------- 1 | 1.3.1.2 -------------------------------------------------------------------------------- /vk_advanced_api/vkapi.py: -------------------------------------------------------------------------------- 1 | # --------------------------- 2 | # | vk_advanced_api 3 | # | Класс: VKAPI 4 | # | Автор: https://vk.com/Ar4ikov 5 | # | Создан 07.03.2018 - 9:29 6 | # --------------------------- 7 | import os 8 | 9 | with open(os.path.join(os.path.dirname(__file__), 'version')) as f: 10 | __version__ = f.readline().strip() 11 | 12 | import re 13 | import time 14 | from threading import Thread 15 | from time import sleep 16 | 17 | import pymitter 18 | import requests 19 | 20 | from vk_advanced_api import API 21 | from vk_advanced_api.Auth import Auth 22 | 23 | 24 | class VKAPI(): 25 | def __init__(self, access_token=None, login=None, password=None, app_id=None, version=None, captcha_key=None, warn_level=None, command_prefix='/'): 26 | """ 27 | 28 | VK Advanced API - Продвинутое OpenSource API на Python для VK 29 | 30 | :param access_token: - Access Token пользователя 31 | :param login: - Логин/Email/Телефон пользователя VK 32 | :param password: - Пароль пользователя VK 33 | :param app_id: - APP_ID Standalone-приложения VK 34 | :param version: - Версия API VK 35 | :param captcha_key: - RuСaptcha API ключ 36 | :param warn_level: - Уровень вывода ошибок и логов: 37 | > 1 - Выводит информацию в консоль 38 | > 2 - Выводит посредством вызова исключений (raise) 39 | :param command_prefix: - Префикс для объявления команд (на них будет реагиорвать API) 40 | """ 41 | self.version = version or 5.73 42 | self.command_prefix = command_prefix or '/' 43 | 44 | self.warn_level = warn_level or 1 45 | 46 | self.poll = pymitter.EventEmitter() 47 | 48 | if not access_token: 49 | self.login = login or None 50 | self.password = password or None 51 | self.app_id = app_id or None 52 | self.Auth = Auth(login=self.login, password=self.password, app_id=self.app_id, version=self.version) 53 | self.access_token = self.Auth.access_token 54 | else: 55 | self.access_token = access_token or None 56 | 57 | self.api = API.API( 58 | warn_level=self.warn_level, 59 | access_token=self.access_token, 60 | version=self.version, 61 | rucaptcha_key=captcha_key or None, 62 | proxy=None 63 | ) 64 | 65 | # Запрос на проверку типа токена: 66 | # - User Access Token 67 | # - Group Access Token 68 | 69 | self.token_type = None 70 | 71 | self.bot_fields = self.api.users.get() 72 | if len(self.bot_fields) > 0: 73 | self.token_type = 'user' 74 | self.bot_id = self.bot_fields[0]['id'] 75 | else: 76 | self.token_type = 'group' 77 | sleep(0.34) 78 | 79 | self.start_time = str(int(time.time()))[0:10] 80 | 81 | @property 82 | def utils(self): 83 | """ 84 | 85 | Получаем прямой доступ к библиотеке методов 86 | 87 | :return: 88 | """ 89 | return self.api 90 | 91 | def sendMessage(self, user_id, message, attachments=[]): 92 | """ 93 | 94 | Улучшенная функция отправки сообщения 95 | 96 | :param user_id: - peer_id чата/беседы или user_id пользователя VK 97 | :param message: - Сообщение, которое необходимо отправить 98 | :param attachments: - Прикрепленные файлы, данные от которых можно получить 99 | из методов ниже 100 | :return: - None 101 | """ 102 | attach = "" 103 | for file in attachments: 104 | attach = attach + file + "," 105 | 106 | try: 107 | self.api.messages.send(peer_id=user_id, message=message, attachment=attach) 108 | except Exception as error: 109 | self.poll.emit('error', {'body': str(error)}) 110 | sleep(0.34) 111 | 112 | def getPollingDetails(self): 113 | """ 114 | 115 | Позволяет получить данные от Polling-сервера VK 116 | :return: - Вернет JSON-схему с содежимым server, key, ts 117 | """ 118 | sleep(0.34) 119 | if self.token_type == 'user': 120 | polling_details = self.api.messages.getLongPollServer() 121 | else: 122 | polling_details = self.api.groups.getLongPollServer() 123 | 124 | # Какова приичина такого выбора через цикл? 125 | # Ошибки были при самом запросе, которые возникали вот просто из ни откуда. 126 | # Пришлось сделать такую конструкцию. В любом случае, больше двух ошибок быть не может, ведь так?( 127 | while True: 128 | try: 129 | server, key, ts = polling_details['server'], polling_details['key'], polling_details['ts'] 130 | return {'server': server, 'key': key, 'ts': ts} 131 | except: 132 | sleep(0.34) 133 | polling_details = self.api.messages.getLongPollServer() 134 | 135 | def __str__(self): 136 | return str({'access_token': self.access_token, 'started_at': self.start_time}) 137 | 138 | def getFlags(self, flags): 139 | """ 140 | 141 | Разбирает ID флагов на строковые параметры 142 | 143 | :param flags: - ID флагов нужного сообщения 144 | :return: - Вернет все ненулевые флаги 145 | """ 146 | HIDDEN = flags // 65536 147 | HIDDEN_mod = flags % 65536 148 | MEDIA = HIDDEN_mod // 512 149 | MEDIA_mod = HIDDEN_mod % 512 150 | FIXED = MEDIA_mod // 256 151 | FIXED_mod = MEDIA_mod % 256 152 | DELЕTЕD = FIXED_mod // 128 153 | DELЕTЕD_mod = FIXED_mod % 128 154 | SPAM = DELЕTЕD_mod // 64 155 | SPAM_mod = DELЕTЕD_mod % 64 156 | FRIENDS = SPAM_mod // 32 157 | FRIENDS_mod = SPAM_mod % 32 158 | CHAT = FRIENDS_mod // 16 159 | CHAT_mod = FRIENDS_mod % 16 160 | IMPORTANT = CHAT_mod // 8 161 | IMPORTANT_mod = CHAT_mod % 8 162 | REPLIED = IMPORTANT_mod // 4 163 | REPLIED_mod = IMPORTANT_mod % 4 164 | OUTBOX = REPLIED_mod // 2 165 | OUTBOX_mod = REPLIED_mod % 2 166 | UNREAD = OUTBOX_mod // 1 167 | 168 | response = [] 169 | 170 | if HIDDEN > 0: response.append('HIDDEN') 171 | if MEDIA > 0: response.append('MEDIA') 172 | if FIXED > 0: response.append('FIXED') 173 | if DELЕTЕD > 0: response.append('DELETED') 174 | if SPAM > 0: response.append('SPAM') 175 | if FRIENDS > 0: response.append('FRIENDS') 176 | if CHAT > 0: response.append('CHAT') 177 | if IMPORTANT > 0: response.append('IMPORTANT') 178 | if REPLIED > 0: response.append('REPLIED') 179 | if OUTBOX > 0: response.append('OUTBOX') 180 | if UNREAD > 0: response.append('UNREAD') 181 | 182 | return response 183 | 184 | def isOut(self, flags): 185 | if self.getFlags(flags).count('OUTBOX'): 186 | return True 187 | else: 188 | return False 189 | 190 | def updatingDetails(self): 191 | """ 192 | 193 | Обновляет каждые 10 минут данные для подключения к Polling-серверу 194 | 195 | :return: - Обновляет self.details 196 | """ 197 | while True: 198 | sleep(600) 199 | try: 200 | self.details = self.getPollingDetails() 201 | except Exception as error: 202 | self.poll.emit('error', {'body': str(error)}) 203 | 204 | def PollingRequesting(self): 205 | """ 206 | 207 | Первая часть -> 208 | Отправка запросов на Polling-сервера VK 209 | Чтобы получить их, используется метод messages.getLongPollingServer 210 | 211 | Вторая часть -> 212 | Обработка ответов с Polling-сервера 213 | Различаются 3 типа эвентов (два с утвердительным ответом и один с ошибочным) 214 | :event new_message: - Новое сообщения в беседе, личных сообщениях (от групп или от человека) 215 | 216 | :event new_action: - Эвент, который возможен только в чатах/беседах 217 | 218 | :return: - Добавляет новый эвент в self.events 219 | """ 220 | 221 | # Часть 1 -> 222 | global response 223 | self.events = [] 224 | self.details = self.getPollingDetails() 225 | while True: 226 | try: 227 | self.details['server'] = re.sub(r'\\/', '/', self.details['server']) 228 | response = eval(requests.get('https://{}'.format(self.details['server']), 229 | params={ 230 | 'act': 'a_check', 231 | 'key': self.details['key'], 232 | 'ts': self.details['ts'], 233 | 'wait': 25, 234 | 'version': 2, 235 | 'mode': 2 236 | }).text) 237 | self.events = response['updates'] 238 | self.details['ts'] = response['ts'] 239 | except Exception as error: 240 | self.poll.emit('error', {'body': str(error) + " -> " + response}) 241 | else: 242 | # Часть два -> 243 | messages = [] 244 | for event in self.events: 245 | if event[0] == 4: 246 | messages.append(event) 247 | 248 | new_events = [] 249 | for event in messages: 250 | from_id = None 251 | if event[-1].get('from'): 252 | msg_type = 'public' 253 | from_id = event[-1].get('from') 254 | else: 255 | msg_type = 'private' 256 | 257 | if event[-1].get('source_act'): 258 | isActed = True 259 | else: 260 | isActed = False 261 | 262 | act = event[-1].get('source_act') 263 | act_mid = event[-1].get('source_mid') 264 | act_text = event[-1].get('source_text') 265 | act_from = event[-1].get('from') 266 | 267 | attachments = [] 268 | attach_key = 'attach1' 269 | attach_type = 'attach1_type' 270 | for i in range(1,11): 271 | if event[-1].get(attach_key): 272 | attachments.append(event[-1].get(attach_type) + event[-1].get(attach_key)) 273 | attach_key = attach_key[0:6] + str(i+1) 274 | attach_type = attach_key + "_type" 275 | else: 276 | break 277 | 278 | args = re.sub(r'\\/', '/', event[5]) 279 | args = args.split(' ') 280 | 281 | isCommand = None 282 | isOut = None 283 | if len(''.join(args)) > 0: 284 | if len(args) > 0: 285 | if len(args[0]) > 0: 286 | if args[0][0] == self.command_prefix: 287 | isCommand = True 288 | else: 289 | isCommand = False 290 | 291 | if self.isOut(event[2]): 292 | isOut = True 293 | else: 294 | isOut = False 295 | 296 | if isActed == False: 297 | new_events.append( 298 | dict(event='new_message', type=msg_type, date=event[4], is_out=isOut, message_id=event[1], args=args, is_command=isCommand, peer_id=event[3], 299 | from_id=from_id, body=event, is_acted=isActed, attachments=attachments)) 300 | else: 301 | new_events.append(dict(event='new_action', message_id=event[1], date=event[4], attachments=attachments, peer_id=event[3], type=msg_type, is_out=isOut, from_id=from_id, is_acted=isActed, 302 | acts=dict(act=act, act_mid=act_mid, act_text=act_text, 303 | act_from=act_from))) 304 | 305 | for new in new_events: 306 | if new['event'] == 'new_action': 307 | self.poll.emit('new_action', new) 308 | elif new['event'] == 'new_message': 309 | if new['is_command'] == True: 310 | self.poll.emit('new_message', new, command=new['args'][0]) 311 | else: 312 | self.poll.emit('new_message', new, command=None) 313 | 314 | # self.details = self.getPollingDetails() 315 | 316 | def polling(self, enable_notifications=False): 317 | """ 318 | 319 | Технология Polling (LongPolling) - универсальное средство получения ответа тогда, когда он поступит 320 | Значительно сокращает количество запросов на сервер, благодаря тому что сервер 321 | отошлет ответ только тогда, когда появится новый эвент. В противном случае вернет пустой 322 | ответ через указанное время ожидание. 323 | 324 | VK и многие другие сервисы имеют в своих API эту возможность 325 | Подробнее -> https://vk.com/dev/using_longpoll 326 | 327 | :return: - None 328 | """ 329 | 330 | tasks = [ 331 | {'name': 'polling', 'object': self.PollingRequesting}, 332 | {'name': 'details', 'object': self.updatingDetails}] 333 | 334 | if enable_notifications: 335 | tasks.append({'name': 'notifications', 'object': self.NotificationPolling}) 336 | 337 | for task in tasks: 338 | thread = Thread(name=task['name'], target=task['object'], args=()) 339 | thread.start() 340 | 341 | def NotificationPolling(self): 342 | """ 343 | 344 | :return: 345 | """ 346 | if self.token_type == 'group': 347 | print('Для токена сообществ не доступен тип эвентов `new_notification`.') 348 | return False 349 | 350 | self.notify_events = [] 351 | start_time = self.api.notifications.get(count=0)['last_viewed'] 352 | 353 | while self.token_type == 'user': 354 | sleep(0.34) 355 | notify_body = self.api.notifications.get(count=100, start_time=start_time) 356 | if notify_body['count'] > 0: 357 | self.notify_events = notify_body['items'] 358 | start_time = self.notify_events[0]['date'] + 1 359 | 360 | new_events = [] 361 | for notify in self.notify_events: 362 | print(notify) 363 | 364 | user_id = None 365 | user_ids = None 366 | 367 | if notify['feedback'].get('items'): 368 | user_ids = [] 369 | for user in notify['feedback']['items']: 370 | user_ids.append(user.get("from_id")) 371 | else: 372 | user_id = notify['feedback']['from_id'] 373 | type = notify['type'] 374 | date = notify['date'] 375 | body = notify['feedback'] 376 | parent = notify['parent'] 377 | parent_id = notify['parent']['id'] 378 | 379 | if user_id: 380 | new_events.append( 381 | dict(user_id=user_id, type=type, date=date, body=body, parent=parent, parent_id=parent_id)) 382 | else: 383 | new_events.append(dict(user_ids=user_ids, type=type, date=date, body=body, parent=parent, 384 | parent_id=parent_id)) 385 | 386 | for event in new_events: 387 | self.poll.emit('new_notification', event) 388 | 389 | def sendAudioMessage(self, files): 390 | """ 391 | 392 | Отправка аудио, как голосовые сообщения в личные сообщения 393 | 394 | :param files: - Список имен файлов, которые нужно загрузить и отправить 395 | :return: - Вернет список с JSON-схемами, хранящие информацию о загруженных аудиозаписей 396 | """ 397 | getUploadServer = self.api.docs.getUploadServer(type="audio_message") 398 | upload_url = getUploadServer["upload_url"].replace("\\", "") 399 | result = [] 400 | for file in files: 401 | try: 402 | up_res = requests.post(upload_url, files={'file': open(file, "rb")}) 403 | up_res = eval(up_res.text) 404 | sleep(0.34) 405 | getVKFile = self.api.docs.save(file=up_res["file"], title="SuperCraft", tags="SuperCraft Audios") 406 | getVKFile = "audio" + str(getVKFile['owner_id']) + "_" + str(getVKFile['id']) 407 | result.append(getVKFile) 408 | except Exception as error: 409 | self.poll.emit('error', {'body': str(error)}) 410 | return result 411 | 412 | def sendVideo(self, files): 413 | """ 414 | 415 | Отправка видео в личные сообщения 416 | 417 | :param files: - Список имен файлов, которые нужно загрузить и отправить 418 | :return: - Вернет список с JSON-схемами, хранящие информацию о загруженных видеороликах 419 | """ 420 | getUploadServer = self.api.video.save(name='SuperCraft', description='SuperCraft', is_private=False) 421 | upload_url = getUploadServer["upload_url"].replace("\\", "") 422 | result = [] 423 | for file in files: 424 | try: 425 | up_res = requests.post(upload_url, files={'file': open(file, "rb")}) 426 | up_res = eval(up_res.text) 427 | sleep(0.34) 428 | getVKFile = 'video' + str(self.bot_id) + "_" + str(up_res['video_id']) 429 | #getVKFile = self.api.photos.saveMessagesPhoto(photo=up_res["photo"], server=up_res["server"], hash=up_res["hash"]) 430 | result.append(getVKFile) 431 | except Exception as error: 432 | self.poll.emit('error', {'body': str(error)}) 433 | return result 434 | 435 | def sendPhoto(self, files): 436 | """ 437 | 438 | Отправка фотографий на сервер в сообщения 439 | 440 | :param files: - Список имен файлов, которые нужно загрузить и отправить 441 | :return: - Вернет список с JSON-схемами, хранящие информацию о загруженных фотографиях 442 | """ 443 | getUploadServer = self.api.photos.getMessagesUploadServer() 444 | upload_url = getUploadServer["upload_url"].replace("\\", "") 445 | result = [] 446 | for file in files: 447 | try: 448 | up_res = requests.post(upload_url, files={'file': open(file, "rb")}) 449 | up_res = eval(up_res.text) 450 | sleep(0.34) 451 | getVKFile = self.api.photos.saveMessagesPhoto(photo=up_res["photo"], server=up_res["server"], hash=up_res["hash"]) 452 | getVKFile = "photo" + str(getVKFile[0]['owner_id']) + "_" + str(getVKFile[0]['id']) 453 | result.append(getVKFile) 454 | except Exception as error: 455 | self.poll.emit('error', {'body': str(error)}) 456 | return result 457 | 458 | def setGroupBanner(self, group_id, file, x1=0, y1=0, x2=1590, y2=400): 459 | """ 460 | 461 | :param group_id: - ID сообщества VK 462 | :param file: - Файл с обложкой для сообщества 463 | 464 | :param x1: - Первая координата по оси X | 465 | :param y1: - Певрая координата по оси Y _| координаты верхнего левого угла 466 | :param x2: - Втора координата по оси X | 467 | :param y2: - Вторая координата по оси Y _| координаты нижнего правого угла 468 | :return: 469 | """ 470 | 471 | url = self.api.photos.getOwnerCoverPhotoUploadServer(group_id=group_id, crop_x=x1, crop_y=y1, crop_x2=x2, crop_y2=y2) 472 | url['upload_url'] = re.sub(r'\\/', '/', url['upload_url']) 473 | 474 | response = eval(requests.post(url['upload_url'], files={'photo': open(file, 'rb')}).text) 475 | return self.api.photos.saveOwnerCoverPhoto(hash=response['hash'], photo=response['photo']) 476 | 477 | def setChatPhoto(self, chat_id, file, x, y, width): 478 | """ 479 | 480 | :param chat_id: - ID беседы 481 | :param file: - Файл с фотографией беседы 482 | :param x: - Первая координата по оси X | 483 | :param y: - Вторая координата по оси Y _| координаты верхнего левого угла 484 | :param width: - Ширина фотографии 485 | :return: 486 | """ 487 | url = self.api.photos.getChatUploadServer(chat_id=chat_id, crop_x=x, crop_y=y, widht=width) 488 | url['upload_url'] = re.sub(r'\\/', '/', url['upload_url']) 489 | 490 | response = eval(requests.post(url['upload_url'], files={'file': open(file, 'rb')}).text) 491 | return self.api.messages.setChatPhoto(file=response['response']) 492 | 493 | def uploadPhotoToAlbum(self, album_id, files=[], group_id=None): 494 | """ 495 | 496 | Загрузка фотографий в альбом 497 | 498 | :param album_id: - ID альбома 499 | :param files: - Спискок из имен файлов для загрузки (максимум - 5) 500 | :param group_id: - ID сообщества. Если не указан, фотографии будут загружены в альбом на стену пользователю 501 | :return: 502 | """ 503 | 504 | data = {} 505 | for i in range(len(files)): 506 | data['file{}'.format(i+1)] = open(files[i], 'rb') 507 | 508 | if len(files) > 0: 509 | url = self.api.photos.getUploadServer(group_id=group_id, album_id=album_id) 510 | url['upload_url'] = re.sub(r'\\/', '/', url['upload_url']) 511 | 512 | response = eval(requests.post(url['upload_url'], files=data).text) 513 | 514 | if group_id: 515 | photos = self.api.photos.save( 516 | album_id=album_id, 517 | group_id=group_id, 518 | server=response['server'], 519 | photos_list=response['photos_list'], 520 | hash=response['hash'], 521 | aid=response['aid'] 522 | ) 523 | else: 524 | photos = self.api.photos.save( 525 | album_id=album_id, 526 | user_id=self.bot_id, 527 | server=response['server'], 528 | photos_list=response['photos_list'], 529 | hash=response['hash'], 530 | aid=response['aid'] 531 | ) 532 | 533 | true_photos = [] 534 | for photo in photos: 535 | true_photos.append('photo{}_{}'.format(photo['owner_id'], photo['id'])) 536 | 537 | return true_photos 538 | 539 | def uploadPhotoToWall(self, file, description='', group_id=None): 540 | """ 541 | 542 | Загрузка фотографий на стену 543 | 544 | :param file: - Имя файла для загрузки 545 | :param description: - Описание фотографии 546 | :param group_id: - ID сообщества. Если не указан, фотографии будут загружены на стену пользователю. 547 | :return: photo_ 548 | """ 549 | 550 | url = self.api.photos.getWallUploadServer(group_id=group_id) 551 | url['upload_url'] = re.sub(r'\\/', '/', url['upload_url']) 552 | 553 | response = eval(requests.post(url['upload_url'], files={'photo': open(file, 'rb')}).text) 554 | 555 | photo = self.api.photos.saveWallPhoto( 556 | group_id=group_id, 557 | hash=response['hash'], 558 | server=response['server'], 559 | photo=response['photo'], 560 | caption=description 561 | ) 562 | 563 | return 'photo{}_{}'.format(photo[0]['owner_id'], photo[0]['id']) 564 | 565 | 566 | def setAvatar(self, file, x, y, width, owner_id=None): 567 | """ 568 | 569 | Загрузка главной фотографии пользователя или сообщества 570 | 571 | :param file: - Имя файла для загрузки 572 | :param x: - Первая координата по оси X | 573 | :param y: - Первая координата по оси Y _| координаты верхнего левого угла 574 | :param width: - Ширина желаемой миниатюры 575 | :param owner_id: - ID владельца. По умолчанию стоит ID текущего пользователя. 576 | 577 | Для пользователей используйте схему: 578 | Для групп используйте схему: - 579 | :return: 580 | """ 581 | 582 | url = self.api.photos.getOwnerPhotoUploadServer(owner_id=owner_id) 583 | url['upload_url'] = re.sub(r'\\/', '/', url['upload_url']) 584 | 585 | response = eval(requests.post(url['upload_url'], files={ 586 | 'photo': open(file, 'rb'), 587 | '_square_crop ': '{},{},{}'.format(x, y, width)}).text) 588 | 589 | return self.api.photos.saveOwnerPhoto( 590 | server=response['server'], 591 | hash=response['hash'], 592 | photo=response['photo'] 593 | ) 594 | 595 | def uploadAudio(self, file, artist='VK Advanced API', title='Sound uploaded via vk_advanced_api'): 596 | """ 597 | 598 | Загрузка аудиозаписей 599 | 600 | :param file: - Имя файла для загрузки 601 | :param artist: - Названия исполнителя трека 602 | :param title: - Название трека 603 | :return: audio_ 604 | """ 605 | 606 | if self.token_type == 'group': 607 | return 'Данный метод не доступен для токена сообществ.' 608 | url = self.api.audio.getUploadServer() 609 | url['upload_url'] = re.sub(r'\\/', '/', url['upload_url']) 610 | 611 | response = eval(requests.post(url['upload_url'], files={'file': open(file, 'rb')}).text) 612 | 613 | audio = self.api.audio.save( 614 | server=response['server'], 615 | hash=response['hash'], 616 | audio=response['audio'], 617 | artist=artist, 618 | title=title 619 | ) 620 | 621 | return 'audio{}_{}'.format(audio['owner_id'], audio['id']) 622 | --------------------------------------------------------------------------------