init, .gitignore

This commit is contained in:
2025-11-11 00:02:49 +01:00
commit 32aac89fdf
164 changed files with 18090 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
.sync.ffs_db
volumes/*
traefik/certs/*
letsencrypt/acme.json
models/*
certs/*
.env*
traefik/letsencrypt/acme.json
+34
View File
@@ -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:
+15
View File
@@ -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
+306
View File
@@ -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
+16
View File
@@ -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"]
+26
View File
@@ -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')) # Секунды ожидания загрузки
+131
View File
@@ -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)
+42
View File
@@ -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()
+5
View File
@@ -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
+258
View File
@@ -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
+32
View File
@@ -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"
+8
View File
@@ -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;'
+87
View File
@@ -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/;
}
}
+15
View File
@@ -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"]
+216
View File
@@ -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
View File
+12
View File
@@ -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
+88
View File
@@ -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
+27
View File
@@ -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
+35
View File
@@ -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"
+144
View File
@@ -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"
+101
View File
@@ -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"
+101
View File
@@ -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"
+8
View File
@@ -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;
}
+10
View File
@@ -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`
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

+1
View File
@@ -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"}]}
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

+17
View File
@@ -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
+42
View File
@@ -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
+10
View File
@@ -0,0 +1,10 @@
version = 1
[[analyzers]]
name = "shell"
[[analyzers]]
name = "python"
[analyzers.meta]
runtime_version = "3.x.x"
+1
View File
@@ -0,0 +1 @@
.unused
+25
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
git
wget
ffmpeg
mediainfo
yt-dlp
+10
View File
@@ -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"]
+1
View File
@@ -0,0 +1 @@
web: bash cloud.sh
+88
View File
@@ -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"
}
]
}
+12
View File
@@ -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()
+17
View File
@@ -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
+17
View File
@@ -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"
+1
View File
@@ -0,0 +1 @@
gunicorn app:app & python3 main.py
+10
View File
@@ -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"]
View File
+76
View File
@@ -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
+115
View File
@@ -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.",
}
+52
View File
@@ -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",
}
+55
View File
@@ -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."
}
+102
View File
@@ -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",
}
+105
View File
@@ -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.",
}
+94
View File
@@ -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.",
}
+103
View File
@@ -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",
}
+52
View File
@@ -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()
+338
View File
@@ -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 .
+179
View File
@@ -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())
+41
View File
@@ -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"
}
+353
View File
@@ -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",
}
+380
View File
@@ -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",
}
+88
View File
@@ -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"}
+129
View File
@@ -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",
}
+58
View File
@@ -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"
}
+218
View File
@@ -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)",
}
File diff suppressed because one or more lines are too long
+243
View File
@@ -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",
}
+69
View File
@@ -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+)",
}
+72
View File
@@ -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",
}
+43
View File
@@ -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",
}
+256
View File
@@ -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",
}
+134
View File
@@ -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",
}
+46
View File
@@ -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"
}
+57
View File
@@ -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",
}
+105
View File
@@ -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",
}
+146
View File
@@ -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.",
}
+79
View File
@@ -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)",
}
+134
View File
@@ -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"
}
+93
View File
@@ -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"
}
+84
View File
@@ -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"}
+33
View File
@@ -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"
}
+313
View File
@@ -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>"
}
+16
View File
@@ -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.")
+17
View File
@@ -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"}
+62
View File
@@ -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)
+58
View File
@@ -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"}
+88
View File
@@ -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",
}
+264
View File
@@ -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",
}
+106
View File
@@ -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"}
+52
View File
@@ -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",
}
+72
View File
@@ -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",
}
+47
View File
@@ -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."
}
+93
View File
@@ -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!"
}
+83
View File
@@ -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)",
}

Some files were not shown because too many files have changed in this diff Show More