From 109cf0ca8763c6ededecd13ea1877531bfbe79a7 Mon Sep 17 00:00:00 2001 From: VladislavOstapov Date: Wed, 22 Mar 2023 10:18:43 +0300 Subject: [PATCH] first commit --- .idea/.gitignore | 8 + .idea/inspectionProfiles/Project_Default.xml | 14 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + README.md | 0 jconfig/__init__.py | 14 + jconfig/base.py | 51 ++ jconfig/jconfig.py | 41 ++ jconfig/memory.py | 24 + main.py | 0 requirements.txt | 3 + vk-userbot-v2.iml | 9 + vk_api/__init__.py | 18 + vk_api/audio.py | 682 +++++++++++++++++ vk_api/audio_url_decoder.py | 141 ++++ vk_api/bot_longpoll.py | 287 ++++++++ vk_api/enums.py | 81 ++ vk_api/exceptions.py | 180 +++++ vk_api/execute.py | 102 +++ vk_api/keyboard.py | 303 ++++++++ vk_api/longpoll.py | 620 ++++++++++++++++ vk_api/requests_pool.py | 261 +++++++ vk_api/streaming.py | 135 ++++ vk_api/tools.py | 254 +++++++ vk_api/upload.py | 618 ++++++++++++++++ vk_api/utils.py | 166 +++++ vk_api/vk_api.py | 737 +++++++++++++++++++ 28 files changed, 4769 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 jconfig/__init__.py create mode 100644 jconfig/base.py create mode 100644 jconfig/jconfig.py create mode 100644 jconfig/memory.py create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 vk-userbot-v2.iml create mode 100644 vk_api/__init__.py create mode 100644 vk_api/audio.py create mode 100644 vk_api/audio_url_decoder.py create mode 100644 vk_api/bot_longpoll.py create mode 100644 vk_api/enums.py create mode 100644 vk_api/exceptions.py create mode 100644 vk_api/execute.py create mode 100644 vk_api/keyboard.py create mode 100644 vk_api/longpoll.py create mode 100644 vk_api/requests_pool.py create mode 100644 vk_api/streaming.py create mode 100644 vk_api/tools.py create mode 100644 vk_api/upload.py create mode 100644 vk_api/utils.py create mode 100644 vk_api/vk_api.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..5c09ca9 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..639900d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e7aaf9b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/jconfig/__init__.py b/jconfig/__init__.py new file mode 100644 index 0000000..370682c --- /dev/null +++ b/jconfig/__init__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +""" +:authors: python273 +:license: Apache License, Version 2.0, see LICENSE file + +:copyright: (c) 2019 python273 +""" + +__author__ = 'python273' +__version__ = '3.0' +__email__ = 'vk_api@python273.pw' + +from .jconfig import Config +from .memory import MemoryConfig diff --git a/jconfig/base.py b/jconfig/base.py new file mode 100644 index 0000000..77fbc51 --- /dev/null +++ b/jconfig/base.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +""" +:authors: python273 +:license: Apache License, Version 2.0, see LICENSE file + +:copyright: (c) 2019 python273 +""" + + +class BaseConfig(object): + """ Абстрактный базовый класс конфигурации. + У наследуемых классов должен быть определен `__slots__` + + :param section: имя подкатегории в конфиге + :param \*\*kwargs: будут переданы в :func:`load` + """ + + __slots__ = ('section_name', '_settings', '_section') + + def __init__(self, section, **kwargs): + self.section_name = section + + self._settings = self.load(**kwargs) + self._section = self._settings.setdefault(section, {}) + + def __getattr__(self, name): + return self._section.get(name) + + __getitem__ = __getattr__ + + def __setattr__(self, name, value): + try: + super(BaseConfig, self).__setattr__(name, value) + except AttributeError: + self._section[name] = value + + __setitem__ = __setattr__ + + def setdefault(self, k, d=None): + return self._section.setdefault(k, d) + + def clear_section(self): + self._section.clear() + + def load(self, **kwargs): + """Абстрактный метод, должен возвращать dict с конфигом""" + raise NotImplementedError + + def save(self): + """Абстрактный метод, должен сохранять конфиг""" + raise NotImplementedError diff --git a/jconfig/jconfig.py b/jconfig/jconfig.py new file mode 100644 index 0000000..604b7d7 --- /dev/null +++ b/jconfig/jconfig.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +""" +:authors: python273 +:license: Apache License, Version 2.0, see LICENSE file + +:copyright: (c) 2019 python273 +""" + + +import json + +from .base import BaseConfig + + +class Config(BaseConfig): + """ Класс конфигурации в файле + + :param filename: имя файла + """ + + __slots__ = ('_filename',) + + def __init__(self, section, filename='.jconfig'): + self._filename = filename + + super(Config, self).__init__(section, filename=filename) + + def load(self, filename, **kwargs): + try: + with open(filename, 'r') as f: + settings = json.load(f) + except (IOError, ValueError): + settings = {} + + settings.setdefault(self.section_name, {}) + + return settings + + def save(self): + with open(self._filename, 'w') as f: + json.dump(self._settings, f, indent=2, sort_keys=True) diff --git a/jconfig/memory.py b/jconfig/memory.py new file mode 100644 index 0000000..145c473 --- /dev/null +++ b/jconfig/memory.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +""" +:authors: python273 +:license: Apache License, Version 2.0, see LICENSE file + +:copyright: (c) 2019 python273 +""" + +from .base import BaseConfig + + +class MemoryConfig(BaseConfig): + """ Класс конфигурации в памяти + + :param settings: существующий dict с конфигом + """ + + __slots__ = tuple() + + def load(self, settings=None, **kwargs): + return {} if settings is None else settings + + def save(self): + pass diff --git a/main.py b/main.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..941a976 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +beautifulsoup4 +requests +websocket-client diff --git a/vk-userbot-v2.iml b/vk-userbot-v2.iml new file mode 100644 index 0000000..e45e2c7 --- /dev/null +++ b/vk-userbot-v2.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/vk_api/__init__.py b/vk_api/__init__.py new file mode 100644 index 0000000..a980d84 --- /dev/null +++ b/vk_api/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +""" +:authors: python273 +:license: Apache License, Version 2.0, see LICENSE file + +:copyright: (c) 2019 python273 +""" +from .enums import * +from .exceptions import * +from .requests_pool import VkRequestsPool, vk_request_one_param_pool +from .tools import VkTools +from .upload import VkUpload +from .vk_api import VkApi + + +__author__ = 'python273' +__version__ = '11.9.9' +__email__ = 'vk_api@python273.pw' diff --git a/vk_api/audio.py b/vk_api/audio.py new file mode 100644 index 0000000..0589755 --- /dev/null +++ b/vk_api/audio.py @@ -0,0 +1,682 @@ +# -*- coding: utf-8 -*- +""" +:authors: python273 +:license: Apache License, Version 2.0, see LICENSE file + +:copyright: (c) 2019 python273 +""" + +import re +import json +import time +from itertools import islice + +from bs4 import BeautifulSoup + +from .audio_url_decoder import decode_audio_url +from .exceptions import AccessDenied +from .utils import set_cookies_from_list + +RE_ALBUM_ID = re.compile(r'act=audio_playlist(-?\d+)_(\d+)') +RE_ACCESS_HASH = re.compile(r'access_hash=(\w+)') +RE_M3U8_TO_MP3 = re.compile(r'/[0-9a-f]+(/audios)?/([0-9a-f]+)/index.m3u8') + +RPS_DELAY_RELOAD_AUDIO = 1.5 +RPS_DELAY_LOAD_SECTION = 2.0 + +TRACKS_PER_USER_PAGE = 2000 +TRACKS_PER_ALBUM_PAGE = 2000 +ALBUMS_PER_USER_PAGE = 100 + + +class VkAudio(object): + """ Модуль для получения аудиозаписей без использования официального API. + + :param vk: Объект :class:`VkApi` + """ + + __slots__ = ('_vk', 'user_id', 'convert_m3u8_links') + + DEFAULT_COOKIES = [ + { # если не установлено, то первый запрос ломается + 'version': 0, + 'name': 'remixaudio_show_alert_today', + 'value': '0', + 'port': None, + 'port_specified': False, + 'domain': '.vk.com', + 'domain_specified': True, + 'domain_initial_dot': True, + 'path': '/', + 'path_specified': True, + 'secure': True, + 'expires': None, + 'discard': False, + 'comment': None, + 'comment_url': None, + 'rfc2109': False, + 'rest': {} + }, { # для аудио из постов + 'version': 0, + 'name': 'remixmdevice', + 'value': '1920/1080/2/!!-!!!!', + 'port': None, + 'port_specified': False, + 'domain': '.vk.com', + 'domain_specified': True, + 'domain_initial_dot': True, + 'path': '/', + 'path_specified': True, + 'secure': True, + 'expires': None, + 'discard': False, + 'comment': None, + 'comment_url': None, + 'rfc2109': False, + 'rest': {} + } + ] + + def __init__(self, vk, convert_m3u8_links=True): + self.user_id = vk.method('users.get')[0]['id'] + self._vk = vk + self.convert_m3u8_links = convert_m3u8_links + + set_cookies_from_list(self._vk.http.cookies, self.DEFAULT_COOKIES) + + self._vk.http.get('https://m.vk.com/') # load cookies + + def get_iter(self, owner_id=None, album_id=None, access_hash=None): + """ Получить список аудиозаписей пользователя (по частям) + + :param owner_id: ID владельца (отрицательные значения для групп) + :param album_id: ID альбома + :param access_hash: ACCESS_HASH альбома + """ + + if owner_id is None: + owner_id = self.user_id + + if album_id is not None: + offset_diff = TRACKS_PER_ALBUM_PAGE + else: + offset_diff = TRACKS_PER_USER_PAGE + + offset = 0 + while True: + response = self._vk.http.post( + 'https://m.vk.com/audio', + data={ + 'act': 'load_section', + 'owner_id': owner_id, + 'playlist_id': album_id if album_id else -1, + 'offset': offset, + 'type': 'playlist', + 'access_hash': access_hash, + 'is_loading_all': 1 + }, + allow_redirects=False + ).json() + + if not response['data'][0]: + raise AccessDenied( + 'You don\'t have permissions to browse {}\'s albums'.format( + owner_id + ) + ) + + ids = scrap_ids( + response['data'][0]['list'] + ) + + tracks = scrap_tracks( + ids, + self.user_id, + self._vk.http, + convert_m3u8_links=self.convert_m3u8_links + ) + + if not tracks: + break + + for i in tracks: + yield i + + if response['data'][0]['hasMore']: + offset += offset_diff + else: + break + + def get(self, owner_id=None, album_id=None, access_hash=None): + """ Получить список аудиозаписей пользователя + + :param owner_id: ID владельца (отрицательные значения для групп) + :param album_id: ID альбома + :param access_hash: ACCESS_HASH альбома + """ + + return list(self.get_iter(owner_id, album_id, access_hash)) + + def get_albums_iter(self, owner_id=None): + """ Получить список альбомов пользователя (по частям) + + :param owner_id: ID владельца (отрицательные значения для групп) + """ + + if owner_id is None: + owner_id = self.user_id + + offset = 0 + + while True: + response = self._vk.http.get( + 'https://m.vk.com/audio?act=audio_playlists{}'.format( + owner_id + ), + params={ + 'offset': offset + }, + allow_redirects=False + ) + + if not response.text: + raise AccessDenied( + 'You don\'t have permissions to browse {}\'s albums'.format( + owner_id + ) + ) + + albums = scrap_albums(response.text) + + if not albums: + break + + for i in albums: + yield i + + offset += ALBUMS_PER_USER_PAGE + + def get_albums(self, owner_id=None): + """ Получить список альбомов пользователя + + :param owner_id: ID владельца (отрицательные значения для групп) + """ + + return list(self.get_albums_iter(owner_id)) + + def search_user(self, owner_id=None, q=''): + """ Искать по аудиозаписям пользователя + + :param owner_id: ID владельца (отрицательные значения для групп) + :param q: запрос + """ + + if owner_id is None: + owner_id = self.user_id + + response = self._vk.http.post( + 'https://vk.com/al_audio.php', + data={ + 'al': 1, + 'act': 'section', + 'claim': 0, + 'is_layer': 0, + 'owner_id': owner_id, + 'section': 'search', + 'q': q + } + ) + json_response = json.loads(response.text.replace('