Compare commits

...

115 Commits
v1.0 ... master

Author SHA1 Message Date
Profitroll cb3ec42f95 Update .renovaterc 2024-03-27 23:14:57 +02:00
Profitroll 43557d6c65 Merge pull request 'Update dependency selenium to ~=4.19.0' (#66) from renovate/selenium-4.x into master
Reviewed-on: #66
2024-03-27 23:13:02 +02:00
Renovate b008b7158c Update dependency selenium to ~=4.19.0 2024-03-27 17:38:55 +02:00
Profitroll 6569ff308f Merge pull request 'Update dependency selenium to ~=4.18.0' (#65) from renovate/selenium-4.x into master
Reviewed-on: #65
2024-02-19 22:50:07 +02:00
Renovate bc740ae6c8 Update dependency selenium to ~=4.18.0 2024-02-19 16:52:46 +02:00
Profitroll 851c3d2399 Update requirements.txt 2024-01-28 21:11:27 +02:00
Profitroll c1ef0778cd Merge pull request 'Update dependency pykeyboard to v0.1.7' (#63) from renovate/pykeyboard-0.x into master
Reviewed-on: #63
2024-01-28 21:11:11 +02:00
Renovate b602a6ecef Update dependency pykeyboard to v0.1.7 2024-01-28 20:59:36 +02:00
Profitroll 6cd54a54f9 Merge pull request 'Update dependency selenium to ~=4.17.2' (#64) from renovate/selenium-4.x into master
Reviewed-on: #64
2024-01-28 20:29:53 +02:00
Renovate 270a99cd85 Update dependency selenium to ~=4.17.2 2024-01-23 17:12:14 +02:00
Profitroll edd58eb830 Merge pull request 'Update dependency libbot to v2.1.0' (#61) from renovate/libbot-2.x into master
Reviewed-on: #61
2023-12-28 13:37:08 +02:00
Renovate 69e8196f6f Update dependency libbot to v2.1.0 2023-12-27 16:03:13 +02:00
Profitroll fc8c3ac7d2 Merge pull request 'Update dependency selenium to ~=4.16.0' (#59) from renovate/selenium-4.x into master
Reviewed-on: #59
2023-12-10 13:03:42 +02:00
Renovate f132b69750 Update dependency selenium to ~=4.16.0 2023-12-06 16:32:24 +02:00
Profitroll 1176d599cd Update dependency selenium to ~=4.15.0 (#49) 2023-11-01 18:28:22 +02:00
Renovate 1a5d402cb0 Update dependency selenium to ~=4.15.0 2023-11-01 16:40:54 +02:00
Profitroll e52e3a6a36 Merge pull request 'Update dependency uvloop to v0.19.0' (#42) from renovate/uvloop-0.x into master
Reviewed-on: #42
2023-10-29 19:34:55 +02:00
Renovate 594b393b71 Update dependency uvloop to v0.19.0 2023-10-23 02:06:52 +03:00
Profitroll 0a61d9549c Merge pull request 'Update dependency uvloop to v0.18.0' (#36) from renovate/uvloop-0.x into master
Reviewed-on: #36
2023-10-15 17:48:45 +03:00
Renovate d626d96eff Update dependency uvloop to v0.18.0 2023-10-14 00:26:45 +03:00
Profitroll 81947b502c Merge pull request 'Update dependency selenium to ~=4.14.0' (#35) from renovate/selenium-4.x into master
Reviewed-on: #35
2023-10-10 09:13:41 +03:00
Renovate 6afb65dfca Update dependency selenium to ~=4.14.0 2023-10-10 07:17:35 +03:00
Profitroll 0a1d029ed8 Merge pull request 'Update dependency selenium to ~=4.13.0' (#31) from renovate/selenium-4.x into master
Reviewed-on: #31
2023-09-28 23:28:34 +03:00
Renovate 623be3e8ea Update dependency selenium to ~=4.13.0 2023-09-25 22:22:13 +03:00
Profitroll fde956d2bc Merge pull request 'Update dependency selenium to ~=4.12.0' (#30) from renovate/selenium-4.x into master
Reviewed-on: #30
2023-09-02 10:39:27 +03:00
Renovate 83efc26aa5 Update dependency selenium to ~=4.12.0 2023-08-31 23:10:30 +03:00
Profitroll 3d139fda27
Fixed main script name 2023-08-23 14:24:12 +02:00
Profitroll 745e89c8a6
Added a few chrome options 2023-08-23 14:19:39 +02:00
Profitroll fbb22875c9
Big and tasty update to v2.0 2023-08-23 14:13:17 +02:00
Profitroll ec35817895 Merge pull request 'Update dependency puppeteer to ~21.1.0' (#29) from renovate/puppeteer-21.x into master
Reviewed-on: #29
2023-08-18 13:26:58 +03:00
Renovate c5a906d405 Update dependency puppeteer to ~21.1.0 2023-08-18 12:32:54 +03:00
Profitroll 38a211a661 Merge pull request 'Update dependency libbot to v2.0.1' (#28) from renovate/libbot-2.x into master
Reviewed-on: #28
2023-08-11 11:31:27 +03:00
Renovate 43d3f075cf Update dependency libbot to v2.0.1 2023-08-10 22:50:22 +03:00
Profitroll 7271624519 Merge pull request 'Update dependency libbot to v2' (#27) from renovate/libbot-2.x into master
Reviewed-on: #27
2023-08-07 13:08:08 +03:00
Renovate 58a1ad0926 Update dependency libbot to v2 2023-08-07 13:05:27 +03:00
Profitroll 03ca3d1eb7
Bump libbot to 0.2.2 2023-08-06 22:15:10 +02:00
Profitroll e1734376fe Merge pull request 'Update dependency puppeteer to v21' (#26) from renovate/puppeteer-21.x into master
Reviewed-on: #26
2023-08-02 17:31:44 +03:00
Renovate 18391544f2 Update dependency puppeteer to v21 2023-08-02 16:24:25 +03:00
Profitroll d97f2fa591 Merge pull request 'Update dependency libbot to v1.9' (#25) from renovate/libbot-1.x into master
Reviewed-on: #25
2023-07-26 15:28:12 +03:00
Renovate f17440a6b8 Update dependency libbot to v1.9 2023-07-26 15:23:58 +03:00
Profitroll 91f701491e Merge pull request 'Update dependency puppeteer to ~20.9.0' (#24) from renovate/puppeteer-20.x into master
Reviewed-on: #24
2023-07-20 11:31:11 +03:00
Renovate e412d09cec Update dependency puppeteer to ~20.9.0 2023-07-20 11:18:57 +03:00
Profitroll 3da8e9c074
Replaced owner_id with app.owner 2023-07-14 12:42:47 +02:00
Profitroll 0fea30ccfd
Moving to example config 2023-07-14 12:40:00 +02:00
Profitroll ff82e19a4f
Replaced app 2023-07-14 12:39:12 +02:00
Profitroll 01f8a73dae
WIP: Improvements 2023-07-14 12:36:42 +02:00
Profitroll d9b72e5ad8
Typo fixed 2023-07-14 12:08:40 +02:00
Profitroll 9d33ca744a
Added executable path 2023-07-14 12:07:06 +02:00
Profitroll 93096eb52b Merge pull request 'Update dependency puppeteer to ~20.8.0' (#23) from renovate/puppeteer-20.x into master
Reviewed-on: #23
2023-07-06 18:51:32 +03:00
Renovate 1ef0976e34 Update dependency puppeteer to ~20.8.0 2023-07-06 18:09:32 +03:00
Profitroll 0ff4ac2cb5 Merge pull request 'Update dependency libbot to v1.8' (#22) from renovate/libbot-1.x into master
Reviewed-on: #22
2023-07-03 13:54:16 +03:00
Renovate a78b471785 Update dependency libbot to v1.8 2023-07-03 12:01:33 +03:00
Profitroll a44d059b5d Merge pull request 'Update dependency libbot to v1.7' (#21) from renovate/libbot-1.x into master
Reviewed-on: #21
2023-06-30 12:46:48 +03:00
Renovate 7ac5252429 Update dependency libbot to v1.7 2023-06-30 11:45:28 +03:00
Profitroll 79b8ebf7d0 Merge pull request 'Update dependency libbot to v1.6' (#20) from renovate/libbot-1.x into master
Reviewed-on: #20
2023-06-29 17:49:54 +03:00
Renovate 209cc60226 Update dependency libbot to v1.6 2023-06-29 17:10:59 +03:00
Profitroll 5391ccfb75 Merge pull request 'Update dependency libbot to v1.5' (#19) from renovate/libbot-1.x into master
Reviewed-on: #19
2023-06-26 15:03:44 +03:00
Renovate 6db861d54b Update dependency libbot to v1.5 2023-06-26 15:00:02 +03:00
Profitroll 3170274a13 Merge pull request 'Update dependency libbot to v1' (#18) from renovate/libbot-1.x into master
Reviewed-on: #18
2023-06-20 14:13:05 +03:00
Renovate 32d9f76e38 Update dependency libbot to v1 2023-06-20 14:02:10 +03:00
Profitroll 0b82d39aab Merge pull request 'Update dependency puppeteer to ~20.7.0' (#16) from renovate/puppeteer-20.x into master
Reviewed-on: #16
2023-06-14 11:48:42 +03:00
Profitroll cf522ab254 Merge pull request 'Update dependency libbot to v0.8' (#15) from renovate/libbot-0.x into master
Reviewed-on: #15
2023-06-14 11:48:20 +03:00
Renovate 7be86f04c2 Update dependency puppeteer to ~20.7.0 2023-06-14 11:40:54 +03:00
Renovate 4bc5ffb867 Update dependency libbot to v0.8 2023-06-14 11:40:51 +03:00
Profitroll 1be04dbea0 Merge pull request 'Update dependency ujson to v5.8.0' (#14) from renovate/ujson-5.x into master
Reviewed-on: #14
2023-06-11 12:29:24 +03:00
Renovate b4b102421f Update dependency ujson to v5.8.0 2023-06-11 12:17:53 +03:00
Profitroll 852c7d962a Merge pull request 'Update dependency libbot to v0.7' (#12) from renovate/libbot-0.x into master
Reviewed-on: #12
2023-06-04 12:46:09 +03:00
Renovate d7c393b5cd Update dependency libbot to v0.7 2023-06-03 20:50:31 +03:00
Profitroll c2828f1baf Merge pull request 'Update dependency puppeteer to ~20.5.0' (#13) from renovate/puppeteer-20.x into master
Reviewed-on: #13
2023-06-03 20:47:26 +03:00
Renovate de3ecc22be Update dependency puppeteer to ~20.5.0 2023-05-31 16:35:01 +03:00
Profitroll d98afc53ca Merge pull request 'Update dependency puppeteer to ~20.4.0' (#11) from renovate/puppeteer-20.x into master
Reviewed-on: #11
2023-05-25 11:45:57 +03:00
Renovate 997da7bd2f Update dependency puppeteer to ~20.4.0 2023-05-25 08:20:21 +03:00
Profitroll 4a90544b52 Added node_modules to ignore 2023-05-22 20:52:44 +02:00
Profitroll 45d8c830d6 Merge pull request 'Update dependency puppeteer to ~20.3.0' (#10) from renovate/puppeteer-20.x into master
Reviewed-on: #10
2023-05-22 14:54:21 +03:00
Renovate 8ca5916be5 Update dependency puppeteer to ~20.3.0 2023-05-22 11:28:51 +03:00
Profitroll 185235a336 Moved command registration to the events on start 2023-05-16 20:53:59 +02:00
Profitroll ccb9e79cd7 Fixed naming error 2023-05-16 20:51:48 +02:00
Profitroll 69d63ca1ce Large refactor 2023-05-16 20:50:18 +02:00
Profitroll fe5f531c8d Updated ignore 2023-05-16 20:49:53 +02:00
Profitroll 671040bd3b Merge pull request 'Update dependency puppeteer to ~20.2.0' (#9) from renovate/puppeteer-20.x into master
Reviewed-on: #9
2023-05-11 23:05:53 +03:00
Renovate 0037b22937 Update dependency puppeteer to ~20.2.0 2023-05-11 23:04:22 +03:00
Profitroll 767ae6b20a Merge pull request 'Update dependency puppeteer to v20' (#8) from renovate/puppeteer-20.x into master
Reviewed-on: #8
2023-05-08 11:50:46 +03:00
Profitroll 65dc06617d Merge pull request 'Update dependency pyrogram to v2.0.106' (#7) from renovate/pyrogram-2.x into master
Reviewed-on: #7
2023-05-08 11:50:33 +03:00
Renovate e38ab26c93 Update dependency puppeteer to v20 2023-05-08 11:46:30 +03:00
Renovate 73a140c0bb Update dependency pyrogram to v2.0.106 2023-05-08 11:46:26 +03:00
Profitroll c2142416a8 Merge pull request 'Update dependency puppeteer to ~19.11.0' (#6) from renovate/puppeteer-19.x into master
Reviewed-on: #6
2023-04-24 14:45:27 +03:00
Renovate 2b2de71719 Update dependency puppeteer to ~19.11.0 2023-04-24 14:37:01 +03:00
Profitroll 585865a61f Merge pull request 'Update dependency pyrogram to v2.0.104' (#5) from renovate/pyrogram-2.x into master
Reviewed-on: #5
2023-04-23 17:32:24 +03:00
Renovate 435a2cc8c7 Update dependency pyrogram to v2.0.104 2023-04-23 12:07:18 +03:00
Profitroll e1bed772ef Made a few dependencies strict 2023-04-23 10:31:35 +02:00
Profitroll 5a99610aa8 Merge pull request 'Update dependency puppeteer to ~19.10.0' (#4) from renovate/puppeteer-19.x into master
Reviewed-on: #4
2023-04-21 09:55:03 +03:00
Renovate 2dc3cefda4 Update dependency puppeteer to ~19.10.0 2023-04-20 19:16:26 +03:00
Profitroll eb913eb42a Merge pull request 'Update dependency puppeteer to ~19.9.0' (#2) from renovate/puppeteer-19.x into master
Reviewed-on: #2
2023-04-20 14:29:12 +03:00
Renovate 8ac38405cf Update dependency puppeteer to ~19.9.0 2023-04-20 14:27:58 +03:00
Profitroll 36cbb1a41f Renamed Renovate config 2023-04-20 13:27:14 +02:00
Profitroll d74826f0b4 Merge pull request 'Configure Renovate' (#1) from renovate/configure into master
Reviewed-on: #1
2023-04-20 14:24:40 +03:00
Renovate b23f0f2436 Add renovate.json 2023-04-20 14:13:38 +03:00
Profitroll 4f423257f3 Bump pyrogram and beautifulsoup4 2023-04-19 14:31:21 +02:00
Profitroll 9fb509fc2b Changed conversation handler to convopyro 2023-03-18 01:15:21 +01:00
Profitroll 82a659cce2 Formatted README 2023-03-18 00:52:36 +01:00
Profitroll c3dd6f61d6 Formatted everything with black 2023-03-18 00:52:15 +01:00
Profitroll dcf82ab6f2 Bump BS4 and Pyrogram versions 2023-03-18 00:47:45 +01:00
Profitroll 1dd8b13297 Improved logging 2023-01-14 13:25:18 +01:00
Profitroll 200f25e130 Updated dependencies 2023-01-14 13:25:09 +01:00
Profitroll a09c3fb0d4 New error message added 2023-01-14 13:25:02 +01:00
Profitroll affb54155c User agent randomized 2023-01-14 13:24:44 +01:00
Profitroll b97b10975d Removed unused imports 2022-12-30 22:15:24 +01:00
Profitroll 27e204d3cc Updated requirements 2022-12-30 22:13:18 +01:00
Profitroll 4fd4f0a6a4 Improved imports 2022-12-30 22:13:10 +01:00
Profitroll 19b83c0631 Using .run instead of .system now 2022-12-30 21:03:04 +01:00
Profitroll fa8bdc0e1f ignoring .vscode now 2022-12-15 12:05:22 +01:00
Profitroll b9a7d85674 Improved requirements 2022-12-15 12:05:14 +01:00
Profitroll fe1c6984b2 Option to use compiled page saver 2022-09-08 13:04:37 +02:00
Profitroll e7ef1d4613 Added encoding header 2022-09-08 12:54:43 +02:00
Profitroll 85a756dcab Added license badge 2022-09-08 12:54:13 +02:00
36 changed files with 765 additions and 445 deletions

11
.gitignore vendored
View File

@ -152,3 +152,14 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Custom
config.json
*.session
*.session-journal
venv
venv_linux
venv_windows
.vscode
data/

21
.renovaterc Normal file
View File

@ -0,0 +1,21 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"baseBranches": [
"dev"
],
"packageRules": [
{
"matchUpdateTypes": [
"minor",
"patch",
"pin",
"digest"
],
"automerge": true
}
]
}

View File

@ -1,5 +0,0 @@
{
"dependencies": {
"puppeteer": "^14.4.0"
}
}

Binary file not shown.

View File

@ -1,18 +0,0 @@
// npm install https://github.com/GoogleChrome/puppeteer/
const puppeteer = require('puppeteer');
(async () => {
const url = process.argv[2];
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url, {waitUntil: 'load'});
const html = await page.content();
browser.close();
console.log(html);
})();

View File

@ -1,26 +1,41 @@
# BWTAqua
[![License: GPL v3](https://img.shields.io/badge/License-GPL_v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0.html)
Simple yet helpful bot to check BWT Aqua's card balance
## Requirements
* nodejs & npm
* python3
* Python 3.8+
* git
## Installation
1. Download package
1. `git clone https://git.end-play.xyz/profitroll/BWTAqua.git`
2. `cd BWTAqua`
2. Install needed modules:
* `python3 -m pip install -r requirements.txt`
3. Install PageSaver:
1. `cd PageSaver`
2. `npm install`
4. Configure the bot:
1. `cd ..`
2. `nano config.json` (You can use any other text editor actually, for example `vim`)
5. Run the bot:
* `python3 bwtbot.py`
2. Create venv
1. `python3 -m venv .venv`
2. `source .venv/bin/activate`
3. Install needed modules
* `pip install -r requirements.txt`
4. Configure the bot
* `nano config.json` (You can use any other text editor actually, for example `vim`)
5. Run the bot
* `python main.py`
## Configuration
You can edit with vim, nano, on Windows it's Notepad or Notepad++. 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).
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).
## 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.

173
bwtbot.py
View File

@ -1,173 +0,0 @@
#-*- coding: utf-8 -*-
import traceback
from pyrogram import Client, filters, idle
from pyrogram.types import ForceReply, BotCommand, BotCommandScopeChat
from pyrogram.enums.chat_action import ChatAction
from functions import *
from modules.colors import *
from modules.bwt import *
import subprocess
import os
config = jsonLoad("config.json")
owner_id = config["owner_id"]
app = Client(config["bot_name"], api_id=config["api_id"], api_hash=config["api_hash"], bot_token=config["bot_token"])
@app.on_message(~ filters.scheduled & filters.command(["setcard", "задать карту"], prefixes=["/", ""]))
async def setcard(_, msg):
if userGet(msg.from_user.id, "context") is None:
userSet(msg.from_user.id, "context", "set")
await msg.reply_text(string("send_number"), reply_markup=ForceReply(placeholder=string("enter_number")))
else:
await msg.reply_text(string("cancel_first"))
@app.on_message(~ filters.scheduled & filters.command(["cancel", "відміна"], prefixes=["/", ""]))
async def cancel(_, msg):
if userGet(msg.from_user.id, "context") is not None:
userReset(msg.from_user.id, "context")
await msg.reply_text(string("cancel"))
else:
await msg.reply_text(string("cancel_none"))
@app.on_message(~ filters.scheduled & filters.command(["resetcard", "забути картку"], prefixes=["/", ""]))
async def resetcard(_, msg):
if userGet(msg.from_user.id, "context") is None:
if "card" in jsonLoad("data/database.json")[str(msg.from_user.id)]:
userReset(msg.from_user.id, "card")
await msg.reply_text(string("card_unlinked"))
appendLog(f"User {str(msg.from_user.id)} reseted his card")
else:
await msg.reply_text(string("card_not_linked").format(string("get_number")))
appendLog(f"User {str(msg.from_user.id)} tried to reset non-existent card")
else:
await msg.reply_text(string("cancel_first"))
@app.on_message(~ filters.scheduled & filters.command(["balance", "баланс"], prefixes=["/", ""]))
async def balance(_, msg):
if userGet(msg.from_user.id, "context") is None:
try:
if "card" in jsonLoad("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 == "":
raise EmptyCardException("Card information is empty")
elif water_left == "Failure":
await msg.reply_text(string("error_occured").format(string("get_number")))
appendLog(f"User {str(msg.from_user.id)} could not get left water amount")
else:
await msg.reply_text(string("card_balance").format(water_left))
appendLog(f"User {str(msg.from_user.id)} has {water_left} liters remaining")
else:
await msg.reply_text(string("card_not_linked").format(string("get_number")))
appendLog(f"User {str(msg.from_user.id)} tried to get balance without card set")
except Exception as exp:
await msg.reply_text(string("error_occured").format(string("get_number")))
await app.send_message(owner_id, f"Error occured by {str(msg.from_user.id)}:\nException: `{exp}`\nTraceback: `{traceback.format_exc()}`")
appendLog(f"User {str(msg.from_user.id)} could not get left water amount")
else:
await msg.reply_text(string("cancel_first"))
@app.on_message(~ filters.scheduled & filters.command(["topup", "refill", "поповнити"], prefixes=["/", ""]))
async def topup_cmd(_, msg):
if userGet(msg.from_user.id, "context") is None:
try:
if "card" in jsonLoad("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(string("top_up").format(str(userGet(msg.from_user.id, "card"))))
appendLog(f"User {str(msg.from_user.id)} requested top up")
else:
await msg.reply_text(string("card_not_linked").format(string("get_number")))
appendLog(f"User {str(msg.from_user.id)} tried to request top up without card set")
except Exception as exp:
await msg.reply_text(exp)
else:
await msg.reply_text(string("cancel_first"))
@app.on_message(~ filters.scheduled & filters.command(["start", "help", "допомога"], prefixes=["/", ""]))
async def help(_, msg):
if userGet(msg.from_user.id, "context") is None:
await msg.reply_text(string("welcome").format(string("get_number")))
if msg.from_user.language_code in jsonLoad("strings.json"):
userSet(msg.from_user.id, "locale", msg.from_user.language_code)
else:
userSet(msg.from_user.id, "locale", "en")
else:
await msg.reply_text(string("cancel_first"))
pid = os.getpid()
@app.on_message(~ filters.scheduled & filters.command(["kill", "die", "shutdown"], prefixes="/"))
async def kill(_, msg):
if msg.from_user.id == owner_id:
await msg.reply_text(f"Shutting down bot with pid **{pid}**")
os.system(f"kill -9 {pid}")
@app.on_message(~ filters.scheduled)
async def any_message_handler(app, msg):
if userGet(msg.from_user.id, "context") == "set":
userSet(msg.from_user.id, "card", msg.text)
userReset(msg.from_user.id, "context")
appendLog(f"User {str(msg.from_user.id)} set card id to {msg.text}")
await msg.reply_text(string("card_linked").format(msg.text))
print(f'{nowtime()} {WHITE}Starting with PID {YELLOW}{pid}{RESET}')
app.start()
app.send_message(owner_id, f"Starting bot with pid **{pid}**")
app.set_bot_commands([
BotCommand("help", "Меню допомоги"),
BotCommand("balance", "Баланс картки"),
BotCommand("topup", "Поповнити картку"),
BotCommand("setcard", "Прив'язати картку"),
BotCommand("resetcard", "Відв'язати картку"),
BotCommand("cancel", "Відмінити операцію"),
],
language_code="uk")
app.set_bot_commands([
BotCommand("help", "Меню допомоги"),
BotCommand("balance", "Баланс картки"),
BotCommand("topup", "Поповнити картку"),
BotCommand("setcard", "Прив'язати картку"),
BotCommand("resetcard", "Відв'язати картку"),
BotCommand("cancel", "Відмінити операцію"),
],
language_code="ru")
app.set_bot_commands([
BotCommand("help", "Help menu"),
BotCommand("balance", "Card's balance"),
BotCommand("topup", "Refill card"),
BotCommand("setcard", "Link card"),
BotCommand("resetcard", "Unlink card"),
BotCommand("cancel", "Cancel operation"),
])
app.set_bot_commands([
BotCommand("help", "Help menu"),
BotCommand("balance", "Card's balance"),
BotCommand("topup", "Refill card"),
BotCommand("setcard", "Link card"),
BotCommand("resetcard", "Unlink card"),
BotCommand("shutdown", "Turn off the bot"),
BotCommand("cancel", "Cancel operation"),
],
scope=BotCommandScopeChat(chat_id=owner_id))
idle()
app.send_message(owner_id, f"Shutting down bot with pid **{pid}**")
print(f'\n{nowtime()} {WHITE}Shutting down with PID {YELLOW}{pid}{RESET}')
subprocess.call(f'kill -9 {pid}', shell=True)

28
classes/callbacks.py Normal file
View 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
View 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
View 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
View 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"
}
]
}
}

View File

@ -1,8 +0,0 @@
{
"owner_id": 0,
"log_size": 1024,
"api_id": 0,
"api_hash": "",
"bot_token": "",
"bot_name": ""
}

15
config_example.json Normal file
View File

@ -0,0 +1,15 @@
{
"locale": "en",
"bot": {
"owner": 0,
"api_id": 0,
"api_hash": "",
"bot_token": "",
"scoped_commands": true
},
"database": "data/database.db",
"reports": {
"chat_id": "owner"
},
"disabled_plugins": []
}

View File

@ -1 +0,0 @@
{}

View File

@ -1,121 +0,0 @@
import json
import os
import shutil
import gzip
import time
from modules.colors import *
from datetime import datetime
from pathlib import Path
path = Path(__file__).resolve().parent
days_path = str(path)+"/assets/days/"
users_path = str(path)+"/users/"
logs_folder = str(path)+"/logs/"
def jsonSave(filename, value):
with open(filename, 'w', encoding="utf-8") as f:
json.dump(value, f, indent=4, ensure_ascii=False)
f.close()
def jsonLoad(filename):
with open(filename, 'r', encoding="utf-8") as f:
value = json.load(f)
f.close()
return value
config = jsonLoad(f"{path}/config.json")
log_size = config["log_size"]
owner_id = config["owner_id"]
def nowtime():
return f'{BBLACK}[{CYAN}{datetime.now().strftime("%H:%M:%S")}{BBLACK}]{RESET}'
def checkSize():
global logs_folder, log_size
i = 0
while i < 2:
try:
log = os.stat(logs_folder + 'latest.log')
if (log.st_size / 1024) > log_size:
with open(logs_folder + 'latest.log', 'rb') as f_in:
with gzip.open(f'{logs_folder}{datetime.now().strftime("%d.%m.%Y_%H:%M:%S")}.zip', 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
open(logs_folder + 'latest.log', 'w').close()
i = 2
except FileNotFoundError:
try:
log = open(logs_folder + 'latest.log', 'a')
open(logs_folder + 'latest.log', 'a').close()
except:
try:
os.mkdir(logs_folder)
log = open(logs_folder + 'latest.log', 'a')
open(logs_folder + 'latest.log', 'a').close()
except:
pass
i += 1
def appendLog(message):
global logs_folder
checkSize()
try:
log = open(logs_folder + 'latest.log', 'a')
open(logs_folder + 'latest.log', 'a').close()
except:
try:
os.mkdir(logs_folder)
log = open(logs_folder + 'latest.log', 'a')
open(logs_folder + 'latest.log', 'a').close()
except:
time.sleep(2)
print('Log file could not be created')
return
log.write(f'[{datetime.now().strftime("%H:%M:%S | %d.%m.%Y")}] {message}\n')
log.close()
def string(key: str, *args: str, userlocale="uk"):
locales = jsonLoad("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 = jsonLoad("data/database.json")
if str(userid) not in database:
database[str(userid)] = {}
database[str(userid)][key] = value
jsonSave("data/database.json", database)
def userReset(userid, key: str):
database = jsonLoad("data/database.json")
del database[str(userid)][key]
jsonSave("data/database.json", database)
def userGet(userid, key: str):
try:
return jsonLoad("data/database.json")[str(userid)][key]
except KeyError:
return None
except FileNotFoundError:
return None

40
locale/en.json Normal file
View File

@ -0,0 +1,40 @@
{
"metadata": {
"flag": "🇬🇧",
"name": "English",
"codes": [
"en",
"en-US",
"en-GB"
]
},
"commands": {
"help": "Help menu",
"balance": "Card's balance",
"topup": "Refill the card",
"setcard": "Link the card",
"resetcard": "Unlink the card",
"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"
}
}

39
locale/uk.json Normal file
View File

@ -0,0 +1,39 @@
{
"metadata": {
"flag": "🇺🇦",
"name": "Українська",
"codes": [
"uk",
"uk-UA"
]
},
"commands": {
"help": "Меню допомоги",
"balance": "Баланс картки",
"topup": "Поповнити картку",
"setcard": "Прив'язати картку",
"resetcard": "Відв'язати картку",
"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": "Введіть номер картки"
}
}

View File

66
main.py Normal file
View 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()

View File

@ -1,50 +0,0 @@
#-*- coding: utf-8 -*-
import os
import traceback
from functions import *
from bs4 import BeautifulSoup
config = jsonLoad("config.json")
class EmptyCardException(Exception):
pass
async def getWaterLeft(cardid, filename, app=None):
url = f"https://bwtaqua.com.ua/card-topup/?id={cardid}"
try:
os.system(f'touch data/pages/{str(filename)}.html')
os.system(f'PageSaver/pageSaver "https://bwtaqua.com.ua/card-topup/?id={cardid}" > data/pages/{str(filename)}.html')
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(" л", "")
appendLog(f"Parsed {output} liters of water remaining (user: {str(filename)}, cardid: {cardid})")
except Exception as exp:
appendLog(f"Exception occured: {exp} (user: {str(filename)}, cardid: {cardid})")
if app != None:
await app.send_message(config["owner_id"], f"**Exception occured:**\n • User: `{str(filename)}`\n • Card: [{cardid}]({url})\n • Exception: `{exp}`\n • Traceback: `{traceback.format_exc()}`", disable_web_page_preview=True)
else:
print(f'Exception occured 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")

36
modules/bwt_scrape.py Normal file
View File

@ -0,0 +1,36 @@
from typing import Union
from bs4 import BeautifulSoup
from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
def get_balance(card_id: Union[str, int]) -> Union[str, None]:
chrome_options = Options()
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-dev-shm-usage')
driver = Chrome(options=chrome_options)
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))

View File

@ -1,22 +0,0 @@
RESET = '\u001b[0m'
BLACK = '\u001b[30m'
RED = '\u001b[31m'
GREEN = '\u001b[32m'
YELLOW = '\u001b[33m'
BLUE = '\u001b[34m'
MAGENTA = '\u001b[35m'
CYAN = '\u001b[36m'
WHITE = '\u001b[37m'
BBLACK = '\u001b[30;1m'
BRED = '\u001b[31;1m'
BGREEN = '\u001b[32;1m'
BYELLOW = '\u001b[33;1m'
BBLUE = '\u001b[34;1m'
BMAGENTA = '\u001b[35;1m'
BCYAN = '\u001b[36;1m'
BWHITE = '\u001b[37;1m'
ULINE = '\u001b[4m'
REVERSE = '\u001b[7m'

11
modules/database.py Normal file
View 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
View 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
View File

@ -0,0 +1,3 @@
from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler = AsyncIOScheduler()

View 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)
)

View 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())

View 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))

View 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(),
)

View 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
View 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
View 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)
)

45
plugins/language.py Normal file
View 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,
)

View File

@ -1 +1,10 @@
beautifulsoup4
apscheduler~=3.10.4
beautifulsoup4~=4.12.2
convopyro==0.5
pykeyboard==0.1.7
requests-html==0.10.0
selenium~=4.19.0
tgcrypto==1.2.5
uvloop==0.19.0
--extra-index-url https://git.end-play.xyz/api/packages/profitroll/pypi/simple
libbot[speed,pyrogram]==3.0.0

View File

@ -1,32 +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Вы можете задать её с помощью команды /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}",
"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",
"cancel_none": "Nothing to cancel",
"cancel_first": "Operation ongoing. Cancel the current one using /cancel to run this action",
"enter_number": "Enter card number",
"send_number": "Please, send your card number"
},
"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}",
"card_balance": "На карточці {0} л. води",
"top_up": "[Натисніть для поповнення](https://bwtaqua.com.ua/card-topup/?id={0})",
"cancel": "Операція відмінена",
"cancel_none": "Нема що відміняти",
"cancel_first": "Триває інша операція. Відмініть триваючу операцію командою /cancel щоб запустити іншу дію",
"enter_number": "Введіть номер картки",
"send_number": "Будь ласка, надішліть номер вашої картки"
}
}