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)",
|
||||||
|
}
|
||||||