diff --git a/Dockerfile b/Dockerfile index d7a95b47..5ad9bfa7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ FROM yasirarism/misskaty-docker:py3.12 ENV HOSTNAME=yasir-server # Copy Files COPY . . -# Instal pip package +# Instal pip package if you use free depedencies # RUN pip3 install --no-cache-dir -r requirements.txt # Set CMD Bot CMD ["bash", "start.sh"] diff --git a/database/payment_db.py b/database/payment_db.py new file mode 100644 index 00000000..915df43b --- /dev/null +++ b/database/payment_db.py @@ -0,0 +1,16 @@ +from database import dbname +from typing import Optional + +autopay = dbname["autpay"] + + +async def delete_autopay(uniqueCode: str): + await autopay.delete_one({"_id": uniqueCode}) + +async def get_autopay(self, uniqueCode: str): + exists = await autopay.find_one({"_id": uniqueCode}) + return exists + +async def autopay_update(self, msg_id: Optional[int] = "", note: Optional[str] = "", user_id: Optional[int] = "", amount: Optional[int] = "", status: Optional[str] = "", uniqueCode: Optional[str] = "", createdAt: Optional[str] = ""): + data = {"msg_id": msg_id, "note": note, "user_id": user_id, "amount": amount, "status": status, "createdAt": createdAt} + await autopay.update_one({"_id": uniqueCode}, {"$set": data}, upsert=True) \ No newline at end of file diff --git a/misskaty/__init__.py b/misskaty/__init__.py index 8dc0c40b..892f8653 100644 --- a/misskaty/__init__.py +++ b/misskaty/__init__.py @@ -8,12 +8,13 @@ from asyncio import get_event_loop from faulthandler import enable as faulthandler_enable from logging import ERROR, INFO, StreamHandler, basicConfig, getLogger, handlers -import uvloop +import uvloop, uvicorn from apscheduler.jobstores.mongodb import MongoDBJobStore from apscheduler.schedulers.asyncio import AsyncIOScheduler from async_pymongo import AsyncClient from pymongo import MongoClient from pyrogram import Client +from web.webserver import api from misskaty.vars import ( API_HASH, @@ -21,6 +22,7 @@ from misskaty.vars import ( BOT_TOKEN, DATABASE_NAME, DATABASE_URI, + PORT, TZ, USER_SESSION, ) @@ -83,6 +85,11 @@ jobstores = { } scheduler = AsyncIOScheduler(jobstores=jobstores, timezone=TZ) +async def run_wsgi(): + config = uvicorn.Config(api, host="0.0.0.0", port=int(PORT)) + server = uvicorn.Server(config) + await server.serve() + app.start() BOT_ID = app.me.id BOT_NAME = app.me.first_name diff --git a/misskaty/__main__.py b/misskaty/__main__.py index 9ab25ecf..95f76f5f 100644 --- a/misskaty/__main__.py +++ b/misskaty/__main__.py @@ -24,6 +24,7 @@ from misskaty import ( app, get_event_loop, scheduler, + run_wsgi ) from misskaty.plugins import ALL_MODULES from misskaty.plugins.web_scraper import web @@ -56,7 +57,6 @@ async def start_bot(): LOGGER.info(bot_modules) LOGGER.info("+===============+===============+===============+===============+") LOGGER.info("[INFO]: BOT STARTED AS @%s!", BOT_USERNAME) - try: LOGGER.info("[INFO]: SENDING ONLINE STATUS") for i in SUDO: @@ -73,6 +73,7 @@ async def start_bot(): except Exception as e: LOGGER.error(str(e)) scheduler.start() + asyncio.create_task(run_wsgi()) if "web" not in await dbname.list_collection_names(): webdb = dbname["web"] for key, value in web.items(): diff --git a/misskaty/plugins/dev.py b/misskaty/plugins/dev.py index 93c356f7..85ae71be 100644 --- a/misskaty/plugins/dev.py +++ b/misskaty/plugins/dev.py @@ -4,10 +4,12 @@ import html import io import json import os +import hashlib import pickle import platform import privatebinapi import re +import secrets import sys import traceback from datetime import datetime @@ -22,6 +24,7 @@ import cloudscraper import requests from apscheduler.schedulers.asyncio import AsyncIOScheduler from bs4 import BeautifulSoup +from urllib.parse import quote from PIL import Image, ImageDraw, ImageFont from psutil import Process, boot_time, cpu_count, cpu_percent from psutil import disk_usage as disk_usage_percent @@ -44,6 +47,7 @@ from pyrogram.types import ( LabeledPrice, Message, PreCheckoutQuery, + WebAppInfo, ) from database.gban_db import add_gban_user, is_gbanned_user, remove_gban_user @@ -55,7 +59,8 @@ from misskaty.helper.functions import extract_user, extract_user_and_reason from misskaty.helper.http import fetch from misskaty.helper.human_read import get_readable_file_size, get_readable_time from misskaty.helper.localization import use_chat_lang -from misskaty.vars import AUTO_RESTART, COMMAND_HANDLER, LOG_CHANNEL, SUDO +from database.payment_db import autopay_update +from misskaty.vars import AUTO_RESTART, COMMAND_HANDLER, LOG_CHANNEL, SUDO, PAYDISINI_CHANNEL_ID, PAYDISINI_KEY __MODULE__ = "DevCommand" __HELP__ = """ @@ -179,6 +184,45 @@ async def log_file(_, ctx: Message, strings): else: await msg.edit_msg("Unsupported parameter") +@app.on_message(filters.command(["payment"], COMMAND_HANDLER)) +async def payment(client: Client, message: Message): + api_url = 'https://api.paydisini.co.id/v1/' + api_key = PAYDISINI_KEY + unique_id = f"VIP-{secrets.token_hex(5)}" + amount = "10000" if len(message.command) == 1 else str(message.command[1]) + id_ = message.from_user.id if message.chat.id != message.from_user.id else message.chat.id + valid_time = str(5*60) + service_id = PAYDISINI_CHANNEL_ID + + params = { + 'key': api_key, + 'request': 'new', + 'unique_code': unique_id, + 'service': service_id, + 'amount': amount, + 'note': f'MissKaty Support by YS Dev', + 'valid_time': valid_time, + 'type_fee': '1', + 'payment_guide': True, + 'signature': hashlib.md5((api_key + unique_id + service_id + amount + valid_time + 'NewTransaction').encode()).hexdigest(), + 'return_url': f'https://t.me/{client.me.username}?start' + } + # if id_ in user_data and user_data[id_].get("is_auth"): + # return await message.reply("Already Authorized!") + rget = await fetch.post(api_url, data=params) + if rget.status_code != 200: + return await message.reply("ERROR: Maybe your IP is not whitelisted or have another error from api.") + res = rget.json() + if not res.get("success"): + return await message.reply(res["msg"]) + qr_photo = f"https://api.qrserver.com/v1/create-qr-code/?size=300x300&data={quote(res['data']['qr_content'])}" + capt = f"𝗠𝗲𝗻𝘂𝗻𝗴𝗴𝘂 𝗽𝗲𝗺𝗯𝗮𝘆𝗮𝗿𝗮𝗻\nKode: {res['data']['unique_code']}\nNote: {res['data']['note']}\nHarga: {res['data']['amount']}\nFee: {res['data']['fee']}\nExpired: {res['data']['expired']}\n\n" + payment_guide = f"{res['payment_guide'][0]['title']}:\n" + "\n".join(f"{i+1}. {step}" for i, step in enumerate(res["payment_guide"][0]['content'])) + if message.chat.type.value != "private": + msg = await message.reply_photo(qr_photo, caption=capt+payment_guide, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text="Payment Web", url=res["data"]["checkout_url_v2"])]]), quote=True) + else: + msg = await message.reply_photo(qr_photo, caption=capt+payment_guide, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(text="Payment Web", web_app=WebAppInfo(url=res["data"]["checkout_url_v2"]))]]), quote=True) + await autopay_update(msg.id, res["data"]["note"], id_, res['data']['amount'], res['data']['status'], res['data']['unique_code'], res['data']['created_at']) @app.on_message(filters.command(["donate"], COMMAND_HANDLER)) async def donate(self: Client, ctx: Message): diff --git a/misskaty/vars.py b/misskaty/vars.py index 21103c1a..8b60656b 100644 --- a/misskaty/vars.py +++ b/misskaty/vars.py @@ -50,6 +50,7 @@ LOG_GROUP_ID = environ.get("LOG_GROUP_ID") USER_SESSION = environ.get("USER_SESSION") DATABASE_NAME = environ.get("DATABASE_NAME", "MissKatyDB") TZ = environ.get("TZ", "Asia/Jakarta") +PORT = environ.get("PORT", 80) COMMAND_HANDLER = environ.get("COMMAND_HANDLER", "! /").split() SUDO = list( { @@ -64,6 +65,8 @@ SUPPORT_CHAT = environ.get("SUPPORT_CHAT", "YasirPediaChannel") AUTO_RESTART = environ.get("AUTO_RESTART", False) OPENAI_KEY = environ.get("OPENAI_KEY") GOOGLEAI_KEY = environ.get("GOOGLEAI_KEY") +PAYDISINI_KEY = environ.get("PAYDISINI_KEY") +PAYDISINI_CHANNEL_ID = environ.get("PAYDISINI_CHANNEL_ID", "17") ## Config For AUtoForwarder # Forward From Chat ID diff --git a/requirements.txt b/requirements.txt index e30cf195..517d7cf5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,3 +34,6 @@ GitPython aiofiles uvloop==0.19.0 lxml_html_clean +fastapi +uvicorn +python-multipart diff --git a/web/webserver.py b/web/webserver.py new file mode 100644 index 00000000..84871642 --- /dev/null +++ b/web/webserver.py @@ -0,0 +1,98 @@ +from logging import INFO, StreamHandler, basicConfig, getLogger, ERROR, handlers +from os import path +from time import time +from datetime import datetime, timedelta + +from fastapi import FastAPI, Request +from fastapi.responses import HTMLResponse, JSONResponse +from starlette.exceptions import HTTPException +from psutil import boot_time, disk_usage, net_io_counters +from contextlib import suppress +from asyncio import to_thread, subprocess, create_subprocess_shell +from apscheduler.triggers.date import DateTrigger +import hashlib + +api = FastAPI() + +basicConfig( + level=INFO, + format="[%(levelname)s] - [%(asctime)s - %(name)s - %(message)s] -> [%(module)s:%(lineno)d]", + datefmt="%d-%b-%y %H:%M:%S", + handlers=[ + handlers.RotatingFileHandler( + "MissKatyLogs.txt", mode="w+", maxBytes=5242880, backupCount=1 + ), + StreamHandler(), + ], +) +botStartTime = time() + +LOGGER = getLogger(__name__) +getLogger("fastapi").setLevel(ERROR) + +@api.post("/callback") +async def autopay(request: Request): + from misskaty import app + from database.payment_db import delete_autopay, get_autopay + from misskaty.vars import PAYDISINI_KEY, OWNER_ID + data = await request.form() + client_ip = request.client.host + if PAYDISINI_KEY != data["key"] and client_ip != "84.247.150.90": + raise HTTPException(status_code=403, detail="Access forbidden") + signature_data = f"{PAYDISINI_KEY}{data['unique_code']}CallbackStatus" + gen_signature = hashlib.md5(signature_data.encode()).hexdigest() + if gen_signature != data["signature"]: + raise HTTPException(status_code=403, detail="Invalid Signature") + unique_code = data['unique_code'] + status = data['status'] + exp_date = (datetime.now(jkt) + timedelta(days=30)).strftime("%Y-%m-%d %H:%M:%S") + r = await get_autopay(unique_code) + msg = f"╭────〔 TRANSAKSI SUKSES🎉 〕──\n│・ Transaksi ID : {unique_code}\n│・ Product : MissKaty Support by YS Dev\n│・ Durasi : 30 hari\n│・ Total Dibayar : {r.get('amount')}\n│・ Langganan Berakhir: {exp_date}\n╰─────────" + if not r: + return JSONResponse({"status": false, "data": "Data not found on DB"}, 404) + if status == "Success": + with suppress(Exception): + await bot.send_message(r.get("user_id"), f"{msg}\n\nJika ada pertanyaan silahkan hubungi pemilik bot ini.") + await bot.delete_messages(r.get("user_id"), r.get("msg_id")) + await bot.send_message(OWNER_ID, msg) + await delete_autopay(unique_code) + return JSONResponse({"status": status, "msg": "Pesanan berhasil dibayar oleh customer."}, 200) + else: + with suppress(Exception): + await bot.send_message(r.get("user_id"), "QRIS Telah Expired, Silahkan Buat Transaksi Baru.") + await bot.delete_messages(r.get("user_id"), r.get("msg_id")) + await delete_autopay(unique_code) + return JSONResponse({"status": status, "msg": "Pesanan telah dibatalkan/gagal dibayar."}, 403) + +@api.get("/status") +async def status(): + from misskaty.helper.human_read import get_readable_file_size, get_readable_time + bot_uptime = get_readable_time(time() - botStartTime) + uptime = get_readable_time(time() - boot_time()) + sent = get_readable_file_size(net_io_counters().bytes_sent) + recv = get_readable_file_size(net_io_counters().bytes_recv) + if path.exists(".git"): + commit_date = (await (await create_subprocess_shell("git log -1 --date=format:'%y/%m/%d %H:%M' --pretty=format:'%cd'", stdout=subprocess.PIPE, stderr=subprocess.STDOUT)).communicate())[0].decode() + else: + commit_date = "No UPSTREAM_REPO" + return { + "commit_date": commit_date, + "uptime": uptime, + "on_time": bot_uptime, + "free_disk": get_readable_file_size(disk_usage(".").free), + "total_disk": get_readable_file_size(disk_usage(".").total), + "network": { + "sent": sent, + "recv": recv, + }, + } + + +@api.api_route("/") +async def homepage(): + return "Hello World" + + +@api.exception_handler(HTTPException) +async def page_not_found(request: Request, exc: HTTPException): + return HTMLResponse(content=f"