telegram payment

This commit is contained in:
Benny 2023-01-07 12:11:26 +01:00
parent 3cbfe3f1f1
commit 7a7b7f3172
No known key found for this signature in database
GPG key ID: 6CD0DBDA5235D481
6 changed files with 148 additions and 77 deletions

View file

@ -441,13 +441,15 @@ ping - Bot running status
help - Help
ytdl - Download video in group
vip - Join VIP
terms - View Terms of Service
settings - Set your preference
direct - Download file directly
sub - Subscribe to YouTube Channel
unsub - Unsubscribe from YouTube Channel
sub_count - Check subscription status, owner only.
uncache - Delete cache for this link
uncache - Delete cache for this link, owner only.
purge - Delete all tasks , owner only.
topup - Top up quota
tgvip - Using Telegram payment to pay for VIP
```
# Test data

View file

@ -27,7 +27,7 @@ TG_MAX_SIZE = 2 * 1024 * 1024 * 1024 * 0.99
# TG_MAX_SIZE = 10 * 1024 * 1024
EX = os.getenv("EX", 24 * 3600)
MULTIPLY = os.getenv("MULTIPLY", 5) # VIP1 is 5*5-25G, VIP2 is 50G
MULTIPLY = os.getenv("MULTIPLY", 10) # VIP1 is 5*5-25G, VIP2 is 50G
USD2CNY = os.getenv("USD2CNY", 6) # $5 --> ¥30
ENABLE_VIP = os.getenv("VIP", False)
@ -58,5 +58,7 @@ ARCHIVE_ID = os.getenv("ARCHIVE_ID")
IPv6 = os.getenv("IPv6", False)
ENABLE_FFMPEG = os.getenv("ENABLE_FFMPEG", False)
RATE = float(os.getenv("RATE", 60 * 5))
# 0.01 means basically no limit
RATE = float(os.getenv("RATE", 0.01))
BURST = int(os.getenv("BURST", 3))
PROVIDER_TOKEN = os.getenv("PROVIDER_TOKEN") or "1234"

View file

@ -22,62 +22,53 @@ class BotText:
start = "Welcome to YouTube Download bot. Type /help for more information."
help = f"""
1. This bot should works at all times. If it doesn't, try to send the link again or DM @BennyThink
1. This bot should works at all times. If it doesn't, wait for a few minutes, try to send the link again.
2. At this time of writing, this bot consumes hundreds of GigaBytes of network traffic per day.
2. At this time of writing, this bot consumes more than 100GB of network traffic per day.
In order to avoid being abused,
every one can use this bot within **{sizeof_fmt(QUOTA)} of quota for every {int(EX / 3600)} hours.**
3. Free users can't receive streaming formats of one video whose duration is longer than 300 seconds.
3. You can optionally choose to become 'VIP' user if you need more traffic. Type /vip for more information.
4. You can optionally choose to become 'VIP' user if you need more traffic. Type /vip for more information.
4. Source code for this bot will always stay open, here-> https://github.com/tgbot-collection/ytdlbot
5. Source code for this bot will always stay open, here-> https://github.com/tgbot-collection/ytdlbot
6. Request limit is applied for everyone, excluding VIP users.
5. Request limit is applied for everyone, excluding VIP users.
""" if ENABLE_VIP else "Help text"
about = "YouTube-DL by @BennyThink. Open source on GitHub: https://github.com/tgbot-collection/ytdlbot"
terms = f"""
1. You can use this service, free of charge, {sizeof_fmt(QUOTA)} per {int(EX / 3600)} hours.
2. The above traffic, is counted for one-way.
For example, if you download a video of 1GB, your current quota will be 9GB instead of 8GB.
3. Streaming support is limited due to high costs of conversion.
4. I won't gather any personal information, which means I don't know how many and what videos did you download.
5. Please try not to abuse this service.
6. It's a open source project, you can always deploy your own bot.
7. For VIPs, please refer to /vip command
""" if ENABLE_VIP else "Please contact the actual owner of this bot"
vip = f"""
**Terms:**
1. No refund, I'll keep it running as long as I can.
2. I'll record your unique ID after a successful payment, usually it's payment ID or email address.
3. VIPs identity won't expire.
1. You can use this service, free of charge, {sizeof_fmt(QUOTA)} per {int(EX / 3600)} hours.
2. The above traffic, is counted one-way. For example, if you download a video of 1GB, your will use 1GB instead of 2GB.
3. Streaming support is limited due to high costs of conversion.
4. I won't gather any personal information, which means I don't know how many and what videos did you download.
5. No rate limit for VIP users.
6. Possible to refund, but you'll have to bear with process fee.
7. I'll record your unique ID after a successful payment, usually it's payment ID or email address.
8. VIP identity won't expire.
9. Please try not to abuse this service. It's a open source project, you can always deploy your own bot.
**Pay Tier:**
1. Everyone: {sizeof_fmt(QUOTA)} per {int(EX / 3600)} hours
2. VIP1: ${MULTIPLY} or ¥{MULTIPLY * USD2CNY}, {sizeof_fmt(QUOTA * 5)} per {int(EX / 3600)} hours
3. VIP2: ${MULTIPLY * 2} or ¥{MULTIPLY * USD2CNY * 2}, {sizeof_fmt(QUOTA * 5 * 2)} per {int(EX / 3600)} hours
2. VIP1: ${MULTIPLY} or ¥{MULTIPLY * USD2CNY}, {sizeof_fmt(QUOTA * 2)} per {int(EX / 3600)} hours
3. VIP2: ${MULTIPLY * 2} or ¥{MULTIPLY * USD2CNY * 2}, {sizeof_fmt(QUOTA * 2 * 2)} per {int(EX / 3600)} hours
4. VIP4....VIPn.
5. Unlimited streaming conversion support.
Note: If you pay $9, you'll become VIP1 instead of VIP2.
**Temporary top up**
Just want more traffic for a short period of time? Don't worry, you can use /topup command to top up your quota.
It's valid permanently, until you use it up.
**Payment method:**
1. (afdian) Mainland China: {AFD_LINK}
2. (buy me a coffee) Other countries or regions: {COFFEE_LINK}
__I live in a place where I don't have access to Telegram Payments. So...__
3. Telegram Payment(stripe), please directly using /tgvip command.
**After payment:**
1. afdian: with your order number `/vip 123456`
2. buy me a coffee: with your email `/vip someone@else.com`
3. Telegram Payment: automatically activated
""" if ENABLE_VIP else "VIP is not enabled."
vip_pay = "Processing your payments...If it's not responding after one minute, please contact @BennyThink."
@ -95,6 +86,8 @@ Video quality: **{0}**
Sending format: **{1}**
"""
custom_text = os.getenv("CUSTOM_TEXT", "")
topup_description = f"US$1 will give you {sizeof_fmt(QUOTA)} traffic permanently"
topup_title = "Pay US$1 for more traffic!"
def remaining_quota_caption(self, chat_id):
if not ENABLE_VIP:

View file

@ -34,26 +34,42 @@ class VIP(Redis, MySQL):
data = self.cur.fetchone()
return data
def add_vip(self, user_data: "dict") -> ("bool", "str"):
def __add_vip(self, user_data: "dict"):
sql = "INSERT INTO vip VALUES (%s,%s,%s,%s,%s,%s);"
self.cur.execute(sql, list(user_data.values()))
self.con.commit()
# also remove redis cache
self.r.delete(user_data["user_id"])
def add_vip(self, user_data: "dict") -> "str":
# first select
self.cur.execute("SELECT * FROM vip WHERE payment_id=%s", (user_data["payment_id"],))
is_exist = self.cur.fetchone()
if is_exist:
return "Failed. {} is being used by user {}".format(user_data["payment_id"], is_exist[0])
self.cur.execute(sql, list(user_data.values()))
self.con.commit()
# also remove redis cache
self.r.delete(user_data["user_id"])
self.__add_vip(user_data)
return "Success! You are VIP{} now!".format(user_data["level"])
def direct_add_vip(self, user_data: "dict") -> ("bool", "str"):
self.__add_vip(user_data)
return "Success payment from Telegram! You are VIP{} now!".format(user_data["level"])
def remove_vip(self, user_id: "int"):
raise NotImplementedError()
def get_user_quota(self, user_id: "int") -> int:
# even VIP have certain quota
q = self.check_vip(user_id)
return q[-1] if q else QUOTA
topup = self.r.hget("topup", user_id)
if q:
return q[-1]
elif topup:
return int(topup) + QUOTA
else:
return QUOTA
def set_topup(self, user_id: "int"):
self.r.hset("topup", user_id, QUOTA)
def check_remaining_quota(self, user_id: "int"):
user_quota = self.get_user_quota(user_id)

View file

@ -32,12 +32,12 @@ from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message
from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
from client_init import create_app
from config import (ARCHIVE_ID, BROKER, ENABLE_CELERY,
ENABLE_QUEUE, ENABLE_VIP, TG_MAX_SIZE, WORKERS)
from config import (ARCHIVE_ID, BROKER, ENABLE_CELERY, ENABLE_QUEUE,
ENABLE_VIP, TG_MAX_SIZE, WORKERS)
from constant import BotText
from db import Redis
from downloader import (edit_text, sizeof_fmt, tqdm_progress,
upload_hook, ytdl_download)
from downloader import (edit_text, sizeof_fmt, tqdm_progress, upload_hook,
ytdl_download)
from limit import VIP
from utils import (apply_log_formatter, auto_restart, customize_logger,
get_metadata, get_revision, get_user_settings)
@ -433,10 +433,12 @@ def run_celery():
argv.extend(["-Q", worker_name])
app.worker_main(argv)
def purge_tasks():
count = app.control.purge()
return f"purged {count} tasks."
if __name__ == '__main__':
celery_client.start()
print("Bootstrapping Celery worker now.....")

View file

@ -20,21 +20,24 @@ import pyrogram.errors
from apscheduler.schedulers.background import BackgroundScheduler
from pyrogram import Client, filters, types
from pyrogram.errors.exceptions.bad_request_400 import UserNotParticipant
from pyrogram.raw import functions
from pyrogram.raw import types as raw_types
from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from tgbot_ping import get_runtime
from token_bucket import Limiter, MemoryStorage
from client_init import create_app
from config import (AUTHORIZED_USER, BURST, ENABLE_CELERY, ENABLE_FFMPEG,
ENABLE_VIP, OWNER, RATE, REQUIRED_MEMBERSHIP)
ENABLE_VIP, MULTIPLY, OWNER, PROVIDER_TOKEN, QUOTA, RATE,
REQUIRED_MEMBERSHIP)
from constant import BotText
from db import InfluxDB, MySQL, Redis
from limit import VIP, verify_payment
from tasks import app as celery_app
from tasks import (audio_entrance, direct_download_entrance, hot_patch, purge_tasks,
ytdl_download_entrance)
from utils import (auto_restart, customize_logger, get_revision, clean_tempfile,
get_user_settings, set_user_settings)
from tasks import (audio_entrance, direct_download_entrance, hot_patch,
purge_tasks, ytdl_download_entrance)
from utils import (auto_restart, clean_tempfile, customize_logger,
get_revision, get_user_settings, set_user_settings)
customize_logger(["pyrogram.client", "pyrogram.session.session", "pyrogram.connection.connection"])
logging.getLogger('apscheduler.executors.default').propagate = False
@ -187,13 +190,6 @@ def about_handler(client: "Client", message: "types.Message"):
client.send_message(chat_id, bot_text.about)
@app.on_message(filters.command(["terms"]))
def terms_handler(client: "Client", message: "types.Message"):
chat_id = message.chat.id
client.send_chat_action(chat_id, "typing")
client.send_message(chat_id, bot_text.terms)
@app.on_message(filters.command(["sub_count"]))
def sub_count_handler(client: "Client", message: "types.Message"):
username = message.from_user.username
@ -268,6 +264,53 @@ def vip_handler(client: "Client", message: "types.Message"):
bm.edit_text(msg)
def generate_invoice(amount: "int", title: "str", description: "str", payload: "bytes"):
invoice = raw_types.input_media_invoice.InputMediaInvoice(
invoice=(
raw_types.invoice.Invoice(currency="USD", prices=[raw_types.LabeledPrice(label="price", amount=amount)])),
title=title,
description=description,
provider=PROVIDER_TOKEN,
provider_data=raw_types.DataJSON(data="{}"),
payload=payload,
)
return invoice
# payment related
@app.on_message(filters.command(["topup"]))
def topup_handler(client: "Client", message: "types.Message"):
chat_id = message.chat.id
client.send_chat_action(chat_id, "typing")
invoice = generate_invoice(100, bot_text.topup_title, bot_text.topup_description,
f"{message.chat.id}-topup".encode())
app.send(
functions.messages.SendMedia(
peer=(raw_types.InputPeerUser(user_id=chat_id, access_hash=0)),
media=invoice,
random_id=app.rnd_id(),
message="Please use your card to pay for more traffic"
)
)
@app.on_message(filters.command(["tgvip"]))
def tgvip_handler(client: "Client", message: "types.Message"):
chat_id = message.chat.id
client.send_chat_action(chat_id, "typing")
invoice = generate_invoice(1000, f"VIP1", f"pay USD${MULTIPLY} for VIP1", f"{message.chat.id}-vip1".encode())
app.send(
functions.messages.SendMedia(
peer=(raw_types.InputPeerUser(user_id=chat_id, access_hash=0)),
media=invoice,
random_id=app.rnd_id(),
message="Please use your card to pay for more traffic"
)
)
@app.on_message(filters.incoming & filters.text)
@private_use
def download_handler(client: "Client", message: "types.Message"):
@ -284,25 +327,6 @@ def download_handler(client: "Client", message: "types.Message"):
red.update_metrics("bad_request")
message.reply_text("I think you should send me a link.", quote=True)
return
# TODO
# red.update_metrics("search_request")
# result = VideosSearch(url, limit=5).result().get("result", [])
# text = ""
# count = 1
# buttons = []
# for item in result:
# text += f"{count}. {item['title']} - {item['link']}\n\n"
# buttons.append(
# InlineKeyboardButton(
# f"{count}",
# callback_data=f"search_{item['id']}"
# )
# )
# count += 1
#
# markup = InlineKeyboardMarkup([buttons])
# client.send_message(chat_id, text, disable_web_page_preview=True, reply_markup=markup)
# return
if re.findall(r"^https://www\.youtube\.com/channel/", VIP.extract_canonical_link(url)) or "list" in url:
message.reply_text("Channel/list download is disabled now. Please send me individual video link.", quote=True)
@ -393,6 +417,38 @@ def periodic_sub_check():
time.sleep(random.random() * 3)
@app.on_raw_update()
def raw_update(client: "Client", update, users, chats):
action = getattr(getattr(update, "message", None), "action", None)
if update.QUALNAME == 'types.UpdateBotPrecheckoutQuery':
client.send(
functions.messages.SetBotPrecheckoutResults(
query_id=update.query_id,
success=True,
)
)
elif action and action.QUALNAME == 'types.MessageActionPaymentSentMe':
logging.info("Payment received. %s", action)
uid = update.message.peer_id.user_id
vip = VIP()
amount = f"{action.total_amount / 100} {action.currency}"
if "vip" in action.payload.decode():
ud = {
"user_id": uid,
"username": users.get(uid).username,
"payment_amount": 10,
"payment_id": 0,
"level": 1,
"quota": QUOTA * 2
}
vip.add_vip(ud)
client.send_message(uid, f"Thank you {uid}. VIP payment received: {amount}")
else:
vip.set_topup(uid)
client.send_message(uid, f"Thank you {uid}. Top up payment received: {amount}")
if __name__ == '__main__':
MySQL()
scheduler = BackgroundScheduler(timezone="Europe/Stockholm", job_defaults={'max_instances': 5})