init, .gitignore
@@ -0,0 +1,8 @@
|
||||
.sync.ffs_db
|
||||
volumes/*
|
||||
traefik/certs/*
|
||||
letsencrypt/acme.json
|
||||
models/*
|
||||
certs/*
|
||||
.env*
|
||||
traefik/letsencrypt/acme.json
|
||||
@@ -0,0 +1,34 @@
|
||||
services:
|
||||
dockmon:
|
||||
image: darthnorse/dockmon:latest
|
||||
container_name: dockmon
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8000:443
|
||||
environment:
|
||||
- TZ=Europe/Bratislava
|
||||
volumes:
|
||||
- ./dockmon_data:/app/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-k", "-f", "https://localhost:443/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
networks:
|
||||
- traefik-proxy
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.dockmon.rule=Host(`dockmon.workstation`)"
|
||||
- "traefik.http.routers.dockmon.entrypoints=websecure"
|
||||
- "traefik.http.services.dockmon.loadbalancer.server.port=443"
|
||||
- "traefik.http.services.dockmon.loadbalancer.server.scheme=https"
|
||||
- "traefik.docker.network=traefik-proxy"
|
||||
- "traefik.http.routers.dockmon.tls.certresolver=le"
|
||||
|
||||
networks:
|
||||
traefik-proxy:
|
||||
external: true
|
||||
|
||||
volumes:
|
||||
dockmon_data:
|
||||
@@ -0,0 +1,15 @@
|
||||
ARG VERSION
|
||||
# Use the official WaterCrawl image as the base image
|
||||
FROM watercrawl/watercrawl:${VERSION:-v0.10.2}
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /var/www
|
||||
|
||||
# Copy the extra requirements file
|
||||
COPY extra_requirements.txt /var/www/extra_requirements.txt
|
||||
|
||||
# Install any additional packages
|
||||
RUN poetry run pip install -r /var/www/extra_requirements.txt
|
||||
|
||||
# The rest of the configuration is inherited from the base image
|
||||
# The entrypoint and command should be defined in docker-compose.yml
|
||||
@@ -0,0 +1 @@
|
||||
# Add your additional Python packages here, one per line
|
||||
@@ -0,0 +1,306 @@
|
||||
x-app: &app
|
||||
build:
|
||||
context: backend/
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- VERSION=${VERSION:-v0.10.2}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
environment:
|
||||
- SECRET_KEY=${SECRET_KEY:-django-insecure-el4wo4a4--=f0+ag#omp@^w4eq^8v4(scda&1a(td_y2@=sh6&}
|
||||
- API_ENCRYPTION_KEY=${API_ENCRYPTION_KEY:-8zSd6JIuC7ovfZ4AoxG_XmhubW6CPnQWW7Qe_4TD1TQ=}
|
||||
- DEBUG=${DEBUG:-True}
|
||||
- ALLOWED_HOSTS=${ALLOWED_HOSTS:-*}
|
||||
- LANGUAGE_CODE=${LANGUAGE_CODE:-en-us}
|
||||
- TIME_ZONE=${TIME_ZONE:-UTC}
|
||||
- USE_I18N=${USE_I18N:-True}
|
||||
- USE_TZ=${USE_TZ:-True}
|
||||
- STATIC_ROOT=${STATIC_ROOT:-storage/static/}
|
||||
- MEDIA_ROOT=${MEDIA_ROOT:-storage/media/}
|
||||
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379/1}
|
||||
- DATABASE_URL=postgres://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@${POSTGRES_HOST:-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
|
||||
- CELERY_BROKER_URL=${CELERY_BROKER_URL:-redis://redis:6379/0}
|
||||
- CELERY_RESULT_BACKEND=${CELERY_RESULT_BACKEND:-django-db}
|
||||
- REDIS_LOCKER_URL=${REDIS_LOCKER_URL:-redis://redis:6379/3}
|
||||
- MINIO_ENDPOINT=minio:9000
|
||||
- MINIO_EXTERNAL_ENDPOINT=nginx
|
||||
- MINIO_REGION=us-east-1
|
||||
- MINIO_ACCESS_KEY=minio
|
||||
- MINIO_SECRET_KEY=minio123
|
||||
- MINIO_USE_HTTPS=False
|
||||
- MINIO_EXTERNAL_ENDPOINT_USE_HTTPS=False
|
||||
- MINIO_URL_EXPIRY_HOURS=7
|
||||
- MINIO_PRIVATE_BUCKET=private
|
||||
- MINIO_PUBLIC_BUCKET=public
|
||||
- CSRF_TRUSTED_ORIGINS=${CSRF_TRUSTED_ORIGINS:-}
|
||||
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-}
|
||||
- CORS_ALLOWED_ORIGIN_REGEXES=${CORS_ALLOWED_ORIGIN_REGEXES:-}
|
||||
- CORS_ALLOW_ALL_ORIGINS=${CORS_ALLOW_ALL_ORIGINS:-False}
|
||||
- FRONTEND_URL=${FRONTEND_URL:-http://localhost}
|
||||
- IS_LOGIN_ACTIVE=${IS_LOGIN_ACTIVE:-True}
|
||||
- IS_SIGNUP_ACTIVE=${IS_SIGNUP_ACTIVE:-True}
|
||||
- IS_GITHUB_LOGIN_ACTIVE=${IS_GITHUB_LOGIN_ACTIVE:-True}
|
||||
- IS_GOOGLE_LOGIN_ACTIVE=${IS_GOOGLE_LOGIN_ACTIVE:-True}
|
||||
- GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID:-}
|
||||
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-}
|
||||
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}
|
||||
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET:-}
|
||||
- ACCESS_TOKEN_LIFETIME_MINUTES=${ACCESS_TOKEN_LIFETIME_MINUTES:-5}
|
||||
- REFRESH_TOKEN_LIFETIME_DAYS=${REFRESH_TOKEN_LIFETIME_DAYS:-30}
|
||||
- EMAIL_BACKEND=${EMAIL_BACKEND:-django.core.mail.backends.smtp.EmailBackend}
|
||||
- EMAIL_HOST=${EMAIL_HOST:-}
|
||||
- EMAIL_PORT=${EMAIL_PORT:-587}
|
||||
- EMAIL_USE_TLS=${EMAIL_USE_TLS:-True}
|
||||
- EMAIL_HOST_USER=${EMAIL_HOST_USER:-}
|
||||
- EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD:-}
|
||||
- DEFAULT_FROM_EMAIL=${DEFAULT_FROM_EMAIL:-}
|
||||
- SCRAPY_USER_AGENT=${SCRAPY_USER_AGENT:-WaterCrawl/0.1 (+https://github.com/watercrawl/watercrawl)}
|
||||
- SCRAPY_ROBOTSTXT_OBEY=${SCRAPY_ROBOTSTXT_OBEY:-True}
|
||||
- SCRAPY_CONCURRENT_REQUESTS=${SCRAPY_CONCURRENT_REQUESTS:-16}
|
||||
- SCRAPY_DOWNLOAD_DELAY=${SCRAPY_DOWNLOAD_DELAY:-0}
|
||||
- SCRAPY_CONCURRENT_REQUESTS_PER_DOMAIN=${SCRAPY_CONCURRENT_REQUESTS_PER_DOMAIN:-4}
|
||||
- SCRAPY_CONCURRENT_REQUESTS_PER_IP=${SCRAPY_CONCURRENT_REQUESTS_PER_IP:-4}
|
||||
- SCRAPY_COOKIES_ENABLED=${SCRAPY_COOKIES_ENABLED:-False}
|
||||
- SCRAPY_HTTPCACHE_ENABLED=${SCRAPY_HTTPCACHE_ENABLED:-True}
|
||||
- SCRAPY_HTTPCACHE_EXPIRATION_SECS=${SCRAPY_HTTPCACHE_EXPIRATION_SECS:-3600}
|
||||
- SCRAPY_HTTPCACHE_DIR=${SCRAPY_HTTPCACHE_DIR:-httpcache}
|
||||
- SCRAPY_LOG_LEVEL=${SCRAPY_LOG_LEVEL:-ERROR}
|
||||
- SCRAPY_GOOGLE_API_KEY=${SCRAPY_GOOGLE_API_KEY:-}
|
||||
- SCRAPY_GOOGLE_CSE_ID=${SCRAPY_GOOGLE_CSE_ID:-}
|
||||
- SCRAPY_MAX_NUMBER_OF_SITEMAP_URLS=${SCRAPY_MAX_NUMBER_OF_SITEMAP_URLS:-20000}
|
||||
- SCRAPY_SITEMAP_CRAWL_PAGE_LIMIT=${SCRAPY_SITEMAP_CRAWL_PAGE_LIMIT:-100}
|
||||
- PLAYWRIGHT_SERVER=${PLAYWRIGHT_SERVER:-http://playwright:8000}
|
||||
- PLAYWRIGHT_API_KEY=${PLAYWRIGHT_API_KEY:-your-secret-api-key}
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
||||
- STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY:-}
|
||||
- STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET:-}
|
||||
- GOOGLE_ANALYTICS_ID=${GOOGLE_ANALYTICS_ID:-}
|
||||
- IS_ENTERPRISE_MODE_ACTIVE=${IS_ENTERPRISE_MODE_ACTIVE:-False}
|
||||
- MAX_CRAWL_DEPTH=${MAX_CRAWL_DEPTH:--1}
|
||||
- CAPTURE_USAGE_HISTORY=${CAPTURE_USAGE_HISTORY:-True}
|
||||
- MCP_SERVER=${MCP_SERVER:-http://localhost/sse}
|
||||
networks:
|
||||
- traefik-proxy
|
||||
- default
|
||||
|
||||
x-frontend: &frontend
|
||||
image: watercrawl/frontend:${VERSION:-v0.10.2}
|
||||
environment:
|
||||
- VITE_API_BASE_URL=${API_BASE_URL:-http://localhost/api}
|
||||
depends_on:
|
||||
- app
|
||||
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf.template
|
||||
- ./nginx/entrypoint.sh:/entrypoint.sh
|
||||
environment:
|
||||
- MINIO_PRIVATE_BUCKET=${MINIO_PRIVATE_BUCKET:-private}
|
||||
- MINIO_PUBLIC_BUCKET=${MINIO_PUBLIC_BUCKET:-public}
|
||||
command: ["/bin/sh", "/entrypoint.sh"]
|
||||
depends_on:
|
||||
- app
|
||||
- frontend
|
||||
- minio
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- traefik-proxy
|
||||
- default
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.watercrawl.rule=Host(`watercrawl.workstation`)"
|
||||
- "traefik.http.routers.watercrawl.entrypoints=websecure"
|
||||
- "traefik.http.services.watercrawl.loadbalancer.server.port=80"
|
||||
- "traefik.docker.network=traefik-proxy"
|
||||
- "traefik.http.routers.watercrawl.tls.certresolver=le"
|
||||
|
||||
app:
|
||||
<<: *app
|
||||
command: [ "gunicorn", "-b", "0.0.0.0:9000", "-w", "2", "watercrawl.wsgi:application", "--access-logfile", "-", "--error-logfile", "-", "--timeout", "60" ]
|
||||
|
||||
celery:
|
||||
<<: *app
|
||||
command: [ "celery", "-A", "watercrawl", "worker", "-l", "info", "-S", "django" ]
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
|
||||
celery-beat:
|
||||
<<: *app
|
||||
command: [ "celery", "-A", "watercrawl", "beat", "-l", "info", "-S", "django" ]
|
||||
|
||||
frontend:
|
||||
<<: *frontend
|
||||
command: [ "npm", "run", "serve" ]
|
||||
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2024-11-07T00-52-20Z
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./volumes/minio-data:/data
|
||||
command: server /data --console-address ":9001"
|
||||
environment:
|
||||
- MINIO_BROWSER_REDIRECT_URL=${MINIO_BROWSER_REDIRECT_URL:-http://localhost/minio-console/}
|
||||
- MINIO_SERVER_URL=${MINIO_SERVER_URL:-http://localhost/}
|
||||
- MINIO_ROOT_USER=${MINIO_ACCESS_KEY:-minio}
|
||||
- MINIO_ROOT_PASSWORD=${MINIO_SECRET_KEY:-minio123}
|
||||
|
||||
playwright:
|
||||
image: watercrawl/playwright:1.1
|
||||
restart: unless-stopped
|
||||
user: root
|
||||
environment:
|
||||
- AUTH_API_KEY=${PLAYWRIGHT_API_KEY:-your-secret-api-key}
|
||||
- PORT=${PLAYWRIGHT_PORT:-8000}
|
||||
- HOST=${PLAYWRIGHT_HOST:-0.0.0.0}
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
networks:
|
||||
- traefik-proxy
|
||||
- default
|
||||
|
||||
db:
|
||||
image: postgres:17.2-alpine3.21
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
|
||||
- POSTGRES_USER=${POSTGRES_USER:-postgres}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-postgres}
|
||||
volumes:
|
||||
- ./volumes/postgres-db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
mcp:
|
||||
image: watercrawl/mcp:v1.2.0
|
||||
restart: unless-stopped
|
||||
command: [ "sse", "--base-url", "http://app:9000", '--port', '3000', '--endpoint', '/sse' ]
|
||||
|
||||
redis:
|
||||
image: redis:latest
|
||||
restart: unless-stopped
|
||||
|
||||
llm:
|
||||
image: ollama/ollama:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./volumes/ollama-models:/root/.ollama
|
||||
environment:
|
||||
- OLLAMA_DISABLE_TELEMETRY=true
|
||||
- OLLAMA_KEEP_ALIVE=5m
|
||||
- OLLAMA_HOST=0.0.0.0:11434
|
||||
- OLLAMA_NUM_PARALLEL=1
|
||||
- OLLAMA_MAX_LOADED_MODELS=1
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
|
||||
n8n:
|
||||
image: docker.n8n.io/n8nio/n8n
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
|
||||
- N8N_PORT=5678
|
||||
- N8N_RUNNERS_ENABLED=true
|
||||
- NODE_ENV=production
|
||||
- GENERIC_TIMEZONE=Europe/Bratislava
|
||||
- TZ=Europe/Bratislava
|
||||
- N8N_SECURE_COOKIE=false
|
||||
- N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true
|
||||
volumes:
|
||||
- n8n_data:/home/node/.n8n
|
||||
- ./n8n/local-files:/files
|
||||
extra_hosts:
|
||||
- "enterprise.n8n.io:104.26.13.187"
|
||||
- "enterprise.n8n.io:104.26.12.187"
|
||||
- "enterprise.n8n.io:172.67.68.102"
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
networks:
|
||||
- traefik-proxy
|
||||
- default
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.n8n.rule=Host(`n8n.workstation`)"
|
||||
- "traefik.http.routers.n8n.entrypoints=websecure"
|
||||
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
|
||||
- "traefik.docker.network=traefik-proxy"
|
||||
- "traefik.http.routers.n8n.tls.certresolver=le"
|
||||
# docker exec -it edu_master-llm-1 ollama pull neural-chat:7b-q4
|
||||
# docker exec -it edu_master-llm-1 ollama pull mistral:7b-q4
|
||||
|
||||
lessons-bot:
|
||||
build:
|
||||
context: lessons_bot/
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- LESSONS_BOT_TOKEN=${LESSONS_BOT_TOKEN}
|
||||
- N8N_WEBHOOK_URL=${N8N_WEBHOOK_URL:-http://n8n:5678/webhook-test/get-lessons}
|
||||
- N8N_SECRET=${N8N_SECRET:-your-secret-token-here}
|
||||
- WATERCRAWL_API_URL=${WATERCRAWL_API_URL:-http://app:9000/api}
|
||||
- PHPSESSID_BOT_URL=${PHPSESSID_BOT_URL:-http://phpsessid-bot:5000}
|
||||
- EDU_HOST=${EDU_HOST:-edu.edu.vn.ua}
|
||||
depends_on:
|
||||
- n8n
|
||||
- app
|
||||
- phpsessid-bot
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
networks:
|
||||
- default
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pgrep -f 'python.*main.py' || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
# webinar-notif-worker:
|
||||
# image: python:3.11-slim
|
||||
# restart: on-failure
|
||||
# volumes:
|
||||
# - ./webinar_notif:/app/webinar_notif:ro
|
||||
# working_dir: /app/webinar_notif
|
||||
# command: ["/bin/sh", "-c", "pip install --no-cache-dir -r requirements.txt 2>/dev/null || true; python -u main.py"]
|
||||
|
||||
|
||||
phpsessid-bot:
|
||||
build:
|
||||
context: phpsessid_bot/
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- EDU_HOST=${EDU_HOST:-edu.edu.vn.ua}
|
||||
- EDU_LOGIN=${EDU_LOGIN}
|
||||
- EDU_PASSWORD=${EDU_PASSWORD}
|
||||
- BOT_PORT=${PHPSESSID_BOT_PORT:-5000}
|
||||
- BOT_HOST=${PHPSESSID_BOT_HOST:-0.0.0.0}
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
networks:
|
||||
- default
|
||||
|
||||
|
||||
volumes:
|
||||
n8n_data:
|
||||
postgres-db:
|
||||
minio-data:
|
||||
ollama-models:
|
||||
lmstudio_data:
|
||||
networks:
|
||||
traefik-proxy:
|
||||
external: true
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Установка зависимостей
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Копирование кода
|
||||
COPY config.py .
|
||||
COPY utils.py .
|
||||
COPY handlers.py .
|
||||
COPY main.py .
|
||||
|
||||
# Запуск бота
|
||||
CMD ["python", "-u", "main.py"]
|
||||
@@ -0,0 +1,26 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Telegram
|
||||
BOT_TOKEN = os.getenv('LESSONS_BOT_TOKEN')
|
||||
|
||||
# n8n
|
||||
N8N_WEBHOOK_URL = os.getenv('N8N_WEBHOOK_URL', 'http://n8n:5678/webhook/homework-check')
|
||||
N8N_SECRET = os.getenv('N8N_SECRET', 'your-secret-token-here')
|
||||
|
||||
# WaterCrawl API
|
||||
WATERCRAWL_API_URL = os.getenv('WATERCRAWL_API_URL', 'http://app:9000/api')
|
||||
|
||||
# PHPSESSID Bot
|
||||
PHPSESSID_BOT_URL = os.getenv('PHPSESSID_BOT_URL', 'http://phpsessid-bot:5000')
|
||||
|
||||
# EDU site
|
||||
EDU_HOST = os.getenv('EDU_HOST', 'edu.edu.vn.ua')
|
||||
EDU_WEBINAR_URL = f'https://{EDU_HOST}/webinar/useractive'
|
||||
|
||||
# Playwright
|
||||
PLAYWRIGHT_SERVER = os.getenv('PLAYWRIGHT_SERVER', 'http://playwright:8000')
|
||||
PLAYWRIGHT_API_KEY = os.getenv('PLAYWRIGHT_API_KEY', 'your-secret-api-key')
|
||||
WEBINAR_WAIT_TIME = int(os.getenv('WEBINAR_WAIT_TIME', '3')) # Секунды ожидания загрузки
|
||||
@@ -0,0 +1,131 @@
|
||||
import logging
|
||||
import requests
|
||||
from telegram import Update
|
||||
from telegram.ext import ContextTypes
|
||||
import config
|
||||
from utils import fetch_webinars, format_webinar_message
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Команда /start"""
|
||||
welcome_message = """
|
||||
Привет! Я бот для проверки домашних заданий и вебинаров.
|
||||
|
||||
<b>Команды:</b>
|
||||
/check - Проверить несделанные уроки
|
||||
/webinar - Проверить активные онлайн уроки
|
||||
/help - Помощь
|
||||
"""
|
||||
await update.message.reply_text(welcome_message, parse_mode='HTML')
|
||||
|
||||
|
||||
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Команда /help"""
|
||||
help_text = """
|
||||
<b>Как пользоваться ботом:</b>
|
||||
|
||||
<b>/check</b> - Проверка домашних заданий
|
||||
- Поиск несделанных уроков
|
||||
|
||||
⏱ Проверка занимает 10-30 секунд
|
||||
|
||||
<b>/webinar</b> - Активные онлайн уроки
|
||||
- Проверка активных вебинаровв
|
||||
⏱ Проверка занимает 3-5 секунд
|
||||
"""
|
||||
await update.message.reply_text(help_text, parse_mode='HTML')
|
||||
|
||||
|
||||
async def check_homework(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Команда /check - запускает проверку уроков"""
|
||||
chat_id = update.effective_chat.id
|
||||
user_id = update.effective_user.id
|
||||
username = update.effective_user.username or "unknown"
|
||||
|
||||
# Отправляем уведомление что начали работу
|
||||
status_message = await update.message.reply_text("Запускаю проверку уроков...")
|
||||
|
||||
# Формируем данные для n8n
|
||||
payload = {
|
||||
"chat_id": chat_id,
|
||||
"user_id": user_id,
|
||||
"username": username,
|
||||
"timestamp": update.message.date.isoformat()
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {config.N8N_SECRET}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
try:
|
||||
logger.info(f"Sending request to n8n for user {user_id}")
|
||||
|
||||
# Отправляем запрос в n8n
|
||||
response = requests.post(
|
||||
config.N8N_WEBHOOK_URL,
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=5 # Короткий таймаут т.к. это асинхронный запрос
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
await status_message.edit_text(
|
||||
"✅ Запрос принят!\n"
|
||||
"🔄 Парсинг сайта и анализ данных...\n"
|
||||
"⏱ Это займет 10-30 секунд"
|
||||
)
|
||||
logger.info(f"Request accepted for user {user_id}")
|
||||
else:
|
||||
await status_message.edit_text(
|
||||
f"Ошибка при отправке запроса. Функция в разработке\n"
|
||||
f"Код: {response.status_code}"
|
||||
)
|
||||
logger.error(f"n8n returned status {response.status_code}")
|
||||
|
||||
except requests.Timeout:
|
||||
await status_message.edit_text("⏱ Запрос обрабатывается (таймаут соединения)")
|
||||
logger.warning(f"Timeout for user {user_id}")
|
||||
except Exception as e:
|
||||
await status_message.edit_text(f"❌ Ошибка: {str(e)}")
|
||||
logger.error(f"Error for user {user_id}: {e}", exc_info=True)
|
||||
|
||||
|
||||
async def check_webinar(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Команда /webinar - проверяет активные онлайн уроки"""
|
||||
user_id = update.effective_user.id
|
||||
|
||||
# Отправляем уведомление что начали работу
|
||||
status_message = await update.message.reply_text("Проверяю активные вебинары...")
|
||||
|
||||
try:
|
||||
logger.info(f"Checking webinars for user {user_id}")
|
||||
|
||||
# Получаем список вебинаров
|
||||
webinars = fetch_webinars()
|
||||
|
||||
if webinars is None:
|
||||
await status_message.edit_text(
|
||||
"❌ Не удалось получить информацию о вебинарах\n"
|
||||
"Попробуйте позже или обратитесь к администратору\n"
|
||||
"|@MrForust|mr.forust| Либо же прямо сюда."
|
||||
)
|
||||
logger.error(f"Failed to fetch webinars for user {user_id}")
|
||||
return
|
||||
|
||||
# Форматируем и отправляем результат
|
||||
message = format_webinar_message(webinars)
|
||||
await status_message.edit_text(message, parse_mode='HTML', disable_web_page_preview=True)
|
||||
|
||||
logger.info(f"Webinar check completed for user {user_id}: found {len(webinars)} webinars")
|
||||
|
||||
except Exception as e:
|
||||
await status_message.edit_text(f"❌ Ошибка: {str(e)}")
|
||||
logger.error(f"Error checking webinars for user {user_id}: {e}", exc_info=True)
|
||||
|
||||
|
||||
async def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Обработчик ошибок"""
|
||||
logger.error(f"Update {update} caused error {context.error}", exc_info=context.error)
|
||||
@@ -0,0 +1,42 @@
|
||||
import logging
|
||||
from telegram import Update
|
||||
from telegram.ext import Application, CommandHandler
|
||||
import config
|
||||
from handlers import start, help_command, check_homework, check_webinar, error_handler
|
||||
|
||||
# Настройка логирования
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main():
|
||||
"""Запуск бота"""
|
||||
if not config.BOT_TOKEN:
|
||||
logger.error("LESSONS_BOT_TOKEN not set!")
|
||||
return
|
||||
|
||||
# Создаем приложение
|
||||
application = Application.builder().token(config.BOT_TOKEN).build()
|
||||
|
||||
# Регистрируем обработчики команд
|
||||
application.add_handler(CommandHandler("start", start))
|
||||
application.add_handler(CommandHandler("help", help_command))
|
||||
application.add_handler(CommandHandler("check", check_homework))
|
||||
application.add_handler(CommandHandler("webinar", check_webinar))
|
||||
|
||||
# Регистрируем обработчик ошибок
|
||||
application.add_error_handler(error_handler)
|
||||
|
||||
# Запускаем бота
|
||||
logger.info("Lessons Bot started!")
|
||||
logger.info(f"PHPSESSID Bot URL: {config.PHPSESSID_BOT_URL}")
|
||||
logger.info(f"n8n Webhook URL: {config.N8N_WEBHOOK_URL}")
|
||||
|
||||
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,5 @@
|
||||
python-telegram-bot==20.7
|
||||
requests==2.31.0
|
||||
beautifulsoup4==4.12.2
|
||||
python-dotenv==1.0.0
|
||||
lxml==4.9.3
|
||||
@@ -0,0 +1,258 @@
|
||||
import logging
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from typing import Optional, Dict, List
|
||||
import config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_phpsessid() -> Optional[str]:
|
||||
"""
|
||||
Получает валидный PHPSESSID через phpsessid-bot
|
||||
|
||||
Returns:
|
||||
str: PHPSESSID или None в случае ошибки
|
||||
"""
|
||||
try:
|
||||
url = f"{config.PHPSESSID_BOT_URL}/get-session"
|
||||
logger.info(f"Requesting PHPSESSID from {url}")
|
||||
|
||||
response = requests.post(url, timeout=10)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if data.get('success'):
|
||||
phpsessid = data.get('phpsessid')
|
||||
logger.info(f"Got PHPSESSID: {phpsessid[:10]}...")
|
||||
return phpsessid
|
||||
else:
|
||||
logger.error(f"Failed to get PHPSESSID: {data.get('error')}")
|
||||
return None
|
||||
else:
|
||||
logger.error(f"PHPSESSID bot returned status {response.status_code}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting PHPSESSID: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
def parse_webinar_table(html_content: str) -> List[Dict[str, str]]:
|
||||
"""
|
||||
Парсит таблицу с вебинарами
|
||||
|
||||
Args:
|
||||
html_content: HTML контент страницы
|
||||
|
||||
Returns:
|
||||
List[Dict]: Список вебинаров или пустой список
|
||||
"""
|
||||
try:
|
||||
soup = BeautifulSoup(html_content, 'html.parser')
|
||||
|
||||
# Находим таблицу с вебинарами
|
||||
meetings_div = soup.find('div', {'id': 'meetings'})
|
||||
if not meetings_div:
|
||||
logger.warning("meetings div not found")
|
||||
return []
|
||||
|
||||
table = meetings_div.find('table', {'class': 'table table-zebra'})
|
||||
if not table:
|
||||
logger.warning("table not found")
|
||||
return []
|
||||
|
||||
tbody = table.find('tbody')
|
||||
if not tbody:
|
||||
logger.warning("tbody not found")
|
||||
return []
|
||||
|
||||
rows = tbody.find_all('tr')
|
||||
if not rows:
|
||||
return []
|
||||
|
||||
# Проверяем на сообщение "Жодного онлайн уроку зараз"
|
||||
first_row = rows[0]
|
||||
td = first_row.find('td')
|
||||
if td and 'Жодного онлайн уроку зараз' in td.get_text(strip=True):
|
||||
logger.info("No webinars available")
|
||||
return []
|
||||
|
||||
# Парсим активные вебинары
|
||||
webinars = []
|
||||
for row in rows:
|
||||
tds = row.find_all('td')
|
||||
if len(tds) >= 4:
|
||||
webinar = {
|
||||
'topic': tds[0].get_text(strip=True),
|
||||
'course': tds[1].get_text(strip=True),
|
||||
'teacher': tds[2].get_text(strip=True),
|
||||
'join_link': tds[3].find('a')['href'] if tds[3].find('a') else ''
|
||||
}
|
||||
webinars.append(webinar)
|
||||
|
||||
logger.info(f"Parsed {len(webinars)} webinars")
|
||||
return webinars
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing webinar table: {e}", exc_info=True)
|
||||
return []
|
||||
|
||||
|
||||
def fetch_webinars_with_playwright() -> Optional[List[Dict[str, str]]]:
|
||||
"""
|
||||
Получает список активных вебинаров используя Playwright для динамического контента
|
||||
|
||||
Returns:
|
||||
List[Dict]: Список вебинаров или None в случае ошибки
|
||||
"""
|
||||
# Получаем PHPSESSID
|
||||
phpsessid = get_phpsessid()
|
||||
if not phpsessid:
|
||||
logger.error("Failed to get PHPSESSID")
|
||||
return None
|
||||
|
||||
try:
|
||||
# Подготавливаем cookies для Playwright
|
||||
cookies = [
|
||||
{
|
||||
'name': 'PHPSESSID',
|
||||
'value': phpsessid,
|
||||
'domain': config.EDU_HOST,
|
||||
'path': '/'
|
||||
}
|
||||
]
|
||||
|
||||
# Запрос к Playwright серверу
|
||||
playwright_request = {
|
||||
'url': config.EDU_WEBINAR_URL,
|
||||
'cookies': cookies,
|
||||
'wait_until': 'networkidle', # Ждем пока сеть успокоится
|
||||
'wait_time': config.WEBINAR_WAIT_TIME * 1000, # Дополнительное ожидание в миллисекундах
|
||||
'user_agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Authorization': f'Bearer {config.PLAYWRIGHT_API_KEY}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
logger.info(f"Fetching webinars via Playwright from {config.EDU_WEBINAR_URL}")
|
||||
logger.info(f"Will wait {config.WEBINAR_WAIT_TIME} seconds for dynamic content")
|
||||
|
||||
response = requests.post(
|
||||
f"{config.PLAYWRIGHT_SERVER}/render",
|
||||
json=playwright_request,
|
||||
headers=headers,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(f"Playwright server returned status {response.status_code}")
|
||||
logger.error(f"Response: {response.text}")
|
||||
return None
|
||||
|
||||
result = response.json()
|
||||
html_content = result.get('html', '')
|
||||
|
||||
if not html_content:
|
||||
logger.error("No HTML content in Playwright response")
|
||||
return None
|
||||
|
||||
# Парсим таблицу
|
||||
webinars = parse_webinar_table(html_content)
|
||||
return webinars
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching webinars via Playwright: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
def fetch_webinars() -> Optional[List[Dict[str, str]]]:
|
||||
"""
|
||||
Получает список активных вебинаров
|
||||
Сначала пробует через Playwright (для динамического контента),
|
||||
при неудаче - через обычный requests
|
||||
|
||||
Returns:
|
||||
List[Dict]: Список вебинаров или None в случае ошибки
|
||||
"""
|
||||
# Пробуем через Playwright
|
||||
logger.info("Attempting to fetch via Playwright for dynamic content")
|
||||
webinars = fetch_webinars_with_playwright()
|
||||
|
||||
if webinars is not None:
|
||||
return webinars
|
||||
|
||||
# Fallback на обычный requests
|
||||
logger.warning("Playwright fetch failed, falling back to simple requests")
|
||||
|
||||
# Получаем PHPSESSID
|
||||
phpsessid = get_phpsessid()
|
||||
if not phpsessid:
|
||||
logger.error("Failed to get PHPSESSID")
|
||||
return None
|
||||
|
||||
# Запрашиваем страницу с вебинарами
|
||||
try:
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'ru-RU,ru;q=0.9,uk;q=0.8',
|
||||
'Referer': f'https://{config.EDU_HOST}/'
|
||||
}
|
||||
|
||||
cookies = {
|
||||
'PHPSESSID': phpsessid
|
||||
}
|
||||
|
||||
logger.info(f"Fetching webinars from {config.EDU_WEBINAR_URL}")
|
||||
response = requests.get(
|
||||
config.EDU_WEBINAR_URL,
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
timeout=15
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(f"Failed to fetch webinars page: {response.status_code}")
|
||||
return None
|
||||
|
||||
# Парсим таблицу
|
||||
webinars = parse_webinar_table(response.text)
|
||||
return webinars
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching webinars: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
|
||||
def format_webinar_message(webinars: List[Dict[str, str]]) -> str:
|
||||
"""
|
||||
Форматирует список вебинаров для отправки в Telegram
|
||||
|
||||
Args:
|
||||
webinars: Список вебинаров
|
||||
|
||||
Returns:
|
||||
str: Отформатированное сообщение
|
||||
"""
|
||||
if not webinars:
|
||||
return "📭 Жодного онлайн уроку зараз"
|
||||
|
||||
message = "🎓 <b>Активні онлайн уроки:</b>\n\n"
|
||||
|
||||
for i, webinar in enumerate(webinars, 1):
|
||||
message += f"<b>{i}. {webinar['topic']}</b>\n"
|
||||
message += f"📚 Курс: {webinar['course']}\n"
|
||||
message += f"👨🏫 Вчитель: {webinar['teacher']}\n"
|
||||
|
||||
if webinar['join_link']:
|
||||
full_link = webinar['join_link']
|
||||
if not full_link.startswith('http'):
|
||||
full_link = f"https://{config.EDU_HOST}{webinar['join_link']}"
|
||||
message += f"🔗 <a href='{full_link}'>Увійти до уроку</a>\n"
|
||||
|
||||
message += "\n"
|
||||
|
||||
return message
|
||||
@@ -0,0 +1,32 @@
|
||||
n8n:
|
||||
image: docker.n8n.io/n8nio/n8n
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
|
||||
- N8N_PORT=5678
|
||||
- N8N_RUNNERS_ENABLED=true
|
||||
- NODE_ENV=production
|
||||
- GENERIC_TIMEZONE=Europe/Bratislava
|
||||
- TZ=Europe/Bratislava
|
||||
- N8N_SECURE_COOKIE=false
|
||||
- N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true
|
||||
volumes:
|
||||
- n8n_data:/home/node/.n8n
|
||||
- ./n8n/local-files:/files
|
||||
extra_hosts:
|
||||
- "enterprise.n8n.io:104.26.13.187"
|
||||
- "enterprise.n8n.io:104.26.12.187"
|
||||
- "enterprise.n8n.io:172.67.68.102"
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
networks:
|
||||
- traefik-proxy
|
||||
- default
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.n8n.rule=Host(`n8n.workstation`)"
|
||||
- "traefik.http.routers.n8n.entrypoints=websecure"
|
||||
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
|
||||
- "traefik.docker.network=traefik-proxy"
|
||||
- "traefik.http.routers.n8n.tls.certresolver=le"
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Replace environment variables in the Nginx configuration template
|
||||
envsubst '${MINIO_PRIVATE_BUCKET} ${MINIO_PUBLIC_BUCKET}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Start Nginx
|
||||
exec nginx -g 'daemon off;'
|
||||
@@ -0,0 +1,87 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
client_max_body_size 100M;
|
||||
|
||||
# Frontend
|
||||
location / {
|
||||
proxy_pass http://frontend:80;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# API
|
||||
location /api/ {
|
||||
proxy_pass http://app:9000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# MCP
|
||||
location ~ ^/(sse|messages) {
|
||||
proxy_pass http://mcp:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
|
||||
# Important SSE settings
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Connection "";
|
||||
|
||||
# Disable buffering so events are sent immediately
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
|
||||
# Increase timeouts so connection stays open
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
}
|
||||
|
||||
|
||||
# MinIO private bucket
|
||||
location /${MINIO_PRIVATE_BUCKET}/ {
|
||||
proxy_pass http://minio:9000/${MINIO_PRIVATE_BUCKET}/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
# MinIO public bucket
|
||||
location /${MINIO_PUBLIC_BUCKET}/ {
|
||||
proxy_pass http://minio:9000/${MINIO_PUBLIC_BUCKET}/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
# MinIO API - for direct S3 operations
|
||||
# location /minio/api/ {
|
||||
# proxy_pass http://minio:9000/;
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# proxy_buffering off;
|
||||
# }
|
||||
|
||||
# MinIO Console
|
||||
location /minio-console/ {
|
||||
proxy_pass http://minio:9001/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# Rewrite location headers
|
||||
proxy_redirect / /minio-console/;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Устанавливаем зависимости
|
||||
RUN pip install --no-cache-dir flask requests
|
||||
|
||||
# Копируем код бота
|
||||
COPY main.py .
|
||||
|
||||
# Открываем порт
|
||||
EXPOSE 5000
|
||||
|
||||
# Запускаем бот
|
||||
CMD ["python", "-u", "main.py"]
|
||||
@@ -0,0 +1,216 @@
|
||||
import os
|
||||
import logging
|
||||
from flask import Flask, request, jsonify
|
||||
import requests
|
||||
from datetime import datetime
|
||||
|
||||
# Настройка логирования
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Конфигурация из переменных окружения
|
||||
EDU_HOST = os.getenv('EDU_HOST', 'edu.edu.vn.ua')
|
||||
EDU_LOGIN = os.getenv('EDU_LOGIN', '')
|
||||
EDU_PASSWORD = os.getenv('EDU_PASSWORD', '')
|
||||
BOT_PORT = int(os.getenv('BOT_PORT', '5000'))
|
||||
BOT_HOST = os.getenv('BOT_HOST', '0.0.0.0')
|
||||
|
||||
# Кэш для хранения актуальной сессии
|
||||
session_cache = {
|
||||
'phpsessid': None,
|
||||
'expires_at': None
|
||||
}
|
||||
|
||||
|
||||
def login_and_get_session():
|
||||
"""
|
||||
Выполняет логин и возвращает новый PHPSESSID
|
||||
"""
|
||||
url = f"https://{EDU_HOST}/user/login"
|
||||
|
||||
headers = {
|
||||
'Cache-Control': 'max-age=0',
|
||||
'Sec-Ch-Ua': '"Chromium";v="141", "Not?A_Brand";v="8"',
|
||||
'Sec-Ch-Ua-Mobile': '?0',
|
||||
'Sec-Ch-Ua-Platform': '"Linux"',
|
||||
'Accept-Language': 'ru-RU,ru;q=0.9',
|
||||
'Origin': f'https://{EDU_HOST}',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'Sec-Fetch-Site': 'same-origin',
|
||||
'Sec-Fetch-Mode': 'navigate',
|
||||
'Sec-Fetch-User': '?1',
|
||||
'Sec-Fetch-Dest': 'document',
|
||||
'Referer': f'https://{EDU_HOST}/',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Priority': 'u=0, i'
|
||||
}
|
||||
|
||||
data = {
|
||||
'login': EDU_LOGIN,
|
||||
'password': EDU_PASSWORD
|
||||
}
|
||||
|
||||
try:
|
||||
logger.info(f"Attempting login to {url}")
|
||||
response = requests.post(
|
||||
url,
|
||||
data=data,
|
||||
headers=headers,
|
||||
allow_redirects=False,
|
||||
timeout=10
|
||||
)
|
||||
|
||||
# Получаем PHPSESSID из cookies
|
||||
phpsessid = response.cookies.get('PHPSESSID')
|
||||
|
||||
if phpsessid:
|
||||
logger.info(f"Login successful, got PHPSESSID: {phpsessid[:10]}...")
|
||||
return {
|
||||
'success': True,
|
||||
'phpsessid': phpsessid,
|
||||
'status_code': response.status_code
|
||||
}
|
||||
else:
|
||||
logger.warning(f"Login failed: no PHPSESSID in response. Status: {response.status_code}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'No PHPSESSID in response',
|
||||
'status_code': response.status_code
|
||||
}
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Login request failed: {str(e)}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
|
||||
def validate_phpsessid(phpsessid):
|
||||
"""
|
||||
Проверяет валидность существующего PHPSESSID
|
||||
"""
|
||||
url = f"https://{EDU_HOST}/"
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
|
||||
}
|
||||
|
||||
cookies = {
|
||||
'PHPSESSID': phpsessid
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, cookies=cookies, timeout=10)
|
||||
|
||||
# Проверяем, не редиректит ли на страницу логина
|
||||
is_valid = response.status_code == 200 and '/user/login' not in response.url
|
||||
|
||||
return {
|
||||
'valid': is_valid,
|
||||
'status_code': response.status_code,
|
||||
'url': response.url
|
||||
}
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"Validation request failed: {str(e)}")
|
||||
return {
|
||||
'valid': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
|
||||
@app.route('/health', methods=['GET'])
|
||||
def health():
|
||||
"""Health check endpoint"""
|
||||
return jsonify({'status': 'ok', 'timestamp': datetime.now().isoformat()})
|
||||
|
||||
|
||||
@app.route('/get-session', methods=['POST', 'GET'])
|
||||
def get_session():
|
||||
"""
|
||||
Основной endpoint для получения валидного PHPSESSID
|
||||
Возвращает кэшированную сессию или создает новую
|
||||
"""
|
||||
result = login_and_get_session()
|
||||
|
||||
if result['success']:
|
||||
session_cache['phpsessid'] = result['phpsessid']
|
||||
session_cache['last_updated'] = datetime.now().isoformat()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'phpsessid': result['phpsessid'],
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': result.get('error', 'Login failed'),
|
||||
'timestamp': datetime.now().isoformat()
|
||||
}), 400
|
||||
|
||||
|
||||
@app.route('/validate-session', methods=['POST'])
|
||||
def validate_session():
|
||||
"""
|
||||
Проверяет валидность переданного PHPSESSID
|
||||
"""
|
||||
data = request.get_json() or {}
|
||||
phpsessid = data.get('phpsessid') or request.args.get('phpsessid')
|
||||
|
||||
if not phpsessid:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'PHPSESSID not provided'
|
||||
}), 400
|
||||
|
||||
validation_result = validate_phpsessid(phpsessid)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'valid': validation_result.get('valid', False),
|
||||
'details': validation_result,
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
|
||||
|
||||
@app.route('/refresh-session', methods=['POST', 'GET'])
|
||||
def refresh_session():
|
||||
"""
|
||||
Принудительно обновляет сессию
|
||||
"""
|
||||
result = login_and_get_session()
|
||||
|
||||
if result['success']:
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'phpsessid': result['phpsessid'],
|
||||
'message': 'Session refreshed successfully',
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': result.get('error', 'Failed to refresh session'),
|
||||
'timestamp': datetime.now().isoformat()
|
||||
}), 400
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if not EDU_LOGIN or not EDU_PASSWORD:
|
||||
logger.error("EDU_LOGIN and EDU_PASSWORD must be set!")
|
||||
exit(1)
|
||||
|
||||
logger.info(f"Starting PHPSESSID validator bot on {BOT_HOST}:{BOT_PORT}")
|
||||
logger.info(f"Target host: {EDU_HOST}")
|
||||
|
||||
app.run(host=BOT_HOST, port=BOT_PORT, debug=False)
|
||||
@@ -0,0 +1,3 @@
|
||||
flask==3.0.0
|
||||
requests==2.31.0
|
||||
Werkzeug==3.0.1
|
||||
@@ -0,0 +1,12 @@
|
||||
server:
|
||||
assets-path: /app/assets
|
||||
|
||||
theme:
|
||||
# Note: assets are cached by the browser, changes to the CSS file
|
||||
# will not be reflected until the browser cache is cleared (Ctrl+F5)
|
||||
custom-css-file: /assets/user.css
|
||||
|
||||
pages:
|
||||
# It's not necessary to create a new file for each page and include it, you can simply
|
||||
# put its contents here, though multiple pages are easier to manage when separated
|
||||
- $include: home.yml
|
||||
@@ -0,0 +1,88 @@
|
||||
- name: Home
|
||||
# Optionally, if you only have a single page you can hide the desktop navigation for a cleaner look
|
||||
# hide-desktop-navigation: true
|
||||
columns:
|
||||
- size: small
|
||||
widgets:
|
||||
- type: calendar
|
||||
first-day-of-week: monday
|
||||
|
||||
- type: rss
|
||||
limit: 10
|
||||
collapse-after: 3
|
||||
cache: 12h
|
||||
feeds:
|
||||
- url: https://selfh.st/rss/
|
||||
title: selfh.st
|
||||
- url: https://ciechanow.ski/atom.xml
|
||||
- url: https://www.joshwcomeau.com/rss.xml
|
||||
title: Josh Comeau
|
||||
- url: https://samwho.dev/rss.xml
|
||||
- url: https://ishadeed.com/feed.xml
|
||||
title: Ahmad Shadeed
|
||||
|
||||
- type: twitch-channels
|
||||
channels:
|
||||
- theprimeagen
|
||||
- j_blow
|
||||
- giantwaffle
|
||||
- cohhcarnage
|
||||
- christitustech
|
||||
- EJ_SA
|
||||
|
||||
- size: full
|
||||
widgets:
|
||||
- type: group
|
||||
widgets:
|
||||
- type: hacker-news
|
||||
- type: lobsters
|
||||
|
||||
- type: videos
|
||||
channels:
|
||||
- UCXuqSBlHAE6Xw-yeJA0Tunw # Linus Tech Tips
|
||||
- UCR-DXc1voovS8nhAvccRZhg # Jeff Geerling
|
||||
- UCsBjURrPoezykLs9EqgamOA # Fireship
|
||||
- UCBJycsmduvYEL83R_U4JriQ # Marques Brownlee
|
||||
- UCHnyfMqiRRG1u-2MsSQLbXA # Veritasium
|
||||
|
||||
- type: group
|
||||
widgets:
|
||||
- type: reddit
|
||||
subreddit: technology
|
||||
show-thumbnails: true
|
||||
- type: reddit
|
||||
subreddit: selfhosted
|
||||
show-thumbnails: true
|
||||
|
||||
- size: small
|
||||
widgets:
|
||||
- type: weather
|
||||
location: London, United Kingdom
|
||||
units: metric # alternatively "imperial"
|
||||
hour-format: 12h # alternatively "24h"
|
||||
# Optionally hide the location from being displayed in the widget
|
||||
# hide-location: true
|
||||
|
||||
- type: markets
|
||||
markets:
|
||||
- symbol: SPY
|
||||
name: S&P 500
|
||||
- symbol: BTC-USD
|
||||
name: Bitcoin
|
||||
- symbol: NVDA
|
||||
name: NVIDIA
|
||||
- symbol: AAPL
|
||||
name: Apple
|
||||
- symbol: MSFT
|
||||
name: Microsoft
|
||||
|
||||
- type: releases
|
||||
cache: 1d
|
||||
# Without authentication the Github API allows for up to 60 requests per hour. You can create a
|
||||
# read-only token from your Github account settings and use it here to increase the limit.
|
||||
# token: ...
|
||||
repositories:
|
||||
- glanceapp/glance
|
||||
- go-gitea/gitea
|
||||
- immich-app/immich
|
||||
- syncthing/syncthing
|
||||
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
glance:
|
||||
container_name: glance
|
||||
image: glanceapp/glance
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./config:/app/config
|
||||
- ./assets:/app/assets
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
env_file: .env
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.glance.rule=Host(`glance.workstation`)"
|
||||
- "traefik.http.routers.glance.entrypoints=websecure"
|
||||
- "traefik.http.routers.glance.tls=true"
|
||||
- "traefik.http.services.glance.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.glance.tls.certresolver=le"
|
||||
|
||||
networks:
|
||||
- traefik-proxy
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
networks:
|
||||
traefik-proxy:
|
||||
external: true
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
# Additional page configuration
|
||||
|
||||
# Additional configurations are loaded using its file name, minus the extension, as an anchor (https://<mydashboad>#<config>).
|
||||
# `config.yml` is still used as a base configuration, and all values here will overwrite it, so you don't have to re-defined everything
|
||||
|
||||
|
||||
subtitle: "this is another dashboard page"
|
||||
|
||||
# This overwrites message config. Setting it to empty to remove message from this page and keep it only in the main one:
|
||||
message: ~
|
||||
|
||||
# as we want to include a differente link here (so we can get back to home page), we need to replicate all links or they will be revome when overwriting the links field:
|
||||
links:
|
||||
- name: "Home"
|
||||
icon: "fas fa-home"
|
||||
url: "#"
|
||||
- name: "Contribute"
|
||||
icon: "fab fa-github"
|
||||
url: "https://github.com/bastienwirtz/homer"
|
||||
target: "_blank" # optional html a tag target attribute
|
||||
- name: "Wiki"
|
||||
icon: "fas fa-book"
|
||||
url: "https://www.wikipedia.org/"
|
||||
|
||||
services:
|
||||
- name: "More apps on another page!"
|
||||
icon: "fas fa-cloud"
|
||||
items:
|
||||
- name: "Awesome app on a second page!"
|
||||
logo: "assets/tools/sample.png"
|
||||
subtitle: "Bookmark example"
|
||||
tag: "app"
|
||||
url: "https://www.reddit.com/r/selfhosted/"
|
||||
target: "_blank"
|
||||
@@ -0,0 +1,144 @@
|
||||
---
|
||||
# Homepage configuration
|
||||
# See https://fontawesome.com/search for icons options
|
||||
|
||||
title: "Demo dashboard"
|
||||
subtitle: "Homer"
|
||||
logo: "logo.png"
|
||||
# icon: "fas fa-skull-crossbones" # Optional icon
|
||||
|
||||
header: true
|
||||
footer: '<p>Created with <span class="has-text-danger">❤️</span> with <a href="https://bulma.io/">Bulma</a>, <a href="https://vuejs.org/">Vue.js</a> & <a href="https://fontawesome.com/">font awesome</a> // Fork me on <a href="https://github.com/bastienwirtz/homer"><i class="fab fa-github-alt"></i></a></p>' # set false if you want to hide it.
|
||||
|
||||
# Optional theme customization
|
||||
theme: default
|
||||
|
||||
columns: "3"
|
||||
|
||||
defaults:
|
||||
layout: list
|
||||
|
||||
# Optional message
|
||||
message:
|
||||
style: "is-dark" # See https://bulma.io/documentation/components/message/#colors for styling options.
|
||||
title: "👋 Welcome!"
|
||||
content: "Homer is a dead simple static HOMepage for your servER (or anything else) to keep your services and favorite links on hand, based on a simple yaml configuration file.<br /> Learn more at <a href='https://github.com/bastienwirtz/homer'>github.com/bastienwirtz/homer</a>"
|
||||
|
||||
# Optional navbar
|
||||
# links: [] # Allows for navbar (dark mode, layout, and search) without any links
|
||||
links:
|
||||
- name: "Contribute!"
|
||||
icon: "fab fa-github"
|
||||
url: "https://github.com/bastienwirtz/homer"
|
||||
target: "_blank" # optional html a tag target attribute
|
||||
- name: "Documentation"
|
||||
icon: "fas fa-book"
|
||||
url: "https://github.com/bastienwirtz/homer/blob/main/README.md#table-of-contents"
|
||||
# this will link to a second homer page that will load config from additional-page.yml and keep default config values as in config.yml file
|
||||
# see url field and assets/additional-page.yml.dist used in this example:
|
||||
- name: "another page!"
|
||||
icon: "fas fa-file-alt"
|
||||
url: "#additional-page"
|
||||
|
||||
# Services
|
||||
# First level array represent a group.
|
||||
# Leave only a "items" key if not using group (group name, icon & tagstyle are optional, section separation will not be displayed).
|
||||
services:
|
||||
- name: "Try Homer"
|
||||
icon: "fa-solid fa-arrow-right"
|
||||
items:
|
||||
- name: "Get started"
|
||||
icon: "fa-solid fa-download"
|
||||
subtitle: "Start using Homer in a few minutes"
|
||||
tag: "setup"
|
||||
url: "https://github.com/bastienwirtz/homer?tab=readme-ov-file#get-started"
|
||||
- name: "Configuration"
|
||||
icon: "fa-solid fa-sliders"
|
||||
subtitle: "Configuration options documentation"
|
||||
tag: "setup"
|
||||
url: "https://github.com/bastienwirtz/homer/blob/main/docs/configuration.md"
|
||||
- name: "Theming"
|
||||
icon: "fa-solid fa-palette"
|
||||
subtitle: "Customize Homer appearance"
|
||||
tag: "theming"
|
||||
url: "https://github.com/bastienwirtz/homer/blob/main/docs/theming.md"
|
||||
- name: "Smart cards"
|
||||
icon: "fa-solid fa-palette"
|
||||
subtitle: "Displays dynamic information or actions."
|
||||
tag: "setup"
|
||||
url: "https://github.com/bastienwirtz/homer/blob/main/docs/customservices.md"
|
||||
- name: "Dashboard icons"
|
||||
icon: "fa-solid fa-icons"
|
||||
tag: "setup"
|
||||
url: ""
|
||||
quick:
|
||||
- name: "selfh.st"
|
||||
url: "https://selfh.st/icons/"
|
||||
icon: "fa-solid fa-arrow-up-right-from-square"
|
||||
target: "_blank"
|
||||
- name: "homarr-labs"
|
||||
url: "https://github.com/homarr-labs/dashboard-icons"
|
||||
icon: "fa-solid fa-arrow-up-right-from-square"
|
||||
target: "_blank"
|
||||
- name: "Buy me a coffee!"
|
||||
subtitle: "Sponsor this project"
|
||||
icon: "fa-solid fa-mug-hot"
|
||||
url: "https://www.buymeacoffee.com/bastien"
|
||||
- name: "Smart cards showcase"
|
||||
icon: "fa-solid fa-brain"
|
||||
class: "highlight-purple"
|
||||
items:
|
||||
- name: "Octoprint"
|
||||
logo: "https://cdn-icons-png.flaticon.com/512/3112/3112529.png"
|
||||
apikey: "xxxxxxxxxxxx"
|
||||
endpoint: "/dummy-data/octoprint"
|
||||
type: "OctoPrint"
|
||||
- name: "Pi-hole"
|
||||
logo: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/pi-hole.png"
|
||||
url: "https://pi-hole.net/"
|
||||
endpoint: "/dummy-data/pihole"
|
||||
type: "PiHole"
|
||||
- name: "Proxmox - Node1"
|
||||
logo: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/proxmox.png"
|
||||
type: "Proxmox"
|
||||
tag: "sys"
|
||||
url: "https://www.proxmox.com/en/"
|
||||
endpoint: "/dummy-data/proxmox"
|
||||
node: "node1"
|
||||
warning_value: 50
|
||||
danger_value: 80
|
||||
api_token: "xxxxxxxxxxxx"
|
||||
- name: "PeaNUT"
|
||||
logo: "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/peanut.png"
|
||||
url: "https://github.com/Brandawg93/PeaNUT"
|
||||
endpoint: "/dummy-data/peanut"
|
||||
type: "PeaNUT"
|
||||
device: "ups"
|
||||
- name: "Weather"
|
||||
location: "Lille"
|
||||
apikey: "xxxxxxxxxxxx" # insert your own API key here. Request one from https://openweathermap.org/api.
|
||||
units: "metric"
|
||||
endpoint: "/dummy-data/openweather/weather"
|
||||
type: "OpenWeather"
|
||||
- name: "Ressources"
|
||||
icon: "fa-regular fa-bookmark"
|
||||
class: highlight-inverted
|
||||
items:
|
||||
- name: "Selfhosted community"
|
||||
icon: "fa-brands fa-reddit-alien"
|
||||
tag: "community"
|
||||
url: ""
|
||||
quick:
|
||||
- name: "r/selfhosted"
|
||||
url: "https://www.reddit.com/r/selfhosted/"
|
||||
icon: "fa-solid fa-arrow-up-right-from-square"
|
||||
target: "_blank"
|
||||
- name: "c/selfhosted"
|
||||
url: "https://lemmy.world/c/selfhosted"
|
||||
icon: "fa-solid fa-arrow-up-right-from-square"
|
||||
target: "_blank"
|
||||
- name: "Awesome selfhosted"
|
||||
icon: "fa-solid fa-star"
|
||||
subtitle: "Another application"
|
||||
tag: "awesome-list"
|
||||
url: "https://github.com/awesome-selfhosted/awesome-selfhosted"
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
# Homepage configuration
|
||||
# See https://fontawesome.com/search for icons options
|
||||
|
||||
title: "Demo dashboard"
|
||||
subtitle: "Homer"
|
||||
logo: "logo.png"
|
||||
# icon: "fas fa-skull-crossbones" # Optional icon
|
||||
|
||||
header: true
|
||||
footer: '<p>Created with <span class="has-text-danger">❤️</span> with <a href="https://bulma.io/">bulma</a>, <a href="https://vuejs.org/">vuejs</a> & <a href="https://fontawesome.com/">font awesome</a> // Fork me on <a href="https://github.com/bastienwirtz/homer"><i class="fab fa-github-alt"></i></a></p>' # set false if you want to hide it.
|
||||
|
||||
# Optional theme customization
|
||||
theme: default
|
||||
|
||||
# Optional theme customization (color overrrides)
|
||||
# overrrides can also be done using CSS vars
|
||||
colors:
|
||||
light:
|
||||
highlight-primary: "#3367d6"
|
||||
highlight-secondary: "#4285f4"
|
||||
highlight-hover: "#5a95f5"
|
||||
background: "#f5f5f5"
|
||||
card-background: "#ffffff"
|
||||
text: "#363636"
|
||||
text-header: "#ffffff"
|
||||
text-title: "#303030"
|
||||
text-subtitle: "#424242"
|
||||
card-shadow: rgba(0, 0, 0, 0.1)
|
||||
link: "#3273dc"
|
||||
link-hover: "#363636"
|
||||
dark:
|
||||
highlight-primary: "#3367d6"
|
||||
highlight-secondary: "#4285f4"
|
||||
highlight-hover: "#5a95f5"
|
||||
background: "#131313"
|
||||
card-background: "#2b2b2b"
|
||||
text: "#eaeaea"
|
||||
text-header: "#ffffff"
|
||||
text-title: "#fafafa"
|
||||
text-subtitle: "#f5f5f5"
|
||||
card-shadow: rgba(0, 0, 0, 0.4)
|
||||
link: "#3273dc"
|
||||
link-hover: "#ffdd57"
|
||||
|
||||
# Optional message
|
||||
message:
|
||||
#url: https://b4bz.io
|
||||
style: "is-dark" # See https://bulma.io/documentation/components/message/#colors for styling options.
|
||||
title: "Demo !"
|
||||
icon: "fa fa-grin"
|
||||
content: "This is a dummy homepage demo. <br /> Find more information on <a href='https://github.com/bastienwirtz/homer'>github.com/bastienwirtz/homer</a>"
|
||||
|
||||
# Optional navbar
|
||||
# links: [] # Allows for navbar (dark mode, layout, and search) without any links
|
||||
links:
|
||||
- name: "Contribute"
|
||||
icon: "fab fa-github"
|
||||
url: "https://github.com/bastienwirtz/homer"
|
||||
target: "_blank" # optional html a tag target attribute
|
||||
- name: "Wiki"
|
||||
icon: "fas fa-book"
|
||||
url: "https://www.wikipedia.org/"
|
||||
# this will link to a second homer page that will load config from additional-page.yml and keep default config values as in config.yml file
|
||||
# see url field and assets/additional-page.yml.dist used in this example:
|
||||
#- name: "another page!"
|
||||
# icon: "fas fa-file-alt"
|
||||
# url: "#additional-page"
|
||||
|
||||
# Services
|
||||
# First level array represent a group.
|
||||
# Leave only a "items" key if not using group (group name, icon & tagstyle are optional, section separation will not be displayed).
|
||||
services:
|
||||
- name: "Applications"
|
||||
icon: "fas fa-cloud"
|
||||
items:
|
||||
- name: "Get started"
|
||||
icon: "fa-solid fa-download"
|
||||
subtitle: "Start using Homer in a few minutes"
|
||||
tag: "setup"
|
||||
url: "https://github.com/bastienwirtz/homer?tab=readme-ov-file#get-started"
|
||||
- name: "Configuration"
|
||||
icon: "fa-solid fa-sliders"
|
||||
subtitle: "Configuration options documentation"
|
||||
tag: "setup"
|
||||
url: "https://github.com/bastienwirtz/homer/blob/main/docs/configuration.md"
|
||||
- name: "Theming"
|
||||
icon: "fa-solid fa-palette"
|
||||
subtitle: "Customize Homer appearance"
|
||||
tag: "theming"
|
||||
url: "https://github.com/bastienwirtz/homer/blob/main/docs/theming.md"
|
||||
- name: "Smart cards"
|
||||
icon: "fa-solid fa-palette"
|
||||
subtitle: "Displays dynamic information or actions."
|
||||
tag: "setup"
|
||||
url: "https://github.com/bastienwirtz/homer/blob/main/docs/customservices.md"
|
||||
- name: "Dashboard icons"
|
||||
icon: "fa-solid fa-icons"
|
||||
subtitle: "Dashboard icons"
|
||||
tag: "setup"
|
||||
url: "https://github.com/walkxcode/dashboard-icons"
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
# Homepage configuration
|
||||
# See https://fontawesome.com/search for icons options
|
||||
|
||||
title: "Demo dashboard"
|
||||
subtitle: "Homer"
|
||||
logo: "logo.png"
|
||||
# icon: "fas fa-skull-crossbones" # Optional icon
|
||||
|
||||
header: true
|
||||
footer: '<p>Created with <span class="has-text-danger">❤️</span> with <a href="https://bulma.io/">bulma</a>, <a href="https://vuejs.org/">vuejs</a> & <a href="https://fontawesome.com/">font awesome</a> // Fork me on <a href="https://github.com/bastienwirtz/homer"><i class="fab fa-github-alt"></i></a></p>' # set false if you want to hide it.
|
||||
|
||||
# Optional theme customization
|
||||
theme: default
|
||||
|
||||
# Optional theme customization (color overrrides)
|
||||
# overrrides can also be done using CSS vars
|
||||
colors:
|
||||
light:
|
||||
highlight-primary: "#3367d6"
|
||||
highlight-secondary: "#4285f4"
|
||||
highlight-hover: "#5a95f5"
|
||||
background: "#f5f5f5"
|
||||
card-background: "#ffffff"
|
||||
text: "#363636"
|
||||
text-header: "#ffffff"
|
||||
text-title: "#303030"
|
||||
text-subtitle: "#424242"
|
||||
card-shadow: rgba(0, 0, 0, 0.1)
|
||||
link: "#3273dc"
|
||||
link-hover: "#363636"
|
||||
dark:
|
||||
highlight-primary: "#3367d6"
|
||||
highlight-secondary: "#4285f4"
|
||||
highlight-hover: "#5a95f5"
|
||||
background: "#131313"
|
||||
card-background: "#2b2b2b"
|
||||
text: "#eaeaea"
|
||||
text-header: "#ffffff"
|
||||
text-title: "#fafafa"
|
||||
text-subtitle: "#f5f5f5"
|
||||
card-shadow: rgba(0, 0, 0, 0.4)
|
||||
link: "#3273dc"
|
||||
link-hover: "#ffdd57"
|
||||
|
||||
# Optional message
|
||||
message:
|
||||
#url: https://b4bz.io
|
||||
style: "is-dark" # See https://bulma.io/documentation/components/message/#colors for styling options.
|
||||
title: "Demo !"
|
||||
icon: "fa fa-grin"
|
||||
content: "This is a dummy homepage demo. <br /> Find more information on <a href='https://github.com/bastienwirtz/homer'>github.com/bastienwirtz/homer</a>"
|
||||
|
||||
# Optional navbar
|
||||
# links: [] # Allows for navbar (dark mode, layout, and search) without any links
|
||||
links:
|
||||
- name: "Contribute"
|
||||
icon: "fab fa-github"
|
||||
url: "https://github.com/bastienwirtz/homer"
|
||||
target: "_blank" # optional html a tag target attribute
|
||||
- name: "Wiki"
|
||||
icon: "fas fa-book"
|
||||
url: "https://www.wikipedia.org/"
|
||||
# this will link to a second homer page that will load config from additional-page.yml and keep default config values as in config.yml file
|
||||
# see url field and assets/additional-page.yml.dist used in this example:
|
||||
#- name: "another page!"
|
||||
# icon: "fas fa-file-alt"
|
||||
# url: "#additional-page"
|
||||
|
||||
# Services
|
||||
# First level array represent a group.
|
||||
# Leave only a "items" key if not using group (group name, icon & tagstyle are optional, section separation will not be displayed).
|
||||
services:
|
||||
- name: "Applications"
|
||||
icon: "fas fa-cloud"
|
||||
items:
|
||||
- name: "Get started"
|
||||
icon: "fa-solid fa-download"
|
||||
subtitle: "Start using Homer in a few minutes"
|
||||
tag: "setup"
|
||||
url: "https://github.com/bastienwirtz/homer?tab=readme-ov-file#get-started"
|
||||
- name: "Configuration"
|
||||
icon: "fa-solid fa-sliders"
|
||||
subtitle: "Configuration options documentation"
|
||||
tag: "setup"
|
||||
url: "https://github.com/bastienwirtz/homer/blob/main/docs/configuration.md"
|
||||
- name: "Theming"
|
||||
icon: "fa-solid fa-palette"
|
||||
subtitle: "Customize Homer appearance"
|
||||
tag: "theming"
|
||||
url: "https://github.com/bastienwirtz/homer/blob/main/docs/theming.md"
|
||||
- name: "Smart cards"
|
||||
icon: "fa-solid fa-palette"
|
||||
subtitle: "Displays dynamic information or actions."
|
||||
tag: "setup"
|
||||
url: "https://github.com/bastienwirtz/homer/blob/main/docs/customservices.md"
|
||||
- name: "Dashboard icons"
|
||||
icon: "fa-solid fa-icons"
|
||||
subtitle: "Dashboard icons"
|
||||
tag: "setup"
|
||||
url: "https://github.com/walkxcode/dashboard-icons"
|
||||
@@ -0,0 +1,8 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
/* Custom card colors */
|
||||
/* Use with `class:` property of services in config.yml */
|
||||
body #app .card.green {
|
||||
background-color: #006600;
|
||||
color: #00ff00;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# PWA Icons / Images
|
||||
|
||||
We suggest you to create a svg or png icon (if it is a png icon, with the maximum resolution possible) for your application and use it to generate a favicon package in [Favicon Generator](https://realfavicongenerator.net/).
|
||||
|
||||
Once generated, download the ZIP and use android-* icons for pwa-*:
|
||||
|
||||
- use `android-chrome-192x192.png` for `pwa-192x192.png`
|
||||
- use `android-chrome-512x512.png` for `pwa-512x512.png`
|
||||
- `apple-touch-icon.png` is `apple-touch-icon.png`
|
||||
- `favicon.ico` is `favicon.ico`
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1 @@
|
||||
{"name":"Homer dashboard","short_name":"Homer","description":"Home Server Dashboard","start_url":"../","display":"standalone","background_color":"#ffffff","theme_color":"#3367D6","lang":"en","scope":"../","icons":[{"src":"./icons/pwa-192x192.png","sizes":"192x192","type":"image/png"},{"src":"./icons/pwa-512x512.png","sizes":"512x512","type":"image/png"}]}
|
||||
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
homer:
|
||||
image: b4bz/homer
|
||||
container_name: homer
|
||||
volumes:
|
||||
- ./config:/www/assets # Make sure your local config directory exists
|
||||
ports:
|
||||
- 8090:8080
|
||||
# - 8080:8080
|
||||
|
||||
user: 1000:1000 # default
|
||||
environment:
|
||||
- INIT_ASSETS=1 # default, requires the config directory to be writable for the container user (see user option)
|
||||
restart: unless-stopped
|
||||
dns:
|
||||
- 1.1.1.1
|
||||
- 8.8.8.8
|
||||
@@ -0,0 +1,42 @@
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:v3.5
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- "--api.insecure=false"
|
||||
- "--api.dashboard=true"
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--providers.docker.network=proxy"
|
||||
- "--entryPoints.web.address=:80"
|
||||
- "--entryPoints.websecure.address=:443"
|
||||
- "--entryPoints.websecure.http.tls=true"
|
||||
- "--entryPoints.web.http.redirections.entryPoint.to=websecure"
|
||||
- "--entryPoints.web.http.redirections.entryPoint.scheme=https"
|
||||
- --serversTransport.insecureSkipVerify=true
|
||||
# Let's Encrypt configuration
|
||||
- "--certificatesresolvers.le.acme.email=bobrovod@national.shitposting.agency"
|
||||
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
|
||||
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.traefik.rule=Host(`traefik.workstation`)"
|
||||
- "traefik.http.routers.traefik.entrypoints=websecure"
|
||||
- "traefik.http.routers.traefik.service=api@internal"
|
||||
- "traefik.http.routers.traefik.tls=true"
|
||||
- "traefik.http.routers.traefik.tls.certresolver=le"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./certs:/certs:ro
|
||||
- ./dynamic:/etc/traefik/dynamic:ro
|
||||
- ./letsencrypt:/letsencrypt
|
||||
networks:
|
||||
- traefik-proxy
|
||||
networks:
|
||||
traefik-proxy:
|
||||
external: true
|
||||
@@ -0,0 +1,10 @@
|
||||
version = 1
|
||||
|
||||
[[analyzers]]
|
||||
name = "shell"
|
||||
|
||||
[[analyzers]]
|
||||
name = "python"
|
||||
|
||||
[analyzers.meta]
|
||||
runtime_version = "3.x.x"
|
||||
@@ -0,0 +1 @@
|
||||
.unused
|
||||
@@ -0,0 +1,25 @@
|
||||
.DS_Store
|
||||
.gitattributes
|
||||
.vscode
|
||||
/modules/__pycache__/
|
||||
__pycache__/
|
||||
*.session
|
||||
*.session-old
|
||||
*.db
|
||||
*.sqlite3
|
||||
*-journal
|
||||
/venv/
|
||||
.venv/
|
||||
/downloads/
|
||||
/Downloads/
|
||||
/modules/custom_modules
|
||||
.idea
|
||||
config.ini
|
||||
unknown_errors.txt
|
||||
moonlogs.txt
|
||||
thumb.jpg
|
||||
antipm_pic.jpg
|
||||
musicbot/
|
||||
.trunk/
|
||||
previous_profiles/
|
||||
.python-version
|
||||
@@ -0,0 +1,5 @@
|
||||
git
|
||||
wget
|
||||
ffmpeg
|
||||
mediainfo
|
||||
yt-dlp
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM python:3.11
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
RUN apt-get -qq update && apt-get -qq install -y git wget ffmpeg mediainfo \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN python -m venv --copies /opt/venv
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
CMD ["bash", "cloud.sh"]
|
||||
@@ -0,0 +1 @@
|
||||
web: bash cloud.sh
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"name": "Moon-userbot",
|
||||
"description": "A Simple, Fast, Customizable, Ai powered Userbot for Telegram with most easiest installation.",
|
||||
"logo": "https://camo.githubusercontent.com/1efdfa6416b3cd08471d865ca9ebf0fbdd38602ea95425f18a9bec6aeeefe49b/68747470733a2f2f74656c656772612e70682f66696c652f3063333763326662306631393463633163303334342e6a7067",
|
||||
"keywords": [
|
||||
"telegram",
|
||||
"Moon-userbot",
|
||||
"bot",
|
||||
"python",
|
||||
"pyrogram"
|
||||
],
|
||||
"env": {
|
||||
"API_ID": {
|
||||
"description": "Get it from my.telegram.org",
|
||||
"required": true
|
||||
},
|
||||
"API_HASH": {
|
||||
"description": "Get it from my.telegram.org",
|
||||
"required": true
|
||||
},
|
||||
"PM_LIMIT": {
|
||||
"description": "set your pm permit warn limit, default is 4",
|
||||
"value": "4",
|
||||
"required": true
|
||||
},
|
||||
"SECOND_SESSION": {
|
||||
"description": "Pyrorogram v2 session string for music bot, only fill this if you want to use music bot feature",
|
||||
"required": false
|
||||
},
|
||||
"DATABASE_URL": {
|
||||
"description": "ONLY for MongoDB, get it from https://cloud.mongodb.com",
|
||||
"required": false
|
||||
},
|
||||
"DATABASE_NAME": {
|
||||
"description": "set database name, if using sqlite then change it to `db.sqlite3`",
|
||||
"value": "moonub",
|
||||
"required": true
|
||||
},
|
||||
"DATABASE_TYPE": {
|
||||
"description": "set to sqlite3 if want to use sqlite3 db",
|
||||
"value": "mongodb",
|
||||
"required": true
|
||||
},
|
||||
"STRINGSESSION": {
|
||||
"description": "Pyrogram V2 Session String. Don't use bots or else you'll be responsible for your actions. Gen yourself https://github.com/The-MoonTg-project/Moon-Userbot?tab=readme-ov-file#-optional-vars.",
|
||||
"required": true
|
||||
},
|
||||
"APIFLASH_KEY": {
|
||||
"description": "ONLY, If you want to use web screenshot plugin You can get it from https://apiflash.com/dashboard/access_keys",
|
||||
"value": "123456779:ABCDE",
|
||||
"required": true
|
||||
},
|
||||
"RMBG_KEY": {
|
||||
"description": "ONLY, If you want to use removbg plugin You can get it from https://www.remove.bg/dashboard#api-key",
|
||||
"value": "123456779:ABCDE",
|
||||
"required": true
|
||||
},
|
||||
"VT_KEY": {
|
||||
"description": "ONLY, If you want to use VirusTotal plugin You can get it from https://www.virustotal.com/gui/",
|
||||
"value": "123456779:ABCDE",
|
||||
"required": true
|
||||
},
|
||||
"GEMINI_KEY": {
|
||||
"description": "ONLY, If you want to use gemini ai plugin You can get it from https://makersuite.google.com/app/apikey",
|
||||
"value": "123456779:ABCDE",
|
||||
"required": true
|
||||
},
|
||||
"COHERE_KEY": {
|
||||
"description": "ONLY, If you want to use cohere ai plugin You can get it from https://dashboard.cohere.com/api-keys",
|
||||
"value": "123456779:ABCDE",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "heroku/python"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/heroku/heroku-buildpack-apt"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/heroku/heroku-buildpack-activestorage-preview"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/The-MoonTg-project/heroku-buildpack-yt-dlp"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def hello_world():
|
||||
return "This is Moon"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cat <<'EOF'
|
||||
_ ____ ____ _
|
||||
/ \__/|/ _ \/ _ \/ \ /|
|
||||
| |\/||| / \|| / \|| |\ ||
|
||||
| | ||| \_/|| \_/|| | \||
|
||||
\_/ \|\____/\____/\_/ \|
|
||||
|
||||
Copyright (C) 2020-2023 by MoonTg-project@Github, < https://github.com/The-MoonTg-project >.
|
||||
This file is part of < https://github.com/The-MoonTg-project/Moon-Userbot > project,
|
||||
and is released under the "GNU v3.0 License Agreement".
|
||||
Please see < https://github.com/The-MoonTg-project/Moon-Userbot/blob/main/LICENSE >
|
||||
All rights reserved.
|
||||
EOF
|
||||
|
||||
gunicorn app:app --daemon && python main.py
|
||||
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
- type: worker
|
||||
name: Moon-Userbot
|
||||
runtime: docker
|
||||
repo: https://github.com/The-MoonTg-project/Moon-Userbot
|
||||
plan: starter
|
||||
envVars:
|
||||
- key: STABILITY_KEY
|
||||
sync: false
|
||||
- key: CLARIFAI_PAT
|
||||
sync: false
|
||||
- key: .env
|
||||
sync: false
|
||||
region: oregon
|
||||
dockerContext: .
|
||||
dockerfilePath: ./Dockerfile
|
||||
version: "1"
|
||||
@@ -0,0 +1 @@
|
||||
gunicorn app:app & python3 main.py
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM python:3.11
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
RUN apt-get -qq update && apt-get -qq install -y git wget ffmpeg mediainfo\
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN python -m venv --copies /opt/venv
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
CMD ["python", "main.py"]
|
||||
@@ -0,0 +1,76 @@
|
||||
services:
|
||||
userbot_forust:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: userbot_forust
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
- .env.forust
|
||||
volumes:
|
||||
- ./volumes/data_forust:/app/data
|
||||
- ./Downloads:/app/downloads
|
||||
- ./volumes/logs_forust:/app/logs
|
||||
networks:
|
||||
- userbot_network
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
develop:
|
||||
watch:
|
||||
- action: sync
|
||||
path: ./modules
|
||||
target: /app/modules
|
||||
- action: sync
|
||||
path: ./utils
|
||||
target: /app/utils
|
||||
- action: sync
|
||||
path: ./main.py
|
||||
target: /app/main.py
|
||||
- action: rebuild
|
||||
path: .env
|
||||
- action: rebuild
|
||||
path: .env.forust
|
||||
|
||||
userbot_anna:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: userbot_anna
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
- .env.anna
|
||||
volumes:
|
||||
- ./volumes/data_anna:/app/data
|
||||
- ./Downloads:/app/downloads
|
||||
- ./volumes/logs_anna:/app/logs
|
||||
networks:
|
||||
- userbot_network
|
||||
dns:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
develop:
|
||||
watch:
|
||||
- action: sync
|
||||
path: ./modules
|
||||
target: /app/modules
|
||||
- action: sync
|
||||
path: ./utils
|
||||
target: /app/utils
|
||||
- action: sync
|
||||
path: ./main.py
|
||||
target: /app/main.py
|
||||
- action: rebuild
|
||||
path: .env
|
||||
- action: rebuild
|
||||
path: .env.anna
|
||||
|
||||
|
||||
volumes:
|
||||
downloads:
|
||||
|
||||
networks:
|
||||
userbot_network:
|
||||
driver: bridge
|
||||
@@ -0,0 +1,115 @@
|
||||
import re
|
||||
import requests
|
||||
import random
|
||||
from io import BytesIO
|
||||
from bs4 import BeautifulSoup as bs
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message, InputMediaPhoto
|
||||
from pyrogram.errors import RPCError
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
|
||||
@Client.on_message(filters.command("icon", prefix) & filters.me)
|
||||
async def search_icon(_, message: Message):
|
||||
if not len(message.command) == 2:
|
||||
return await message.edit_text(
|
||||
"Please provide some text to search icons from Flaticon.com."
|
||||
)
|
||||
query = message.text.split(maxsplit=1)[1]
|
||||
|
||||
await message.edit_text("Searching for icons...")
|
||||
search_query = query.replace(" ", "%20")
|
||||
url = f"https://www.flaticon.com/search?word={search_query}"
|
||||
|
||||
try:
|
||||
html_content = requests.get(url).text
|
||||
soup = bs(html_content, "html.parser")
|
||||
results = soup.find_all(
|
||||
"img",
|
||||
src=re.compile(r"https://cdn-icons-png.flaticon.com/128/[0-9]+/[0-9]+.png"),
|
||||
)
|
||||
|
||||
if not results:
|
||||
return await message.edit("No results found.")
|
||||
|
||||
random.shuffle(results)
|
||||
icons = []
|
||||
for i in range(5):
|
||||
icons.append(results[i]["src"].replace("128", "512"))
|
||||
|
||||
for icon in icons:
|
||||
await message.reply_document(icon)
|
||||
|
||||
return await message.delete()
|
||||
except Exception as e:
|
||||
await message.edit(f"An error occurred: {e}")
|
||||
print(f"Error: {e}")
|
||||
|
||||
|
||||
@Client.on_message(filters.command("freepik", prefix) & filters.me)
|
||||
async def freepik_search(client: Client, message: Message):
|
||||
parts = message.text.split(" ", 1)
|
||||
if len(parts) < 2:
|
||||
await message.edit_text("Please provide a search query!")
|
||||
return
|
||||
|
||||
query = parts[1]
|
||||
limit = 5
|
||||
if " ; " in query:
|
||||
match, limit_str = query.split(" ; ", 1)
|
||||
try:
|
||||
limit = int(limit_str)
|
||||
except ValueError:
|
||||
await message.edit_text("Invalid limit! Using the default value of 5.")
|
||||
else:
|
||||
match = query
|
||||
|
||||
match = match.replace(" ", "%20")
|
||||
await message.edit_text("Searching Freepik...")
|
||||
|
||||
try:
|
||||
url = f"https://www.freepik.com/api/regular/search?locale=en&term={match}"
|
||||
json_content = requests.get(url).json()
|
||||
results = []
|
||||
for i in json_content["items"]:
|
||||
results.append(i["preview"]["url"])
|
||||
|
||||
if results is None:
|
||||
return await message.edit_text("No results found.")
|
||||
|
||||
random.shuffle(results)
|
||||
img_urls = results[:limit]
|
||||
|
||||
media_group = []
|
||||
for img_url in img_urls:
|
||||
icon = requests.get(img_url)
|
||||
if icon.status_code == 200:
|
||||
media_group.append(InputMediaPhoto(media=BytesIO(icon.content)))
|
||||
|
||||
if not media_group:
|
||||
await message.edit_text("No images could be downloaded.")
|
||||
return
|
||||
|
||||
try:
|
||||
await client.send_media_group(chat_id=message.chat.id, media=media_group)
|
||||
except RPCError:
|
||||
await message.edit_text(
|
||||
"Failed to send some images. Retrying individually..."
|
||||
)
|
||||
for media in media_group:
|
||||
try:
|
||||
await message.reply_photo(photo=media.media)
|
||||
except Exception as e:
|
||||
await message.edit_text(f"Error sending image: {e}")
|
||||
|
||||
except Exception as e:
|
||||
await message.edit_text(f"Failed to fetch data: {e}")
|
||||
print(f"Error: {e}")
|
||||
|
||||
|
||||
modules_help["icons"] = {
|
||||
"icon [query]": "Search for icons on Flaticon.",
|
||||
"freepik [query] [limit]": "Search for images on Freepik. Limit is optional and defaults to 5.",
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import os
|
||||
import base64
|
||||
import requests
|
||||
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["imgur"], prefix) & filters.me)
|
||||
async def imgur(_, message: Message):
|
||||
# Check if a reply exists
|
||||
msg = await message.edit_text("🎉 Please wait. trying to upload...")
|
||||
if message.reply_to_message and message.reply_to_message.photo:
|
||||
# Download the photo
|
||||
photo_path = await message.reply_to_message.download()
|
||||
# Read the photo file and encode as base64
|
||||
with open(photo_path, "rb") as file:
|
||||
data = file.read()
|
||||
base64_data = base64.b64encode(data)
|
||||
# Set API endpoint and headers for image upload
|
||||
url = "https://api.imgur.com/3/image"
|
||||
headers = {"Authorization": "Client-ID a10ad04550b0648"}
|
||||
# Upload image to Imgur and get URL
|
||||
response = requests.post(url, headers=headers, data={"image": base64_data})
|
||||
result = response.json()
|
||||
await msg.edit_text(result["data"]["link"])
|
||||
elif message.reply_to_message and message.reply_to_message.animation:
|
||||
# Download the animation (GIF)
|
||||
animation_path = await message.reply_to_message.download()
|
||||
# Read the animation file and encode as base64
|
||||
with open(animation_path, "rb") as file:
|
||||
data = file.read()
|
||||
base64_data = base64.b64encode(data)
|
||||
# Set API endpoint and headers for animation upload
|
||||
url = "https://api.imgur.com/3/image"
|
||||
headers = {"Authorization": "Client-ID a10ad04550b0648"}
|
||||
# Upload animation to Imgur and get URL
|
||||
response = requests.post(url, headers=headers, data={"image": base64_data})
|
||||
result = response.json()
|
||||
await msg.edit_text(result["data"]["link"])
|
||||
else:
|
||||
await msg.edit_text(
|
||||
"Please reply to a photo or animation (GIF) to upload to Imgur."
|
||||
)
|
||||
|
||||
|
||||
modules_help["imgur"] = {
|
||||
"imgur [img]*": "upload a photo or animation (GIF) to imgur",
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import prefix, modules_help
|
||||
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
from pyrogram.errors import MessageNotModified
|
||||
import os
|
||||
import pygments
|
||||
from pygments.formatters import ImageFormatter
|
||||
from pygments.lexers import Python3Lexer
|
||||
|
||||
|
||||
@Client.on_message(filters.command("ncode", prefix) & filters.me)
|
||||
async def coder_print(client, message: Message):
|
||||
if message.reply_to_message:
|
||||
reply_message = message.reply_to_message
|
||||
if reply_message.media:
|
||||
download_path = await client.download_media(reply_message)
|
||||
with open(download_path, "r") as file:
|
||||
code = file.read()
|
||||
if os.path.exists(download_path):
|
||||
os.remove(download_path)
|
||||
pygments.highlight(
|
||||
f"{code}",
|
||||
Python3Lexer(),
|
||||
ImageFormatter(font_name="DejaVu Sans Mono", line_numbers=True),
|
||||
"result.png",
|
||||
)
|
||||
try:
|
||||
sent_message = await message.edit_text(
|
||||
"Pasting this code on my page..."
|
||||
)
|
||||
await client.send_document(
|
||||
chat_id=message.chat.id,
|
||||
document="result.png",
|
||||
caption="Code highlighted by Pygments",
|
||||
reply_to_message_id=message.id,
|
||||
)
|
||||
except MessageNotModified:
|
||||
pass
|
||||
await sent_message.delete()
|
||||
if os.path.exists("result.png"):
|
||||
os.remove("result.png")
|
||||
else:
|
||||
return await message.reply_text("Please reply to a text or a file.")
|
||||
else:
|
||||
return await message.reply_text("Please reply to a text or a file.")
|
||||
|
||||
|
||||
modules_help["ncode"] = {
|
||||
"ncode": "Highlight the code using Pygments and send it as an image."
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message, InputMediaPhoto
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
import requests
|
||||
import asyncio
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
# Pinterest API URL
|
||||
API_URL = "https://bk9.fun/pinterest/search?q="
|
||||
|
||||
|
||||
def resize_image(image_bytes):
|
||||
try:
|
||||
with Image.open(image_bytes) as img:
|
||||
max_size = (1280, 1280)
|
||||
if img.size > max_size:
|
||||
img.thumbnail(max_size)
|
||||
output = BytesIO()
|
||||
img.save(output, format="JPEG")
|
||||
output.seek(0)
|
||||
return output
|
||||
image_bytes.seek(0) # Reset pointer if not resized
|
||||
return image_bytes
|
||||
except Exception as e:
|
||||
print(f"Error resizing image: {e}")
|
||||
return image_bytes
|
||||
|
||||
|
||||
async def download_image(url):
|
||||
try:
|
||||
response = requests.get(url)
|
||||
if response.status_code == 200:
|
||||
img_bytes = BytesIO(response.content)
|
||||
return resize_image(img_bytes)
|
||||
except Exception as e:
|
||||
print(f"Error downloading image: {e}")
|
||||
return None
|
||||
|
||||
|
||||
@Client.on_message(filters.command("pinterest", prefix) & filters.me)
|
||||
async def pinterest_search(client: Client, message: Message):
|
||||
if len(message.command) < 2:
|
||||
await message.edit(
|
||||
"Usage: `pinterest [number] <query>`", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
return
|
||||
|
||||
num_pics = int(message.command[1]) if message.command[1].isdigit() else 10
|
||||
query = " ".join(message.command[2:])
|
||||
|
||||
# Update status
|
||||
status_message = await message.edit(
|
||||
"Searching for images...", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
|
||||
url = f"{API_URL}{query}"
|
||||
response = requests.get(url)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if data.get("status"):
|
||||
urls = [item["images_url"] for item in data.get("BK9", [])[:num_pics]]
|
||||
images = [download_image(img_url) for img_url in urls]
|
||||
|
||||
# Download images
|
||||
downloaded_images = await asyncio.gather(*images)
|
||||
|
||||
media = [
|
||||
InputMediaPhoto(media=img_bytes)
|
||||
for img_bytes in downloaded_images
|
||||
if img_bytes
|
||||
]
|
||||
|
||||
if media:
|
||||
await status_message.edit(
|
||||
"Uploading pictures...", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
while media:
|
||||
batch = media[:10]
|
||||
media = media[10:]
|
||||
await message.reply_media_group(batch)
|
||||
await status_message.delete() # Delete status message after uploading
|
||||
else:
|
||||
await status_message.edit(
|
||||
"No valid images found.", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
else:
|
||||
await status_message.edit(
|
||||
"No images found for the given query.",
|
||||
parse_mode=enums.ParseMode.MARKDOWN,
|
||||
)
|
||||
else:
|
||||
await status_message.edit(
|
||||
"An error occurred, please try again later.",
|
||||
parse_mode=enums.ParseMode.MARKDOWN,
|
||||
)
|
||||
|
||||
|
||||
modules_help["pinterest"] = {
|
||||
"pinterest [number]* [query]": "Get images from Pinterest. Default number of images is 10",
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
from utils.misc import modules_help, prefix
|
||||
import requests
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
from modules.url import generate_screenshot
|
||||
import os
|
||||
|
||||
# API endpoints for reverse image search engines
|
||||
SEARCH_ENGINES = {
|
||||
"lens": "https://lens.google.com/uploadbyurl?url={image}",
|
||||
"reverse": "https://www.google.com/searchbyimage?sbisrc=4chanx&image_url={image}&safe=off",
|
||||
"tineye": "https://www.tineye.com/search?url={image}",
|
||||
"bing": "https://www.bing.com/images/search?view=detailv2&iss=sbi&form=SBIVSP&sbisrc=UrlPaste&q=imgurl:{image}",
|
||||
"yandex": "https://yandex.com/images/search?source=collections&&url={image}&rpt=imageview",
|
||||
"saucenao": "https://saucenao.com/search.php?db=999&url={image}",
|
||||
}
|
||||
|
||||
|
||||
@Client.on_message(filters.command("risearch", prefix) & filters.reply)
|
||||
async def reverse_image_search(client: Client, message: Message):
|
||||
if not message.reply_to_message or not message.reply_to_message.photo:
|
||||
await message.reply_text(
|
||||
f"Please reply to an image with <code>{prefix}risearch [engine]</code> or <code>{prefix}risearch</code>."
|
||||
)
|
||||
return
|
||||
|
||||
command_parts = message.text.split(maxsplit=1)
|
||||
engines_to_use = (
|
||||
[command_parts[1].strip().lower()]
|
||||
if len(command_parts) > 1 and command_parts[1].strip()
|
||||
else list(SEARCH_ENGINES.keys())
|
||||
)
|
||||
|
||||
invalid_engines = [
|
||||
engine for engine in engines_to_use if engine not in SEARCH_ENGINES
|
||||
]
|
||||
if invalid_engines:
|
||||
await message.reply_text(
|
||||
f"Invalid engine(s): {', '.join(invalid_engines)}. Available: {', '.join(SEARCH_ENGINES.keys())}"
|
||||
)
|
||||
return
|
||||
|
||||
processing_message = await message.edit_text("Processing the image...")
|
||||
|
||||
try:
|
||||
# Download and upload the image
|
||||
photo_path = await message.reply_to_message.download()
|
||||
img_url = upload_image(photo_path)
|
||||
print(img_url)
|
||||
if not img_url:
|
||||
await processing_message.edit("Error: Could not upload the image.")
|
||||
return
|
||||
|
||||
# Perform searches for the selected engines
|
||||
for engine in engines_to_use:
|
||||
search_url = SEARCH_ENGINES[engine].format(image=img_url)
|
||||
await send_screenshot(client, message, search_url, engine)
|
||||
except Exception as e:
|
||||
await processing_message.edit(f"An error occurred: {e}")
|
||||
finally:
|
||||
if photo_path and os.path.exists(photo_path):
|
||||
os.remove(photo_path)
|
||||
|
||||
|
||||
def upload_image(photo_path):
|
||||
"""Uploads an image to tmpfiles.org and returns the direct download URL."""
|
||||
try:
|
||||
with open(photo_path, "rb") as image_file:
|
||||
response = requests.post(
|
||||
"https://tmpfiles.org/api/v1/upload", files={"file": image_file}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
url = data["data"]["url"]
|
||||
pic_url = url.split("/")[-2] + "/" + url.split("/")[-1]
|
||||
direct_download_url = url.replace(f"/{pic_url}", f"/dl/{pic_url}")
|
||||
print(direct_download_url)
|
||||
return direct_download_url
|
||||
else:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
async def send_screenshot(client, message, url, engine_name):
|
||||
"""Takes a screenshot of the URL and sends it to the chat."""
|
||||
screenshot_data = generate_screenshot(url)
|
||||
if screenshot_data:
|
||||
await client.send_photo(
|
||||
message.chat.id,
|
||||
screenshot_data,
|
||||
caption=f"<b>{engine_name.capitalize()} Result</b>\nURL: <code>{url}</code>",
|
||||
reply_to_message_id=message.id,
|
||||
)
|
||||
else:
|
||||
await message.reply(
|
||||
f"Failed to take screenshot for {engine_name.capitalize()}."
|
||||
)
|
||||
|
||||
|
||||
# Add module details to help
|
||||
modules_help["risearch"] = {
|
||||
"risearch": f"Reply to a photo with `{prefix}risearch [engine]` (e.g., `{prefix}risearch lens`, `{prefix}risearch bing`) "
|
||||
f"\nor use `{prefix}risearch` to analyze the image with all engines.",
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import aiohttp
|
||||
from pyrogram import Client, enums, filters
|
||||
from pyrogram.types import Message
|
||||
import requests
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
|
||||
class AioHttp:
|
||||
async def get_json(self, link):
|
||||
headers = {
|
||||
"accept": "*/*",
|
||||
"accept-language": "en-US",
|
||||
"cache-control": "no-cache",
|
||||
"client-geo-region": "global",
|
||||
"dnt": "1",
|
||||
"pragma": "no-cache",
|
||||
"priority": "u=1, i",
|
||||
"sec-ch-ua": '"Chromium";v="130", "Microsoft Edge";v="130", "Not?A_Brand";v="99"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "empty",
|
||||
"sec-fetch-mode": "cors",
|
||||
"sec-fetch-site": "same-origin",
|
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0",
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(link, headers=headers) as resp:
|
||||
return await resp.json()
|
||||
|
||||
|
||||
@Client.on_message(filters.command("unsplash", prefix) & filters.me)
|
||||
async def unsplash(client: Client, message: Message):
|
||||
if len(message.command) > 1 and isinstance(message.command[1], str):
|
||||
keyword = message.command[1]
|
||||
unsplash_dir = "downloads/unsplash/"
|
||||
if not os.path.exists(unsplash_dir):
|
||||
os.makedirs(unsplash_dir)
|
||||
|
||||
if len(message.command) > 2 and 2 <= int(message.command[2]) <= 10:
|
||||
await message.edit(
|
||||
"<b>Getting Pictures</b>", parse_mode=enums.ParseMode.HTML
|
||||
)
|
||||
count = int(message.command[2])
|
||||
images = []
|
||||
data = await AioHttp().get_json(
|
||||
f"https://unsplash.com/napi/search/photos?page=1&per_page={count}&query={keyword}"
|
||||
)
|
||||
while len(images) < count:
|
||||
for ia in range(len(images), count):
|
||||
img = data["results"][ia]["urls"]["raw"]
|
||||
if img.startswith("https://images.unsplash.com/photo"):
|
||||
image_content = requests.get(img).content
|
||||
with open(f"{unsplash_dir}/unsplash_{ia}.jpg", "wb") as f:
|
||||
f.write(image_content)
|
||||
imgr = f"{unsplash_dir}/unsplash_{ia}.jpg"
|
||||
images.append(imgr)
|
||||
else:
|
||||
images.append(img)
|
||||
if len(images) == count:
|
||||
break
|
||||
|
||||
for img in images:
|
||||
await client.send_document(message.chat.id, img)
|
||||
|
||||
await message.delete()
|
||||
shutil.rmtree(unsplash_dir)
|
||||
return
|
||||
else:
|
||||
await message.edit(
|
||||
"<b>Getting Picture</b>", parse_mode=enums.ParseMode.HTML
|
||||
)
|
||||
data = await AioHttp().get_json(
|
||||
f"https://unsplash.com/napi/search/photos?page=1&per_page=1&query={keyword}"
|
||||
)
|
||||
img = data["results"][0]["urls"]["raw"]
|
||||
await asyncio.gather(
|
||||
message.delete(), client.send_document(message.chat.id, str(img))
|
||||
)
|
||||
|
||||
|
||||
modules_help["unsplash"] = {
|
||||
"unsplash": f"[keyword]*",
|
||||
"unsplash": f"[keyword]* [number of results you want]*\n"
|
||||
"Makes a request to <code>unsplash.com</code> and sends the image with the keyword you provided.\n\n"
|
||||
"<b>Note:</b>\n1. The number of results you can get is limited to 10.\n"
|
||||
"2. Keyword is required and should be of one word only!.\n"
|
||||
"3. Images are sent as document to maintain quality.",
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message, InputMediaPhoto
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
import requests
|
||||
import asyncio
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
API_URL = "https://bk9.fun/search/unsplash?q="
|
||||
|
||||
|
||||
def resize_image(image_bytes):
|
||||
try:
|
||||
with Image.open(image_bytes) as img:
|
||||
max_size = (1280, 1280)
|
||||
if img.size > max_size:
|
||||
img.thumbnail(max_size)
|
||||
output = BytesIO()
|
||||
img.save(output, format="JPEG")
|
||||
output.seek(0)
|
||||
return output
|
||||
image_bytes.seek(0) # Reset pointer if not resized
|
||||
return image_bytes
|
||||
except Exception as e:
|
||||
print(f"Error resizing image: {e}")
|
||||
return image_bytes
|
||||
|
||||
|
||||
async def download_image(url):
|
||||
try:
|
||||
response = requests.get(url)
|
||||
if response.status_code == 200:
|
||||
img_bytes = BytesIO(response.content)
|
||||
resized_img_bytes = resize_image(img_bytes)
|
||||
return resized_img_bytes
|
||||
except Exception as e:
|
||||
print(f"Error downloading image: {e}")
|
||||
return None
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["unsplash2", "usp2"], prefix) & filters.me)
|
||||
async def imgsearch(client: Client, message: Message):
|
||||
if len(message.command) < 2:
|
||||
await message.edit(
|
||||
"Usage: `img [number] <query>`", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
return
|
||||
|
||||
num_pics = int(message.command[1]) if message.command[1].isdigit() else 10
|
||||
query = " ".join(message.command[2:])
|
||||
|
||||
# Update status
|
||||
status_message = await message.edit(
|
||||
"Searching for images...", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
|
||||
url = f"{API_URL}{query}"
|
||||
response = requests.get(url)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if data.get("status"):
|
||||
urls = data.get("BK9", [])[:num_pics]
|
||||
images = [download_image(img_url) for img_url in urls]
|
||||
|
||||
# Download images
|
||||
downloaded_images = await asyncio.gather(*images)
|
||||
|
||||
media = [
|
||||
InputMediaPhoto(media=img_bytes)
|
||||
for img_bytes in downloaded_images
|
||||
if img_bytes
|
||||
]
|
||||
|
||||
if media:
|
||||
await status_message.edit(
|
||||
"Uploading pictures...", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
while media:
|
||||
batch = media[:10]
|
||||
media = media[10:]
|
||||
await message.reply_media_group(batch)
|
||||
await status_message.delete() # Delete status message after uploading
|
||||
else:
|
||||
await status_message.edit(
|
||||
"No valid images found.", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
else:
|
||||
await status_message.edit(
|
||||
"No images found for the given query.",
|
||||
parse_mode=enums.ParseMode.MARKDOWN,
|
||||
)
|
||||
else:
|
||||
await status_message.edit(
|
||||
"An error occurred, please try again later.",
|
||||
parse_mode=enums.ParseMode.MARKDOWN,
|
||||
)
|
||||
|
||||
|
||||
modules_help["unsplash2"] = {
|
||||
"unsplash2 [number]* [query]": "Get HD images. Default number of images is 10",
|
||||
"usp2 [number]* [query]": "Get HD images. Default number of images is 10",
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
from datetime import datetime
|
||||
import sys
|
||||
from pyrogram import Client
|
||||
|
||||
from utils import config
|
||||
|
||||
common_params = {
|
||||
"api_id": config.api_id,
|
||||
"api_hash": config.api_hash,
|
||||
"hide_password": True,
|
||||
"test_mode": config.test_server,
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
if config.STRINGSESSION:
|
||||
common_params["session_string"] = config.STRINGSESSION
|
||||
|
||||
app = Client("my_account", **common_params)
|
||||
|
||||
if config.db_type in ["mongo", "mongodb"]:
|
||||
from pymongo import MongoClient, errors
|
||||
|
||||
db = MongoClient(config.db_url)
|
||||
try:
|
||||
db.server_info()
|
||||
except errors.ConnectionFailure as e:
|
||||
raise RuntimeError(
|
||||
"MongoDB server isn't available! "
|
||||
f"Provided url: {config.db_url}. "
|
||||
"Enter valid URL and restart installation"
|
||||
) from e
|
||||
|
||||
install_type = sys.argv[1] if len(sys.argv) > 1 else "3"
|
||||
if install_type == "1":
|
||||
restart = "pm2 restart Moon"
|
||||
elif install_type == "2":
|
||||
restart = "sudo systemctl restart Moon"
|
||||
else:
|
||||
restart = "cd Moon-Userbot/ && python main.py"
|
||||
|
||||
app.start()
|
||||
try:
|
||||
app.send_message(
|
||||
"me",
|
||||
f"<b>[{datetime.now()}] Userbot launched! \n"
|
||||
"Custom modules: @moonub_modules\n"
|
||||
f"For restart, enter:</b>\n"
|
||||
f"<code>{restart}</code>",
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[ERROR]: Sending Message to me failed! {e}")
|
||||
app.stop()
|
||||
@@ -0,0 +1,338 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Define color codes
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
INPUT='\033[1;30m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
PACKAGE_MANAGER=""
|
||||
|
||||
# Ensure the script is run with root privileges
|
||||
if [[ $UID != 0 ]]; then
|
||||
printf "${YELLOW}This script requires root privileges.${NC}\n" # skipcq
|
||||
printf "Please enter the root password to continue.\n"
|
||||
exec sudo "$0" "$@"
|
||||
else
|
||||
printf "${YELLOW}Running with root privileges${NC}\n" # skipcq
|
||||
fi
|
||||
|
||||
# Detect available package manager
|
||||
if command -v apt &>/dev/null; then
|
||||
PACKAGE_MANAGER="apt"
|
||||
elif command -v apk &>/dev/null; then
|
||||
PACKAGE_MANAGER="apk"
|
||||
elif command -v yum &>/dev/null; then
|
||||
PACKAGE_MANAGER="yum"
|
||||
elif command -v pacman &>/dev/null; then
|
||||
PACKAGE_MANAGER="pacman"
|
||||
else
|
||||
printf "${RED}Unsupported package manager. Please use a compatible distribution or update the installer script.${NC}\n" # skipcq
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v termux-setup-storage; then
|
||||
printf "${RED}For termux, please use https://raw.githubusercontent.com/The-MoonTg-project/Moon-Userbot/main/termux-install.sh${NC}\n" # skipcq
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install necessary packages based on detected package manager
|
||||
case "$PACKAGE_MANAGER" in
|
||||
apt)
|
||||
apt update -y
|
||||
apt install python3 python3-venv git wget -y || exit 2
|
||||
;;
|
||||
apk)
|
||||
apk update
|
||||
apk add python3 py3-virtualenv git wget || exit 2 # Packages here may be wrong, to verify
|
||||
;;
|
||||
yum)
|
||||
yum update -y
|
||||
yum install python3 python3-venv git wget -y || exit 2 # Packages here may be wrong, to verify
|
||||
;;
|
||||
pacman)
|
||||
pacman -S --noconfirm python python-virtualenv git wget || exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
# Clone repository if not exists
|
||||
if [[ -d "Moon-Userbot" && "$(basename "$PWD")" != "Moon-Userbot" ]]; then
|
||||
cd Moon-Userbot || exit 2
|
||||
elif [[ "$(basename "$PWD")" == "Moon-Userbot" && -f ".env.dist" && -f "main.py" && -d "modules" ]]; then
|
||||
printf "${BLUE}Already inside the Moon-Userbot repo, proceeding...${NC}\n" # skipcq
|
||||
else
|
||||
git clone https://github.com/The-MoonTg-project/Moon-Userbot || exit 2
|
||||
cd Moon-Userbot || exit 2
|
||||
fi
|
||||
|
||||
if [[ -f ".env" ]] && [[ -f "my_account.session" ]]; then
|
||||
printf "${GREEN}It seems that Moon-Userbot is already installed. Exiting...${NC}\n" # skipcq
|
||||
exit
|
||||
fi
|
||||
|
||||
# Prompt user if they want to proceed with creating a virtual environment
|
||||
printf "${YELLOW}It's recommended to use a virtual environment for Python projects.${NC}\n" # skipcq
|
||||
printf "Note: If your drive resources are very limited, you might consider not creating a virtual environment, but it shouldn't be rejected otherwise unless you know what you're doing.\n"
|
||||
printf "If you're unsure, it's better to create a virtual environment.\n"
|
||||
printf "${INPUT}Would you like to create a virtual environment? (Y/n)${NC} > " # skipcq
|
||||
read -r create_venv
|
||||
|
||||
if [[ "$create_venv" != "n" ]] && [[ "$create_venv" != "N" ]]; then
|
||||
# Create a virtual environment inside the cloned repository and activate it
|
||||
python3 -m venv venv
|
||||
. venv/bin/activate
|
||||
|
||||
# Upgrade pip and install wheel and pillow
|
||||
pip install -U pip wheel pillow
|
||||
fi
|
||||
|
||||
if [ -d ".venv" ]; then
|
||||
. .venv/bin/activate
|
||||
elif [ -d "venv" ]; then
|
||||
. venv/bin/activate
|
||||
fi
|
||||
|
||||
# Install Python requirements
|
||||
pip install -U -r requirements.txt || exit 2
|
||||
# Prompt for API_ID and API_HASH
|
||||
printf "Enter API_ID and API_HASH\n"
|
||||
printf "You can get it here -> https://my.telegram.org/\n"
|
||||
printf "Leave empty to use defaults (please note that using default keys is a ${RED}very bad idea${NC} and significantly increases your ban chances)\n" # skipcq
|
||||
read -r -p "API_ID > " api_id
|
||||
|
||||
# Default API_ID and API_HASH
|
||||
if [[ $api_id = "" ]]; then
|
||||
printf "${RED}You have chosen to use the default API_ID and API_HASH, which is strongly discouraged.${NC}\n" # skipcq
|
||||
printf "${YELLOW}Please type${NC} '${BLUE}I agree${NC}'${YELLOW} to confirm that you understand the risks and still wish to proceed.${NC}\n" # skipcq
|
||||
read -r -p "Confirmation > " confirmation
|
||||
if [[ $confirmation = "I agree" ]]; then
|
||||
api_id="2040"
|
||||
api_hash="b18441a1ff607e10a989891a5462e627"
|
||||
else
|
||||
printf "${RED}Confirmation not provided. Exiting...${NC}\n" # skipcq
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
read -r -p "API_HASH > " api_hash
|
||||
fi
|
||||
# Prompt for PM PERMIT warn limit
|
||||
# PM PERMIT warn limit is the number of messages a user can receive from others before giving them a warning, requires `antipm` plugin to be enabled
|
||||
printf "SET PM PERMIT warn limit\n"
|
||||
# Now below is more clear version:
|
||||
printf "The number of messages others can send you before receiving a warning, and eventually a ban or leave empty for default (3), requires antipm plugin to be enabled\n"
|
||||
read -r -p "PM_LIMIT warn limit > " pm_limit
|
||||
|
||||
if [[ $pm_limit = "" ]]; then
|
||||
pm_limit="3"
|
||||
printf "Limit not provided by user; set to default\n"
|
||||
fi
|
||||
|
||||
# Prompt for musicbot usage
|
||||
printf "Do you want to use musicbot? (y/N)"
|
||||
read -r musicbot
|
||||
if [[ $musicbot = "y" ]]; then
|
||||
printf "Enter SECOND_SESSION_STRING to be used by musicbot\n"
|
||||
read -r -p "SECOND_SESSION > " second_session
|
||||
if [[ $second_session = "" ]]; then
|
||||
printf "SECOND_SESSION not provided by user\n"
|
||||
second_session=""
|
||||
fi
|
||||
fi
|
||||
|
||||
# Prompt for various API keys
|
||||
printf "Enter APIFLASH_KEY for webshot plugin\n"
|
||||
printf "You can get it here -> https://apiflash.com/dashboard/access_keys\n"
|
||||
read -r -p "APIFLASH_KEY > " apiflash_key
|
||||
|
||||
if [[ $apiflash_key = "" ]]; then
|
||||
printf "NOTE: API Not set; you'll get errors with webshot & ws module\n"
|
||||
fi
|
||||
|
||||
printf "Enter RMBG_KEY for remove background module\n"
|
||||
printf "You can get it here -> https://www.remove.bg/dashboard#api-key\n"
|
||||
read -r -p "RMBG_KEY > " rmbg_key
|
||||
|
||||
if [[ $rmbg_key = "" ]]; then
|
||||
printf "NOTE: API Not set; you'll not be able to use remove background modules\n"
|
||||
fi
|
||||
|
||||
printf "Enter VT_KEY for VirusTotal\n"
|
||||
printf "You can get it here -> https://www.virustotal.com/\n"
|
||||
read -r -p "VT_KEY > " vt_key
|
||||
|
||||
if [[ $vt_key = "" ]]; then
|
||||
printf "NOTE: API Not set; you'll not be able to use VirusTotal module\n"
|
||||
fi
|
||||
|
||||
printf "Enter GEMINI_KEY if you want to use AI\n"
|
||||
printf "You can get it here -> https://makersuite.google.com/app/apikey\n"
|
||||
read -r -p "GEMINI_KEY > " gemini_key
|
||||
|
||||
if [[ $gemini_key = "" ]]; then
|
||||
printf "NOTE: API Not set; you'll not be able to use Gemini AI modules\n"
|
||||
fi
|
||||
|
||||
printf "Enter COHERE_KEY if you want to use AI"
|
||||
printf "You can get it here -> https://dashboard.cohere.com/api-keys\n"
|
||||
read -r -p "COHERE_KEY > " cohere_key
|
||||
|
||||
if [[ $cohere_key = "" ]]; then
|
||||
printf "NOTE: API Not set; you'll not be able to use Coral AI modules\n"
|
||||
fi
|
||||
|
||||
while true; do
|
||||
# Prompt for database type and database URL if MongoDB is selected
|
||||
printf "${YELLOW}Choose database type:${NC}\n" # skipcq
|
||||
printf "[1] MongoDB db_url\n"
|
||||
printf "[2] MongoDB localhost\n"
|
||||
printf "[3] Sqlite (default)\n"
|
||||
read -r -p "> " db_type
|
||||
|
||||
case $db_type in
|
||||
1)
|
||||
printf "Please enter db_url\n"
|
||||
printf "You can get it here -> https://mongodb.com/atlas\n"
|
||||
read -r -p "> " db_url
|
||||
db_name=Moon_Userbot
|
||||
db_type=mongodb
|
||||
break
|
||||
;;
|
||||
2)
|
||||
if ! command -v apt &>/dev/null; then
|
||||
printf "This option requires apt package manager, which is not available on your system.\n"
|
||||
printf "Please choose a different database type.\n"
|
||||
continue
|
||||
fi
|
||||
|
||||
if systemctl status mongodb; then
|
||||
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | apt-key add -
|
||||
source /etc/os-release
|
||||
printf "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu %s/mongodb-org/5.0 multiverse\n" "${UBUNTU_CODENAME}" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list
|
||||
apt update
|
||||
apt install mongodb -y
|
||||
systemctl daemon-reload
|
||||
systemctl enable mongodb
|
||||
fi
|
||||
systemctl start mongodb
|
||||
|
||||
db_url=mongodb://localhost:27017
|
||||
db_name=Moon_Userbot
|
||||
db_type=mongodb
|
||||
break
|
||||
;;
|
||||
3)
|
||||
db_name=db.sqlite3
|
||||
db_type=sqlite3
|
||||
break
|
||||
;;
|
||||
*)
|
||||
printf "${RED}Invalid choice!${NC}\n" # skipcq
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Generate .env file with collected variables
|
||||
cat >.env <<EOL
|
||||
API_ID=${api_id}
|
||||
API_HASH=${api_hash}
|
||||
STRINGSESSION=
|
||||
# sqlite/sqlite3 or mongo/mongodb
|
||||
DATABASE_TYPE=${db_type}
|
||||
# file name for sqlite3, database name for mongodb
|
||||
DATABASE_NAME=${db_name}
|
||||
# only for mongodb
|
||||
DATABASE_URL=${db_url}
|
||||
APIFLASH_KEY=${apiflash_key}
|
||||
RMBG_KEY=${rmbg_key}
|
||||
VT_KEY=${vt_key}
|
||||
GEMINI_KEY=${gemini_key}
|
||||
COHERE_KEY=${cohere_key}
|
||||
PM_LIMIT=${pm_limit}
|
||||
SECOND_SESSION=${second_session}
|
||||
EOL
|
||||
|
||||
# Adjust the ownership of the Moon-Userbot directory
|
||||
chown -R $SUDO_USER:$SUDO_USER .
|
||||
|
||||
# Configure the bot based on selected installation type
|
||||
while true; do
|
||||
# Prompt for installation type and execute accordingly
|
||||
printf "${YELLOW}Choose installation type:${NC}\n" # skipcq
|
||||
printf "[1] PM2\n"
|
||||
printf "[2] Systemd service\n"
|
||||
printf "[3] Custom (default)\n"
|
||||
read -r -p "> " install_type
|
||||
|
||||
case $install_type in
|
||||
1)
|
||||
if ! command -v apt &>/dev/null; then
|
||||
printf "This option requires apt package manager, which is not available on your system.\n"
|
||||
printf "Please choose a different installation type.\n"
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! command -v pm2 &>/dev/null; then
|
||||
curl -fsSL https://deb.nodesource.com/setup_17.x | bash
|
||||
apt install nodejs -y
|
||||
npm install pm2 -g
|
||||
su -c "pm2 startup" $SUDO_USER
|
||||
env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u $SUDO_USER --hp /home/$SUDO_USER
|
||||
fi
|
||||
su -c "pm2 start main.py --name Moon --interpreter python3" $SUDO_USER
|
||||
su -c "pm2 save" $SUDO_USER
|
||||
|
||||
printf "${GREEN}============================\\n" # skipcq
|
||||
printf "Great! Moon-Userbot installed successfully and running now!\n"
|
||||
printf "Installation type: PM2\n"
|
||||
printf "Start with: \"pm2 start Moon\"\n"
|
||||
printf "Stop with: \"pm2 stop Moon\"\n"
|
||||
printf "Process name: Moon\n"
|
||||
printf "============================${NC}\n" # skipcq
|
||||
break
|
||||
;;
|
||||
2)
|
||||
cat >/etc/systemd/system/Moon.service <<EOL
|
||||
[Unit]
|
||||
Description=Service for Moon Userbot
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=$(which python3) ${PWD}/main.py
|
||||
WorkingDirectory=${PWD}
|
||||
Restart=always
|
||||
User=${SUDO_USER}
|
||||
Group=${SUDO_USER}
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOL
|
||||
systemctl daemon-reload
|
||||
systemctl start Moon
|
||||
systemctl enable Moon
|
||||
|
||||
printf "${GREEN}============================\\n" # skipcq
|
||||
printf "Great! Moon-Userbot installed successfully and running now!\n"
|
||||
printf "Installation type: Systemd service\n"
|
||||
printf "Start with: \"sudo systemctl start Moon\"\n"
|
||||
printf "Stop with: \"sudo systemctl stop Moon\"\n"
|
||||
printf "============================${NC}\n" # skipcq
|
||||
break
|
||||
;;
|
||||
3)
|
||||
printf "${GREEN}============================\\n" # skipcq
|
||||
printf "Great! Moon-Userbot installed successfully!\n"
|
||||
printf "Installation type: Custom\n"
|
||||
printf "Start with: \"python3 main.py\"\n"
|
||||
printf "============================${NC}\n" # skipcq
|
||||
break
|
||||
;;
|
||||
*)
|
||||
printf "Invalid choice! Please enter 1, 2, or 3."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
su -c "python3 install.py ${install_type}" $SUDO_USER || exit 3
|
||||
|
||||
# Adjust the ownership of the Moon-Userbot directory again as a final step
|
||||
chown -R $SUDO_USER:$SUDO_USER .
|
||||
@@ -0,0 +1,179 @@
|
||||
# Moon-Userbot - telegram userbot
|
||||
# Copyright (C) 2020-present Moon Userbot Organization
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "pip",
|
||||
# "pyrofork",
|
||||
# "tgcrypto",
|
||||
# "wheel",
|
||||
# "gunicorn",
|
||||
# "flask",
|
||||
# "humanize",
|
||||
# "pygments",
|
||||
# "pymongo",
|
||||
# "psutil",
|
||||
# "Pillow>=10.3.0",
|
||||
# "click",
|
||||
# "dnspython",
|
||||
# "requests",
|
||||
# "environs",
|
||||
# "GitPython",
|
||||
# "beautifulsoup4",
|
||||
# "aiohttp",
|
||||
# "aiofiles",
|
||||
# "pySmartDL",
|
||||
# ]
|
||||
# ///
|
||||
import os
|
||||
import logging
|
||||
|
||||
import sqlite3
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
from pyrogram import Client, idle, errors
|
||||
from pyrogram.enums.parse_mode import ParseMode
|
||||
from pyrogram.raw.functions.account import GetAuthorizations, DeleteAccount
|
||||
import requests
|
||||
|
||||
from utils import config
|
||||
from utils.db import db
|
||||
from utils.misc import gitrepo, userbot_version
|
||||
from utils.scripts import restart
|
||||
from utils.rentry import rentry_cleanup_job
|
||||
from utils.module import ModuleManager
|
||||
|
||||
SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
if SCRIPT_PATH != os.getcwd():
|
||||
os.chdir(SCRIPT_PATH)
|
||||
|
||||
common_params = {
|
||||
"api_id": config.api_id,
|
||||
"api_hash": config.api_hash,
|
||||
"hide_password": True,
|
||||
"workdir": SCRIPT_PATH,
|
||||
"app_version": userbot_version,
|
||||
"device_model": f"mUserbot",
|
||||
"system_version": platform.version() + " " + platform.machine(),
|
||||
"sleep_threshold": 30,
|
||||
"test_mode": config.test_server,
|
||||
"parse_mode": ParseMode.HTML,
|
||||
}
|
||||
|
||||
if config.STRINGSESSION:
|
||||
common_params["session_string"] = config.STRINGSESSION
|
||||
|
||||
app = Client("my_account", **common_params)
|
||||
|
||||
|
||||
def load_missing_modules():
|
||||
all_modules = db.get("custom.modules", "allModules", [])
|
||||
if not all_modules:
|
||||
return
|
||||
|
||||
custom_modules_path = f"{SCRIPT_PATH}/modules/custom_modules"
|
||||
os.makedirs(custom_modules_path, exist_ok=True)
|
||||
|
||||
try:
|
||||
f = requests.get(
|
||||
"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/full.txt"
|
||||
).text
|
||||
except Exception:
|
||||
logging.error("Failed to fetch custom modules list")
|
||||
return
|
||||
modules_dict = {
|
||||
line.split("/")[-1].split()[0]: line.strip() for line in f.splitlines()
|
||||
}
|
||||
|
||||
for module_name in all_modules:
|
||||
module_path = f"{custom_modules_path}/{module_name}.py"
|
||||
if not os.path.exists(module_path) and module_name in modules_dict:
|
||||
url = f"https://raw.githubusercontent.com/The-MoonTg-project/custom_modules/main/{modules_dict[module_name]}.py"
|
||||
resp = requests.get(url)
|
||||
if resp.ok:
|
||||
with open(module_path, "wb") as f:
|
||||
f.write(resp.content)
|
||||
logging.info("Loaded missing module: %s", module_name)
|
||||
else:
|
||||
logging.warning("Failed to load module: %s", module_name)
|
||||
|
||||
|
||||
async def main():
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
handlers=[logging.FileHandler("moonlogs.txt"), logging.StreamHandler()],
|
||||
level=logging.INFO,
|
||||
)
|
||||
DeleteAccount.__new__ = None
|
||||
|
||||
try:
|
||||
await app.start()
|
||||
except sqlite3.OperationalError as e:
|
||||
if str(e) == "database is locked" and os.name == "posix":
|
||||
logging.warning(
|
||||
"Session file is locked. Trying to kill blocking process..."
|
||||
)
|
||||
subprocess.run(["fuser", "-k", "my_account.session"], check=True)
|
||||
restart()
|
||||
raise
|
||||
except (errors.NotAcceptable, errors.Unauthorized) as e:
|
||||
logging.error(
|
||||
"%s: %s\nMoving session file to my_account.session-old...",
|
||||
e.__class__.__name__,
|
||||
e,
|
||||
)
|
||||
os.rename("./my_account.session", "./my_account.session-old")
|
||||
restart()
|
||||
|
||||
load_missing_modules()
|
||||
module_manager = ModuleManager.get_instance()
|
||||
await module_manager.load_modules(app)
|
||||
|
||||
if info := db.get("core.updater", "restart_info"):
|
||||
text = {
|
||||
"restart": "<b>Restart completed!</b>",
|
||||
"update": "<b>Update process completed!</b>",
|
||||
}[info["type"]]
|
||||
try:
|
||||
await app.edit_message_text(info["chat_id"], info["message_id"], text)
|
||||
except errors.RPCError:
|
||||
pass
|
||||
db.remove("core.updater", "restart_info")
|
||||
|
||||
# required for sessionkiller module
|
||||
if db.get("core.sessionkiller", "enabled", False):
|
||||
db.set(
|
||||
"core.sessionkiller",
|
||||
"auths_hashes",
|
||||
[
|
||||
auth.hash
|
||||
for auth in (await app.invoke(GetAuthorizations())).authorizations
|
||||
],
|
||||
)
|
||||
|
||||
logging.info("Moon-Userbot started!")
|
||||
|
||||
app.loop.create_task(rentry_cleanup_job())
|
||||
|
||||
await idle()
|
||||
|
||||
await app.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(main())
|
||||
@@ -0,0 +1,41 @@
|
||||
from asyncio import sleep
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
digits = {
|
||||
str(i): el
|
||||
for i, el in enumerate(["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣"])
|
||||
}
|
||||
|
||||
|
||||
def prettify(val: int) -> str:
|
||||
return "".join(digits[i] for i in str(val))
|
||||
|
||||
|
||||
@Client.on_message(filters.command("ghoul", prefix) & filters.me)
|
||||
async def ghoul_counter(_, message: Message):
|
||||
await message.delete()
|
||||
|
||||
if len(message.command) > 1 and message.command[1].isdigit():
|
||||
counter = int(message.command[1])
|
||||
else:
|
||||
counter = 1000
|
||||
|
||||
msg = await message.reply(prettify(counter), quote=False)
|
||||
|
||||
await sleep(1)
|
||||
|
||||
while counter // 7:
|
||||
counter -= 7
|
||||
await msg.edit(prettify(counter))
|
||||
await sleep(1)
|
||||
|
||||
await msg.edit("<b>🤡 GHOUL 🤡</b>")
|
||||
|
||||
|
||||
modules_help["1000-7"] = {
|
||||
"ghoul [count_from]": "counting from 1000 (or given [count_from] to 0 as a ghoul"
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
# Moon-Userbot - telegram userbot
|
||||
# Copyright (C) 2020-present Moon Userbot Organization
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from contextlib import suppress
|
||||
|
||||
from pyrogram.enums import ChatType
|
||||
from pyrogram import Client, ContinuePropagation, filters
|
||||
from pyrogram.errors import (
|
||||
UserAdminInvalid,
|
||||
ChatAdminRequired,
|
||||
RPCError,
|
||||
)
|
||||
from pyrogram.raw import functions
|
||||
from pyrogram.types import Message, ChatPermissions
|
||||
|
||||
from utils.db import db
|
||||
from utils.scripts import format_exc, with_reply
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
from utils.handlers import (
|
||||
BanHandler,
|
||||
UnbanHandler,
|
||||
KickHandler,
|
||||
KickDeletedAccountsHandler,
|
||||
TimeMuteHandler,
|
||||
TimeUnmuteHandler,
|
||||
TimeMuteUsersHandler,
|
||||
UnmuteHandler,
|
||||
MuteHandler,
|
||||
DemoteHandler,
|
||||
PromoteHandler,
|
||||
AntiChannelsHandler,
|
||||
DeleteHistoryHandler,
|
||||
AntiRaidHandler,
|
||||
)
|
||||
|
||||
|
||||
db_cache: dict = db.get_collection("core.ats")
|
||||
|
||||
|
||||
def update_cache():
|
||||
db_cache.clear()
|
||||
db_cache.update(db.get_collection("core.ats"))
|
||||
|
||||
|
||||
@Client.on_message(filters.group & ~filters.me)
|
||||
async def admintool_handler(_, message: Message):
|
||||
if message.sender_chat and (
|
||||
message.sender_chat.type == "supergroup"
|
||||
or message.sender_chat.id == db_cache.get(f"linked{message.chat.id}", 0)
|
||||
):
|
||||
raise ContinuePropagation
|
||||
|
||||
if message.sender_chat and db_cache.get(f"antich{message.chat.id}", False):
|
||||
with suppress(RPCError):
|
||||
await message.delete()
|
||||
await message.chat.ban_member(message.sender_chat.id)
|
||||
|
||||
tmuted_users = db_cache.get(f"c{message.chat.id}", [])
|
||||
if (
|
||||
message.from_user
|
||||
and message.from_user.id in tmuted_users
|
||||
or message.sender_chat
|
||||
and message.sender_chat.id in tmuted_users
|
||||
):
|
||||
with suppress(RPCError):
|
||||
await message.delete()
|
||||
|
||||
if db_cache.get(f"antiraid{message.chat.id}", False):
|
||||
with suppress(RPCError):
|
||||
await message.delete()
|
||||
if message.from_user:
|
||||
await message.chat.ban_member(message.from_user.id)
|
||||
elif message.sender_chat:
|
||||
await message.chat.ban_member(message.sender_chat.id)
|
||||
|
||||
if message.new_chat_members and db_cache.get(
|
||||
f"welcome_enabled{message.chat.id}", False
|
||||
):
|
||||
await message.reply(
|
||||
db_cache.get(f"welcome_text{message.chat.id}"),
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
|
||||
raise ContinuePropagation
|
||||
|
||||
|
||||
async def get_user_and_name(message):
|
||||
if message.reply_to_message.from_user:
|
||||
return (
|
||||
message.reply_to_message.from_user.id,
|
||||
message.reply_to_message.from_user.first_name,
|
||||
)
|
||||
if message.reply_to_message.sender_chat:
|
||||
return (
|
||||
message.reply_to_message.sender_chat.id,
|
||||
message.reply_to_message.sender_chat.title,
|
||||
)
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["ban"], prefix) & filters.me)
|
||||
async def ban_command(client: Client, message: Message):
|
||||
handler = BanHandler(client, message)
|
||||
await handler.handle_ban()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["unban"], prefix) & filters.me)
|
||||
async def unban_command(client: Client, message: Message):
|
||||
handler = UnbanHandler(client, message)
|
||||
await handler.handle_unban()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["kick"], prefix) & filters.me)
|
||||
async def kick_command(client: Client, message: Message):
|
||||
handler = KickHandler(client, message)
|
||||
await handler.handle_kick()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["kickdel"], prefix) & filters.me)
|
||||
async def kickdel_cmd(client: Client, message: Message):
|
||||
handler = KickDeletedAccountsHandler(client, message)
|
||||
await handler.kick_deleted_accounts()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["tmute"], prefix) & filters.me)
|
||||
async def tmute_command(client: Client, message: Message):
|
||||
handler = TimeMuteHandler(client, message)
|
||||
await handler.handle_tmute()
|
||||
update_cache()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["tunmute"], prefix) & filters.me)
|
||||
async def tunmute_command(client: Client, message: Message):
|
||||
handler = TimeUnmuteHandler(client, message)
|
||||
await handler.handle_tunmute()
|
||||
update_cache()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["tmute_users"], prefix) & filters.me)
|
||||
async def tunmute_users_command(client: Client, message: Message):
|
||||
handler = TimeMuteUsersHandler(client, message)
|
||||
await handler.list_tmuted_users()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["unmute"], prefix) & filters.me)
|
||||
async def unmute_command(client: Client, message: Message):
|
||||
handler = UnmuteHandler(client, message)
|
||||
await handler.handle_unmute()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["mute"], prefix) & filters.me)
|
||||
async def mute_command(client: Client, message: Message):
|
||||
handler = MuteHandler(client, message)
|
||||
await handler.handle_mute()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["demote"], prefix) & filters.me)
|
||||
async def demote_command(client: Client, message: Message):
|
||||
handler = DemoteHandler(client, message)
|
||||
await handler.handle_demote()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["promote"], prefix) & filters.me)
|
||||
async def promote_command(client: Client, message: Message):
|
||||
handler = PromoteHandler(client, message)
|
||||
await handler.handle_promote()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["antich"], prefix))
|
||||
async def anti_channels(client: Client, message: Message):
|
||||
handler = AntiChannelsHandler(client, message)
|
||||
await handler.handle_anti_channels()
|
||||
update_cache()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["delete_history", "dh"], prefix))
|
||||
async def delete_history(client: Client, message: Message):
|
||||
handler = DeleteHistoryHandler(client, message)
|
||||
await handler.handle_delete_history()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["report_spam", "rs"], prefix))
|
||||
@with_reply
|
||||
async def report_spam(client: Client, message: Message):
|
||||
try:
|
||||
channel = await client.resolve_peer(message.chat.id)
|
||||
|
||||
user_id, name = await get_user_and_name(message)
|
||||
peer = await client.resolve_peer(user_id)
|
||||
await client.invoke(
|
||||
functions.channels.ReportSpam(
|
||||
channel=channel,
|
||||
participant=peer,
|
||||
id=[message.reply_to_message.id],
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
await message.edit(format_exc(e))
|
||||
else:
|
||||
await message.edit(f"<b>Message</a> from {name} was reported</b>")
|
||||
|
||||
|
||||
@Client.on_message(filters.command("pin", prefix) & filters.me)
|
||||
@with_reply
|
||||
async def pin(_, message: Message):
|
||||
try:
|
||||
await message.reply_to_message.pin()
|
||||
await message.edit("<b>Pinned!</b>")
|
||||
except Exception as e:
|
||||
await message.edit(format_exc(e))
|
||||
|
||||
|
||||
@Client.on_message(filters.command("unpin", prefix) & filters.me)
|
||||
@with_reply
|
||||
async def unpin(_, message: Message):
|
||||
try:
|
||||
await message.reply_to_message.unpin()
|
||||
await message.edit("<b>Unpinned!</b>")
|
||||
except Exception as e:
|
||||
await message.edit(format_exc(e))
|
||||
|
||||
|
||||
@Client.on_message(filters.command("ro", prefix) & filters.me)
|
||||
async def ro(client: Client, message: Message):
|
||||
if message.chat.type not in [ChatType.GROUP, ChatType.SUPERGROUP]:
|
||||
await message.edit("<b>Invalid chat type</b>")
|
||||
return
|
||||
|
||||
try:
|
||||
perms = message.chat.permissions
|
||||
perms_list = [
|
||||
perms.can_send_messages,
|
||||
perms.can_send_media_messages,
|
||||
perms.can_send_polls,
|
||||
perms.can_add_web_page_previews,
|
||||
perms.can_change_info,
|
||||
perms.can_invite_users,
|
||||
perms.can_pin_messages,
|
||||
]
|
||||
db.set("core.ats", f"ro{message.chat.id}", perms_list)
|
||||
|
||||
try:
|
||||
await client.set_chat_permissions(message.chat.id, ChatPermissions())
|
||||
except (UserAdminInvalid, ChatAdminRequired):
|
||||
await message.edit("<b>No rights</b>")
|
||||
else:
|
||||
await message.edit(
|
||||
"<b>Read-only mode activated!\n"
|
||||
f"Turn off with:</b><code>{prefix}unro</code>"
|
||||
)
|
||||
except Exception as e:
|
||||
await message.edit(format_exc(e))
|
||||
|
||||
|
||||
@Client.on_message(filters.command("unro", prefix) & filters.me)
|
||||
async def unro(client: Client, message: Message):
|
||||
if message.chat.type not in [ChatType.GROUP, ChatType.SUPERGROUP]:
|
||||
await message.edit("<b>Invalid chat type</b>")
|
||||
return
|
||||
|
||||
try:
|
||||
perms_list = db.get(
|
||||
"core.ats",
|
||||
f"ro{message.chat.id}",
|
||||
[True, True, False, False, False, False, False],
|
||||
)
|
||||
|
||||
common_perms = {
|
||||
"can_send_messages": perms_list[0],
|
||||
"can_send_media_messages": perms_list[1],
|
||||
"can_send_polls": perms_list[2],
|
||||
"can_add_web_page_previews": perms_list[3],
|
||||
"can_change_info": perms_list[4],
|
||||
"can_invite_users": perms_list[5],
|
||||
"can_pin_messages": perms_list[6],
|
||||
}
|
||||
|
||||
perms = ChatPermissions(**common_perms)
|
||||
|
||||
try:
|
||||
await client.set_chat_permissions(message.chat.id, perms)
|
||||
except (UserAdminInvalid, ChatAdminRequired):
|
||||
await message.edit("<b>No rights</b>")
|
||||
else:
|
||||
await message.edit("<b>Read-only mode disabled!</b>")
|
||||
except Exception as e:
|
||||
await message.edit(format_exc(e))
|
||||
|
||||
|
||||
@Client.on_message(filters.command("antiraid", prefix) & filters.me)
|
||||
async def antiraid(client: Client, message: Message):
|
||||
handler = AntiRaidHandler(client, message)
|
||||
await handler.handle_antiraid()
|
||||
update_cache()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["welcome", "wc"], prefix) & filters.me)
|
||||
async def welcome(_, message: Message):
|
||||
if message.chat.type not in [ChatType.GROUP, ChatType.SUPERGROUP]:
|
||||
return await message.edit("<b>Unsupported chat type</b>")
|
||||
|
||||
if len(message.command) > 1:
|
||||
text = message.text.split(maxsplit=1)[1]
|
||||
db.set("core.ats", f"welcome_enabled{message.chat.id}", True)
|
||||
db.set("core.ats", f"welcome_text{message.chat.id}", text)
|
||||
|
||||
await message.edit(
|
||||
f"<b>Welcome enabled in this chat\nText:</b> <code>{text}</code>"
|
||||
)
|
||||
else:
|
||||
db.set("core.ats", f"welcome_enabled{message.chat.id}", False)
|
||||
await message.edit("<b>Welcome disabled in this chat</b>")
|
||||
|
||||
update_cache()
|
||||
|
||||
|
||||
modules_help["admintool"] = {
|
||||
"ban [reply]/[username/id]* [reason] [report_spam] [delete_history]": "ban user in chat",
|
||||
"unban [reply]/[username/id]* [reason]": "unban user in chat",
|
||||
"kick [reply]/[userid]* [reason] [report_spam] [delete_history]": "kick user out of chat",
|
||||
"mute [reply]/[userid]* [reason] [1m]/[1h]/[1d]/[1w]": "mute user in chat",
|
||||
"unmute [reply]/[userid]* [reason]": "unmute user in chat",
|
||||
"promote [reply]/[userid]* [prefix]": "promote user in chat",
|
||||
"demote [reply]/[userid]* [reason]": "demote user in chat",
|
||||
"tmute [reply]/[username/id]* [reason]": "delete all new messages from user in chat",
|
||||
"tunmute [reply]/[username/id]* [reason]": "stop deleting all messages from user in chat",
|
||||
"tmute_users": "list of tmuted (.tmute) users",
|
||||
"antich [enable/disable]": "turn on/off blocking channels in this chat",
|
||||
"delete_history [reply]/[username/id]* [reason]": "delete history from member in chat",
|
||||
"report_spam [reply]*": "report spam message in chat",
|
||||
"pin [reply]*": "Pin replied message",
|
||||
"unpin [reply]*": "Unpin replied message",
|
||||
"ro": "enable read-only mode",
|
||||
"unro": "disable read-only mode",
|
||||
"antiraid [on|off]": "when enabled, anyone who writes message will be blocked. Useful in raids. "
|
||||
"Running without arguments equals to toggling state",
|
||||
"welcome [text]*": "enable auto-welcome to new users in groups. "
|
||||
"Running without text equals to disable",
|
||||
"kickdel": "Kick all deleted accounts",
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
# Moon-Userbot - telegram userbot
|
||||
# Copyright (C) 2020-present Moon Userbot Organization
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from time import perf_counter
|
||||
from typing import AsyncGenerator, List, Optional, Union
|
||||
|
||||
from pyrogram import Client, enums, filters, raw, types, utils
|
||||
from pyrogram.types.object import Object
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc
|
||||
|
||||
|
||||
class Chat(Object):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
client: "Client" = None,
|
||||
id: id,
|
||||
type: type,
|
||||
is_verified: bool = None,
|
||||
is_restricted: bool = None,
|
||||
is_creator: bool = None,
|
||||
is_scam: bool = None,
|
||||
is_fake: bool = None,
|
||||
is_support: bool = None,
|
||||
title: str = None,
|
||||
username: str = None,
|
||||
first_name: str = None,
|
||||
last_name: str = None,
|
||||
photo: "types.ChatPhoto" = None,
|
||||
bio: str = None,
|
||||
description: str = None,
|
||||
dc_id: int = None,
|
||||
has_protected_content: bool = None,
|
||||
invite_link: str = None,
|
||||
pinned_message=None,
|
||||
sticker_set_name: str = None,
|
||||
can_set_sticker_set: bool = None,
|
||||
members_count: int = None,
|
||||
restrictions: List["types.Restriction"] = None,
|
||||
permissions: "types.ChatPermissions" = None,
|
||||
distance: int = None,
|
||||
linked_chat: "types.Chat" = None,
|
||||
send_as_chat: "types.Chat" = None,
|
||||
available_reactions: Optional["types.ChatReactions"] = None,
|
||||
is_admin: bool = False,
|
||||
deactivated: bool = False,
|
||||
):
|
||||
super().__init__(client)
|
||||
|
||||
self.id = id
|
||||
self.type = type
|
||||
self.is_verified = is_verified
|
||||
self.is_restricted = is_restricted
|
||||
self.is_creator = is_creator
|
||||
self.is_scam = is_scam
|
||||
self.is_fake = is_fake
|
||||
self.is_support = is_support
|
||||
self.title = title
|
||||
self.username = username
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.photo = photo
|
||||
self.bio = bio
|
||||
self.description = description
|
||||
self.dc_id = dc_id
|
||||
self.has_protected_content = has_protected_content
|
||||
self.invite_link = invite_link
|
||||
self.pinned_message = pinned_message
|
||||
self.sticker_set_name = sticker_set_name
|
||||
self.can_set_sticker_set = can_set_sticker_set
|
||||
self.members_count = members_count
|
||||
self.restrictions = restrictions
|
||||
self.permissions = permissions
|
||||
self.distance = distance
|
||||
self.linked_chat = linked_chat
|
||||
self.send_as_chat = send_as_chat
|
||||
self.available_reactions = available_reactions
|
||||
self.is_admin = is_admin
|
||||
self.deactivated = deactivated
|
||||
|
||||
@staticmethod
|
||||
def _parse_user_chat(client, user: raw.types.User) -> "Chat":
|
||||
peer_id = user.id
|
||||
|
||||
return Chat(
|
||||
id=peer_id,
|
||||
type=enums.ChatType.BOT if user.bot else enums.ChatType.PRIVATE,
|
||||
is_verified=getattr(user, "verified", None),
|
||||
is_restricted=getattr(user, "restricted", None),
|
||||
is_scam=getattr(user, "scam", None),
|
||||
is_fake=getattr(user, "fake", None),
|
||||
is_support=getattr(user, "support", None),
|
||||
username=user.username,
|
||||
first_name=user.first_name,
|
||||
last_name=user.last_name,
|
||||
photo=types.ChatPhoto._parse(client, user.photo, peer_id, user.access_hash),
|
||||
restrictions=types.List(
|
||||
[types.Restriction._parse(r) for r in user.restriction_reason]
|
||||
)
|
||||
or None,
|
||||
dc_id=getattr(getattr(user, "photo", None), "dc_id", None),
|
||||
client=client,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _parse_chat_chat(client, chat: raw.types.Chat) -> "Chat":
|
||||
peer_id = -chat.id
|
||||
return Chat(
|
||||
id=peer_id,
|
||||
type=enums.ChatType.GROUP,
|
||||
title=chat.title,
|
||||
is_creator=getattr(chat, "creator", None),
|
||||
photo=types.ChatPhoto._parse(
|
||||
client, getattr(chat, "photo", None), peer_id, 0
|
||||
),
|
||||
permissions=types.ChatPermissions._parse(
|
||||
getattr(chat, "default_banned_rights", None)
|
||||
),
|
||||
members_count=getattr(chat, "participants_count", None),
|
||||
dc_id=getattr(getattr(chat, "photo", None), "dc_id", None),
|
||||
has_protected_content=getattr(chat, "noforwards", None),
|
||||
client=client,
|
||||
is_admin=bool(getattr(chat, "admin_rights", False)),
|
||||
deactivated=chat.deactivated,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat":
|
||||
peer_id = utils.get_channel_id(channel.id)
|
||||
restriction_reason = getattr(channel, "restriction_reason", [])
|
||||
|
||||
return Chat(
|
||||
id=peer_id,
|
||||
type=(
|
||||
enums.ChatType.SUPERGROUP
|
||||
if getattr(channel, "megagroup", None)
|
||||
else enums.ChatType.CHANNEL
|
||||
),
|
||||
is_verified=getattr(channel, "verified", None),
|
||||
is_restricted=getattr(channel, "restricted", None),
|
||||
is_creator=getattr(channel, "creator", None),
|
||||
is_scam=getattr(channel, "scam", None),
|
||||
is_fake=getattr(channel, "fake", None),
|
||||
title=channel.title,
|
||||
username=getattr(channel, "username", None),
|
||||
photo=types.ChatPhoto._parse(
|
||||
client,
|
||||
getattr(channel, "photo", None),
|
||||
peer_id,
|
||||
getattr(channel, "access_hash", 0),
|
||||
),
|
||||
restrictions=types.List(
|
||||
[types.Restriction._parse(r) for r in restriction_reason]
|
||||
)
|
||||
or None,
|
||||
permissions=types.ChatPermissions._parse(
|
||||
getattr(channel, "default_banned_rights", None)
|
||||
),
|
||||
members_count=getattr(channel, "participants_count", None),
|
||||
dc_id=getattr(getattr(channel, "photo", None), "dc_id", None),
|
||||
has_protected_content=getattr(channel, "noforwards", None),
|
||||
is_admin=bool(getattr(channel, "admin_rights", False)),
|
||||
client=client,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _parse(
|
||||
client,
|
||||
message: Union[raw.types.Message, raw.types.MessageService],
|
||||
users: dict,
|
||||
chats: dict,
|
||||
is_chat: bool,
|
||||
) -> "Chat":
|
||||
from_id = utils.get_raw_peer_id(message.from_id)
|
||||
peer_id = utils.get_raw_peer_id(message.peer_id)
|
||||
chat_id = (peer_id or from_id) if is_chat else (from_id or peer_id)
|
||||
|
||||
if isinstance(message.peer_id, raw.types.PeerUser):
|
||||
return Chat._parse_user_chat(client, users[chat_id])
|
||||
|
||||
if isinstance(message.peer_id, raw.types.PeerChat):
|
||||
return Chat._parse_chat_chat(client, chats[chat_id])
|
||||
|
||||
return Chat._parse_channel_chat(client, chats[chat_id])
|
||||
|
||||
@staticmethod
|
||||
def _parse_dialog(client, peer, users: dict, chats: dict):
|
||||
if isinstance(peer, raw.types.PeerUser):
|
||||
return Chat._parse_user_chat(client, users[peer.user_id])
|
||||
if isinstance(peer, raw.types.PeerChat):
|
||||
return Chat._parse_chat_chat(client, chats[peer.chat_id])
|
||||
return Chat._parse_channel_chat(client, chats[peer.channel_id])
|
||||
|
||||
|
||||
class Dialog(Object):
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
client: "Client" = None,
|
||||
chat: "types.Chat",
|
||||
top_message: "types.Message",
|
||||
unread_messages_count: int,
|
||||
unread_mentions_count: int,
|
||||
unread_mark: bool,
|
||||
is_pinned: bool,
|
||||
):
|
||||
super().__init__(client)
|
||||
|
||||
self.chat = chat
|
||||
self.top_message = top_message
|
||||
self.unread_messages_count = unread_messages_count
|
||||
self.unread_mentions_count = unread_mentions_count
|
||||
self.unread_mark = unread_mark
|
||||
self.is_pinned = is_pinned
|
||||
|
||||
@staticmethod
|
||||
def _parse(client, dialog: "raw.types.Dialog", messages, users, chats) -> "Dialog":
|
||||
return Dialog(
|
||||
chat=Chat._parse_dialog(client, dialog.peer, users, chats),
|
||||
top_message=messages.get(utils.get_peer_id(dialog.peer)),
|
||||
unread_messages_count=dialog.unread_count,
|
||||
unread_mentions_count=dialog.unread_mentions_count,
|
||||
unread_mark=dialog.unread_mark,
|
||||
is_pinned=dialog.pinned,
|
||||
client=client,
|
||||
)
|
||||
|
||||
|
||||
async def get_dialogs(
|
||||
self: "Client", limit: int = 0
|
||||
) -> Optional[AsyncGenerator["types.Dialog", None]]:
|
||||
current = 0
|
||||
total = limit or (1 << 31) - 1
|
||||
limit = min(100, total)
|
||||
offset_date = 0
|
||||
offset_id = 0
|
||||
offset_peer = raw.types.InputPeerEmpty()
|
||||
while True:
|
||||
r = await self.invoke(
|
||||
raw.functions.messages.GetDialogs(
|
||||
offset_date=offset_date,
|
||||
offset_id=offset_id,
|
||||
offset_peer=offset_peer,
|
||||
limit=limit,
|
||||
hash=0,
|
||||
),
|
||||
sleep_threshold=60,
|
||||
)
|
||||
users = {i.id: i for i in r.users}
|
||||
chats = {i.id: i for i in r.chats}
|
||||
messages = {}
|
||||
for message in r.messages:
|
||||
if isinstance(message, raw.types.MessageEmpty):
|
||||
continue
|
||||
chat_id = utils.get_peer_id(message.peer_id)
|
||||
messages[chat_id] = await types.Message._parse(self, message, users, chats)
|
||||
dialogs = []
|
||||
for dialog in r.dialogs:
|
||||
if not isinstance(dialog, raw.types.Dialog):
|
||||
continue
|
||||
dialogs.append(Dialog._parse(self, dialog, messages, users, chats))
|
||||
if not dialogs:
|
||||
return
|
||||
last = dialogs[-1]
|
||||
offset_id = last.top_message.id
|
||||
offset_date = utils.datetime_to_timestamp(last.top_message.date)
|
||||
offset_peer = await self.resolve_peer(last.chat.id)
|
||||
for dialog in dialogs:
|
||||
yield dialog
|
||||
current += 1
|
||||
if current >= total:
|
||||
return
|
||||
|
||||
|
||||
@Client.on_message(filters.command("admlist", prefix) & filters.me)
|
||||
async def admlist(client: Client, message: types.Message):
|
||||
await message.edit("<b>Retrieving information... (it'll take some time)</b>")
|
||||
|
||||
start = perf_counter()
|
||||
try:
|
||||
adminned_chats = []
|
||||
owned_chats = []
|
||||
owned_usernamed_chats = []
|
||||
async for dialog in get_dialogs(client):
|
||||
chat = dialog.chat
|
||||
if getattr(chat, "deactivated", False):
|
||||
continue
|
||||
if getattr(chat, "is_creator", False) and getattr(chat, "username", None):
|
||||
owned_usernamed_chats.append(chat)
|
||||
elif getattr(chat, "is_creator", False):
|
||||
owned_chats.append(chat)
|
||||
elif getattr(chat, "is_admin", False):
|
||||
adminned_chats.append(chat)
|
||||
|
||||
text = "<b>Adminned chats:</b>\n"
|
||||
for index, chat in enumerate(adminned_chats):
|
||||
cid = str(chat.id).replace("-100", "")
|
||||
text += f"{index + 1}. <a href=https://t.me/c/{cid}/1>{chat.title}</a>\n"
|
||||
|
||||
text += "\n<b>Owned chats:</b>\n"
|
||||
for index, chat in enumerate(owned_chats):
|
||||
cid = str(chat.id).replace("-100", "")
|
||||
text += f"{index + 1}. <a href=https://t.me/c/{cid}/1>{chat.title}</a>\n"
|
||||
|
||||
text += "\n<b>Owned chats with username:</b>\n"
|
||||
for index, chat in enumerate(owned_usernamed_chats):
|
||||
cid = str(chat.id).replace("-100", "")
|
||||
text += f"{index + 1}. <a href=https://t.me/c/{cid}/1>{chat.title}</a>\n"
|
||||
|
||||
stop = perf_counter()
|
||||
total_count = (
|
||||
len(adminned_chats) + len(owned_chats) + len(owned_usernamed_chats)
|
||||
)
|
||||
await message.edit(
|
||||
text + "\n"
|
||||
f"<b><u>Total:</u></b> {total_count}"
|
||||
f"\n<b><u>Adminned chats:</u></b> {len(adminned_chats)}\n"
|
||||
f"<b><u>Owned chats:</u></b> {len(owned_chats)}\n"
|
||||
f"<b><u>Owned chats with username:</u></b> {len(owned_usernamed_chats)}\n\n"
|
||||
f"Done in {round(stop - start, 3)} seconds.",
|
||||
)
|
||||
except Exception as e:
|
||||
await message.edit(format_exc(e))
|
||||
return
|
||||
|
||||
|
||||
@Client.on_message(filters.command("admcount", prefix) & filters.me)
|
||||
async def admcount(client: Client, message: types.Message):
|
||||
await message.edit("<b>Retrieving information... (it'll take some time)</b>")
|
||||
|
||||
start = perf_counter()
|
||||
try:
|
||||
adminned_chats = 0
|
||||
owned_chats = 0
|
||||
owned_usernamed_chats = 0
|
||||
async for dialog in get_dialogs(client):
|
||||
chat = dialog.chat
|
||||
if getattr(chat, "deactivated", False):
|
||||
continue
|
||||
if getattr(chat, "is_creator", False) and getattr(chat, "username", None):
|
||||
owned_usernamed_chats += 1
|
||||
elif getattr(chat, "is_creator", False):
|
||||
owned_chats += 1
|
||||
elif getattr(chat, "is_admin", False):
|
||||
adminned_chats += 1
|
||||
|
||||
stop = perf_counter()
|
||||
total_count = adminned_chats + owned_chats + owned_usernamed_chats
|
||||
await message.edit(
|
||||
f"<b><u>Total:</u></b> {total_count}"
|
||||
f"\n<b><u>Adminned chats:</u></b> {adminned_chats}\n"
|
||||
f"<b><u>Owned chats:</u></b> {owned_chats}\n"
|
||||
f"<b><u>Owned chats with username:</u></b> {owned_usernamed_chats}\n\n"
|
||||
f"Done in {round(stop - start, 3)} seconds.\n\n"
|
||||
f"<b>Get full list: </b><code>{prefix}admlist</code>",
|
||||
)
|
||||
except Exception as e:
|
||||
await message.edit(format_exc(e))
|
||||
return
|
||||
|
||||
|
||||
modules_help["admlist"] = {
|
||||
"admcount": "Get count of adminned and owned chats",
|
||||
"admlist": "Get list of adminned and owned chats",
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
# Dragon-Userbot - telegram userbot
|
||||
# Copyright (C) 2020-present Dragon Userbot Organization
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
|
||||
from pyrogram import Client, filters, types
|
||||
|
||||
from utils.db import db
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
# avoid using global variables
|
||||
afk_info = db.get(
|
||||
"core.afk",
|
||||
"afk_info",
|
||||
{
|
||||
"start": 0,
|
||||
"is_afk": False,
|
||||
"reason": "",
|
||||
},
|
||||
)
|
||||
|
||||
is_afk = filters.create(lambda _, __, ___: afk_info["is_afk"])
|
||||
is_support = filters.create(lambda _, __, message: message.chat.is_support)
|
||||
|
||||
|
||||
@Client.on_message(
|
||||
is_afk
|
||||
& (filters.private | filters.mentioned)
|
||||
& ~filters.channel
|
||||
& ~filters.me
|
||||
& ~filters.bot
|
||||
& ~is_support
|
||||
)
|
||||
async def afk_handler(_, message: types.Message):
|
||||
start = datetime.datetime.fromtimestamp(afk_info["start"])
|
||||
end = datetime.datetime.now().replace(microsecond=0)
|
||||
afk_time = end - start
|
||||
await message.reply(
|
||||
f"<b>I'm AFK {afk_time}\nReason:</b> <i>{afk_info['reason']}</i>"
|
||||
)
|
||||
|
||||
|
||||
@Client.on_message(filters.command("afk", prefix) & filters.me)
|
||||
async def afk(_, message):
|
||||
if len(message.text.split()) >= 2:
|
||||
reason = message.text.split(" ", maxsplit=1)[1]
|
||||
else:
|
||||
reason = "None"
|
||||
|
||||
afk_info["start"] = int(datetime.datetime.now().timestamp())
|
||||
afk_info["is_afk"] = True
|
||||
afk_info["reason"] = reason
|
||||
|
||||
await message.edit(f"<b>I'm going AFK.\n" f"Reason:</b> <i>{reason}</i>")
|
||||
|
||||
db.set("core.afk", "afk_info", afk_info)
|
||||
|
||||
|
||||
@Client.on_message(filters.command("unafk", prefix) & filters.me)
|
||||
async def unafk(_, message):
|
||||
if afk_info["is_afk"]:
|
||||
start = datetime.datetime.fromtimestamp(afk_info["start"])
|
||||
end = datetime.datetime.now().replace(microsecond=0)
|
||||
afk_time = end - start
|
||||
await message.edit(
|
||||
f"<b>I'm not AFK anymore.\n" f"I was afk {afk_time}</b>"
|
||||
)
|
||||
afk_info["is_afk"] = False
|
||||
else:
|
||||
await message.edit("<b>You weren't afk</b>")
|
||||
|
||||
db.set("core.afk", "afk_info", afk_info)
|
||||
|
||||
|
||||
modules_help["afk"] = {"afk [reason]": "Go to afk", "unafk": "Get out of AFK"}
|
||||
@@ -0,0 +1,129 @@
|
||||
import os
|
||||
from PIL import Image
|
||||
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc, import_library
|
||||
|
||||
genai = import_library("google.generativeai", "google-generativeai")
|
||||
|
||||
from utils.config import gemini_key
|
||||
|
||||
genai.configure(api_key=gemini_key)
|
||||
|
||||
generation_config_cook = {
|
||||
"temperature": 0.35,
|
||||
"top_p": 0.95,
|
||||
"top_k": 40,
|
||||
"max_output_tokens": 1024,
|
||||
}
|
||||
|
||||
model = genai.GenerativeModel("gemini-1.5-flash-latest")
|
||||
model_cook = genai.GenerativeModel(
|
||||
model_name="gemini-1.5-flash-latest", generation_config=generation_config_cook
|
||||
)
|
||||
|
||||
|
||||
@Client.on_message(filters.command("getai", prefix) & filters.me)
|
||||
async def getai(_, message: Message):
|
||||
try:
|
||||
await message.edit_text("<code>Please Wait...</code>")
|
||||
try:
|
||||
base_img = await message.reply_to_message.download()
|
||||
except AttributeError:
|
||||
return await message.edit_text("<code>Please reply to an image...</code>")
|
||||
|
||||
img = Image.open(base_img)
|
||||
prompt = "Get details of given image, be as accurate as possible."
|
||||
|
||||
response = model.generate_content([prompt, img])
|
||||
|
||||
await message.edit_text(
|
||||
f"**Detail Of Image:** {response.text}", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
os.remove(base_img)
|
||||
return
|
||||
except Exception as e:
|
||||
await message.edit_text(f"An error occurred: {format_exc(e)}")
|
||||
|
||||
|
||||
@Client.on_message(filters.command("aicook", prefix) & filters.me)
|
||||
async def aicook(_, message: Message):
|
||||
if message.reply_to_message:
|
||||
try:
|
||||
await message.edit_text("<code>Cooking...</code>")
|
||||
|
||||
try:
|
||||
base_img = await message.reply_to_message.download()
|
||||
except AttributeError:
|
||||
return await message.edit_text(
|
||||
"<code>Please reply to an image...</code>"
|
||||
)
|
||||
|
||||
img = Image.open(base_img)
|
||||
cook_img = [
|
||||
"Accurately identify the baked good in the image and provide an appropriate and recipe consistent with your analysis. ",
|
||||
img,
|
||||
]
|
||||
|
||||
response = model_cook.generate_content(cook_img)
|
||||
|
||||
await message.edit_text(
|
||||
f"{response.text}", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
os.remove(base_img)
|
||||
return
|
||||
except Exception as e:
|
||||
await message.edit_text(str(e))
|
||||
return await message.edit_text("<code>Please reply to an image...</code>")
|
||||
|
||||
|
||||
@Client.on_message(filters.command("aiseller", prefix) & filters.me)
|
||||
async def aiseller(_, message: Message):
|
||||
if message.reply_to_message:
|
||||
try:
|
||||
await message.edit_text("<code>Generating...</code>")
|
||||
if len(message.command) > 1:
|
||||
taud = message.text.split(maxsplit=1)[1]
|
||||
else:
|
||||
return await message.edit_text(
|
||||
f"<b>Usage: </b><code>{prefix}aiseller [target audience] [reply to product image]</code>"
|
||||
)
|
||||
|
||||
try:
|
||||
base_img = await message.reply_to_message.download()
|
||||
except AttributeError:
|
||||
return await message.edit_text(
|
||||
"<code>Please reply to an image...</code>"
|
||||
)
|
||||
|
||||
img = Image.open(base_img)
|
||||
sell_img = [
|
||||
"Given an image of a product and its target audience, write an engaging marketing description",
|
||||
"Product Image: ",
|
||||
img,
|
||||
"Target Audience: ",
|
||||
taud,
|
||||
]
|
||||
|
||||
response = model.generate_content(sell_img)
|
||||
|
||||
await message.edit_text(
|
||||
f"{response.text}", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
os.remove(base_img)
|
||||
return
|
||||
except Exception:
|
||||
await message.edit_text(
|
||||
f"<b>Usage: </b><code>{prefix}aiseller [target audience] [reply to product image]</code>"
|
||||
)
|
||||
return await message.edit_text("<code>Please reply to an image...</code>")
|
||||
|
||||
|
||||
modules_help["aimage"] = {
|
||||
"getai [reply to image]*": "Get details of image with Ai",
|
||||
"aicook [reply to image]*": "Generate Cooking instrunctions of the given food image",
|
||||
"aiseller [target audience] [reply to product image]*": "Generate a promotional message for the given image product for the given target audience",
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
from io import BytesIO
|
||||
from random import randint
|
||||
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
|
||||
from requests import get
|
||||
from PIL import Image, ImageFont, ImageDraw
|
||||
from textwrap import wrap
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
|
||||
@Client.on_message(filters.command("amogus", prefix) & filters.me)
|
||||
async def amogus(client: Client, message: Message):
|
||||
text = " ".join(message.command[1:])
|
||||
|
||||
await message.edit(
|
||||
"<b>amgus, tun tun tun tun tun tun tun tudududn tun tun...</b>",
|
||||
parse_mode=enums.ParseMode.HTML,
|
||||
)
|
||||
|
||||
clr = randint(1, 12)
|
||||
|
||||
url = "https://raw.githubusercontent.com/The-MoonTg-project/AmongUs/master/"
|
||||
font = ImageFont.truetype(BytesIO(get(url + "bold.ttf").content), 60)
|
||||
imposter = Image.open(BytesIO(get(f"{url}{clr}.png").content))
|
||||
|
||||
text_ = "\n".join(["\n".join(wrap(part, 30)) for part in text.split("\n")])
|
||||
bbox = ImageDraw.Draw(Image.new("RGB", (1, 1))).multiline_textbbox(
|
||||
(0, 0), text_, font, stroke_width=2
|
||||
)
|
||||
# w, h = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
||||
w, h = bbox[2], bbox[3]
|
||||
text = Image.new("RGBA", (w + 30, h + 30))
|
||||
ImageDraw.Draw(text).multiline_text(
|
||||
(15, 15), text_, "#FFF", font, stroke_width=2, stroke_fill="#000"
|
||||
)
|
||||
w = imposter.width + text.width + 10
|
||||
h = max(imposter.height, text.height)
|
||||
|
||||
image = Image.new("RGBA", (w, h))
|
||||
image.paste(imposter, (0, h - imposter.height), imposter)
|
||||
image.paste(text, (w - text.width, 0), text)
|
||||
image.thumbnail((512, 512))
|
||||
|
||||
output = BytesIO()
|
||||
output.name = "imposter.webp"
|
||||
image.save(output)
|
||||
output.seek(0)
|
||||
|
||||
await message.delete()
|
||||
await client.send_sticker(message.chat.id, output)
|
||||
|
||||
|
||||
modules_help["amogus"] = {
|
||||
"amogus [text]": "amgus, tun tun tun tun tun tun tun tudududn tun tun"
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
# From CatUB
|
||||
import asyncio
|
||||
import re
|
||||
from io import BytesIO
|
||||
from random import choice, randint
|
||||
from textwrap import wrap
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from click import edit
|
||||
import requests
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.scripts import edit_or_reply, ReplyCheck
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
|
||||
async def amongus_gen(text: str, clr: int) -> str:
|
||||
url = (
|
||||
"https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/Amongus/"
|
||||
)
|
||||
font = ImageFont.truetype(
|
||||
BytesIO(
|
||||
requests.get(
|
||||
"https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/fonts/bold.ttf"
|
||||
).content
|
||||
),
|
||||
60,
|
||||
)
|
||||
imposter = Image.open(BytesIO(requests.get(f"{url}{clr}.png").content))
|
||||
text_ = "\n".join("\n".join(wrap(part, 30)) for part in text.split("\n"))
|
||||
bbox = ImageDraw.Draw(Image.new("RGB", (1, 1))).multiline_textbbox(
|
||||
(0, 0), text_, font, stroke_width=2
|
||||
)
|
||||
w, h = bbox[2], bbox[3]
|
||||
text = Image.new("RGBA", (w + 30, h + 30))
|
||||
ImageDraw.Draw(text).multiline_text(
|
||||
(15, 15), text_, "#FFF", font, stroke_width=2, stroke_fill="#000"
|
||||
)
|
||||
w = imposter.width + text.width + 10
|
||||
h = max(imposter.height, text.height)
|
||||
image = Image.new("RGBA", (w, h))
|
||||
image.paste(imposter, (0, h - imposter.height), imposter)
|
||||
image.paste(text, (w - text.width, 0), text)
|
||||
image.thumbnail((512, 512))
|
||||
output = BytesIO()
|
||||
output.name = "imposter.webp"
|
||||
image.save(output, "WebP")
|
||||
output.seek(0)
|
||||
return output
|
||||
|
||||
|
||||
async def get_imposter_img(text: str) -> BytesIO:
|
||||
background = requests.get(
|
||||
f"https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/imposter/impostor{randint(1, 22)}.png"
|
||||
).content
|
||||
font = requests.get(
|
||||
"https://github.com/TgCatUB/CatUserbot-Resources/raw/master/Resources/fonts/roboto_regular.ttf"
|
||||
).content
|
||||
font = BytesIO(font)
|
||||
font = ImageFont.truetype(font, 30)
|
||||
image = Image.new("RGBA", (1, 1), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(image)
|
||||
bbox = ImageDraw.Draw(Image.new("RGB", (1, 1))).multiline_textbbox(
|
||||
(0, 0), text, font, stroke_width=2
|
||||
)
|
||||
w, h = bbox[2], bbox[3]
|
||||
image = Image.open(BytesIO(background))
|
||||
x, y = image.size
|
||||
draw = ImageDraw.Draw(image)
|
||||
draw.multiline_text(
|
||||
((x - w) // 2, (y - h) // 2), text=text, font=font, fill="white", align="center"
|
||||
)
|
||||
output = BytesIO()
|
||||
output.name = "impostor.png"
|
||||
image.save(output, "PNG")
|
||||
output.seek(0)
|
||||
return output
|
||||
|
||||
|
||||
@Client.on_message(filters.command("amongus", prefix) & filters.me)
|
||||
async def amongus_cmd(client: Client, message: Message):
|
||||
text = " ".join(message.command[1:]) if len(message.command) > 1 else ""
|
||||
reply = message.reply_to_message
|
||||
await message.edit("tun tun tun...")
|
||||
if not text and reply:
|
||||
text = reply.text or reply.caption or ""
|
||||
|
||||
clr = re.findall(r"-c\d+", text)
|
||||
try:
|
||||
clr = clr[0]
|
||||
clr = clr.replace("-c", "")
|
||||
text = text.replace(f"-c{clr}", "")
|
||||
clr = int(clr)
|
||||
if clr > 12 or clr < 1:
|
||||
clr = randint(1, 12)
|
||||
except IndexError:
|
||||
clr = randint(1, 12)
|
||||
|
||||
if not text:
|
||||
if not reply:
|
||||
text = f"{message.from_user.first_name} Was a traitor!"
|
||||
else:
|
||||
text = f"{reply.from_user.first_name} Was a traitor!"
|
||||
|
||||
imposter_file = await amongus_gen(text, clr)
|
||||
await message.delete()
|
||||
await client.send_sticker(
|
||||
message.chat.id,
|
||||
imposter_file,
|
||||
reply_to_message_id=ReplyCheck(message),
|
||||
)
|
||||
|
||||
|
||||
@Client.on_message(filters.command("imposter", prefix) & filters.me)
|
||||
async def imposter_cmd(client: Client, message: Message):
|
||||
remain = randint(1, 2)
|
||||
imps = ["wasn't the impostor", "was the impostor"]
|
||||
|
||||
if message.reply_to_message:
|
||||
user = message.reply_to_message.from_user
|
||||
text = f"{user.first_name} {choice(imps)}."
|
||||
else:
|
||||
args = message.text.split()[1:]
|
||||
if args:
|
||||
text = " ".join(args)
|
||||
else:
|
||||
text = f"{message.from_user.first_name} {choice(imps)}."
|
||||
|
||||
text += f"\n{remain} impostor(s) remain."
|
||||
imposter_file = await get_imposter_img(text)
|
||||
await message.delete()
|
||||
await client.send_photo(
|
||||
message.chat.id,
|
||||
imposter_file,
|
||||
reply_to_message_id=ReplyCheck(message),
|
||||
)
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["imp", "impn"], prefix) & filters.me)
|
||||
async def imp_animation(client: Client, message: Message):
|
||||
name = " ".join(message.command[1:]) if len(message.command) > 1 else ""
|
||||
if not name:
|
||||
reply = message.reply_to_message
|
||||
if reply:
|
||||
name = reply.from_user.first_name
|
||||
else:
|
||||
name = message.from_user.first_name
|
||||
cmd = message.command[0].lower()
|
||||
|
||||
text1 = await edit_or_reply(message, "Uhmm... Something is wrong here!!")
|
||||
await asyncio.sleep(2)
|
||||
await text1.delete()
|
||||
|
||||
stcr1 = await client.send_sticker(message.chat.id, "CAADAQADRwADnjOcH98isYD5RJTwAg")
|
||||
text2 = await message.reply(
|
||||
f"<b>{message.from_user.first_name}:</b> I have to call discussion"
|
||||
)
|
||||
await asyncio.sleep(3)
|
||||
await stcr1.delete()
|
||||
await text2.delete()
|
||||
|
||||
stcr2 = await client.send_sticker(message.chat.id, "CAADAQADRgADnjOcH9odHIXtfgmvAg")
|
||||
text3 = await message.reply(
|
||||
f"<b>{message.from_user.first_name}:</b> We have to eject the imposter or will lose"
|
||||
)
|
||||
await asyncio.sleep(3)
|
||||
await stcr2.delete()
|
||||
await text3.delete()
|
||||
|
||||
stcr3 = await client.send_sticker(message.chat.id, "CAADAQADOwADnjOcH77v3Ap51R7gAg")
|
||||
text4 = await message.reply("<b>Others:</b> Where???")
|
||||
await asyncio.sleep(2)
|
||||
await text4.edit("<b>Others:</b> Who??")
|
||||
await asyncio.sleep(2)
|
||||
await text4.edit(
|
||||
f"<b>{message.from_user.first_name}:</b> Its {name}, I saw {name} using vent"
|
||||
)
|
||||
await asyncio.sleep(3)
|
||||
await text4.edit(f"<b>Others:</b> Okay.. Vote {name}")
|
||||
await asyncio.sleep(2)
|
||||
await stcr3.delete()
|
||||
await text4.delete()
|
||||
|
||||
stcr4 = await client.send_sticker(message.chat.id, "CAADAQADLwADnjOcH-wxu-ehy6NRAg")
|
||||
event = await message.reply(f"{name} is ejected.......")
|
||||
|
||||
# Ejection animation
|
||||
for _ in range(9):
|
||||
await asyncio.sleep(0.5)
|
||||
curr_pos = _ + 1
|
||||
spaces_before = "ㅤ" * curr_pos
|
||||
await event.edit(f"{spaces_before}ඞ{'ㅤ' * (9 - curr_pos)}")
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
await event.edit("ㅤㅤㅤㅤㅤㅤㅤㅤㅤ")
|
||||
await asyncio.sleep(0.2)
|
||||
await stcr4.delete()
|
||||
|
||||
if cmd == "imp":
|
||||
text = f". 。 • ゚ 。 .\n . . 。 。 . \n\n . 。 ඞ 。 . • •\n\n ゚{name} was an Imposter. 。 . 。 . 。 . \n . 。 . \n ' 0 Impostor remains 。 . . 。 . 。 . 。 . . . , 。\n ゚ . . , 。 . . 。"
|
||||
sticker_id = "CAADAQADLQADnjOcH39IqwyR6Q_0Ag"
|
||||
else:
|
||||
text = f". 。 • ゚ 。 .\n . . 。 。 . \n\n . 。 ඞ 。 . • •\n\n ゚{name} was not an Imposter. 。 . 。 . 。 . \n . 。 . \n ' 1 Impostor remains 。 . . 。 . 。 . 。 . . . , 。\n ゚ . . , 。 . . 。"
|
||||
sticker_id = "CAADAQADQAADnjOcH-WOkB8DEctJAg"
|
||||
|
||||
await event.edit(text)
|
||||
await asyncio.sleep(4)
|
||||
await event.delete()
|
||||
await client.send_sticker(message.chat.id, sticker_id)
|
||||
|
||||
|
||||
modules_help["amongus"] = {
|
||||
"amongus": "Create Among Us themed sticker [text/reply] [-c1 to -c12 for colors]",
|
||||
"imposter": "Create Among Us imposter image [username/reply]",
|
||||
"imp": "Create Among Us ejection animation (imposter)",
|
||||
"impn": "Create Among Us ejection animation (not imposter)",
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
import os
|
||||
import requests
|
||||
import aiofiles
|
||||
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message, InputMediaPhoto
|
||||
from pyrogram.errors import MediaCaptionTooLong
|
||||
|
||||
from utils.misc import prefix, modules_help
|
||||
from utils.scripts import format_exc
|
||||
|
||||
url = "https://api.safone.co"
|
||||
|
||||
headers = {
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Connection": "keep-alive",
|
||||
"DNT": "1",
|
||||
"Referer": "https://api.safone.co/docs",
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
"accept": "application/json",
|
||||
"sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Linux"',
|
||||
}
|
||||
|
||||
|
||||
@Client.on_message(filters.command("anime_search", prefix) & filters.me)
|
||||
async def anime_search(client: Client, message: Message):
|
||||
try:
|
||||
chat_id = message.chat.id
|
||||
await message.edit_text("Processing...")
|
||||
if len(message.command) > 1:
|
||||
query = message.text.split(maxsplit=1)[1]
|
||||
else:
|
||||
message.edit_text(
|
||||
"What should i search? You didn't provided me with any value to search"
|
||||
)
|
||||
|
||||
response = requests.get(
|
||||
url=f"{url}/anime/search?query={query}", headers=headers, timeout=5
|
||||
)
|
||||
if response.status_code != 200:
|
||||
await message.edit_text("Something went wrong")
|
||||
return
|
||||
|
||||
result = response.json()
|
||||
|
||||
averageScore = result["averageScore"]
|
||||
try:
|
||||
coverImage_url = result["imageUrl"]
|
||||
coverImage = requests.get(url=coverImage_url).content
|
||||
async with aiofiles.open("coverImage.jpg", mode="wb") as f:
|
||||
await f.write(coverImage)
|
||||
|
||||
except Exception:
|
||||
coverImage = None
|
||||
|
||||
title = result["title"]["english"]
|
||||
trailer = result["trailer"]["id"]
|
||||
description = result["description"]
|
||||
episodes = result["episodes"]
|
||||
genres = ", ".join(result["genres"])
|
||||
isAdult = result["isAdult"]
|
||||
status = result["status"]
|
||||
studios = ", ".join(result["studios"])
|
||||
|
||||
await message.delete()
|
||||
await client.send_media_group(
|
||||
chat_id,
|
||||
[
|
||||
InputMediaPhoto(
|
||||
"coverImage.jpg",
|
||||
caption=f"<b>Title:</b> <code>{title}</code>\n<b>Average Score:</b> <code>{averageScore}</code>\n<b>Status:</b> <code>{status}</code>\n<b>Genres:</b> <code>{genres}</code>\n<b>Episodes:</b> <code>{episodes}</code>\n<b>Is Adult:</b> <code>{isAdult}</code>\n<b>Studios:</b> <code>{studios}</code>\n<b>Description:</b> <code>{description}</code>\n<b>Trailer:</b> <a href='https://youtu.be/{trailer}'>Click Here</a>",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
except MediaCaptionTooLong:
|
||||
description = description[:850]
|
||||
await message.delete()
|
||||
await client.send_media_group(
|
||||
chat_id,
|
||||
[
|
||||
InputMediaPhoto(
|
||||
"coverImage.jpg",
|
||||
caption=f"<b>Title:</b> <code>{title}</code>\n<b>Average Score:</b> <code>{averageScore}</code>\n<b>Status:</b> <code>{status}</code>\n<b>Genres:</b> <code>{genres}</code>\n<b>Episodes:</b> <code>{episodes}</code>\n<b>Is Adult:</b> <code>{isAdult}</code>\n<b>Studios:</b> <code>{studios}</code>\n<b>Description:</b> <code>{description}</code>\n<b>Trailer:</b> <a href='https://youtu.be/{trailer}'>Click Here</a>",
|
||||
)
|
||||
],
|
||||
)
|
||||
except Exception as e:
|
||||
await message.edit_text(format_exc(e))
|
||||
finally:
|
||||
if os.path.exists("coverImage.jpg"):
|
||||
os.remove("coverImage.jpg")
|
||||
|
||||
|
||||
@Client.on_message(filters.command("manga_search", prefix) & filters.me)
|
||||
async def manga_search(client: Client, message: Message):
|
||||
try:
|
||||
chat_id = message.chat.id
|
||||
await message.edit_text("Processing...")
|
||||
if len(message.command) > 1:
|
||||
query = message.text.split(maxsplit=1)[1]
|
||||
else:
|
||||
message.edit_text(
|
||||
"What should i search? You didn't provided me with any value to search"
|
||||
)
|
||||
|
||||
response = requests.get(
|
||||
url=f"{url}/anime/manga?query={query}", headers=headers, timeout=5
|
||||
)
|
||||
if response.status_code != 200:
|
||||
await message.edit_text("Something went wrong")
|
||||
return
|
||||
|
||||
result = response.json()
|
||||
|
||||
averageScore = result["averageScore"]
|
||||
try:
|
||||
coverImage_url = result["imageUrl"]
|
||||
coverImage = requests.get(url=coverImage_url).content
|
||||
async with aiofiles.open("coverImage.jpg", mode="wb") as f:
|
||||
await f.write(coverImage)
|
||||
|
||||
except Exception:
|
||||
coverImage = None
|
||||
|
||||
title = result["title"]["english"]
|
||||
trailer = result["trailer"]["id"]
|
||||
description = result["description"]
|
||||
chapters = result["chapters"]
|
||||
genres = ", ".join(result["genres"])
|
||||
isAdult = result["isAdult"]
|
||||
status = result["status"]
|
||||
studios = ", ".join(result["studios"])
|
||||
|
||||
await message.delete()
|
||||
await client.send_media_group(
|
||||
chat_id,
|
||||
[
|
||||
InputMediaPhoto(
|
||||
"coverImage.jpg",
|
||||
caption=f"<b>Title:</b> <code>{title}</code>\n<b>Average Score:</b> <code>{averageScore}</code>\n<b>Status:</b> <code>{status}</code>\n<b>Genres:</b> <code>{genres}</code>\n<b>Chapters:</b> <code>{chapters}</code>\n<b>Is Adult:</b> <code>{isAdult}</code>\n<b>Studios:</b> <code>{studios}</code>\n<b>Description:</b> <code>{description}</code>\n<b>Trailer:</b> <a href='https://youtu.be/{trailer}'>Click Here</a>",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
except MediaCaptionTooLong:
|
||||
description = description[:850]
|
||||
await message.delete()
|
||||
await client.send_media_group(
|
||||
chat_id,
|
||||
[
|
||||
InputMediaPhoto(
|
||||
"coverImage.jpg",
|
||||
caption=f"<b>Title:</b> <code>{title}</code>\n<b>Average Score:</b> <code>{averageScore}</code>\n<b>Status:</b> <code>{status}</code>\n<b>Genres:</b> <code>{genres}</code>\n<b>Chapters:</b> <code>{chapters}</code>\n<b>Is Adult:</b> <code>{isAdult}</code>\n<b>Studios:</b> <code>{studios}</code>\n<b>Description:</b> <code>{description}</code>\n<b>Trailer:</b> <a href='https://youtu.be/{trailer}'>Click Here</a>",
|
||||
)
|
||||
],
|
||||
)
|
||||
except Exception as e:
|
||||
await message.edit_text(format_exc(e))
|
||||
finally:
|
||||
if os.path.exists("coverImage.jpg"):
|
||||
os.remove("coverImage.jpg")
|
||||
|
||||
|
||||
@Client.on_message(filters.command("character", prefix) & filters.me)
|
||||
async def character(client: Client, message: Message):
|
||||
try:
|
||||
chat_id = message.chat.id
|
||||
await message.edit_text("Processing...")
|
||||
if len(message.command) > 1:
|
||||
query = message.text.split(maxsplit=1)[1]
|
||||
else:
|
||||
message.edit_text(
|
||||
"What should i search? You didn't provided me with any value to search"
|
||||
)
|
||||
|
||||
response = requests.get(
|
||||
url=f"{url}/anime/character?query={query}", headers=headers, timeout=5
|
||||
)
|
||||
if response.status_code != 200:
|
||||
await message.edit_text("Something went wrong")
|
||||
return
|
||||
|
||||
result = response.json()
|
||||
|
||||
try:
|
||||
coverImage_url = result["image"]["large"]
|
||||
coverImage = requests.get(url=coverImage_url).content
|
||||
async with aiofiles.open("coverImage.jpg", mode="wb") as f:
|
||||
await f.write(coverImage)
|
||||
|
||||
except Exception:
|
||||
coverImage = None
|
||||
|
||||
age = result["age"]
|
||||
description = result["description"]
|
||||
height = result["height"]
|
||||
name = result["name"]["full"]
|
||||
native_name = result["name"]["native"]
|
||||
read_more = result["siteUrl"]
|
||||
|
||||
await message.delete()
|
||||
await client.send_media_group(
|
||||
chat_id,
|
||||
[
|
||||
InputMediaPhoto(
|
||||
"coverImage.jpg",
|
||||
caption=f"**Name:** `{name}`\n**Native Name:** `{native_name}`\n**Age:** `{age}`\n**Height:** {height}\n**Description:** `{description}`[read more...]({read_more})",
|
||||
parse_mode=enums.ParseMode.MARKDOWN,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
except MediaCaptionTooLong:
|
||||
description = description[:850]
|
||||
await message.delete()
|
||||
await client.send_media_group(
|
||||
chat_id,
|
||||
[
|
||||
InputMediaPhoto(
|
||||
"coverImage.jpg",
|
||||
caption=f"**Name:** `{name}`\n**Native Name:** `{native_name}`\n**Age:** `{age}`\n**Height:** {height}\n**Description:** `{description}`[read more...]({read_more})",
|
||||
parse_mode=enums.ParseMode.MARKDOWN,
|
||||
)
|
||||
],
|
||||
)
|
||||
except Exception as e:
|
||||
await message.edit_text(format_exc(e))
|
||||
finally:
|
||||
if os.path.exists("coverImage.jpg"):
|
||||
os.remove("coverImage.jpg")
|
||||
|
||||
|
||||
modules_help["anilist"] = {
|
||||
"anime_search": "Search for anime on Anilist",
|
||||
"manga_search": "Search for manga on Anilist",
|
||||
"character": "Search for character on Anilist",
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc
|
||||
from aiohttp import ClientSession
|
||||
from io import BytesIO
|
||||
|
||||
session = ClientSession()
|
||||
|
||||
|
||||
class Post:
|
||||
def __init__(self, source: dict, session: ClientSession):
|
||||
self._json = source
|
||||
self.session = session
|
||||
|
||||
@property
|
||||
async def image(self):
|
||||
return (
|
||||
self.file_url
|
||||
if self.file_url
|
||||
else (
|
||||
self.large_file_url
|
||||
if self.large_file_url
|
||||
else (
|
||||
self.source
|
||||
if self.source and "pximg" not in self.source
|
||||
else await self.pximg if self.source else None
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
async def pximg(self):
|
||||
async with self.session.get(self.source) as response:
|
||||
return BytesIO(await response.read())
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._json.get(item)
|
||||
|
||||
|
||||
async def random():
|
||||
async with session.get(
|
||||
url="https://danbooru.donmai.us/posts/random.json"
|
||||
) as response:
|
||||
return Post(await response.json(encoding="utf-8"), session)
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["arnd", "arandom"], prefix) & filters.me)
|
||||
async def anime_handler(client: Client, message: Message):
|
||||
try:
|
||||
await message.edit("<b>Searching art</b>", parse_mode=enums.ParseMode.HTML)
|
||||
ra = await random()
|
||||
img = await ra.image
|
||||
await message.reply_photo(
|
||||
photo=img,
|
||||
caption=f'<b>{ra.tag_string_general if ra.tag_string_general else "Untitled"}</b>',
|
||||
parse_mode=enums.ParseMode.HTML,
|
||||
)
|
||||
return await message.delete()
|
||||
except Exception as e:
|
||||
await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML)
|
||||
|
||||
|
||||
modules_help["anime"] = {
|
||||
"arnd": "Random anime art (May get caught 18+)",
|
||||
"arandom": "Random anime art (May get caught 18+)",
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
# Moon-Userbot - telegram userbot
|
||||
# Copyright (C) 2020-present Moon Userbot Organization
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import asyncio
|
||||
|
||||
import requests
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc
|
||||
|
||||
|
||||
def get_neko_media(query):
|
||||
return requests.get(f"https://nekos.life/api/v2/img/{query}").json()["url"]
|
||||
|
||||
|
||||
@Client.on_message(filters.command("neko", prefix) & filters.me)
|
||||
async def neko(_, message: Message):
|
||||
if len(message.command) == 1:
|
||||
await message.edit(
|
||||
"<b>Neko type isn't provided\n"
|
||||
f"You can get available neko types with <code>{prefix}neko_types</code></b>"
|
||||
)
|
||||
|
||||
query = message.command[1]
|
||||
await message.edit("<b>Loading...</b>")
|
||||
try:
|
||||
await message.edit(f"{get_neko_media(query)}", disable_web_page_preview=False)
|
||||
except Exception as e:
|
||||
await message.edit(format_exc(e))
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["nekotypes", "neko_types"], prefix) & filters.me)
|
||||
async def neko_types_func(_, message: Message):
|
||||
neko_types = """hug kiss tickle lewd neko pat lizard 8ball cat chat fact smug woof gasm goose cuddle avatar slap gecg feed fox_girl meow wallpaper spank waifu ngif name owoify spoiler why"""
|
||||
await message.edit(" ".join(f"<code>{n}</code>" for n in neko_types.split()))
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["nekospam", "neko_spam"], prefix) & filters.me)
|
||||
async def neko_spam(client: Client, message: Message):
|
||||
query = message.command[1]
|
||||
amount = int(message.command[2])
|
||||
|
||||
await message.delete()
|
||||
|
||||
for _ in range(amount):
|
||||
if message.reply_to_message:
|
||||
await message.reply_to_message.reply(get_neko_media(query))
|
||||
else:
|
||||
await client.send_message(message.chat.id, get_neko_media(query))
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
|
||||
modules_help["neko"] = {
|
||||
"neko [type]*": "Get neko media",
|
||||
"neko_types": "Available neko types",
|
||||
"neko_spam [type]* [amount]*": "Start spam with neko media",
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
from random import choice, randint
|
||||
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["aniq", "aq"], prefix) & filters.me)
|
||||
async def aniquotes_handler(client: Client, message: Message):
|
||||
if message.reply_to_message and message.reply_to_message.text:
|
||||
query = message.reply_to_message.text[:512]
|
||||
elif message.reply_to_message and message.reply_to_message.caption:
|
||||
query = message.reply_to_message.caption[:512]
|
||||
elif len(message.command) > 1:
|
||||
query = message.text.split(maxsplit=1)[1][:512]
|
||||
else:
|
||||
return await message.edit(
|
||||
"<b>[💮 Aniquotes] <i>Please enter text to create sticker.</i></b>",
|
||||
parse_mode=enums.ParseMode.HTML,
|
||||
)
|
||||
|
||||
try:
|
||||
await message.delete()
|
||||
result = await client.get_inline_bot_results("@quotafbot", query)
|
||||
return await message.reply_inline_bot_result(
|
||||
query_id=result.query_id,
|
||||
result_id=result.results[randint(1, 2)].id,
|
||||
reply_to_message_id=(
|
||||
message.reply_to_message.id if message.reply_to_message else None
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
return await message.reply(
|
||||
f"<b>[💮 Aniquotes]</b>\n<code>{format_exc(e)}</code>",
|
||||
parse_mode=enums.ParseMode.HTML,
|
||||
)
|
||||
|
||||
|
||||
modules_help["aniquotes"] = {
|
||||
"aq [text]": "Create animated sticker with text",
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
# Moon-Userbot - telegram userbot
|
||||
# Copyright (C) 2020-present Moon Userbot Organization
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.raw import functions
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.config import pm_limit
|
||||
from utils.db import db
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
anti_pm_enabled = filters.create(
|
||||
lambda _, __, ___: db.get("core.antipm", "status", False)
|
||||
)
|
||||
|
||||
in_contact_list = filters.create(lambda _, __, message: message.from_user.is_contact)
|
||||
|
||||
is_support = filters.create(lambda _, __, message: message.chat.is_support)
|
||||
|
||||
USER_WARNINGS = {}
|
||||
|
||||
|
||||
@Client.on_message(
|
||||
filters.private
|
||||
& ~filters.me
|
||||
& ~filters.bot
|
||||
& ~in_contact_list
|
||||
& ~is_support
|
||||
& anti_pm_enabled
|
||||
)
|
||||
async def anti_pm_handler(client: Client, message: Message):
|
||||
user_id = message.from_user.id
|
||||
ids = message.chat.id
|
||||
b_f = await client.get_me()
|
||||
u_n = b_f.first_name
|
||||
user = await client.get_users(ids)
|
||||
u_f = user.first_name
|
||||
default_text = db.get("core.antipm", "antipm_msg", None)
|
||||
if default_text is None:
|
||||
default_text = f"""<b>Hello, {u_f}!
|
||||
This is the Assistant Of {u_n}.</b>
|
||||
<i>My Boss is away or busy as of now, You can wait for him to respond.
|
||||
Do not spam further messages else I may have to block you!</i>
|
||||
|
||||
<b>This is an automated message by the assistant.</b>
|
||||
<b><u>Currently You Have <code>{USER_WARNINGS.get(user_id, 0)}</code> Warnings.</u></b>
|
||||
"""
|
||||
else:
|
||||
default_text = default_text.format(
|
||||
user=u_f, my_name=u_n, warns=USER_WARNINGS.get(user_id, 0)
|
||||
)
|
||||
|
||||
if db.get("core.antipm", "spamrep", False):
|
||||
user_info = await client.resolve_peer(ids)
|
||||
await client.invoke(functions.messages.ReportSpam(peer=user_info))
|
||||
|
||||
if db.get("core.antipm", "block", False):
|
||||
await client.block_user(user_id)
|
||||
|
||||
if db.get("core.antipm", f"disallowusers{ids}") == user_id != db.get(
|
||||
"core.antipm", f"allowusers{ids}"
|
||||
) or db.get("core.antipm", f"disallowusers{ids}") != user_id != db.get(
|
||||
"core.antipm", f"allowusers{ids}"
|
||||
):
|
||||
default_pic = db.get("core.antipm", "antipm_pic", None)
|
||||
if default_pic and os.path.exists(default_pic):
|
||||
await client.send_photo(message.chat.id, default_pic, caption=default_text)
|
||||
else:
|
||||
await client.send_message(message.chat.id, default_text)
|
||||
|
||||
if user_id in USER_WARNINGS:
|
||||
USER_WARNINGS[user_id] += 1
|
||||
else:
|
||||
USER_WARNINGS[user_id] = 1
|
||||
|
||||
if USER_WARNINGS[user_id] > pm_limit:
|
||||
await client.send_message(
|
||||
message.chat.id,
|
||||
"<b>Ehm...! That was your Last warn, Bye Bye see you L0L</b>",
|
||||
)
|
||||
await client.block_user(user_id)
|
||||
del USER_WARNINGS[user_id]
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["antipm", "anti_pm"], prefix) & filters.me)
|
||||
async def anti_pm(_, message: Message):
|
||||
if len(message.command) == 1:
|
||||
if db.get("core.antipm", "status", False):
|
||||
await message.edit(
|
||||
"<b>Anti-PM status: enabled\n"
|
||||
f"Disable with: </b><code>{prefix}antipm disable</code>"
|
||||
)
|
||||
else:
|
||||
await message.edit(
|
||||
"<b>Anti-PM status: disabled\n"
|
||||
f"Enable with: </b><code>{prefix}antipm enable</code>"
|
||||
)
|
||||
elif message.command[1] in ["enable", "on", "1", "yes", "true"]:
|
||||
db.set("core.antipm", "status", True)
|
||||
await message.edit("<b>Anti-PM enabled!</b>")
|
||||
elif message.command[1] in ["disable", "off", "0", "no", "false"]:
|
||||
db.set("core.antipm", "status", False)
|
||||
await message.edit("<b>Anti-PM disabled!</b>")
|
||||
else:
|
||||
await message.edit(f"<b>Usage: {prefix}antipm [enable|disable]</b>")
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["antipm_report"], prefix) & filters.me)
|
||||
async def antipm_report(_, message: Message):
|
||||
if len(message.command) == 1:
|
||||
if db.get("core.antipm", "spamrep", False):
|
||||
await message.edit(
|
||||
"<b>Spam-reporting enabled.\n"
|
||||
f"Disable with: </b><code>{prefix}antipm_report disable</code>"
|
||||
)
|
||||
else:
|
||||
await message.edit(
|
||||
"<b>Spam-reporting disabled.\n"
|
||||
f"Enable with: </b><code>{prefix}antipm_report enable</code>"
|
||||
)
|
||||
elif message.command[1] in ["enable", "on", "1", "yes", "true"]:
|
||||
db.set("core.antipm", "spamrep", True)
|
||||
await message.edit("<b>Spam-reporting enabled!</b>")
|
||||
elif message.command[1] in ["disable", "off", "0", "no", "false"]:
|
||||
db.set("core.antipm", "spamrep", False)
|
||||
await message.edit("<b>Spam-reporting disabled!</b>")
|
||||
else:
|
||||
await message.edit(f"<b>Usage: {prefix}antipm_report [enable|disable]</b>")
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["antipm_block"], prefix) & filters.me)
|
||||
async def antipm_block(_, message: Message):
|
||||
if len(message.command) == 1:
|
||||
if db.get("core.antipm", "block", False):
|
||||
await message.edit(
|
||||
"<b>Blocking users enabled.\n"
|
||||
f"Disable with: </b><code>{prefix}antipm_block disable</code>"
|
||||
)
|
||||
else:
|
||||
await message.edit(
|
||||
"<b>Blocking users disabled.\n"
|
||||
f"Enable with: </b><code>{prefix}antipm_block enable</code>"
|
||||
)
|
||||
elif message.command[1] in ["enable", "on", "1", "yes", "true"]:
|
||||
db.set("core.antipm", "block", True)
|
||||
await message.edit("<b>Blocking users enabled!</b>")
|
||||
elif message.command[1] in ["disable", "off", "0", "no", "false"]:
|
||||
db.set("core.antipm", "block", False)
|
||||
await message.edit("<b>Blocking users disabled!</b>")
|
||||
else:
|
||||
await message.edit(f"<b>Usage: {prefix}antipm_block [enable|disable]</b>")
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["a"], prefix) & filters.me)
|
||||
async def add_contact(_, message: Message):
|
||||
ids = message.chat.id
|
||||
|
||||
db.set("core.antipm", f"allowusers{ids}", ids)
|
||||
if ids in USER_WARNINGS:
|
||||
del USER_WARNINGS[ids]
|
||||
await message.edit("User Approved!")
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["d"], prefix) & filters.me)
|
||||
async def del_contact(_, message: Message):
|
||||
ids = message.chat.id
|
||||
|
||||
db.set("core.antipm", f"disallowusers{ids}", ids)
|
||||
db.remove("core.antipm", f"allowusers{ids}")
|
||||
await message.edit("User DisApproved!")
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["setantipmmsg", "sam"], prefix) & filters.me)
|
||||
async def set_antipm_msg(_, message: Message):
|
||||
if not message.reply_to_message:
|
||||
db.set("core.antipm", "antipm_msg", None)
|
||||
await message.edit("antipm message set to default.")
|
||||
return
|
||||
|
||||
msg = message.reply_to_message
|
||||
afk_msg = msg.text or msg.caption
|
||||
|
||||
if not afk_msg:
|
||||
return await message.edit(
|
||||
"Reply to a text or caption message to set it as your antipm message."
|
||||
)
|
||||
|
||||
if len(afk_msg) > 200:
|
||||
return await message.edit(
|
||||
"antipm message is too long. It should be less than 200 characters."
|
||||
)
|
||||
|
||||
if "{user}" not in afk_msg:
|
||||
return await message.edit(
|
||||
"antipm message must contain <code>{user}</code> to mention the user."
|
||||
)
|
||||
if "{my_name}" not in afk_msg:
|
||||
return await message.edit(
|
||||
"antipm message must contain <code>{my_name}</code> to mention your name."
|
||||
)
|
||||
if "{warns}" not in afk_msg:
|
||||
return await message.edit(
|
||||
"antipm message must contain <code>{warns}</code> to mention the warns count."
|
||||
)
|
||||
|
||||
old_afk_msg = db.get("core.antipm", "antipm_msg", None)
|
||||
if old_afk_msg:
|
||||
db.remove("core.antipm", "antipm_msg")
|
||||
db.set("core.antipm", "antipm_msg", afk_msg)
|
||||
await message.edit(f"antipm message set to:\n\n{afk_msg}")
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["setantipmpic", "sap"], prefix) & filters.me)
|
||||
async def set_antipm_pic(_, message: Message):
|
||||
if not message.reply_to_message or not message.reply_to_message.photo:
|
||||
db.set("core.antipm", "antipm_pic", None)
|
||||
await message.edit("antipm picture set to default.")
|
||||
return
|
||||
|
||||
await message.edit("Setting antipm picture...")
|
||||
|
||||
photo = await message.reply_to_message.download("antipm_pic.jpg")
|
||||
|
||||
old_antipm_pic = db.get("core.antipm", "antipm_pic", None)
|
||||
if old_antipm_pic:
|
||||
db.remove("core.antipm", "antipm_pic")
|
||||
db.set("core.antipm", "antipm_pic", photo)
|
||||
await message.edit("antipm picture set successfully.")
|
||||
|
||||
|
||||
modules_help["antipm"] = {
|
||||
"antipm [enable|disable]*": "Enable Pm permit",
|
||||
"antipm_report [enable|disable]*": "Enable spam reporting",
|
||||
"antipm_block [enable|disable]*": "Enable user blocking",
|
||||
"setantipmmsg [reply to message]*": "Set antipm message. Use {user} to mention the user and {my_name} to mention your name and {warns} to mention the warns count.",
|
||||
"sam [reply to message]*": "Set antipm message. Use {user} to mention the user and {my_name} to mention your name and {warns} to mention the warns count.",
|
||||
"setantipmpic [reply to photo]*": "Set antipm picture.",
|
||||
"sap [reply to photo]*": "Set antipm picture.",
|
||||
"a": "Approve User",
|
||||
"d": "DisApprove User",
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import uuid
|
||||
import re
|
||||
|
||||
from aiohttp import ClientSession, FormData
|
||||
|
||||
from pyrogram import Client, filters
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
|
||||
def id_generator() -> str:
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["bbox", "blackbox"], prefix) & filters.me)
|
||||
async def blackbox(client, message):
|
||||
m = message
|
||||
msg = await m.edit_text("🔍")
|
||||
|
||||
if len(m.text.split()) == 1:
|
||||
return await msg.edit_text(
|
||||
"Type some query buddy 🐼\n"
|
||||
f"{prefix}blackbox text with reply to the photo or just text"
|
||||
)
|
||||
else:
|
||||
try:
|
||||
session = ClientSession()
|
||||
prompt = m.text.split(maxsplit=1)[1]
|
||||
user_id = id_generator()
|
||||
image = None
|
||||
|
||||
if m.reply_to_message and (
|
||||
m.reply_to_message.photo
|
||||
or (
|
||||
m.reply_to_message.sticker
|
||||
and not m.reply_to_message.sticker.is_video
|
||||
)
|
||||
):
|
||||
file_name = f"blackbox_{m.chat.id}.jpeg"
|
||||
file_path = await m.reply_to_message.download(file_name=file_name)
|
||||
with open(file_path, "rb") as file:
|
||||
image = file.read()
|
||||
|
||||
if image:
|
||||
data = FormData()
|
||||
data.add_field("fileName", file_name)
|
||||
data.add_field("userId", user_id)
|
||||
data.add_field(
|
||||
"image", image, filename=file_name, content_type="image/jpeg"
|
||||
)
|
||||
api_url = "https://www.blackbox.ai/api/upload"
|
||||
try:
|
||||
async with session.post(api_url, data=data) as response:
|
||||
response_json = await response.json()
|
||||
except Exception as e:
|
||||
return await msg.edit(f"❌ Error: {str(e)}")
|
||||
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": response_json["response"] + "\n#\n" + prompt,
|
||||
}
|
||||
]
|
||||
data = {
|
||||
"messages": messages,
|
||||
"user_id": user_id,
|
||||
"codeModelMode": True,
|
||||
"agentMode": {},
|
||||
"trendingAgentMode": {},
|
||||
}
|
||||
headers = {"Content-Type": "application/json"}
|
||||
url = "https://www.blackbox.ai/api/chat"
|
||||
try:
|
||||
async with session.post(
|
||||
url, headers=headers, json=data
|
||||
) as response:
|
||||
response_text = await response.text()
|
||||
except Exception as e:
|
||||
return await msg.edit(f"❌ Error: {str(e)}")
|
||||
|
||||
cleaned_response_text = re.sub(
|
||||
r"^\$?@?\$?v=undefined-rv\d+@?\$?|\$?@?\$?v=v\d+\.\d+-rv\d+@?\$?",
|
||||
"",
|
||||
response_text,
|
||||
)
|
||||
text = cleaned_response_text.strip()[2:]
|
||||
if "$~~~$" in text:
|
||||
text = re.sub(r"\$~~~\$.*?\$~~~\$", "", text, flags=re.DOTALL)
|
||||
rdata = {"reply": text}
|
||||
|
||||
return await msg.edit_text(text=rdata["reply"])
|
||||
else:
|
||||
reply = m.reply_to_message
|
||||
if reply and reply.text:
|
||||
prompt = f"Old conversation:\n{reply.text}\n\nQuestion:\n{prompt}"
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
data = {
|
||||
"messages": messages,
|
||||
"user_id": user_id,
|
||||
"codeModelMode": True,
|
||||
"agentMode": {},
|
||||
"trendingAgentMode": {},
|
||||
}
|
||||
headers = {"Content-Type": "application/json"}
|
||||
url = "https://www.blackbox.ai/api/chat"
|
||||
try:
|
||||
async with session.post(
|
||||
url, headers=headers, json=data
|
||||
) as response:
|
||||
response_text = await response.text()
|
||||
except Exception as e:
|
||||
return await msg.edit(f"❌ Error: {str(e)}")
|
||||
|
||||
cleaned_response_text = re.sub(
|
||||
r"^\$?@?\$?v=undefined-rv\d+@?\$?|\$?@?\$?v=v\d+\.\d+-rv\d+@?\$?",
|
||||
"",
|
||||
response_text,
|
||||
)
|
||||
text = cleaned_response_text.strip()[2:]
|
||||
if "$~~~$" in text:
|
||||
text = re.sub(r"\$~~~\$.*?\$~~~\$", "", text, flags=re.DOTALL)
|
||||
rdata = {"reply": text}
|
||||
|
||||
return await msg.edit_text(text=rdata["reply"])
|
||||
except Exception as e:
|
||||
return await msg.edit(f"�� Error: {str(e)}")
|
||||
finally:
|
||||
await session.close()
|
||||
|
||||
|
||||
modules_help["blackbox"] = {
|
||||
"blackbox [query]*": "Ask anything to Blackbox",
|
||||
"bbox [query]*": "Ask anything to Blackbox",
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import asyncio
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
|
||||
@Client.on_message(filters.command("calc", prefix) & filters.me)
|
||||
async def calc(_, message: Message):
|
||||
if len(message.command) <= 1:
|
||||
return
|
||||
args = " ".join(message.command[1:])
|
||||
try:
|
||||
result = str(eval(args))
|
||||
|
||||
if len(result) > 4096:
|
||||
i = 0
|
||||
for x in range(0, len(result), 4096):
|
||||
if i == 0:
|
||||
await message.edit(
|
||||
f"<i>{args}</i><b>=</b><code>{result[x:x + 4000]}</code>",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
else:
|
||||
await message.reply(
|
||||
f"<code>{result[x:x + 4096]}</code>", parse_mode="HTML"
|
||||
)
|
||||
i += 1
|
||||
await asyncio.sleep(0.18)
|
||||
else:
|
||||
await message.edit(
|
||||
f"<i>{args}</i><b>=</b><code>{result}</code>", parse_mode="HTML"
|
||||
)
|
||||
except Exception as e:
|
||||
await message.edit(f"<i>{args}=</i><b>=</b><code>{e}</code>", parse_mode="HTML")
|
||||
|
||||
|
||||
modules_help["calculator"] = {
|
||||
"calc [expression]*": "solve a math problem\n"
|
||||
"+ – addition\n"
|
||||
"– – subtraction\n"
|
||||
"* – multiplication\n"
|
||||
"/ – division\n"
|
||||
"** – degree"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import base64, os
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc, import_library
|
||||
|
||||
clarifai = import_library("clarifai")
|
||||
|
||||
from clarifai.client.model import Model
|
||||
|
||||
|
||||
@Client.on_message(filters.command("cdxl", prefix) & filters.me)
|
||||
async def cdxl(c: Client, message: Message):
|
||||
try:
|
||||
chat_id = message.chat.id
|
||||
await message.edit_text("<code>Please Wait...</code>")
|
||||
|
||||
if len(message.command) > 1:
|
||||
prompt = message.text.split(maxsplit=1)[1]
|
||||
elif message.reply_to_message:
|
||||
prompt = message.reply_to_message.text
|
||||
else:
|
||||
await message.edit_text(
|
||||
f"<b>Usage: </b><code>{prefix}vdxl [prompt/reply to prompt]</code>"
|
||||
)
|
||||
return
|
||||
|
||||
inference_params = dict(width=1024, height=1024, steps=50, cfg_scale=9.0)
|
||||
|
||||
model_prediction = Model(
|
||||
"https://clarifai.com/stability-ai/stable-diffusion-2/models/stable-diffusion-xl"
|
||||
).predict_by_bytes(
|
||||
prompt.encode(), input_type="text", inference_params=inference_params
|
||||
)
|
||||
|
||||
output_base64 = model_prediction.outputs[0].data.image.base64
|
||||
|
||||
with open("sdxl_out.png", "wb") as f:
|
||||
f.write(output_base64)
|
||||
|
||||
await message.delete()
|
||||
await c.send_photo(
|
||||
chat_id,
|
||||
photo=f"sdxl_out.png",
|
||||
caption=f"<b>Prompt:</b><code>{prompt}</code>",
|
||||
)
|
||||
os.remove(f"sdxl_out.png")
|
||||
|
||||
except Exception as e:
|
||||
await message.edit_text(f"An error occurred: {format_exc(e)}")
|
||||
|
||||
|
||||
modules_help["cdxl"] = {
|
||||
"cdxl [prompt/reply to prompt]*": "Text to Image with SDXL model",
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
from pyrogram import Client, enums, filters
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.config import cohere_key
|
||||
from utils.db import db
|
||||
from utils.scripts import format_exc, import_library, restart
|
||||
|
||||
cohere = import_library("cohere")
|
||||
|
||||
co = cohere.Client(cohere_key)
|
||||
|
||||
chatai_users = db.getaiusers()
|
||||
|
||||
|
||||
@Client.on_message(filters.command("addai", prefix) & filters.me)
|
||||
async def adduser(_, message: Message):
|
||||
if len(message.command) > 1:
|
||||
user_id = message.text.split(maxsplit=1)[1]
|
||||
if user_id.isdigit():
|
||||
user_id = int(user_id)
|
||||
db.addaiuser(user_id)
|
||||
await message.edit_text("<b>User ID Added</b>")
|
||||
restart()
|
||||
else:
|
||||
await message.edit_text("<b>User ID is invalid.</b>")
|
||||
return
|
||||
else:
|
||||
await message.edit_text(f"<b>Usage: </b><code>{prefix}addai [user_id]</code>")
|
||||
return
|
||||
|
||||
|
||||
@Client.on_message(filters.command("remai", prefix) & filters.me)
|
||||
async def remuser(_, message: Message):
|
||||
if len(message.command) > 1:
|
||||
user_id = message.text.split(maxsplit=1)[1]
|
||||
if user_id.isdigit():
|
||||
user_id = int(user_id)
|
||||
db.remaiuser(user_id)
|
||||
await message.edit_text("<b>User ID Removed</b>")
|
||||
restart()
|
||||
else:
|
||||
await message.edit_text("<b>User ID is invalid.</b>")
|
||||
return
|
||||
else:
|
||||
await message.edit_text(f"<b>Usage: </b><code>{prefix}remai [user_id]</code>")
|
||||
return
|
||||
|
||||
|
||||
@Client.on_message(filters.user(users=chatai_users) & filters.text)
|
||||
async def chatbot(_, message: Message):
|
||||
user_id = message.chat.id
|
||||
|
||||
if user_id in chatai_users:
|
||||
pass
|
||||
else:
|
||||
return
|
||||
try:
|
||||
await message.reply_chat_action(enums.ChatAction.TYPING)
|
||||
|
||||
chat_history = db.get_chat_history(user_id)
|
||||
|
||||
prompt = message.text
|
||||
|
||||
db.add_chat_history(user_id, {"role": "USER", "message": prompt})
|
||||
|
||||
response = co.chat(
|
||||
chat_history=chat_history,
|
||||
model="command-r-plus",
|
||||
message=prompt,
|
||||
temperature=0.3,
|
||||
connectors=[{"id": "web-search", "options": {"site": "wikipedia.com"}}],
|
||||
prompt_truncation="AUTO",
|
||||
)
|
||||
|
||||
db.add_chat_history(user_id, {"role": "CHATBOT", "message": response.text})
|
||||
|
||||
await message.reply_text(
|
||||
f"{response.text}", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
await message.reply_text(f"An error occurred: {format_exc(e)}")
|
||||
|
||||
|
||||
@Client.on_message(filters.command("chatoff", prefix) & filters.me)
|
||||
async def chatoff(_, message: Message):
|
||||
db.remove("core.chatbot", "chatai_users")
|
||||
await message.reply_text("<b>ChatBot is off now</b>")
|
||||
restart()
|
||||
|
||||
|
||||
@Client.on_message(filters.command("listai", prefix) & filters.me)
|
||||
async def listai(_, message: Message):
|
||||
await message.edit_text(
|
||||
f"<b>User ID's Currently in AI ChatBot List:</b>\n <code>{chatai_users}</code>"
|
||||
)
|
||||
|
||||
|
||||
modules_help["chatbot"] = {
|
||||
"addai [user_id]*": "Add A user to AI ChatBot List",
|
||||
"remai [user_id]*": "Remove A user from AI ChatBot List",
|
||||
"listai": "List A user from AI ChatBot List",
|
||||
"chatoff": "Turn off AI ChatBot",
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import asyncio
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFilter, ImageOps
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from utils.scripts import import_library, format_exc
|
||||
|
||||
|
||||
VideoFileClip = import_library("moviepy", "moviepy==2.2.1").VideoFileClip
|
||||
|
||||
im = None
|
||||
|
||||
|
||||
def process_img(filename):
|
||||
global im
|
||||
im = Image.open(f"downloads/{filename}")
|
||||
w, h = im.size
|
||||
img = Image.new("RGBA", (w, h), (0, 0, 0, 0))
|
||||
img.paste(im, (0, 0))
|
||||
m = min(w, h)
|
||||
img = img.crop(((w - m) // 2, (h - m) // 2, (w + m) // 2, (h + m) // 2))
|
||||
w, h = img.size
|
||||
mask = Image.new("L", (w, h), 0)
|
||||
draw = ImageDraw.Draw(mask)
|
||||
draw.ellipse((10, 10, w - 10, h - 10), fill=255)
|
||||
mask = mask.filter(ImageFilter.GaussianBlur(2))
|
||||
img = ImageOps.fit(img, (w, h))
|
||||
img.putalpha(mask)
|
||||
im = BytesIO()
|
||||
im.name = "img.webp"
|
||||
img.save(im)
|
||||
im.seek(0)
|
||||
|
||||
|
||||
video = None
|
||||
|
||||
|
||||
def process_vid(filename):
|
||||
global video
|
||||
video = VideoFileClip(f"downloads/{filename}")
|
||||
w, h = video.size
|
||||
m = min(w, h)
|
||||
box = {
|
||||
"x1": (w - m) // 2,
|
||||
"y1": (h - m) // 2,
|
||||
"x2": (w + m) // 2,
|
||||
"y2": (h + m) // 2,
|
||||
}
|
||||
video = video.cropped(**box)
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["circle", "round"], prefix) & filters.me)
|
||||
async def circle(_, message: Message):
|
||||
try:
|
||||
if not message.reply_to_message:
|
||||
return await message.reply(
|
||||
"<b>Reply is required for this command</b>",
|
||||
parse_mode=enums.ParseMode.HTML,
|
||||
)
|
||||
if message.reply_to_message.photo:
|
||||
filename = "circle.jpg"
|
||||
typ = "photo"
|
||||
elif message.reply_to_message.sticker:
|
||||
if message.reply_to_message.sticker.is_video:
|
||||
return await message.reply(
|
||||
"<b>Video stickers is not supported</b>",
|
||||
parse_mode=enums.ParseMode.HTML,
|
||||
)
|
||||
filename = "circle.webp"
|
||||
typ = "photo"
|
||||
elif message.reply_to_message.video:
|
||||
filename = "circle.mp4"
|
||||
typ = "video"
|
||||
elif message.reply_to_message.document:
|
||||
_filename = message.reply_to_message.document.file_name.casefold()
|
||||
if _filename.endswith(".png"):
|
||||
filename = "circle.png"
|
||||
typ = "photo"
|
||||
elif _filename.endswith(".jpg"):
|
||||
filename = "circle.jpg"
|
||||
typ = "photo"
|
||||
elif _filename.endswith(".jpeg"):
|
||||
filename = "circle.jpeg"
|
||||
typ = "photo"
|
||||
elif _filename.endswith(".webp"):
|
||||
filename = "circle.webp"
|
||||
typ = "photo"
|
||||
elif _filename.endswith(".mp4"):
|
||||
filename = "circle.mp4"
|
||||
typ = "video"
|
||||
else:
|
||||
return await message.reply(
|
||||
"<b>Invalid file type</b>", parse_mode=enums.ParseMode.HTML
|
||||
)
|
||||
else:
|
||||
return await message.reply(
|
||||
"<b>Invalid file type</b>", parse_mode=enums.ParseMode.HTML
|
||||
)
|
||||
|
||||
if typ == "photo":
|
||||
await message.edit(
|
||||
"<b>Processing image</b>📷", parse_mode=enums.ParseMode.HTML
|
||||
)
|
||||
await message.reply_to_message.download(f"downloads/{filename}")
|
||||
await asyncio.get_event_loop().run_in_executor(None, process_img, filename)
|
||||
await message.delete()
|
||||
return await message.reply_sticker(
|
||||
sticker=im, reply_to_message_id=message.reply_to_message.id
|
||||
)
|
||||
else:
|
||||
await message.edit(
|
||||
"<b>Processing video</b>🎥", parse_mode=enums.ParseMode.HTML
|
||||
)
|
||||
await message.reply_to_message.download(f"downloads/{filename}")
|
||||
await asyncio.get_event_loop().run_in_executor(None, process_vid, filename)
|
||||
|
||||
await message.edit("<b>Saving video</b>📼", parse_mode=enums.ParseMode.HTML)
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None, video.write_videofile, "downloads/result.mp4"
|
||||
)
|
||||
|
||||
await message.delete()
|
||||
await message.reply_video_note(
|
||||
video_note="downloads/result.mp4",
|
||||
duration=int(video.duration),
|
||||
reply_to_message_id=message.reply_to_message.id,
|
||||
)
|
||||
if isinstance(video, VideoFileClip):
|
||||
video.close()
|
||||
os.remove(f"downloads/{filename}")
|
||||
os.remove("downloads/result.mp4")
|
||||
except Exception as e:
|
||||
await message.reply(format_exc(e), parse_mode=enums.ParseMode.HTML)
|
||||
|
||||
|
||||
modules_help["circle"] = {
|
||||
"round": "Round a photo or video.",
|
||||
"circle": "Circle a photo or video.",
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
# Moon-Userbot - telegram userbot
|
||||
# Copyright (C) 2020-present Moon Userbot Organization
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.raw import functions
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["clear_@"], prefix) & filters.me)
|
||||
async def solo_mention_clear(client: Client, message: Message):
|
||||
await message.delete()
|
||||
peer = await client.resolve_peer(message.chat.id)
|
||||
request = functions.messages.ReadMentions(peer=peer)
|
||||
await client.invoke(request)
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["clear_all_@"], prefix) & filters.me)
|
||||
async def global_mention_clear(client: Client, message: Message):
|
||||
counter: int = 0
|
||||
await message.edit_text(
|
||||
f"<b>Clearing all mentions...</b>\n\n<b>Cleared:</b> <code>{counter}</code> chats"
|
||||
)
|
||||
async for dialog in client.get_dialogs():
|
||||
peer = await client.resolve_peer(dialog.chat.id)
|
||||
request = functions.messages.ReadMentions(peer=peer)
|
||||
await client.invoke(request)
|
||||
counter += 1
|
||||
await message.edit_text(
|
||||
f"<b>Clearing all mentions...</b>\n\n<b>Cleared:</b> <code>{counter}</code> chats"
|
||||
)
|
||||
await message.delete()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["clear_reacts"], prefix) & filters.me)
|
||||
async def solo_reaction_clear(client: Client, message: Message):
|
||||
await message.delete()
|
||||
peer = await client.resolve_peer(message.chat.id)
|
||||
request = functions.messages.ReadReactions(peer=peer)
|
||||
await client.invoke(request)
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["clear_all_reacts"], prefix) & filters.me)
|
||||
async def global_reaction_clear(client: Client, message: Message):
|
||||
counter: int = 0
|
||||
await message.edit_text(
|
||||
f"<b>Clearing all reactions...</b>\n\n<b>Cleared:</b> <code>{counter}</code> chats"
|
||||
)
|
||||
async for dialog in client.get_dialogs():
|
||||
peer = await client.resolve_peer(dialog.chat.id)
|
||||
request = functions.messages.ReadReactions(peer=peer)
|
||||
await client.invoke(request)
|
||||
counter += 1
|
||||
await message.edit_text(
|
||||
f"<b>Clearing all reactions...</b>\n\n<b>Cleared:</b> <code>{counter}</code> chats"
|
||||
)
|
||||
await message.delete()
|
||||
|
||||
|
||||
modules_help["clear_notifs"] = {
|
||||
"clear_@": "clear all mentions in this chat",
|
||||
"clear_all_@": "clear all mentions in all chats",
|
||||
"clear_reacts": "clear all reactions in this chat",
|
||||
"clear_all_reacts": "clear all reactions in all chats (except private chats)",
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import asyncio
|
||||
from json import tool
|
||||
from utils.scripts import import_library
|
||||
from utils.config import cohere_key
|
||||
|
||||
cohere = import_library("cohere")
|
||||
|
||||
import cohere
|
||||
|
||||
co = cohere.Client(cohere_key)
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc
|
||||
from utils.db import db
|
||||
from utils.rentry import paste as rentry_paste
|
||||
|
||||
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
from pyrogram.errors import MessageTooLong
|
||||
|
||||
|
||||
@Client.on_message(filters.command("cohere", prefix) & filters.me)
|
||||
async def cohere(c: Client, message: Message):
|
||||
try:
|
||||
user_id = message.from_user.id
|
||||
chat_history = db.get_chat_history(user_id)
|
||||
|
||||
if len(message.command) > 1:
|
||||
prompt = message.text.split(maxsplit=1)[1]
|
||||
elif message.reply_to_message:
|
||||
prompt = message.reply_to_message.text
|
||||
else:
|
||||
await message.edit_text(
|
||||
f"<b>Usage: </b><code>{prefix}cohere [prompt/reply to message]</code>"
|
||||
)
|
||||
return
|
||||
|
||||
db.add_chat_history(user_id, {"role": "USER", "message": prompt})
|
||||
|
||||
await message.edit_text("<code>Umm, lemme think...</code>")
|
||||
|
||||
response = co.chat_stream(
|
||||
chat_history=chat_history,
|
||||
model="command-r-plus",
|
||||
message=prompt,
|
||||
temperature=0.8,
|
||||
tools=[{"name": "internet_search"}],
|
||||
connectors=[],
|
||||
prompt_truncation="OFF",
|
||||
)
|
||||
output = ""
|
||||
tool_message = ""
|
||||
data = []
|
||||
for event in response:
|
||||
if event.event_type == "tool-calls-chunk":
|
||||
if event.tool_call_delta and event.tool_call_delta.text is None:
|
||||
tool_message += ""
|
||||
else:
|
||||
tool_message += event.text
|
||||
if event.event_type == "search-results":
|
||||
data.append(event.documents)
|
||||
if event.event_type == "text-generation":
|
||||
output += event.text
|
||||
if output == "":
|
||||
output = "I can't seem to find an answer to that"
|
||||
|
||||
db.add_chat_history(user_id, {"role": "CHATBOT", "message": output})
|
||||
|
||||
await message.edit_text(f"<code>{tool_message}</code>")
|
||||
|
||||
await asyncio.sleep(5)
|
||||
|
||||
try:
|
||||
data = data[0]
|
||||
references = ""
|
||||
reference_dict = {}
|
||||
for item in data:
|
||||
title = item["title"]
|
||||
url = item["url"]
|
||||
if title not in reference_dict:
|
||||
reference_dict[title] = url
|
||||
|
||||
i = 1
|
||||
for title, url in reference_dict.items():
|
||||
references += f"**{i}.** [{title}]({url})\n"
|
||||
i += 1
|
||||
|
||||
await message.edit_text(
|
||||
f"**Question:**`{prompt}`\n**Answer:** {output}\n\n**References:**\n{references}",
|
||||
parse_mode=enums.ParseMode.MARKDOWN,
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
|
||||
except IndexError:
|
||||
references = ""
|
||||
await message.edit_text(
|
||||
f"**Question:**`{prompt}`\n**Answer:** {output}\n",
|
||||
parse_mode=enums.ParseMode.MARKDOWN,
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
except MessageTooLong:
|
||||
await message.edit_text(
|
||||
"<code>Output is too long... Pasting to rentry...</code>"
|
||||
)
|
||||
try:
|
||||
output = output + "\n\n" + references if references else output
|
||||
rentry_url, edit_code = await rentry_paste(
|
||||
text=output, return_edit=True
|
||||
)
|
||||
except RuntimeError:
|
||||
await message.edit_text(
|
||||
"<b>Error:</b> <code>Failed to paste to rentry</code>"
|
||||
)
|
||||
return
|
||||
await c.send_message(
|
||||
"me",
|
||||
f"Here's your edit code for Url: {rentry_url}\nEdit code: <code>{edit_code}</code>",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
await message.edit_text(
|
||||
f"<b>Output:</b> {rentry_url}\n<b>Note:</b> <code>Edit Code has been sent to your saved messages</code>",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
await message.edit_text(f"An error occurred: {format_exc(e)}")
|
||||
|
||||
|
||||
modules_help["cohere"] = {
|
||||
"cohere": "Chat with cohere ai"
|
||||
+ "\nSupports Chat History\n"
|
||||
+ "Supports real time internet search"
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import random
|
||||
from io import BytesIO
|
||||
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import import_library
|
||||
|
||||
requests = import_library("requests")
|
||||
PIL = import_library("PIL", "pillow")
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["dem"], prefix) & filters.me)
|
||||
async def demotivator(client: Client, message: Message):
|
||||
await message.edit(
|
||||
"<code>Process of demotivation...</code>", parse_mode=enums.ParseMode.HTML
|
||||
)
|
||||
font = requests.get(
|
||||
"https://github.com/The-MoonTg-project/files/blob/main/Times%20New%20Roman.ttf?raw=true"
|
||||
)
|
||||
f = font.content
|
||||
template_dem = requests.get(
|
||||
"https://raw.githubusercontent.com/files/main/demotivator.png"
|
||||
)
|
||||
if message.reply_to_message:
|
||||
words = ["random", "text", "typing", "fuck"]
|
||||
if message.reply_to_message.photo:
|
||||
donwloads = await client.download_media(
|
||||
message.reply_to_message.photo.file_id
|
||||
)
|
||||
photo = Image.open(f"{donwloads}")
|
||||
resize_photo = photo.resize((469, 312))
|
||||
text = (
|
||||
message.text.split(" ", maxsplit=1)[1]
|
||||
if len(message.text.split()) > 1
|
||||
else random.choice(words)
|
||||
)
|
||||
im = Image.open(BytesIO(template_dem.content))
|
||||
im.paste(resize_photo, (65, 48))
|
||||
text_font = ImageFont.truetype(BytesIO(f), 22)
|
||||
text_draw = ImageDraw.Draw(im)
|
||||
text_draw.multiline_text(
|
||||
(299, 412), text, font=text_font, fill=(255, 255, 255), anchor="ms"
|
||||
)
|
||||
im.save(f"downloads/{message.id}.png")
|
||||
await message.reply_to_message.reply_photo(f"downloads/{message.id}.png")
|
||||
await message.delete()
|
||||
elif message.reply_to_message.sticker:
|
||||
if not message.reply_to_message.sticker.is_animated:
|
||||
donwloads = await client.download_media(
|
||||
message.reply_to_message.sticker.file_id
|
||||
)
|
||||
photo = Image.open(f"{donwloads}")
|
||||
resize_photo = photo.resize((469, 312))
|
||||
text = (
|
||||
message.text.split(" ", maxsplit=1)[1]
|
||||
if len(message.text.split()) > 1
|
||||
else random.choice(words)
|
||||
)
|
||||
im = Image.open(BytesIO(template_dem.content))
|
||||
im.paste(resize_photo, (65, 48))
|
||||
text_font = ImageFont.truetype(BytesIO(f), 22)
|
||||
text_draw = ImageDraw.Draw(im)
|
||||
text_draw.multiline_text(
|
||||
(299, 412), text, font=text_font, fill=(255, 255, 255), anchor="ms"
|
||||
)
|
||||
im.save(f"downloads/{message.id}.png")
|
||||
await message.reply_to_message.reply_photo(
|
||||
f"downloads/{message.id}.png"
|
||||
)
|
||||
await message.delete()
|
||||
else:
|
||||
await message.edit(
|
||||
"<b>Animated stickers are not supported</b>",
|
||||
parse_mode=enums.ParseMode.HTML,
|
||||
)
|
||||
else:
|
||||
await message.edit(
|
||||
"<b>Need to answer the photo/sticker</b>",
|
||||
parse_mode=enums.ParseMode.HTML,
|
||||
)
|
||||
else:
|
||||
await message.edit(
|
||||
"<b>Need to answer the photo/sticker</b>", parse_mode=enums.ParseMode.HTML
|
||||
)
|
||||
|
||||
|
||||
modules_help["demotivator"] = {
|
||||
"dem [text]*": "Reply to the picture to make a demotivator out of it"
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import os
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import import_library
|
||||
|
||||
lottie = import_library("lottie")
|
||||
from lottie.exporters import exporters
|
||||
from lottie.importers import importers
|
||||
|
||||
|
||||
@Client.on_message(filters.command("destroy", prefix) & filters.me)
|
||||
async def destroy_sticker(client: Client, message: Message):
|
||||
"""Destroy animated stickers by modifying their animation properties"""
|
||||
try:
|
||||
reply = message.reply_to_message
|
||||
if not reply or not reply.sticker or not reply.sticker.is_animated:
|
||||
return await message.edit(
|
||||
"**Please reply to an animated sticker!**",
|
||||
parse_mode=enums.ParseMode.MARKDOWN,
|
||||
)
|
||||
|
||||
edit_msg = await message.edit(
|
||||
"**🔄 Destroying sticker...**", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
|
||||
# Download sticker
|
||||
tgs_path = await reply.download()
|
||||
if not tgs_path or not os.path.exists(tgs_path):
|
||||
return await edit_msg.edit(
|
||||
"**❌ Download failed!**", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
|
||||
# Conversion process
|
||||
json_path = "temp.json"
|
||||
output_path = "MoonUB.tgs"
|
||||
|
||||
importer = importers.get_from_filename(tgs_path)
|
||||
if not importer:
|
||||
return await edit_msg.edit(
|
||||
"**❌ JSON conversion failed!**", parse_mode=enums.ParseMode.MARKDOWN
|
||||
)
|
||||
|
||||
animation = importer.process(tgs_path)
|
||||
exporter = exporters.get_from_filename(json_path)
|
||||
exporter.process(animation, json_path)
|
||||
|
||||
# Modify JSON data
|
||||
with open(json_path, "r+") as f:
|
||||
content = f.read()
|
||||
modified = (
|
||||
content.replace("[1]", "[2]")
|
||||
.replace("[2]", "[3]")
|
||||
.replace("[3]", "[4]")
|
||||
.replace("[4]", "[5]")
|
||||
.replace("[5]", "[6]")
|
||||
)
|
||||
f.seek(0)
|
||||
f.write(modified)
|
||||
f.truncate()
|
||||
|
||||
importer = importers.get_from_filename(json_path)
|
||||
animation = importer.process(json_path)
|
||||
exporter = exporters.get_from_filename(output_path)
|
||||
exporter.process(animation, output_path)
|
||||
|
||||
# Send result
|
||||
await message.reply_document(output_path, reply_to_message_id=reply.id)
|
||||
await edit_msg.delete()
|
||||
|
||||
except Exception as e:
|
||||
await message.edit(f"**❌ Error:** `{e}`", parse_mode=enums.ParseMode.MARKDOWN)
|
||||
finally:
|
||||
# Cleanup temporary files
|
||||
for file_path in [tgs_path, json_path, output_path]:
|
||||
if file_path and os.path.exists(file_path):
|
||||
try:
|
||||
os.remove(file_path)
|
||||
except Exception as clean_error:
|
||||
print(f"Cleanup error: {clean_error}")
|
||||
|
||||
|
||||
modules_help["destroy"] = {"destroy [reply]": "Modify and destroy animated stickers"}
|
||||
@@ -0,0 +1,33 @@
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc
|
||||
import asyncio
|
||||
|
||||
|
||||
@Client.on_message(filters.command("dice", prefix) & filters.me)
|
||||
async def dice_text(client: Client, message: Message):
|
||||
try:
|
||||
value = int(message.command[1])
|
||||
if value not in range(1, 7):
|
||||
raise AssertionError
|
||||
except (ValueError, IndexError, AssertionError):
|
||||
return await message.edit(
|
||||
"<b>Invalid value</b>", parse_mode=enums.ParseMode.HTML
|
||||
)
|
||||
|
||||
try:
|
||||
message.dice = type("bruh", (), {"value": 0})()
|
||||
while message.dice.value != value:
|
||||
message = (
|
||||
await asyncio.gather(
|
||||
message.delete(), client.send_dice(message.chat.id)
|
||||
)
|
||||
)[1]
|
||||
except Exception as e:
|
||||
await message.edit(format_exc(e), parse_mode=enums.ParseMode.HTML)
|
||||
|
||||
|
||||
modules_help["dice"] = {
|
||||
"dice [1-6]*": "Generate dice with specified value. Works only in groups"
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
# Copyright (C) 2019 The Raphielscape Company LLC.
|
||||
#
|
||||
# Licensed under the Raphielscape Public License, Version 1.d (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
#
|
||||
""" Userbot module containing various sites direct links generators"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import urllib.parse
|
||||
from random import choice
|
||||
from subprocess import PIPE, Popen
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from humanize import naturalsize
|
||||
|
||||
from pyrogram import Client, enums, filters
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
|
||||
def subprocess_run(cmd):
|
||||
reply = ""
|
||||
cmd_args = cmd.split()
|
||||
subproc = Popen(
|
||||
cmd_args,
|
||||
stdout=PIPE,
|
||||
stderr=PIPE,
|
||||
universal_newlines=True,
|
||||
executable="bash",
|
||||
)
|
||||
talk = subproc.communicate()
|
||||
exitCode = subproc.returncode
|
||||
if exitCode != 0:
|
||||
reply += (
|
||||
"```An error was detected while running the subprocess:\n"
|
||||
f"exit code: {exitCode}\n"
|
||||
f"stdout: {talk[0]}\n"
|
||||
f"stderr: {talk[1]}```"
|
||||
)
|
||||
return reply
|
||||
return talk
|
||||
|
||||
|
||||
@Client.on_message(filters.command("direct", prefix) & filters.me)
|
||||
async def direct_link_generator(_, m: Message):
|
||||
if len(m.command) > 1:
|
||||
message = m.text.split(maxsplit=1)[1]
|
||||
elif m.reply_to_message:
|
||||
message = m.reply_to_message.text
|
||||
else:
|
||||
await m.edit(f"<b>Usage: </b><code>{prefix}direct [url]</code>")
|
||||
return
|
||||
reply = ""
|
||||
links = re.findall(r"\bhttps?://.*\.\S+", message)
|
||||
if not links:
|
||||
reply = "`No links found!`"
|
||||
await m.edit(reply, parse_mode=enums.ParseMode.MARKDOWN)
|
||||
for link in links:
|
||||
if "drive.google.com" in link:
|
||||
reply += gdrive(link)
|
||||
elif "yadi.sk" in link:
|
||||
reply += yandex_disk(link)
|
||||
elif "cloud.mail.ru" in link:
|
||||
reply += cm_ru(link)
|
||||
elif "mediafire.com" in link:
|
||||
reply += mediafire(link)
|
||||
elif "sourceforge.net" in link:
|
||||
reply += sourceforge(link)
|
||||
elif "osdn.net" in link:
|
||||
reply += osdn(link)
|
||||
elif "androidfilehost.com" in link:
|
||||
reply += androidfilehost(link)
|
||||
else:
|
||||
reply += re.findall(r"\bhttps?://(.*?[^/]+)", link)[0] + " is not supported"
|
||||
await m.edit(reply, parse_mode=enums.ParseMode.MARKDOWN)
|
||||
|
||||
|
||||
def gdrive(url: str) -> str:
|
||||
"""GDrive direct links generator"""
|
||||
drive = "https://drive.google.com"
|
||||
try:
|
||||
link = re.findall(r"\bhttps?://drive\.google\.com\S+", url)[0]
|
||||
except IndexError:
|
||||
reply = "`No Google drive links found`\n"
|
||||
return reply
|
||||
file_id = ""
|
||||
reply = ""
|
||||
if link.find("view") != -1:
|
||||
file_id = link.split("/")[-2]
|
||||
elif link.find("open?id=") != -1:
|
||||
file_id = link.split("open?id=")[1].strip()
|
||||
elif link.find("uc?id=") != -1:
|
||||
file_id = link.split("uc?id=")[1].strip()
|
||||
url = f"{drive}/uc?export=download&id={file_id}"
|
||||
download = requests.get(url, stream=True, allow_redirects=False)
|
||||
cookies = download.cookies
|
||||
try:
|
||||
# In case of small file size, Google downloads directly
|
||||
dl_url = download.headers["location"]
|
||||
page = BeautifulSoup(download.content, "html.parser")
|
||||
if "accounts.google.com" in dl_url: # non-public file
|
||||
reply += "`Link is not public!`\n"
|
||||
return reply
|
||||
name = "Direct Download Link"
|
||||
except KeyError:
|
||||
# In case of download warning page
|
||||
page = BeautifulSoup(download.content, "html.parser")
|
||||
if download.headers is not None:
|
||||
dl_url = download.headers.get("location")
|
||||
page_element = page.find("a", {"id": "uc-download-link"})
|
||||
if page_element is not None:
|
||||
export = drive + page_element.get("href")
|
||||
name = page.find("span", {"class": "uc-name-size"}).text
|
||||
response = requests.get(
|
||||
export, stream=True, allow_redirects=False, cookies=cookies
|
||||
)
|
||||
dl_url = response.headers["location"]
|
||||
if "accounts.google.com" in dl_url:
|
||||
name = page.find("span", {"class": "uc-name-size"}).text
|
||||
reply += "Link is not public!"
|
||||
return reply
|
||||
if "=sharing" in dl_url:
|
||||
name = page.find("span", {"class": "uc-name-size"}).text
|
||||
reply += "```Provide GDrive Link not directc sharing of GDrive!```"
|
||||
return reply
|
||||
|
||||
reply += f"[{name}]({dl_url})\n"
|
||||
return reply
|
||||
|
||||
|
||||
def yandex_disk(url: str) -> str:
|
||||
"""Yandex.Disk direct links generator
|
||||
Based on https://github.com/wldhx/yadisk-direct"""
|
||||
reply = ""
|
||||
try:
|
||||
link = re.findall(r"\bhttps?://.*yadi\.sk\S+", url)[0]
|
||||
except IndexError:
|
||||
reply = "`No Yandex.Disk links found`\n"
|
||||
return reply
|
||||
api = "https://cloud-api.yandex.net/v1/disk/public/resources/download?public_key={}"
|
||||
try:
|
||||
dl_url = requests.get(api.format(link)).json()["href"]
|
||||
name = dl_url.split("filename=")[1].split("&disposition")[0]
|
||||
reply += f"[{name}]({dl_url})\n"
|
||||
except KeyError:
|
||||
reply += "`Error: File not found / Download limit reached`\n"
|
||||
return reply
|
||||
return reply
|
||||
|
||||
|
||||
def cm_ru(url: str) -> str:
|
||||
"""cloud.mail.ru direct links generator
|
||||
Using https://github.com/JrMasterModelBuilder/cmrudl.py"""
|
||||
reply = ""
|
||||
try:
|
||||
link = re.findall(r"\bhttps?://.*cloud\.mail\.ru\S+", url)[0]
|
||||
except IndexError:
|
||||
reply = "`No cloud.mail.ru links found`\n"
|
||||
return reply
|
||||
cmd = f"bin/cmrudl -s {link}"
|
||||
result = subprocess_run(cmd)
|
||||
try:
|
||||
result = result[0].splitlines()[-1]
|
||||
data = json.loads(result)
|
||||
except json.decoder.JSONDecodeError:
|
||||
reply += "`Error: Can't extract the link`\n"
|
||||
return reply
|
||||
except IndexError:
|
||||
return reply
|
||||
dl_url = data["download"]
|
||||
name = data["file_name"]
|
||||
size = naturalsize(int(data["file_size"]))
|
||||
reply += f"[{name} ({size})]({dl_url})\n"
|
||||
return reply
|
||||
|
||||
|
||||
def mediafire(url: str) -> str:
|
||||
"""MediaFire direct links generator"""
|
||||
try:
|
||||
link = re.findall(r"\bhttps?://.*mediafire\.com\S+", url)[0]
|
||||
except IndexError:
|
||||
reply = "`No MediaFire links found`\n"
|
||||
return reply
|
||||
reply = ""
|
||||
page = BeautifulSoup(requests.get(link).content, "lxml")
|
||||
info = page.find("a", {"aria-label": "Download file"})
|
||||
dl_url = info.get("href")
|
||||
size = re.findall(r"\(.*\)", info.text)[0]
|
||||
name = page.find("div", {"class": "filename"}).text
|
||||
reply += f"[{name} {size}]({dl_url})\n"
|
||||
return reply
|
||||
|
||||
|
||||
def sourceforge(url: str) -> str:
|
||||
"""SourceForge direct links generator"""
|
||||
try:
|
||||
link = re.findall(r"\bhttps?://.*sourceforge\.net\S+", url)[0]
|
||||
except IndexError:
|
||||
reply = "`No SourceForge links found`\n"
|
||||
return reply
|
||||
file_path = re.findall(r"files(.*)/download", link)[0]
|
||||
reply = f"Mirrors for __{file_path.split('/')[-1]}__\n"
|
||||
project = re.findall(r"projects?/(.*?)/files", link)[0]
|
||||
mirrors = (
|
||||
f"https://sourceforge.net/settings/mirror_choices?"
|
||||
f"projectname={project}&filename={file_path}"
|
||||
)
|
||||
page = BeautifulSoup(requests.get(mirrors).content, "html.parser")
|
||||
info = page.find("ul", {"id": "mirrorList"}).findAll("li")
|
||||
for mirror in info[1:]:
|
||||
name = re.findall(r"\((.*)\)", mirror.text.strip())[0]
|
||||
dl_url = (
|
||||
f'https://{mirror["id"]}.dl.sourceforge.net/project/{project}/{file_path}'
|
||||
)
|
||||
reply += f"[{name}]({dl_url}) "
|
||||
return reply
|
||||
|
||||
|
||||
def osdn(url: str) -> str:
|
||||
"""OSDN direct links generator"""
|
||||
osdn_link = "https://osdn.net"
|
||||
try:
|
||||
link = re.findall(r"\bhttps?://.*osdn\.net\S+", url)[0]
|
||||
except IndexError:
|
||||
reply = "`No OSDN links found`\n"
|
||||
return reply
|
||||
page = BeautifulSoup(requests.get(link, allow_redirects=True).content, "lxml")
|
||||
info = page.find("a", {"class": "mirror_link"})
|
||||
link = urllib.parse.unquote(osdn_link + info["href"])
|
||||
reply = f"Mirrors for __{link.split('/')[-1]}__\n"
|
||||
mirrors = page.find("form", {"id": "mirror-select-form"}).findAll("tr")
|
||||
for data in mirrors[1:]:
|
||||
mirror = data.find("input")["value"]
|
||||
name = re.findall(r"\((.*)\)", data.findAll("td")[-1].text.strip())[0]
|
||||
dl_url = re.sub(r"m=(.*)&f", f"m={mirror}&f", link)
|
||||
reply += f"[{name}]({dl_url}) "
|
||||
return reply
|
||||
|
||||
|
||||
def androidfilehost(url: str) -> str:
|
||||
"""AFH direct links generator"""
|
||||
try:
|
||||
link = re.findall(r"\bhttps?://.*androidfilehost.*fid.*\S+", url)[0]
|
||||
except IndexError:
|
||||
reply = "`No AFH links found`\n"
|
||||
return reply
|
||||
fid = re.findall(r"\?fid=(.*)", link)[0]
|
||||
session = requests.Session()
|
||||
user_agent = useragent()
|
||||
headers = {"user-agent": user_agent}
|
||||
res = session.get(link, headers=headers, allow_redirects=True)
|
||||
headers = {
|
||||
"origin": "https://androidfilehost.com",
|
||||
"accept-encoding": "gzip, deflate, br",
|
||||
"accept-language": "en-US,en;q=0.9",
|
||||
"user-agent": user_agent,
|
||||
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"x-mod-sbb-ctype": "xhr",
|
||||
"accept": "*/*",
|
||||
"referer": f"https://androidfilehost.com/?fid={fid}",
|
||||
"authority": "androidfilehost.com",
|
||||
"x-requested-with": "XMLHttpRequest",
|
||||
}
|
||||
data = {"submit": "submit", "action": "getdownloadmirrors", "fid": f"{fid}"}
|
||||
mirrors = None
|
||||
reply = ""
|
||||
error = "`Error: Can't find Mirrors for the link`\n"
|
||||
try:
|
||||
req = session.post(
|
||||
"https://androidfilehost.com/libs/otf/mirrors.otf.php",
|
||||
headers=headers,
|
||||
data=data,
|
||||
cookies=res.cookies,
|
||||
)
|
||||
mirrors = req.json()["MIRRORS"]
|
||||
except (json.decoder.JSONDecodeError, TypeError):
|
||||
reply += error
|
||||
if not mirrors:
|
||||
reply += error
|
||||
return reply
|
||||
for item in mirrors:
|
||||
name = item["name"]
|
||||
dl_url = item["url"]
|
||||
reply += f"[{name}]({dl_url}) "
|
||||
return reply
|
||||
|
||||
|
||||
def useragent():
|
||||
"""
|
||||
useragent random setter
|
||||
"""
|
||||
useragents = BeautifulSoup(
|
||||
requests.get(
|
||||
"https://developers.whatismybrowser.com/"
|
||||
"useragents/explore/operating_system_name/android/"
|
||||
).content,
|
||||
"lxml",
|
||||
).findAll("td", {"class": "useragent"})
|
||||
if not useragents:
|
||||
return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"
|
||||
user_agent = choice(useragents)
|
||||
return user_agent.text
|
||||
|
||||
|
||||
modules_help["direct"] = {
|
||||
"direct": "Url/reply to Url\
|
||||
\n\n<b>Syntax : </b><code>.direct [url/reply] </code>\
|
||||
\n<b>Usage :</b> Generates direct download link from supported URL(s)\
|
||||
\n\n<b>Supported websites : </b><code>Google Drive - MEGA.nz - Cloud Mail - Yandex.Disk - AFH - MediaFire - SourceForge - OSDN</code>"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
from utils.misc import modules_help, requirements_list, prefix
|
||||
|
||||
|
||||
@Client.on_message(filters.command("duck", prefix) & filters.me)
|
||||
async def duckgo(client: Client, message: Message):
|
||||
input_str = " ".join(message.command[1:])
|
||||
sample_url = "https://duckduckgo.com/?q={}".format(input_str.replace(" ", "+"))
|
||||
if sample_url:
|
||||
link = sample_url.rstrip()
|
||||
await message.edit_text(
|
||||
"Let me 🦆 DuckDuckGo that for you:\n🔎 [{}]({})".format(input_str, link)
|
||||
)
|
||||
else:
|
||||
await message.edit_text("something is wrong. please try again later.")
|
||||
@@ -0,0 +1,17 @@
|
||||
from random import randint
|
||||
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
|
||||
@Client.on_message(filters.command("durov", prefix) & filters.me)
|
||||
async def durov(_, message: Message):
|
||||
await message.edit(
|
||||
f"<b>Random post from channel: https://t.me/durov/{randint(21, 36500)}</b>",
|
||||
parse_mode=enums.ParseMode.HTML,
|
||||
)
|
||||
|
||||
|
||||
modules_help["durov"] = {"durov": "Send random post from durov channel"}
|
||||
@@ -0,0 +1,62 @@
|
||||
# Moon-Userbot - telegram userbot
|
||||
# Copyright (C) 2020-present Moon Userbot Organization
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram import Client, enums, filters
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
# if your module has packages from PyPi
|
||||
|
||||
# from utils.scripts import import_library
|
||||
# example_1 = import_library("example_1")
|
||||
# example_2 = import_library("example_2")
|
||||
|
||||
# import_library() will automatically install required library
|
||||
# if it isn't installed
|
||||
|
||||
|
||||
@Client.on_message(filters.command("example_edit", prefix) & filters.me)
|
||||
async def example_edit(client: Client, message: Message):
|
||||
try:
|
||||
await message.edit("<code>This is an example module</code>")
|
||||
except Exception as e:
|
||||
await message.edit(
|
||||
f"<code>[{e.error_code}: {enums.MessageType.TEXT}] - {e.error_details}</code>"
|
||||
)
|
||||
|
||||
|
||||
@Client.on_message(filters.command("example_send", prefix) & filters.me)
|
||||
async def example_send(client: Client, message: Message):
|
||||
try:
|
||||
await client.send_message(message.chat.id, "<b>This is an example module</b>")
|
||||
except Exception as e:
|
||||
await message.edit(
|
||||
f"<code>[{e.error_code}: {enums.MessageType.TEXT}] - {e.error_details}</code>"
|
||||
)
|
||||
|
||||
|
||||
# This adds instructions for your module
|
||||
modules_help["example"] = {
|
||||
"example_send": "example send",
|
||||
"example_edit": "example edit",
|
||||
}
|
||||
|
||||
# modules_help["example"] = { "example_send [text]": "example send" }
|
||||
# | | | |
|
||||
# | | | └─ command description
|
||||
# module_name command_name └─ optional command arguments
|
||||
# (only snake_case) (only snake_case too)
|
||||
@@ -0,0 +1,58 @@
|
||||
import os
|
||||
from random import randint
|
||||
|
||||
import aiohttp
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
|
||||
async def download_sticker(url, filename):
|
||||
headers = {
|
||||
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
"accept-language": "en-US,en;q=0.9;q=0.8",
|
||||
"cache-control": "no-cache",
|
||||
"dnt": "1",
|
||||
"pragma": "no-cache",
|
||||
"priority": "u=0, i",
|
||||
"sec-ch-ua": '"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "document",
|
||||
"sec-fetch-mode": "navigate",
|
||||
"sec-fetch-site": "none",
|
||||
"sec-fetch-user": "?1",
|
||||
"upgrade-insecure-requests": "1",
|
||||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0",
|
||||
}
|
||||
cookies = {"country": "US", "lang": "en"}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, headers=headers, cookies=cookies) as response:
|
||||
if response.status == 200:
|
||||
with open(filename, "wb") as f:
|
||||
f.write(await response.read())
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["f"], prefix) & filters.me)
|
||||
async def random_stiker(client: Client, message: Message):
|
||||
await message.delete()
|
||||
random = randint(1, 63)
|
||||
index = f"00{random}" if random < 10 else f"0{random}"
|
||||
sticker = (
|
||||
f"https://www.chpic.su/_data/stickers/f/FforRespect/FforRespect_{index}.webp"
|
||||
)
|
||||
await download_sticker(sticker, "f.webp")
|
||||
if os.path.exists("f.webp"):
|
||||
await client.send_document(
|
||||
message.chat.id,
|
||||
"f.webp",
|
||||
reply_to_message_id=message.reply_to_message.id
|
||||
if message.reply_to_message
|
||||
else None,
|
||||
)
|
||||
os.remove("f.webp")
|
||||
|
||||
|
||||
modules_help["f"] = {"f": "Send f to pay respect"}
|
||||
@@ -0,0 +1,88 @@
|
||||
from asyncio import sleep
|
||||
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.raw import functions
|
||||
from pyrogram.types import Message, InputReplyToMessage
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc
|
||||
|
||||
commands = {
|
||||
"ftype": enums.ChatAction.TYPING,
|
||||
"faudio": enums.ChatAction.UPLOAD_AUDIO,
|
||||
"fvideo": enums.ChatAction.UPLOAD_VIDEO,
|
||||
"fphoto": enums.ChatAction.UPLOAD_PHOTO,
|
||||
"fdocument": enums.ChatAction.UPLOAD_DOCUMENT,
|
||||
"flocation": enums.ChatAction.FIND_LOCATION,
|
||||
"frvideo": enums.ChatAction.RECORD_VIDEO,
|
||||
"frvoice": enums.ChatAction.RECORD_AUDIO,
|
||||
"frvideor": enums.ChatAction.RECORD_VIDEO_NOTE,
|
||||
"fvideor": enums.ChatAction.UPLOAD_VIDEO_NOTE,
|
||||
"fgame": enums.ChatAction.PLAYING,
|
||||
"fcontact": enums.ChatAction.CHOOSE_CONTACT,
|
||||
"fstop": enums.ChatAction.CANCEL,
|
||||
"fscrn": "screenshot",
|
||||
}
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@Client.on_message(filters.command(list(commands), prefix) & filters.me)
|
||||
async def fakeactions_handler(client: Client, message: Message):
|
||||
cmd = message.command[0]
|
||||
try:
|
||||
sec = int(message.command[1])
|
||||
if sec > 60:
|
||||
sec = 60
|
||||
except:
|
||||
sec = None
|
||||
await message.delete()
|
||||
|
||||
action = commands[cmd]
|
||||
|
||||
try:
|
||||
if action != "screenshot":
|
||||
if sec and action != enums.ChatAction.CANCEL:
|
||||
while sec > 0:
|
||||
await client.send_chat_action(
|
||||
chat_id=message.chat.id, action=action
|
||||
)
|
||||
await sleep(1)
|
||||
sec -= 1
|
||||
return await client.send_chat_action(chat_id=message.chat.id, action=action)
|
||||
else:
|
||||
for _ in range(sec if sec else 1):
|
||||
await client.invoke(
|
||||
functions.messages.SendScreenshotNotification(
|
||||
peer=await client.resolve_peer(message.chat.id),
|
||||
reply_to=InputReplyToMessage(
|
||||
reply_to_message_id=message.reply_to_message.id
|
||||
),
|
||||
random_id=client.rnd_id(),
|
||||
)
|
||||
)
|
||||
await sleep(0.1)
|
||||
except AttributeError:
|
||||
return await client.send_message(
|
||||
"me", f"Error in <b>fakeactions</b>" "reply to message is required"
|
||||
)
|
||||
except Exception as e:
|
||||
return await client.send_message(
|
||||
"me", f"Error in <b>fakeactions</b>" f" module:\n" + format_exc(e)
|
||||
)
|
||||
|
||||
|
||||
modules_help["fakeactions"] = {
|
||||
"ftype [sec]": "Typing... action",
|
||||
"faudio [sec]": "Sending voice... action",
|
||||
"fvideo [sec]": "Sending video... action",
|
||||
"fphoto [sec]": "Sending photo... action",
|
||||
"fdocument [sec]": "Sending document... action",
|
||||
"flocation [sec]": "Find location... action",
|
||||
"fcontact [sec]": "Sending contact... action",
|
||||
"frvideo [sec]": "Recording video... action",
|
||||
"frvoice [sec]": "Recording voice... action",
|
||||
"frvideor [sec]": "Recording round video... action",
|
||||
"fvideor [sec]": "Uploading round video... action",
|
||||
"fgame [sec]": "Playing game... action",
|
||||
"fstop": "Stop actions",
|
||||
"fscrn [amount] [reply_to_message]*": "Make screenshot action",
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
# Moon-Userbot - telegram userbot
|
||||
# Copyright (C) 2020-present Moon Userbot Organization
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram import Client, ContinuePropagation, errors, filters
|
||||
from pyrogram.types import (
|
||||
InputMediaAudio,
|
||||
InputMediaDocument,
|
||||
InputMediaPhoto,
|
||||
InputMediaVideo,
|
||||
Message,
|
||||
)
|
||||
|
||||
from utils.db import db
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc
|
||||
|
||||
|
||||
def get_filters_chat(chat_id):
|
||||
return db.get("core.filters", f"{chat_id}", {})
|
||||
|
||||
|
||||
def set_filters_chat(chat_id, filters_):
|
||||
return db.set("core.filters", f"{chat_id}", filters_)
|
||||
|
||||
|
||||
async def contains_filter(_, __, m):
|
||||
return m.text and m.text.lower() in get_filters_chat(m.chat.id).keys()
|
||||
|
||||
|
||||
contains = filters.create(contains_filter)
|
||||
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
@Client.on_message(contains)
|
||||
async def filters_main_handler(client: Client, message: Message):
|
||||
value = get_filters_chat(message.chat.id)[message.text.lower()]
|
||||
try:
|
||||
await client.get_messages(int(value["CHAT_ID"]), int(value["MESSAGE_ID"]))
|
||||
except errors.RPCError as exc:
|
||||
raise ContinuePropagation from exc
|
||||
|
||||
if value.get("MEDIA_GROUP"):
|
||||
messages_grouped = await client.get_media_group(
|
||||
int(value["CHAT_ID"]), int(value["MESSAGE_ID"])
|
||||
)
|
||||
media_grouped_list = []
|
||||
for _ in messages_grouped:
|
||||
if _.photo:
|
||||
if _.caption:
|
||||
media_grouped_list.append(
|
||||
InputMediaPhoto(_.photo.file_id, _.caption.HTML)
|
||||
)
|
||||
else:
|
||||
media_grouped_list.append(InputMediaPhoto(_.photo.file_id))
|
||||
elif _.video:
|
||||
if _.caption:
|
||||
if _.video.thumbs:
|
||||
media_grouped_list.append(
|
||||
InputMediaVideo(
|
||||
_.video.file_id,
|
||||
_.video.thumbs[0].file_id,
|
||||
_.caption.HTML,
|
||||
)
|
||||
)
|
||||
else:
|
||||
media_grouped_list.append(
|
||||
InputMediaVideo(_.video.file_id, _.caption.HTML)
|
||||
)
|
||||
elif _.video.thumbs:
|
||||
media_grouped_list.append(
|
||||
InputMediaVideo(_.video.file_id, _.video.thumbs[0].file_id)
|
||||
)
|
||||
else:
|
||||
media_grouped_list.append(InputMediaVideo(_.video.file_id))
|
||||
elif _.audio:
|
||||
if _.caption:
|
||||
media_grouped_list.append(
|
||||
InputMediaAudio(_.audio.file_id, _.caption.HTML)
|
||||
)
|
||||
else:
|
||||
media_grouped_list.append(InputMediaAudio(_.audio.file_id))
|
||||
elif _.document:
|
||||
if _.caption:
|
||||
if _.document.thumbs:
|
||||
media_grouped_list.append(
|
||||
InputMediaDocument(
|
||||
_.document.file_id,
|
||||
_.document.thumbs[0].file_id,
|
||||
_.caption.HTML,
|
||||
)
|
||||
)
|
||||
else:
|
||||
media_grouped_list.append(
|
||||
InputMediaDocument(_.document.file_id, _.caption.HTML)
|
||||
)
|
||||
elif _.document.thumbs:
|
||||
media_grouped_list.append(
|
||||
InputMediaDocument(
|
||||
_.document.file_id, _.document.thumbs[0].file_id
|
||||
)
|
||||
)
|
||||
else:
|
||||
media_grouped_list.append(InputMediaDocument(_.document.file_id))
|
||||
await client.send_media_group(
|
||||
message.chat.id, media_grouped_list, reply_to_message_id=message.id
|
||||
)
|
||||
else:
|
||||
await client.copy_message(
|
||||
message.chat.id,
|
||||
int(value["CHAT_ID"]),
|
||||
int(value["MESSAGE_ID"]),
|
||||
reply_to_message_id=message.id,
|
||||
)
|
||||
raise ContinuePropagation
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["filter"], prefix) & filters.me)
|
||||
async def filter_handler(client: Client, message: Message):
|
||||
try:
|
||||
if len(message.text.split()) < 2:
|
||||
return await message.edit(
|
||||
f"<b>Usage</b>: <code>{prefix}filter [name] (Reply required)</code>"
|
||||
)
|
||||
name = message.text.split(maxsplit=1)[1].lower()
|
||||
chat_filters = get_filters_chat(message.chat.id)
|
||||
if name in chat_filters.keys():
|
||||
return await message.edit(
|
||||
f"<b>Filter</b> <code>{name}</code> already exists."
|
||||
)
|
||||
if not message.reply_to_message:
|
||||
return await message.edit("<b>Reply to message</b> please.")
|
||||
|
||||
try:
|
||||
chat = await client.get_chat(db.get("core.notes", "chat_id", 0))
|
||||
except (errors.RPCError, ValueError, KeyError):
|
||||
# group is not accessible or isn't created
|
||||
chat = await client.create_supergroup(
|
||||
"Userbot_Notes_Filters", "Don't touch this group, please"
|
||||
)
|
||||
db.set("core.notes", "chat_id", chat.id)
|
||||
|
||||
chat_id = chat.id
|
||||
|
||||
if message.reply_to_message.media_group_id:
|
||||
get_media_group = [
|
||||
_.id
|
||||
for _ in await client.get_media_group(
|
||||
message.chat.id, message.reply_to_message.id
|
||||
)
|
||||
]
|
||||
try:
|
||||
message_id = await client.forward_messages(
|
||||
chat_id, message.chat.id, get_media_group
|
||||
)
|
||||
except errors.ChatForwardsRestricted:
|
||||
await message.edit(
|
||||
"<b>Forwarding messages is restricted by chat admins</b>"
|
||||
)
|
||||
return
|
||||
filter_ = {
|
||||
"MESSAGE_ID": str(message_id[1].id),
|
||||
"MEDIA_GROUP": True,
|
||||
"CHAT_ID": str(chat_id),
|
||||
}
|
||||
else:
|
||||
try:
|
||||
message_id = await message.reply_to_message.forward(chat_id)
|
||||
except errors.ChatForwardsRestricted:
|
||||
message_id = await message.copy(chat_id)
|
||||
filter_ = {
|
||||
"MEDIA_GROUP": False,
|
||||
"MESSAGE_ID": str(message_id.id),
|
||||
"CHAT_ID": str(chat_id),
|
||||
}
|
||||
|
||||
chat_filters.update({name: filter_})
|
||||
|
||||
set_filters_chat(message.chat.id, chat_filters)
|
||||
return await message.edit(
|
||||
f"<b>Filter</b> <code>{name}</code> has been added.",
|
||||
)
|
||||
except Exception as e:
|
||||
return await message.edit(format_exc(e))
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["filters"], prefix) & filters.me)
|
||||
async def filters_handler(_, message: Message):
|
||||
try:
|
||||
text = ""
|
||||
for index, a in enumerate(get_filters_chat(message.chat.id).items(), start=1):
|
||||
key, _ = a
|
||||
key = key.replace("<", "").replace(">", "")
|
||||
text += f"{index}. <code>{key}</code>\n"
|
||||
text = f"<b>Your filters in current chat</b>:\n\n" f"{text}"
|
||||
text = text[:4096]
|
||||
return await message.edit(text)
|
||||
except Exception as e:
|
||||
return await message.edit(format_exc(e))
|
||||
|
||||
|
||||
@Client.on_message(
|
||||
filters.command(["delfilter", "filterdel", "fdel"], prefix) & filters.me
|
||||
)
|
||||
async def filter_del_handler(_, message: Message):
|
||||
try:
|
||||
if len(message.text.split()) < 2:
|
||||
return await message.edit(
|
||||
f"<b>Usage</b>: <code>{prefix}fdel [name]</code>",
|
||||
)
|
||||
name = message.text.split(maxsplit=1)[1].lower()
|
||||
chat_filters = get_filters_chat(message.chat.id)
|
||||
if name not in chat_filters.keys():
|
||||
return await message.edit(
|
||||
f"<b>Filter</b> <code>{name}</code> doesn't exists.",
|
||||
)
|
||||
del chat_filters[name]
|
||||
set_filters_chat(message.chat.id, chat_filters)
|
||||
return await message.edit(
|
||||
f"<b>Filter</b> <code>{name}</code> has been deleted.",
|
||||
)
|
||||
except Exception as e:
|
||||
return await message.edit(format_exc(e))
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["fsearch"], prefix) & filters.me)
|
||||
async def filter_search_handler(_, message: Message):
|
||||
try:
|
||||
if len(message.text.split()) < 2:
|
||||
return await message.edit(
|
||||
f"<b>Usage</b>: <code>{prefix}fsearch [name]</code>",
|
||||
)
|
||||
name = message.text.split(maxsplit=1)[1].lower()
|
||||
chat_filters = get_filters_chat(message.chat.id)
|
||||
if name not in chat_filters.keys():
|
||||
return await message.edit(
|
||||
f"<b>Filter</b> <code>{name}</code> doesn't exists.",
|
||||
)
|
||||
return await message.edit(
|
||||
f"<b>Trigger</b>:\n<code>{name}</code"
|
||||
f">\n<b>Answer</b>:\n{chat_filters[name]}"
|
||||
)
|
||||
except Exception as e:
|
||||
return await message.edit(format_exc(e))
|
||||
|
||||
|
||||
modules_help["filters"] = {
|
||||
"filter [name]": "Create filter (Reply required)",
|
||||
"filters": "List of all triggers",
|
||||
"fdel [name]": "Delete filter by name",
|
||||
"fsearch [name]": "Info filter by name",
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import asyncio
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.raw import functions
|
||||
from pyrogram.types import Message
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc
|
||||
|
||||
REPLACEMENT_MAP = {
|
||||
"a": "ɐ",
|
||||
"b": "q",
|
||||
"c": "ɔ",
|
||||
"d": "p",
|
||||
"e": "ǝ",
|
||||
"f": "ɟ",
|
||||
"g": "ƃ",
|
||||
"h": "ɥ",
|
||||
"i": "ᴉ",
|
||||
"j": "ɾ",
|
||||
"k": "ʞ",
|
||||
"l": "l",
|
||||
"m": "ɯ",
|
||||
"n": "u",
|
||||
"o": "o",
|
||||
"p": "d",
|
||||
"q": "b",
|
||||
"r": "ɹ",
|
||||
"s": "s",
|
||||
"t": "ʇ",
|
||||
"u": "n",
|
||||
"v": "ʌ",
|
||||
"w": "ʍ",
|
||||
"x": "x",
|
||||
"y": "ʎ",
|
||||
"z": "z",
|
||||
"A": "∀",
|
||||
"B": "B",
|
||||
"C": "Ɔ",
|
||||
"D": "D",
|
||||
"E": "Ǝ",
|
||||
"F": "Ⅎ",
|
||||
"G": "פ",
|
||||
"H": "H",
|
||||
"I": "I",
|
||||
"J": "ſ",
|
||||
"K": "K",
|
||||
"L": "˥",
|
||||
"M": "W",
|
||||
"N": "N",
|
||||
"O": "O",
|
||||
"P": "Ԁ",
|
||||
"Q": "Q",
|
||||
"R": "R",
|
||||
"S": "S",
|
||||
"T": "┴",
|
||||
"U": "∩",
|
||||
"V": "Λ",
|
||||
"W": "M",
|
||||
"X": "X",
|
||||
"Y": "⅄",
|
||||
"Z": "Z",
|
||||
"0": "0",
|
||||
"1": "Ɩ",
|
||||
"2": "ᄅ",
|
||||
"3": "Ɛ",
|
||||
"4": "ㄣ",
|
||||
"5": "ϛ",
|
||||
"6": "9",
|
||||
"7": "ㄥ",
|
||||
"8": "8",
|
||||
"9": "6",
|
||||
",": "'",
|
||||
".": "˙",
|
||||
"?": "¿",
|
||||
"!": "¡",
|
||||
'"': ",,",
|
||||
"'": ",",
|
||||
"(": ")",
|
||||
")": "(",
|
||||
"[": "]",
|
||||
"]": "[",
|
||||
"{": "}",
|
||||
"}": "{",
|
||||
"<": ">",
|
||||
">": "<",
|
||||
"&": "⅋",
|
||||
"_": "‾",
|
||||
}
|
||||
|
||||
|
||||
@Client.on_message(filters.command("flip", prefix) & filters.me)
|
||||
async def flip(client: Client, message: Message):
|
||||
text = " ".join(message.command[1:])
|
||||
final_str = ""
|
||||
for char in text:
|
||||
if char in REPLACEMENT_MAP.keys():
|
||||
new_char = REPLACEMENT_MAP[char]
|
||||
else:
|
||||
new_char = char
|
||||
final_str += new_char
|
||||
if text != final_str:
|
||||
await message.edit(final_str)
|
||||
else:
|
||||
await message.edit(text)
|
||||
|
||||
|
||||
modules_help["fliptext"] = {"flip [amount]*": "flip text upside down"}
|
||||
@@ -0,0 +1,52 @@
|
||||
import os
|
||||
import io
|
||||
import time
|
||||
import requests
|
||||
from pyrogram import filters, Client
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc, progress
|
||||
|
||||
|
||||
def schellwithflux(args):
|
||||
API_URL = "https://randydev-ryuzaki-api.hf.space/api/v1/akeno/fluxai"
|
||||
payload = {"user_id": 1191668125, "args": args} # Please don't edit here
|
||||
response = requests.post(API_URL, json=payload)
|
||||
if response.status_code != 200:
|
||||
print(f"Error status {response.status_code}")
|
||||
return None
|
||||
return response.content
|
||||
|
||||
|
||||
@Client.on_message(filters.command("fluxai", prefix) & filters.me)
|
||||
async def imgfluxai_(client: Client, message: Message):
|
||||
question = message.text.split(" ", 1)[1] if len(message.command) > 1 else None
|
||||
if not question:
|
||||
return await message.reply_text("Please provide a question for Flux.")
|
||||
try:
|
||||
image_bytes = schellwithflux(question)
|
||||
if image_bytes is None:
|
||||
return await message.reply_text("Failed to generate an image.")
|
||||
pro = await message.reply_text("Generating image, please wait...")
|
||||
|
||||
# Write the image bytes directly to a file
|
||||
with open("flux_gen.jpg", "wb") as f:
|
||||
f.write(image_bytes)
|
||||
|
||||
ok = await pro.edit_text("Uploading image...")
|
||||
await message.reply_photo(
|
||||
"flux_gen.jpg",
|
||||
progress=progress,
|
||||
progress_args=(ok, time.time(), "Uploading image..."),
|
||||
)
|
||||
await ok.delete()
|
||||
if os.path.exists("flux_gen.jpg"):
|
||||
os.remove("flux_gen.jpg")
|
||||
except Exception as e:
|
||||
await message.edit_text(format_exc(e))
|
||||
|
||||
|
||||
modules_help["fluxai"] = {
|
||||
"fluxai [prompt]*": "text to image fluxai",
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
# This scripts contains use cases for userbots
|
||||
# This is used on my Moon-Userbot: https://github.com/The-MoonTg-project/Moon-Userbot
|
||||
# YOu can check it out for uses example
|
||||
import os
|
||||
|
||||
from pyrogram import Client, filters, enums
|
||||
from pyrogram.types import Message
|
||||
from pyrogram.errors import MessageTooLong
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_exc, import_library
|
||||
from utils.config import gemini_key
|
||||
from utils.rentry import paste as rentry_paste
|
||||
|
||||
genai = import_library("google.generativeai", "google-generativeai")
|
||||
|
||||
genai.configure(api_key=gemini_key)
|
||||
|
||||
model = genai.GenerativeModel("gemini-2.0-flash")
|
||||
|
||||
|
||||
@Client.on_message(filters.command("gemini", prefix) & filters.me)
|
||||
async def say(client: Client, message: Message):
|
||||
try:
|
||||
await message.edit_text("<code>Please Wait...</code>")
|
||||
|
||||
if len(message.command) > 1:
|
||||
prompt = message.text.split(maxsplit=1)[1]
|
||||
elif message.reply_to_message:
|
||||
prompt = message.reply_to_message.text
|
||||
else:
|
||||
await message.edit_text(
|
||||
f"<b>Usage: </b><code>{prefix}gemini [prompt/reply to message]</code>"
|
||||
)
|
||||
return
|
||||
|
||||
chat = model.start_chat()
|
||||
response = chat.send_message(prompt)
|
||||
|
||||
await message.edit_text(
|
||||
f"**Question:**`{prompt}`\n**Answer:** {response.text}",
|
||||
parse_mode=enums.ParseMode.MARKDOWN,
|
||||
)
|
||||
except MessageTooLong:
|
||||
await message.edit_text(
|
||||
"<code>Output is too long... Pasting to rentry...</code>"
|
||||
)
|
||||
try:
|
||||
rentry_url, edit_code = await rentry_paste(
|
||||
text=response.text, return_edit=True
|
||||
)
|
||||
except RuntimeError:
|
||||
await message.edit_text(
|
||||
"<b>Error:</b> <code>Failed to paste to rentry</code>"
|
||||
)
|
||||
return
|
||||
await client.send_message(
|
||||
"me",
|
||||
f"Here's your edit code for Url: {rentry_url}\nEdit code: <code>{edit_code}</code>",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
await message.edit_text(
|
||||
f"<b>Output:</b> {rentry_url}\n<b>Note:</b> <code>Edit Code has been sent to your saved messages</code>",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
except Exception as e:
|
||||
await message.edit_text(f"An error occurred: {format_exc(e)}")
|
||||
|
||||
|
||||
modules_help["gemini"] = {
|
||||
"gemini [prompt]*": "Ask questions with Gemini Ai",
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
# Dragon-Userbot - telegram userbot
|
||||
# Copyright (C) 2020-present Dragon Userbot Organization
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["google", "g"], prefix) & filters.me)
|
||||
async def webshot(_, message: Message):
|
||||
user_request = " ".join(message.command[1:])
|
||||
|
||||
if user_request == "":
|
||||
if message.reply_to_message:
|
||||
reply_user_request = message.reply_to_message.text
|
||||
request = reply_user_request.replace(" ", "+")
|
||||
full_request = f"https://lmgtfy.app/?s=g&iie=1&q={request}"
|
||||
await message.edit(
|
||||
f"<a href={full_request}>{reply_user_request}</a>",
|
||||
disable_web_page_preview=True,
|
||||
)
|
||||
|
||||
else:
|
||||
request = user_request.replace(" ", "+")
|
||||
full_request = f"https://lmgtfy.app/?s=g&iie=1&q={request}"
|
||||
await message.edit(
|
||||
f"<a href={full_request}>{user_request}</a>", disable_web_page_preview=True
|
||||
)
|
||||
|
||||
|
||||
modules_help["google"] = {
|
||||
"google [request]": "To teach the interlocutor to use Google. Request isn't required."
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import random
|
||||
import asyncio
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
from pyrogram.errors.exceptions.flood_420 import FloodWait
|
||||
from utils.misc import modules_help, prefix
|
||||
|
||||
R = "❤️"
|
||||
W = "🤍"
|
||||
|
||||
heart_list = [
|
||||
W * 9,
|
||||
W * 2 + R * 2 + W + R * 2 + W * 2,
|
||||
W + R * 7 + W,
|
||||
W + R * 7 + W,
|
||||
W + R * 7 + W,
|
||||
W * 2 + R * 5 + W * 2,
|
||||
W * 3 + R * 3 + W * 3,
|
||||
W * 4 + R + W * 4,
|
||||
W * 9,
|
||||
]
|
||||
joined_heart = "\n".join(heart_list)
|
||||
|
||||
heartlet_len = joined_heart.count(R)
|
||||
|
||||
SLEEP = 0.1
|
||||
|
||||
|
||||
async def _wrap_edit(message: Message, text: str):
|
||||
"""Floodwait-safe utility wrapper for edit"""
|
||||
try:
|
||||
await message.edit(text)
|
||||
except FloodWait as fl:
|
||||
await asyncio.sleep(fl.x)
|
||||
|
||||
|
||||
async def phase1(message: Message):
|
||||
"""Big scroll"""
|
||||
BIG_SCROLL = "🧡💛💚💙💜🖤🤎"
|
||||
await _wrap_edit(message, joined_heart)
|
||||
for heart in BIG_SCROLL:
|
||||
await _wrap_edit(message, joined_heart.replace(R, heart))
|
||||
await asyncio.sleep(SLEEP)
|
||||
|
||||
|
||||
async def phase2(message: Message):
|
||||
"""Per-heart randomiser"""
|
||||
ALL = ["❤️"] + list("🧡💛💚💙💜🤎🖤") # don't include white heart
|
||||
|
||||
format_heart = joined_heart.replace(R, "{}")
|
||||
for _ in range(5):
|
||||
heart = format_heart.format(*random.choices(ALL, k=heartlet_len))
|
||||
await _wrap_edit(message, heart)
|
||||
await asyncio.sleep(SLEEP)
|
||||
|
||||
|
||||
async def phase3(message: Message):
|
||||
"""Fill up heartlet matrix"""
|
||||
await _wrap_edit(message, joined_heart)
|
||||
await asyncio.sleep(SLEEP * 2)
|
||||
repl = joined_heart
|
||||
for _ in range(joined_heart.count(W)):
|
||||
repl = repl.replace(W, R, 1)
|
||||
await _wrap_edit(message, repl)
|
||||
await asyncio.sleep(SLEEP)
|
||||
|
||||
|
||||
async def phase4(message: Message):
|
||||
"""Matrix shrinking"""
|
||||
for i in range(7, 0, -1):
|
||||
heart_matrix = "\n".join([R * i] * i)
|
||||
await _wrap_edit(message, heart_matrix)
|
||||
await asyncio.sleep(SLEEP)
|
||||
|
||||
|
||||
@Client.on_message(filters.command("hearts", prefix) & filters.me)
|
||||
async def hearts(client: Client, message: Message):
|
||||
await phase1(message)
|
||||
await phase2(message)
|
||||
await phase3(message)
|
||||
await phase4(message)
|
||||
await asyncio.sleep(SLEEP * 3)
|
||||
|
||||
final_caption = " ".join(message.command[1:])
|
||||
if not final_caption:
|
||||
final_caption = "💕 by @moonuserbot"
|
||||
await message.edit(final_caption)
|
||||
|
||||
|
||||
modules_help["hearts"] = {
|
||||
"hearts": "Heart animation. May cause floodwaits, use at your own risk!"
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
# Moon-Userbot - telegram userbot
|
||||
# Copyright (C) 2020-present Moon Userbot Organization
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram import Client, filters
|
||||
from pyrogram.types import Message
|
||||
|
||||
from utils.misc import modules_help, prefix
|
||||
from utils.scripts import format_module_help, with_reply
|
||||
from utils.module import ModuleManager
|
||||
|
||||
module_manager = ModuleManager.get_instance()
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["help", "h"], prefix) & filters.me)
|
||||
async def help_cmd(_, message: Message):
|
||||
if not module_manager.help_navigator:
|
||||
await message.edit("<b>Help system is not initialized yet. Please wait...</b>")
|
||||
return
|
||||
|
||||
if len(message.command) == 1:
|
||||
await module_manager.help_navigator.send_page(message)
|
||||
elif message.command[1].lower() in modules_help:
|
||||
await message.edit(format_module_help(message.command[1].lower(), prefix))
|
||||
else:
|
||||
command_name = message.command[1].lower()
|
||||
module_found = False
|
||||
for module_name, commands in modules_help.items():
|
||||
for command in commands.keys():
|
||||
if command.split()[0] == command_name:
|
||||
cmd = command.split(maxsplit=1)
|
||||
cmd_desc = commands[command]
|
||||
module_found = True
|
||||
return await message.edit(
|
||||
f"<b>Help for command <code>{prefix}{command_name}</code></b>\n"
|
||||
f"Module: {module_name} (<code>{prefix}help {module_name}</code>)\n\n"
|
||||
f"<code>{prefix}{cmd[0]}</code>"
|
||||
f"{' <code>' + cmd[1] + '</code>' if len(cmd) > 1 else ''}"
|
||||
f" — <i>{cmd_desc}</i>",
|
||||
)
|
||||
if not module_found:
|
||||
await message.edit(f"<b>Module or command {command_name} not found</b>")
|
||||
|
||||
|
||||
@Client.on_message(filters.command(["pn", "pp", "pq"], prefix) & filters.me)
|
||||
@with_reply
|
||||
async def handle_navigation(_, message: Message):
|
||||
if not module_manager.help_navigator:
|
||||
await message.edit("<b>Help system is not initialized yet. Please wait...</b>")
|
||||
return
|
||||
|
||||
reply_message = message.reply_to_message
|
||||
if reply_message and "Help Page No:" in message.reply_to_message.text:
|
||||
cmd = message.command[0].lower()
|
||||
if cmd == "pn":
|
||||
if module_manager.help_navigator.next_page():
|
||||
await module_manager.help_navigator.send_page(reply_message)
|
||||
return await message.delete()
|
||||
await message.edit("No more pages available.")
|
||||
elif cmd == "pp":
|
||||
if module_manager.help_navigator.prev_page():
|
||||
await module_manager.help_navigator.send_page(reply_message)
|
||||
return await message.delete()
|
||||
return await message.edit("This is the first page.")
|
||||
elif cmd == "pq":
|
||||
await reply_message.delete()
|
||||
return await message.edit("Help closed.")
|
||||
|
||||
|
||||
modules_help["help"] = {
|
||||
"help [module/command name]": "Get common/module/command help",
|
||||
"pn/pp/pq": "Navigate through help pages"
|
||||
+ " (pn: next page, pp: previous page, pq: quit help)",
|
||||
}
|
||||