From 332cb3733cfcc681dd8977175d884255970ac602 Mon Sep 17 00:00:00 2001 From: ramjm Date: Sat, 31 Aug 2024 16:50:02 +0000 Subject: [PATCH] Initial commit --- LICENSE | 21 + README.md | 27 ++ config.py | 2 + jconfig/__init__.py | 14 + jconfig/base.py | 51 +++ jconfig/jconfig.py | 41 ++ jconfig/memory.py | 24 ++ requirements.txt | 5 + tg_bot.py | 54 +++ 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 ++++++++++++++++++++++++++++++++++++ vk_bot.py | 55 +++ 25 files changed, 4879 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 config.py 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 requirements.txt create mode 100644 tg_bot.py 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 create mode 100644 vk_bot.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d1db9ec --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Justuser + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..49b5255 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# mrp_bot +Бот парсер онлайна проекта Mordor Role Play. +VK, TG +## Установка зависимостей: +```bash +pip install -r requirements.txt +``` +## Что требуется для запуска ботов? +Создать файл .env: +```bash +touch .env +``` +Заполнить следующие переменные любым текстовым редактором: +* tg_token = "API токен телеграм бота" +* vk_token = "API токен вк бота" +* vk_group_id = 123 + +ПРИМЕЧАНИЕ: вместо 123 впишите айди группы (тип - int) + +--- +Запустить ботов командами: +```bash +nohup python3 vk_bot.py & +nohup python3 tg_bot.py & +``` +## Основная команда ботов +* /mrp_online - получение онлайна diff --git a/config.py b/config.py new file mode 100644 index 0000000..cf1d520 --- /dev/null +++ b/config.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0"} \ No newline at end of file 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/requirements.txt b/requirements.txt new file mode 100644 index 0000000..14caccf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +telebot +vk_api +requests +random +python-dotenv \ No newline at end of file diff --git a/tg_bot.py b/tg_bot.py new file mode 100644 index 0000000..cfdaee9 --- /dev/null +++ b/tg_bot.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +import telebot +from os import getenv +from requests import get +from dotenv import load_dotenv +from pathlib import Path +dotenv_path = f"{Path(__file__).parent.resolve()}/.env" +load_dotenv(dotenv_path=dotenv_path) +bot = telebot.TeleBot(getenv("tg_token")) + +@bot.message_handler(commands=["mrp_online"]) +def get_online(message): + try: + response = get("https://l.mordor-rp.com/launcher/monitoring/online.php").json() + rponl = 0; obsonl = 0; funonl = 0; text = "" + for element in response: + text += f'[{element["name"]}]: {element["min"]}\n' + obsonl += int(element["min"]) + if element["tag"] == "roleplay": rponl += int(element["min"]) + if element["tag"] == "fun": funonl += int(element["min"]) + text += f"========================\n" \ + f"ROLEPLAY ONLINE: {rponl}\n" \ + f"FUN ONLINE: {funonl}\n" \ + f"========================\n" \ + f"FULL ONLINE: {obsonl}" + bot.reply_to(message, text) + except Exception: + bot.reply_to(message, "Ошибка.") + +@bot.message_handler(commands=['start', 'help']) +def process_start_command(message): + bot.reply_to( + message, + f"Команды:\n"\ + f"- /my_id - получение уникального идентификатора пользователя.\n"\ + f"- /mrp_online - получение онлайна на проекте Mordor Role Play" + ) + +@bot.message_handler(commands=["my_id"]) +def user_id(message): + uid = message.from_user.id + bot.reply_to( + message, + f"Пользователь, "\ + f"твой уникальный идентификатор (ID):\n\n"\ + f"{uid}", + parse_mode="HTML" + ) + +if __name__ == '__main__': + while True: + try: bot.polling() + except KeyboardInterrupt: exit() + except: pass 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('