Big and tasty update to v2.0
This commit is contained in:
parent
ec35817895
commit
fbb22875c9
9
.gitignore
vendored
9
.gitignore
vendored
@ -152,17 +152,14 @@ cython_debug/
|
|||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
# Project specific
|
# Custom
|
||||||
.vscode
|
|
||||||
config.json
|
config.json
|
||||||
|
|
||||||
*.session
|
*.session
|
||||||
*.session-journal
|
*.session-journal
|
||||||
|
|
||||||
.venv
|
|
||||||
venv
|
venv
|
||||||
venv_linux
|
venv_linux
|
||||||
venv_windows
|
venv_windows
|
||||||
|
|
||||||
PageSaver/node_modules
|
.vscode
|
||||||
bin/
|
data/
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"dependencies": {
|
|
||||||
"puppeteer": "~21.1.0",
|
|
||||||
"user-agents": "~1.0.1260"
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
@ -1,21 +0,0 @@
|
|||||||
// npm install https://github.com/GoogleChrome/puppeteer/
|
|
||||||
|
|
||||||
const puppeteer = require('puppeteer');
|
|
||||||
const userAgent = require('user-agents');
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
|
|
||||||
const url = process.argv[2];
|
|
||||||
const binary = process.argv[3];
|
|
||||||
const browser = await puppeteer.launch({ executablePath: binary });
|
|
||||||
const page = await browser.newPage();
|
|
||||||
await page.setUserAgent(userAgent.random().toString());
|
|
||||||
|
|
||||||
await page.goto(url, { waitUntil: 'load' });
|
|
||||||
|
|
||||||
const html = await page.content();
|
|
||||||
|
|
||||||
browser.close();
|
|
||||||
console.log(html);
|
|
||||||
|
|
||||||
})();
|
|
25
README.md
25
README.md
@ -6,8 +6,7 @@ Simple yet helpful bot to check BWT Aqua's card balance
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
* nodejs & npm
|
* Python 3.8+
|
||||||
* python3
|
|
||||||
* git
|
* git
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@ -15,19 +14,25 @@ Simple yet helpful bot to check BWT Aqua's card balance
|
|||||||
1. Download package
|
1. Download package
|
||||||
1. `git clone https://git.end-play.xyz/profitroll/BWTAqua.git`
|
1. `git clone https://git.end-play.xyz/profitroll/BWTAqua.git`
|
||||||
2. `cd BWTAqua`
|
2. `cd BWTAqua`
|
||||||
2. Install needed modules:
|
2. Create venv
|
||||||
* `python3 -m pip install -r requirements.txt`
|
1. `python3 -m venv .venv`
|
||||||
3. Install PageSaver:
|
2. `source .venv/bin/activate`
|
||||||
1. `cd PageSaver`
|
3. Install needed modules:
|
||||||
2. `npm install`
|
* `pip install -r requirements.txt`
|
||||||
3. `chmod +x pageSaver` (If you want to use compiled page saver)
|
|
||||||
4. Configure the bot:
|
4. Configure the bot:
|
||||||
1. `cd ..`
|
1. `cd ..`
|
||||||
2. `nano config.json` (You can use any other text editor actually, for example `vim`)
|
2. `nano config.json` (You can use any other text editor actually, for example `vim`)
|
||||||
5. Run the bot:
|
5. Run the bot:
|
||||||
* `python3 bwtbot.py`
|
* `python bwtbot.py`
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
You can edit with vim, nano, whatever.
|
You can edit with vim, nano, whatever.
|
||||||
If you don't know where to find bot_token and your id - here you can find some hints: [get bot token](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token), [get your id](https://www.alphr.com/telegram-find-user-id/), [get api_hash and api_id](https://core.telegram.org/api/obtaining_api_id).
|
If you don't know where to find bot_token and your id - here you can find some hints:
|
||||||
|
[get bot token](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token),
|
||||||
|
[get your id](https://www.alphr.com/telegram-find-user-id/),
|
||||||
|
[get api_hash and api_id](https://core.telegram.org/api/obtaining_api_id).
|
||||||
|
|
||||||
|
## Upgrading from v1.x
|
||||||
|
|
||||||
|
If you have just installed your fresh and new v2.x, migrate the database by starting the bot with `--migrate` argument.
|
||||||
|
36
bwtbot.py
36
bwtbot.py
@ -1,36 +0,0 @@
|
|||||||
import logging
|
|
||||||
from os import getpid
|
|
||||||
|
|
||||||
from convopyro import Conversation
|
|
||||||
from libbot.pyrogram.classes import PyroClient
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format="%(name)s.%(funcName)s | %(levelname)s | %(message)s",
|
|
||||||
datefmt="[%X]",
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
try:
|
|
||||||
import uvloop
|
|
||||||
|
|
||||||
uvloop.install()
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
client = PyroClient()
|
|
||||||
Conversation(client)
|
|
||||||
|
|
||||||
try:
|
|
||||||
client.run()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
logger.warning(f"Forcefully shutting down with PID {getpid()}...")
|
|
||||||
finally:
|
|
||||||
exit()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
28
classes/callbacks.py
Normal file
28
classes/callbacks.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from pyrogram.types import CallbackQuery
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CallbackLanguage:
|
||||||
|
language: str
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_callback(cls, callback: CallbackQuery):
|
||||||
|
"""Parse callback query and extract language data from it.
|
||||||
|
|
||||||
|
### Args:
|
||||||
|
* callback (`CallbackQuery`): Callback query got from user interaction.
|
||||||
|
|
||||||
|
### Raises:
|
||||||
|
* `ValueError`: Raised when callback provided is not a language one.
|
||||||
|
|
||||||
|
### Returns:
|
||||||
|
* `CallbackLanguage`: Parsed callback query.
|
||||||
|
"""
|
||||||
|
action, language = str(callback.data).split(":")
|
||||||
|
|
||||||
|
if action.lower() != "language":
|
||||||
|
raise ValueError("Callback provided is not a language callback")
|
||||||
|
|
||||||
|
return cls(language)
|
24
classes/pyroclient.py
Normal file
24
classes/pyroclient.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from libbot.pyrogram.classes import PyroClient as LibPyroClient
|
||||||
|
from pyrogram.types import User
|
||||||
|
|
||||||
|
from classes.pyrouser import PyroUser
|
||||||
|
|
||||||
|
|
||||||
|
class PyroClient(LibPyroClient):
|
||||||
|
async def find_user(self, user: Union[int, User]) -> PyroUser:
|
||||||
|
"""Find User by it's ID or User object.
|
||||||
|
|
||||||
|
### Args:
|
||||||
|
* user (`Union[int, User]`): ID or User object to extract ID from.
|
||||||
|
|
||||||
|
### Returns:
|
||||||
|
* `PyroUser`: User in database representation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
await PyroUser.find(user)
|
||||||
|
if isinstance(user, int)
|
||||||
|
else await PyroUser.find(user.id, locale=user.language_code)
|
||||||
|
)
|
77
classes/pyrouser.py
Normal file
77
classes/pyrouser.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import logging
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from modules.database import cursor
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PyroUser:
|
||||||
|
"""Dataclass of DB entry of a user"""
|
||||||
|
|
||||||
|
__slots__ = ("id", "card", "locale")
|
||||||
|
|
||||||
|
id: int
|
||||||
|
card: Union[str, None]
|
||||||
|
locale: Union[str, None]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def find(
|
||||||
|
cls, id: int, card: Union[str, None] = None, locale: Union[str, None] = None
|
||||||
|
):
|
||||||
|
"""Find user in database and create new record if user does not exist.
|
||||||
|
|
||||||
|
### Args:
|
||||||
|
* id (`int`): User's Telegram ID
|
||||||
|
* card (`Union[str, None]`, *optional*): User's card number. Defaults to `None`.
|
||||||
|
* locale (`Union[str, None]`, *optional*): User's locale. Defaults to `None`.
|
||||||
|
|
||||||
|
### Raises:
|
||||||
|
* `RuntimeError`: Raised when user entry after insertion could not be found.
|
||||||
|
|
||||||
|
### Returns:
|
||||||
|
* `PyroUser`: User with its database data.
|
||||||
|
"""
|
||||||
|
db_entry = cursor.execute(
|
||||||
|
"SELECT id, card, locale FROM users WHERE id = ?", (id,)
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
if db_entry is None:
|
||||||
|
cursor.execute("INSERT INTO users VALUES (?, ?, ?)", (id, card, locale))
|
||||||
|
cursor.connection.commit()
|
||||||
|
db_entry = cursor.execute(
|
||||||
|
"SELECT id, card, locale FROM users WHERE id = ?", (id,)
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
if db_entry is None:
|
||||||
|
raise RuntimeError("Could not find inserted user entry.")
|
||||||
|
|
||||||
|
return cls(*db_entry)
|
||||||
|
|
||||||
|
async def update_locale(self, locale: Union[str, None]) -> None:
|
||||||
|
"""Change user's locale stored in the database.
|
||||||
|
|
||||||
|
### Args:
|
||||||
|
* locale (`Union[str, None]`): New locale to be set.
|
||||||
|
"""
|
||||||
|
logger.debug("%s's locale has been set to %s", self.id, locale)
|
||||||
|
cursor.execute(
|
||||||
|
"UPDATE users SET locale = ? WHERE id = ?",
|
||||||
|
(locale, self.id),
|
||||||
|
)
|
||||||
|
cursor.connection.commit()
|
||||||
|
|
||||||
|
async def update_card(self, card: Union[str, None]) -> None:
|
||||||
|
"""Change user's card stored in the database.
|
||||||
|
|
||||||
|
### Args:
|
||||||
|
* card (`Union[str, None]`): New card to be set.
|
||||||
|
"""
|
||||||
|
logger.debug("%s's card has been set to %s", self.id, card)
|
||||||
|
cursor.execute(
|
||||||
|
"UPDATE users SET card = ? WHERE id = ?",
|
||||||
|
(card, self.id),
|
||||||
|
)
|
||||||
|
cursor.connection.commit()
|
84
commands.json
Normal file
84
commands.json
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"help": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeDefault"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"balance": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeDefault"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"topup": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeDefault"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"setcard": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeDefault"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"resetcard": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeDefault"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeDefault"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"shutdown": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"remove_commands": {
|
||||||
|
"scopes": [
|
||||||
|
{
|
||||||
|
"name": "BotCommandScopeChat",
|
||||||
|
"chat_id": "owner"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -7,74 +7,9 @@
|
|||||||
"bot_token": "",
|
"bot_token": "",
|
||||||
"scoped_commands": true
|
"scoped_commands": true
|
||||||
},
|
},
|
||||||
"use_compiled_page_saver": false,
|
"database": "data/database.db",
|
||||||
"reports": {
|
"reports": {
|
||||||
"chat_id": "owner"
|
"chat_id": "owner"
|
||||||
},
|
},
|
||||||
"disabled_plugins": [],
|
"disabled_plugins": []
|
||||||
"commands": {
|
|
||||||
"help": {
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "BotCommandScopeDefault"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "BotCommandScopeChat",
|
|
||||||
"chat_id": "owner"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"balance": {
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "BotCommandScopeDefault"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "BotCommandScopeChat",
|
|
||||||
"chat_id": "owner"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"topup": {
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "BotCommandScopeDefault"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "BotCommandScopeChat",
|
|
||||||
"chat_id": "owner"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"setcard": {
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "BotCommandScopeDefault"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "BotCommandScopeChat",
|
|
||||||
"chat_id": "owner"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"resetcard": {
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "BotCommandScopeDefault"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "BotCommandScopeChat",
|
|
||||||
"chat_id": "owner"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"shutdown": {
|
|
||||||
"scopes": [
|
|
||||||
{
|
|
||||||
"name": "BotCommandScopeChat",
|
|
||||||
"chat_id": "owner"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1 +0,0 @@
|
|||||||
{}
|
|
@ -1,10 +1,40 @@
|
|||||||
{
|
{
|
||||||
|
"metadata": {
|
||||||
|
"flag": "🇬🇧",
|
||||||
|
"name": "English",
|
||||||
|
"codes": [
|
||||||
|
"en",
|
||||||
|
"en-US",
|
||||||
|
"en-GB"
|
||||||
|
]
|
||||||
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
"help": "Help menu",
|
"help": "Help menu",
|
||||||
"balance": "Card's balance",
|
"balance": "Card's balance",
|
||||||
"topup": "Refill the card",
|
"topup": "Refill the card",
|
||||||
"setcard": "Link the card",
|
"setcard": "Link the card",
|
||||||
"resetcard": "Unlink the card",
|
"resetcard": "Unlink the card",
|
||||||
"shutdown": "Turn the bot off"
|
"language": "Change bot's language",
|
||||||
|
"shutdown": "Turn the bot off",
|
||||||
|
"remove_commands": "Unregister all commands"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"welcome": "Welcome!\n\nThis bot allows you to get liters left on your personal BWT card.\n\n**Commands:**\n • /balance – get card balance\n • /setcard – link your card\n • /resetcard – unlink your card\n\n{notice}\n\nDeveloper **is not affiliated with BWT Aqua** and this bot is made for personal usage only.",
|
||||||
|
"cancel": "Operation cancelled",
|
||||||
|
"card_balance": "Card's balance is {balance} l. of water",
|
||||||
|
"card_linked": "Linked card: `{card_id}`\n\nPlease, make sure the number is correct before using the bot",
|
||||||
|
"card_not_linked": "You don't have any linked card.\n\nВYou can set it using /setcard\n\n{notice}",
|
||||||
|
"card_unlinked": "Card was unlinked from your Telegram",
|
||||||
|
"card_error": "An error occurred while getting the amount of remaining water on the card.\n\nBWT seems to return empty string to balance requests from bot's server lately, as well as bot cannot use BWT's \"clean\" API to get this data.\n\nTo check your balance you can use official [BWT App](https://bwtaqua.com.ua/en/#app) or simply bookmark this page: {link}.",
|
||||||
|
"get_number": "**Get card number (Var. 1):**\nOn the front bottom side of your card, number may be found\n\n**Get card number (Var. 2):**\n1. Scan QR on the card\n2. Open webpage from code\n3. Number should be found in **Номер карти \"Здорова Вода\"** or **BWT Aqua card number** fields",
|
||||||
|
"locale_choice": "Alright. Please choose the language using keyboard below.",
|
||||||
|
"send_number": "Please, send your card number\nIf you want to abort this operation, use /cancel",
|
||||||
|
"top_up": "[Click here to top up](https://bwtaqua.com.ua/en/card-topup/?id={card_id})"
|
||||||
|
},
|
||||||
|
"callbacks": {
|
||||||
|
"locale_set": "Your language now is: {locale}"
|
||||||
|
},
|
||||||
|
"force_replies": {
|
||||||
|
"enter_number": "Enter card number"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,39 @@
|
|||||||
{
|
{
|
||||||
|
"metadata": {
|
||||||
|
"flag": "🇺🇦",
|
||||||
|
"name": "Українська",
|
||||||
|
"codes": [
|
||||||
|
"uk",
|
||||||
|
"uk-UA"
|
||||||
|
]
|
||||||
|
},
|
||||||
"commands": {
|
"commands": {
|
||||||
"help": "Меню допомоги",
|
"help": "Меню допомоги",
|
||||||
"balance": "Баланс картки",
|
"balance": "Баланс картки",
|
||||||
"topup": "Поповнити картку",
|
"topup": "Поповнити картку",
|
||||||
"setcard": "Прив'язати картку",
|
"setcard": "Прив'язати картку",
|
||||||
"resetcard": "Відв'язати картку",
|
"resetcard": "Відв'язати картку",
|
||||||
"shutdown": "Вимкнути бота"
|
"language": "Змінити мову бота",
|
||||||
|
"shutdown": "Вимкнути бота",
|
||||||
|
"remove_commands": "Видалити всі команди"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"welcome": "Привіт-привіт!\n\nЦей бот дозволяє дізнатись скільки літрів залишилось на вашій карточці.\n\n**Команди:**\n • /balance – дізнатись баланс карти\n • /setcard – прив'язати карту\n • /resetcard – відв'язати карту\n\n{notice}\n\nРозробник **не має жодного відношення до BWT Aqua**, а бот створений лише для особистого, некомерційного використання.",
|
||||||
|
"cancel": "Операцію скасовано",
|
||||||
|
"card_balance": "На карточці {balance} л. води",
|
||||||
|
"card_linked": "Прив'язана карточка: `{card_id}`\n\nБудь ласка, упевніться що номер правильний перед використанням інших команд",
|
||||||
|
"card_not_linked": "У вас немає прив'язаної картки.\n\nВи можете зробити це за допомогою команди /setcard\n\n{notice}",
|
||||||
|
"card_unlinked": "Картку відв'язано від вашого Telegram",
|
||||||
|
"card_error": "При отриманні води на карточці виникла помилка.\n\nОстаннім часом BWT часто повертає нашому серверу порожні строки замість балансу. На жаль, бот не може використовувати \"чисте\" API BWT для отримання даних про баланс, оскільки воно не є публічним.\n\nДля перевірки балансу рекомендуємо користуватись офіційним [додатком BWT](https://bwtaqua.com.ua/#app) або просто додати цю сторінку у закладки: {link}.",
|
||||||
|
"get_number": "**Дізнатись номер картки (Вар. 1):**\nЗ лицевої сторони картки знизу може бут вказано номер цієї картки\n\n**Дізнатись номер картки (Вар. 2):**\n1. Відсканувати QR код на картці\n2. Відкрити веб-сторінку з кода\n3. Номер буде знаходитись в полі **Номер карти \"Здорова Вода\"** або **Номер карти BWT Aqua**",
|
||||||
|
"locale_choice": "Гаразд. Будь ласка, оберіть мову за допомогою клавіатури нижче.",
|
||||||
|
"send_number": "Будь ласка, надішліть номер вашої картки\nЯкщо ви хочете скасувати цю операцію, використовуйте /cancel",
|
||||||
|
"top_up": "[Натисніть для поповнення](https://bwtaqua.com.ua/card-topup/?id={card_id})"
|
||||||
|
},
|
||||||
|
"callbacks": {
|
||||||
|
"locale_set": "Встановлено мову: {locale}"
|
||||||
|
},
|
||||||
|
"force_replies": {
|
||||||
|
"enter_number": "Введіть номер картки"
|
||||||
}
|
}
|
||||||
}
|
}
|
66
main.py
Normal file
66
main.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import contextlib
|
||||||
|
import logging
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from os import getpid
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from convopyro import Conversation
|
||||||
|
from libbot import sync
|
||||||
|
|
||||||
|
from classes.pyroclient import PyroClient
|
||||||
|
from modules.database import cursor
|
||||||
|
from modules.migrator import migrate_database
|
||||||
|
from modules.scheduler import scheduler
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(name)s.%(funcName)s | %(levelname)s | %(message)s",
|
||||||
|
datefmt="[%X]",
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
parser = ArgumentParser(
|
||||||
|
prog="BWTAqua Bot",
|
||||||
|
description="Small web scraper for BWT cards' balance parsing",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument("--migrate", action="store_true")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
with contextlib.suppress(ImportError):
|
||||||
|
import uvloop
|
||||||
|
|
||||||
|
uvloop.install()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
client = PyroClient(
|
||||||
|
scheduler=scheduler, commands_source=sync.json_read(Path("commands.json"))
|
||||||
|
)
|
||||||
|
Conversation(client)
|
||||||
|
|
||||||
|
if args.migrate:
|
||||||
|
migrate_database()
|
||||||
|
elif Path("data/database.json").exists():
|
||||||
|
logger.info(
|
||||||
|
"You have an old unmigrated JSON database. Start the bot with --migrate argument to migrate the database to SQLite."
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
client.run()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.warning("Forcefully shutting down with PID %s...", getpid())
|
||||||
|
finally:
|
||||||
|
if client.scheduler is not None:
|
||||||
|
client.scheduler.shutdown()
|
||||||
|
cursor.close()
|
||||||
|
cursor.connection.commit()
|
||||||
|
cursor.connection.close()
|
||||||
|
exit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
103
modules/bwt.py
103
modules/bwt.py
@ -1,103 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from os import makedirs, path
|
|
||||||
from subprocess import check_output
|
|
||||||
from traceback import format_exc
|
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from libbot import config_get
|
|
||||||
|
|
||||||
from modules.utils import *
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class EmptyCardException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
async def getWaterLeft(cardid, filename, app=None):
|
|
||||||
url = f"https://bwtaqua.com.ua/card-topup/?id={cardid}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
# if path.exists(f"data/pages/{str(filename)}.html") is False:
|
|
||||||
# run(["touch", f"data/pages/{str(filename)}.html"])
|
|
||||||
|
|
||||||
logger.info(f"Trying to get liters for url '{url}'")
|
|
||||||
|
|
||||||
if await config_get("use_compiled_page_saver") is True:
|
|
||||||
proc = check_output(
|
|
||||||
[
|
|
||||||
"PageSaver/pageSaver",
|
|
||||||
f"https://bwtaqua.com.ua/card-topup/?id={cardid}",
|
|
||||||
"./bin/chrome",
|
|
||||||
]
|
|
||||||
) # , ">", f"data/pages/{str(filename)}.html"])
|
|
||||||
html_file = proc.decode("utf-8")
|
|
||||||
else:
|
|
||||||
proc = check_output(
|
|
||||||
[
|
|
||||||
"node",
|
|
||||||
"./PageSaver/pageSaver.js",
|
|
||||||
f"https://bwtaqua.com.ua/card-topup/?id={cardid}",
|
|
||||||
"./bin/chrome",
|
|
||||||
]
|
|
||||||
) # , ">", f"data/pages/{str(filename)}.html"])
|
|
||||||
html_file = proc.decode("utf-8")
|
|
||||||
|
|
||||||
# with open(f'data/pages/{str(filename)}.html') as f:
|
|
||||||
# html_file = f.read()
|
|
||||||
# f.close()
|
|
||||||
|
|
||||||
soup = BeautifulSoup(html_file, "html.parser")
|
|
||||||
|
|
||||||
output = (
|
|
||||||
(
|
|
||||||
soup.find_all(
|
|
||||||
"h3",
|
|
||||||
class_="headline headline_center headline_pink js-payment-balance",
|
|
||||||
)[0].getText()
|
|
||||||
)
|
|
||||||
.replace("Твій баланс ", "")
|
|
||||||
.replace(" л", "")
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"Parsed {output} liters of water remaining (user: {str(filename)}, cardid: {cardid})"
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as exp:
|
|
||||||
logger.exception(
|
|
||||||
f"Exception occured: {exp} (user: {str(filename)}, cardid: {cardid})", exp
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
tmp_name = str(uuid4())
|
|
||||||
makedirs("tmp", exist_ok=True)
|
|
||||||
with open(path.join("tmp", tmp_name), "w", encoding="utf-8") as f:
|
|
||||||
f.write(html_file)
|
|
||||||
except NameError:
|
|
||||||
tmp_name = "N/A"
|
|
||||||
logger.warning(f"'html_file' is not defined so I won't gather any tmp data")
|
|
||||||
|
|
||||||
if app != None:
|
|
||||||
await app.send_message(
|
|
||||||
app.owner,
|
|
||||||
f"**Exception occured:**\n • User: `{str(filename)}`\n • Card: [{cardid}]({url})\n • Exception: `{exp}`\n • TMP UUID: `{tmp_name}`\n • Traceback: `{format_exc()}`",
|
|
||||||
disable_web_page_preview=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.warning(f"Exception occurred and could not send to user: {exp}")
|
|
||||||
|
|
||||||
output = "Failure"
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
cardid = input("Enter card number: ")
|
|
||||||
userid = input("Enter Telegram ID (optional): ")
|
|
||||||
|
|
||||||
print(f"Card has {str(getWaterLeft(cardid, userid, app=None))} l. left")
|
|
30
modules/bwt_scrape.py
Normal file
30
modules/bwt_scrape.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from selenium import webdriver
|
||||||
|
|
||||||
|
|
||||||
|
def get_balance(card_id: Union[str, int]) -> Union[str, None]:
|
||||||
|
driver = webdriver.Chrome()
|
||||||
|
|
||||||
|
driver.get(f"https://bwtaqua.com.ua/card-topup/?id={card_id}")
|
||||||
|
|
||||||
|
html = driver.page_source
|
||||||
|
|
||||||
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
|
|
||||||
|
return (
|
||||||
|
(
|
||||||
|
soup.find_all(
|
||||||
|
"h3",
|
||||||
|
class_="headline headline_center headline_pink js-payment-balance",
|
||||||
|
)[0].getText()
|
||||||
|
)
|
||||||
|
.replace("Твій баланс ", "")
|
||||||
|
.replace(" л", "")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
card = input("Type your card ID: ")
|
||||||
|
print(get_balance(card))
|
11
modules/database.py
Normal file
11
modules/database.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"""Module that provides all database collections"""
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from libbot.sync import config_get
|
||||||
|
|
||||||
|
db: sqlite3.Connection = sqlite3.connect(Path(config_get("database")))
|
||||||
|
cursor: sqlite3.Cursor = db.cursor()
|
||||||
|
|
||||||
|
cursor.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER, card TEXT, locale TEXT)")
|
26
modules/migrator.py
Normal file
26
modules/migrator.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from os import rename
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Mapping
|
||||||
|
|
||||||
|
from libbot.sync import json_read
|
||||||
|
|
||||||
|
from modules.database import cursor
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_database() -> None:
|
||||||
|
"""Apply migrations from old JSON database to SQLite"""
|
||||||
|
if not Path("data/database.json").exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
db_old: Mapping[str, Mapping[str, str]] = json_read(Path("data/database.json"))
|
||||||
|
|
||||||
|
for user, keys in db_old.items():
|
||||||
|
user_locale = None if "locale" not in keys else keys["locale"]
|
||||||
|
user_card = None if "card" not in keys else keys["card"]
|
||||||
|
|
||||||
|
cursor.execute(
|
||||||
|
"INSERT INTO users VALUES (?, ?, ?)", (int(user), user_card, user_locale)
|
||||||
|
)
|
||||||
|
|
||||||
|
cursor.connection.commit()
|
||||||
|
rename(Path("data/database.json"), Path("data/database.migrated.json"))
|
3
modules/scheduler.py
Normal file
3
modules/scheduler.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
|
|
||||||
|
scheduler = AsyncIOScheduler()
|
@ -1,33 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from libbot import json_read, sync
|
|
||||||
|
|
||||||
|
|
||||||
async def string(key: str, *args: str, userlocale="uk"):
|
|
||||||
locales = await json_read("strings.json")
|
|
||||||
strings = locales[userlocale]
|
|
||||||
string = strings
|
|
||||||
for dict_key in args:
|
|
||||||
string = string[dict_key]
|
|
||||||
return string[key]
|
|
||||||
|
|
||||||
|
|
||||||
def userSet(userid, key: str, value):
|
|
||||||
database = sync.json_read("data/database.json")
|
|
||||||
if str(userid) not in database:
|
|
||||||
database[str(userid)] = {}
|
|
||||||
database[str(userid)][key] = value
|
|
||||||
sync.json_write(database, "data/database.json")
|
|
||||||
|
|
||||||
|
|
||||||
def userReset(userid, key: str):
|
|
||||||
database = sync.json_read("data/database.json")
|
|
||||||
del database[str(userid)][key]
|
|
||||||
sync.json_write(database, "data/database.json")
|
|
||||||
|
|
||||||
|
|
||||||
def userGet(userid, key: str):
|
|
||||||
try:
|
|
||||||
return sync.json_read("data/database.json")[str(userid)][key]
|
|
||||||
except (KeyError, FileNotFoundError):
|
|
||||||
return None
|
|
@ -1,63 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from traceback import format_exc
|
|
||||||
|
|
||||||
from libbot import config_get, json_read
|
|
||||||
from pyrogram import filters
|
|
||||||
from pyrogram.client import Client
|
|
||||||
from pyrogram.enums.chat_action import ChatAction
|
|
||||||
from pyrogram.types import Message
|
|
||||||
|
|
||||||
from modules.utils import string, userGet
|
|
||||||
from modules.bwt import getWaterLeft
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@Client.on_message(
|
|
||||||
~filters.scheduled & filters.command(["balance", "баланс"], prefixes=["/", ""]) # type: ignore
|
|
||||||
)
|
|
||||||
async def command_balance(app: Client, msg: Message):
|
|
||||||
try:
|
|
||||||
if "card" in (await json_read("data/database.json"))[str(msg.from_user.id)]:
|
|
||||||
await app.send_chat_action(chat_id=msg.chat.id, action=ChatAction.TYPING)
|
|
||||||
water_left = await getWaterLeft(
|
|
||||||
userGet(msg.from_user.id, "card"), msg.from_user.id, app
|
|
||||||
)
|
|
||||||
if water_left == "":
|
|
||||||
await msg.reply_text(
|
|
||||||
(await string("error_new")).format(
|
|
||||||
f'https://bwtaqua.com.ua/card-topup/?id={userGet(msg.from_user.id, "card")}'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# raise EmptyCardException("Card information is empty")
|
|
||||||
elif water_left == "Failure":
|
|
||||||
await msg.reply_text(
|
|
||||||
(await string("error_occured")).format(await string("get_number"))
|
|
||||||
)
|
|
||||||
logger.warning(
|
|
||||||
f"User {str(msg.from_user.id)} could not get left water amount"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await msg.reply_text((await string("card_balance")).format(water_left))
|
|
||||||
logger.info(
|
|
||||||
f"User {str(msg.from_user.id)} has {water_left} liters remaining"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await msg.reply_text(
|
|
||||||
(await string("card_not_linked")).format(await string("get_number"))
|
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
f"User {str(msg.from_user.id)} tried to get balance without card set"
|
|
||||||
)
|
|
||||||
except Exception as exp:
|
|
||||||
if msg.from_user.id != app.owner:
|
|
||||||
await msg.reply_text(
|
|
||||||
(await string("error_occured")).format(await string("get_number"))
|
|
||||||
)
|
|
||||||
await app.send_message(
|
|
||||||
app.owner,
|
|
||||||
f"Error occured by {str(msg.from_user.id)}:\nException: `{exp}`\nTraceback: `{format_exc()}`",
|
|
||||||
)
|
|
||||||
logger.warning(f"User {str(msg.from_user.id)} could not get left water amount")
|
|
43
plugins/commands/balance.py
Normal file
43
plugins/commands/balance.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from pyrogram import filters
|
||||||
|
from pyrogram.enums.chat_action import ChatAction
|
||||||
|
from pyrogram.types import Message
|
||||||
|
|
||||||
|
from classes.pyroclient import PyroClient
|
||||||
|
from modules.bwt_scrape import get_balance
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@PyroClient.on_message(
|
||||||
|
~filters.scheduled & filters.private & filters.command(["balance"], prefixes=["/"]) # type: ignore
|
||||||
|
)
|
||||||
|
async def command_balance(app: PyroClient, message: Message):
|
||||||
|
user = await app.find_user(message.from_user)
|
||||||
|
|
||||||
|
if user.card is None:
|
||||||
|
logger.info("User %s tried to get balance without card set", user.id)
|
||||||
|
await message.reply_text(
|
||||||
|
app._("card_not_linked", "messages", locale=user.locale).format(
|
||||||
|
notice=app._("get_number", "messages", locale=user.locale)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await app.send_chat_action(chat_id=message.chat.id, action=ChatAction.TYPING)
|
||||||
|
balance = get_balance(user.card)
|
||||||
|
|
||||||
|
if balance is None or balance == "":
|
||||||
|
logger.warning("User %s could not get water balance of their card", user.id)
|
||||||
|
await message.reply_text(
|
||||||
|
app._("card_error", "messages", locale=user.locale).format(
|
||||||
|
link=f"https://bwtaqua.com.ua/card-topup/?id={user.card}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("User %s has %s liters on balance", user.id, balance)
|
||||||
|
await message.reply_text(
|
||||||
|
app._("card_balance", "messages", locale=user.locale).format(balance=balance)
|
||||||
|
)
|
12
plugins/commands/remove_commands.py
Normal file
12
plugins/commands/remove_commands.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from pyrogram import filters
|
||||||
|
from pyrogram.types import Message
|
||||||
|
|
||||||
|
from classes.pyroclient import PyroClient
|
||||||
|
|
||||||
|
|
||||||
|
@PyroClient.on_message(
|
||||||
|
~filters.scheduled & filters.private & filters.command(["remove_commands"], prefixes=["/"]) # type: ignore
|
||||||
|
)
|
||||||
|
async def command_remove_commands(app: PyroClient, message: Message):
|
||||||
|
await message.reply_text("Okay.")
|
||||||
|
await app.remove_commands(command_sets=await app.collect_commands())
|
30
plugins/commands/resetcard.py
Normal file
30
plugins/commands/resetcard.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from pyrogram import filters
|
||||||
|
from pyrogram.types import Message
|
||||||
|
|
||||||
|
from classes.pyroclient import PyroClient
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@PyroClient.on_message(
|
||||||
|
~filters.scheduled
|
||||||
|
& filters.command(["resetcard", "забути картку"], prefixes=["/", ""]) # type: ignore
|
||||||
|
)
|
||||||
|
async def command_resetcard(app: PyroClient, message: Message):
|
||||||
|
user = await app.find_user(message.from_user)
|
||||||
|
|
||||||
|
if user.card is None:
|
||||||
|
logger.info("User %s tried to reset their card, but it's null", user.id)
|
||||||
|
await message.reply_text(
|
||||||
|
app._("card_not_linked", "messages", locale=user.locale).format(
|
||||||
|
notice=app._("get_number", "messages", locale=user.locale)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await user.update_card(None)
|
||||||
|
|
||||||
|
logger.info("User %s has reset their card", user.id)
|
||||||
|
await message.reply_text(app._("card_unlinked", "messages", locale=user.locale))
|
53
plugins/commands/setcard.py
Normal file
53
plugins/commands/setcard.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from convopyro import listen_message
|
||||||
|
from pyrogram import filters
|
||||||
|
from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove
|
||||||
|
|
||||||
|
from classes.pyroclient import PyroClient
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@PyroClient.on_message(
|
||||||
|
~filters.scheduled
|
||||||
|
& filters.command(["setcard", "задати картку"], prefixes=["/", ""]) # type: ignore
|
||||||
|
)
|
||||||
|
async def command_setcard(app: PyroClient, message: Message):
|
||||||
|
user = await app.find_user(message.from_user)
|
||||||
|
|
||||||
|
await message.reply_text(
|
||||||
|
app._("send_number", "messages", locale=user.locale),
|
||||||
|
reply_markup=ForceReply(
|
||||||
|
placeholder=app._("enter_number", "force_replies", locale=user.locale)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
answer = await listen_message(app, message.chat.id, timeout=500)
|
||||||
|
|
||||||
|
if (
|
||||||
|
answer is None
|
||||||
|
or answer.text is None
|
||||||
|
or answer.text.strip()
|
||||||
|
in [
|
||||||
|
"/cancel",
|
||||||
|
"cancel",
|
||||||
|
"/відміна",
|
||||||
|
"відміна",
|
||||||
|
]
|
||||||
|
):
|
||||||
|
await message.reply_text(
|
||||||
|
app._("cancel", "messages", locale=user.locale),
|
||||||
|
reply_markup=ReplyKeyboardRemove(),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await user.update_card(answer.text)
|
||||||
|
|
||||||
|
logger.info("User %s set their card id to %s", user.id, answer.text)
|
||||||
|
await message.reply_text(
|
||||||
|
app._("card_linked", "messages", locale=user.locale).format(
|
||||||
|
card_id=answer.text
|
||||||
|
),
|
||||||
|
reply_markup=ReplyKeyboardRemove(),
|
||||||
|
)
|
15
plugins/commands/shutdown.py
Normal file
15
plugins/commands/shutdown.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from pyrogram import filters
|
||||||
|
from pyrogram.types import Message
|
||||||
|
|
||||||
|
from classes.pyroclient import PyroClient
|
||||||
|
|
||||||
|
|
||||||
|
@PyroClient.on_message(
|
||||||
|
~filters.scheduled
|
||||||
|
& filters.command(["shutdown", "reboot", "restart"], prefixes=["/", ""]) # type: ignore
|
||||||
|
)
|
||||||
|
async def command_shutdown(app: PyroClient, message: Message):
|
||||||
|
if message.from_user.id == app.owner:
|
||||||
|
asyncio.get_event_loop().create_task(app.stop())
|
17
plugins/commands/start.py
Normal file
17
plugins/commands/start.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from pyrogram import filters
|
||||||
|
from pyrogram.types import Message
|
||||||
|
|
||||||
|
from classes.pyroclient import PyroClient
|
||||||
|
|
||||||
|
|
||||||
|
@PyroClient.on_message(
|
||||||
|
~filters.scheduled & filters.private & filters.command(["start", "welcome", "help"], prefixes=["/", ""]) # type: ignore
|
||||||
|
)
|
||||||
|
async def command_start(app: PyroClient, message: Message):
|
||||||
|
user = await app.find_user(message.from_user)
|
||||||
|
|
||||||
|
await message.reply_text(
|
||||||
|
app._("welcome", "messages", locale=user.locale).format(
|
||||||
|
notice=app._("get_number", "messages", locale=user.locale)
|
||||||
|
)
|
||||||
|
)
|
30
plugins/commands/topup.py
Normal file
30
plugins/commands/topup.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from pyrogram import filters
|
||||||
|
from pyrogram.types import Message
|
||||||
|
|
||||||
|
from classes.pyroclient import PyroClient
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@PyroClient.on_message(
|
||||||
|
~filters.scheduled
|
||||||
|
& filters.command(["topup", "refill", "поповнити"], prefixes=["/", ""]) # type: ignore
|
||||||
|
)
|
||||||
|
async def command_topup(app: PyroClient, message: Message):
|
||||||
|
user = await app.find_user(message.from_user)
|
||||||
|
|
||||||
|
if user.card is None:
|
||||||
|
logger.info("User %s tried to get card's top-up link, but it's null", user.id)
|
||||||
|
await message.reply_text(
|
||||||
|
app._("card_not_linked", "messages", locale=user.locale).format(
|
||||||
|
notice=app._("get_number", "messages", locale=user.locale)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("User %s requested top-up link", user.id)
|
||||||
|
await message.reply_text(
|
||||||
|
app._("top_up", "messages", locale=user.locale).format(card_id=user.card)
|
||||||
|
)
|
@ -1,24 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from libbot import json_read
|
|
||||||
from pyrogram import filters
|
|
||||||
from pyrogram.client import Client
|
|
||||||
from pyrogram.types import Message
|
|
||||||
|
|
||||||
from modules.utils import string, userSet
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@Client.on_message(
|
|
||||||
~filters.scheduled
|
|
||||||
& filters.command(["start", "help", "допомога"], prefixes=["/", ""]) # type: ignore
|
|
||||||
)
|
|
||||||
async def command_help(app: Client, msg: Message):
|
|
||||||
await msg.reply_text((await string("welcome")).format(await string("get_number")))
|
|
||||||
if msg.from_user.language_code in await json_read("strings.json"):
|
|
||||||
userSet(msg.from_user.id, "locale", msg.from_user.language_code)
|
|
||||||
else:
|
|
||||||
userSet(msg.from_user.id, "locale", "en")
|
|
45
plugins/language.py
Normal file
45
plugins/language.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from pykeyboard import InlineButton, InlineKeyboard
|
||||||
|
from pyrogram import filters
|
||||||
|
from pyrogram.types import CallbackQuery, Message
|
||||||
|
|
||||||
|
from classes.callbacks import CallbackLanguage
|
||||||
|
from classes.pyroclient import PyroClient
|
||||||
|
|
||||||
|
|
||||||
|
@PyroClient.on_message(
|
||||||
|
~filters.scheduled & filters.private & filters.command(["language"], prefixes=["/"]) # type: ignore
|
||||||
|
)
|
||||||
|
async def command_language(app: PyroClient, message: Message):
|
||||||
|
user = await app.find_user(message.from_user)
|
||||||
|
|
||||||
|
keyboard = InlineKeyboard(row_width=2)
|
||||||
|
buttons: List[InlineButton] = []
|
||||||
|
|
||||||
|
for locale, data in app.in_every_locale("metadata").items():
|
||||||
|
buttons.append(
|
||||||
|
InlineButton(f"{data['flag']} {data['name']}", f"language:{locale}")
|
||||||
|
)
|
||||||
|
|
||||||
|
keyboard.add(*buttons)
|
||||||
|
|
||||||
|
await message.reply_text(
|
||||||
|
app._("locale_choice", "messages", locale=user.locale),
|
||||||
|
reply_markup=keyboard,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@PyroClient.on_callback_query(filters.regex(r"language:[\s\S]*")) # type: ignore
|
||||||
|
async def callback_language(app: PyroClient, callback: CallbackQuery):
|
||||||
|
user = await app.find_user(callback.from_user)
|
||||||
|
parsed = CallbackLanguage.from_callback(callback)
|
||||||
|
|
||||||
|
await user.update_locale(parsed.language)
|
||||||
|
|
||||||
|
await callback.answer(
|
||||||
|
app._("locale_set", "callbacks", locale=parsed.language).format(
|
||||||
|
locale=app._("name", "metadata", locale=parsed.language)
|
||||||
|
),
|
||||||
|
show_alert=True,
|
||||||
|
)
|
@ -1,28 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from libbot import json_read
|
|
||||||
from pyrogram import filters
|
|
||||||
from pyrogram.client import Client
|
|
||||||
from pyrogram.types import Message
|
|
||||||
|
|
||||||
from modules.utils import string, userReset
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@Client.on_message(
|
|
||||||
~filters.scheduled
|
|
||||||
& filters.command(["resetcard", "забути картку"], prefixes=["/", ""]) # type: ignore
|
|
||||||
)
|
|
||||||
async def command_resetcard(app: Client, msg: Message):
|
|
||||||
if "card" in (await json_read("data/database.json"))[str(msg.from_user.id)]:
|
|
||||||
userReset(msg.from_user.id, "card")
|
|
||||||
await msg.reply_text(await string("card_unlinked"))
|
|
||||||
logger.info(f"User {str(msg.from_user.id)} reseted his card")
|
|
||||||
else:
|
|
||||||
await msg.reply_text(
|
|
||||||
(await string("card_not_linked")).format(await string("get_number"))
|
|
||||||
)
|
|
||||||
logger.info(f"User {str(msg.from_user.id)} tried to reset non-existent card")
|
|
@ -1,35 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from convopyro import listen_message
|
|
||||||
from pyrogram import filters
|
|
||||||
from pyrogram.client import Client
|
|
||||||
from pyrogram.types import ForceReply, Message, ReplyKeyboardRemove
|
|
||||||
|
|
||||||
from modules.utils import string, userSet
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@Client.on_message(
|
|
||||||
~filters.scheduled
|
|
||||||
& filters.command(["setcard", "задать карту"], prefixes=["/", ""]) # type: ignore
|
|
||||||
)
|
|
||||||
async def command_setcard(app: Client, msg: Message):
|
|
||||||
await msg.reply_text(
|
|
||||||
await string("send_number"),
|
|
||||||
reply_markup=ForceReply(placeholder=await string("enter_number")),
|
|
||||||
)
|
|
||||||
answer = await listen_message(app, msg.chat.id, timeout=None)
|
|
||||||
if answer is None:
|
|
||||||
return
|
|
||||||
elif answer.text.strip() in ["/cancel", "cancel", "/відміна", "відміна"]:
|
|
||||||
await msg.reply_text(await string("cancel"), reply_markup=ReplyKeyboardRemove())
|
|
||||||
return
|
|
||||||
userSet(answer.from_user.id, "card", answer.text)
|
|
||||||
logger.info(f"User {str(msg.from_user.id)} set card id to {answer.text}")
|
|
||||||
await msg.reply_text(
|
|
||||||
(await string("card_linked")).format(answer.text),
|
|
||||||
reply_markup=ReplyKeyboardRemove(),
|
|
||||||
)
|
|
@ -1,21 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from os import getpid
|
|
||||||
|
|
||||||
from libbot import config_get
|
|
||||||
from pyrogram import filters
|
|
||||||
from pyrogram.client import Client
|
|
||||||
from pyrogram.types import Message
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@Client.on_message(
|
|
||||||
~filters.scheduled & filters.command(["kill", "die", "shutdown"], prefixes="/") # type: ignore
|
|
||||||
)
|
|
||||||
async def command_shutdown(app: Client, msg: Message):
|
|
||||||
if msg.from_user.id == app.owner:
|
|
||||||
await msg.reply_text(f"Shutting down bot with pid **{getpid()}**")
|
|
||||||
logger.info(f"Shutting down as requested by {msg.from_user.id}")
|
|
||||||
exit()
|
|
@ -1,36 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from libbot import json_read
|
|
||||||
from pyrogram import filters
|
|
||||||
from pyrogram.client import Client
|
|
||||||
from pyrogram.enums.chat_action import ChatAction
|
|
||||||
from pyrogram.types import Message
|
|
||||||
|
|
||||||
from modules.utils import string, userGet
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
@Client.on_message(
|
|
||||||
~filters.scheduled
|
|
||||||
& filters.command(["topup", "refill", "поповнити"], prefixes=["/", ""]) # type: ignore
|
|
||||||
)
|
|
||||||
async def command_topup(app: Client, msg: Message):
|
|
||||||
try:
|
|
||||||
if "card" in (await json_read("data/database.json"))[str(msg.from_user.id)]:
|
|
||||||
await app.send_chat_action(chat_id=msg.chat.id, action=ChatAction.TYPING)
|
|
||||||
await msg.reply_text(
|
|
||||||
(await string("top_up")).format(str(userGet(msg.from_user.id, "card")))
|
|
||||||
)
|
|
||||||
logger.info(f"User {str(msg.from_user.id)} requested top up")
|
|
||||||
else:
|
|
||||||
await msg.reply_text(
|
|
||||||
(await string("card_not_linked")).format(await string("get_number"))
|
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
f"User {str(msg.from_user.id)} tried to request top up without card set"
|
|
||||||
)
|
|
||||||
except Exception as exp:
|
|
||||||
await msg.reply_text(str(exp))
|
|
@ -1,9 +1,10 @@
|
|||||||
beautifulsoup4==4.12.2
|
apscheduler~=3.10.4
|
||||||
|
beautifulsoup4~=4.12.2
|
||||||
convopyro==0.5
|
convopyro==0.5
|
||||||
pathlib~=1.0.1
|
pykeyboard==0.1.5
|
||||||
pyrogram==2.0.106
|
requests-html==0.10.0
|
||||||
|
selenium~=4.11.2
|
||||||
tgcrypto==1.2.5
|
tgcrypto==1.2.5
|
||||||
ujson==5.8.0
|
|
||||||
uvloop==0.17.0
|
uvloop==0.17.0
|
||||||
--extra-index-url https://git.end-play.xyz/api/packages/profitroll/pypi/simple
|
--extra-index-url https://git.end-play.xyz/api/packages/profitroll/pypi/simple
|
||||||
libbot[speed,pyrogram]==2.0.1
|
libbot[speed,pyrogram]==2.0.1
|
30
strings.json
30
strings.json
@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"en": {
|
|
||||||
"welcome": "Welcome!\n\nThis bot allows you to get liters left on your personal BWT card.\n\n**Commands:**\n • /balance – get card balance\n • /setcard – link your card\n • /resetcard – unlink your card\n\n{0}\n\nDeveloper **is not affiliated with BWT Aqua** and this bot is made for personal usage only.",
|
|
||||||
"get_number": "**Get card number (Var. 1):**\nOn the front bottom side of your card, number may be found\n\n**Get card number (Var. 2):**\n1. Scan QR on the card\n2. Open webpage from code\n3. Numer should be found in **Номер карти \"Здорова Вода\"** or **Номер карти BWT Aqua** fields",
|
|
||||||
"card_linked": "Linked card: `{0}`\n\nPlease, make sure the number is correct before using the bot",
|
|
||||||
"card_unlinked": "Card was unlinked from your Telegram",
|
|
||||||
"card_not_linked": "You don't have any linked card.\n\nВYou can set it using /setcard\n\n{0}",
|
|
||||||
"error_occured": "An error occurred while getting the amount of remaining water on the card.\n\nPlease make sure the linked card number is correct. If you are sure that the bot is broken, please contact @profitroll.\n\nLink your card: /setcard\n\n{0}",
|
|
||||||
"error_new": "An error occurred while getting the amount of remaining water on the card.\n\nLast a few weeks BWT seems to return empty string to balance request from our server. We assume that our server has been blacklisted.\n\nTo check your balance you can use official [BWT App](https://bwtaqua.com.ua/en/#app) or simply bookmark this page: {0}.",
|
|
||||||
"card_balance": "Card's balance is {0} l. of water",
|
|
||||||
"top_up": "[Click here to top up](https://bwtaqua.com.ua/card-topup/?id={0})",
|
|
||||||
"cancel": "Operation cancelled",
|
|
||||||
"enter_number": "Enter card number",
|
|
||||||
"send_number": "Please, send your card number\nIf you want to abort this operation, use /cancel"
|
|
||||||
},
|
|
||||||
"uk": {
|
|
||||||
"welcome": "Привіт-привіт!\n\nЦей бот дозволяє дізнатись скільки літрів залишилось на вашій карточці.\n\n**Команди:**\n • /balance – дізнатись баланс карти\n • /setcard – приав'язати карту\n • /resetcard – відв'язати карту\n\n{0}\n\nРозробник **не має жодного відношення до BWT Aqua**, а бот створений лише для особистого, некомерційного використання.",
|
|
||||||
"get_number": "**Дізнатись номер картки (Вар. 1):**\nЗ лицевої сторони картки знизу може бут вказано номер цієї картки\n\n**Дізнатись номер картки (Вар. 2):**\n1. Отсканувати QR код на картці\n2. Відкрити веб-сторінку з кода\n3. Номер буде знаходитись в полі **Номер карти \"Здорова Вода\"** або **Номер карти BWT Aqua**",
|
|
||||||
"card_linked": "Прив'язана карточка: `{0}`\n\nБудь ласка, упевніться що номер правильний перед використанням інших команд",
|
|
||||||
"card_unlinked": "Картку відв'язано від вашого Telegram",
|
|
||||||
"card_not_linked": "У вас немає прив'язаної картки.\n\nВи можете зробити це за допомогою команди /setcard\n\n{0}",
|
|
||||||
"error_occured": "При отриманні води на карточці виникла помилка.\n\nБудь ласка, упевніться що номер карти правильний. Якщо ви впевнені, що номер картки правильний та бот зламався – зв'яжіться з @profitroll.\n\nПрив'язати карту: /setcard\n\n{0}",
|
|
||||||
"error_new": "При отриманні води на карточці виникла помилка.\n\nОстанні тижні BWT повертає нашому серверу порожні строки замість балансу. Є підозри, що сервер потрапив у блеклист.\n\nДля перевірки балансу рекомендуємо користуватись офіційним [додатком BWT](https://bwtaqua.com.ua/#app) або просто додати цю сторінку у закладки: {0}.",
|
|
||||||
"card_balance": "На карточці {0} л. води",
|
|
||||||
"top_up": "[Натисніть для поповнення](https://bwtaqua.com.ua/card-topup/?id={0})",
|
|
||||||
"cancel": "Операцію скасовано",
|
|
||||||
"enter_number": "Введіть номер картки",
|
|
||||||
"send_number": "Будь ласка, надішліть номер вашої картки\nЯкщо ви хочете скасувати цю операцію, використовуйте /cancel"
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user