mirror of
https://github.com/kurbezz/utility-bot.git
synced 2025-12-06 19:25:36 +01:00
Init
This commit is contained in:
7
.github/dependabot.yml
vendored
Normal file
7
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# Maintain dependencies for GitHub Actions
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
50
.github/workflows/build_docker_image.yml
vendored
Normal file
50
.github/workflows/build_docker_image.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: Build docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Build-Docker-Image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- id: repository_name
|
||||||
|
uses: ASzc/change-string-case-action@v6
|
||||||
|
with:
|
||||||
|
string: ${{ github.repository }}
|
||||||
|
|
||||||
|
-
|
||||||
|
name: Login to ghcr.io
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
-
|
||||||
|
name: Build and push
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
env:
|
||||||
|
IMAGE: ${{ steps.repository_name.outputs.lowercase }}
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64
|
||||||
|
tags: ghcr.io/${{ env.IMAGE }}:latest
|
||||||
|
context: .
|
||||||
|
file: ./docker/build.dockerfile
|
||||||
|
|
||||||
|
# -
|
||||||
|
# name: Invoke deployment hook
|
||||||
|
# uses: joelwmale/webhook-action@master
|
||||||
|
# with:
|
||||||
|
# url: ${{ secrets.WEBHOOK_URL }}
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
venv
|
||||||
28
docker/build.dockerfile
Normal file
28
docker/build.dockerfile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
FROM python:3.12-slim AS build
|
||||||
|
|
||||||
|
ARG POETRY_EXPORT_EXTRA_ARGS=''
|
||||||
|
|
||||||
|
WORKDIR /opt/venv
|
||||||
|
RUN python -m venv /opt/venv && /opt/venv/bin/pip install --upgrade pip && /opt/venv/bin/pip install --no-cache-dir httpx poetry
|
||||||
|
|
||||||
|
COPY ./pyproject.toml ./poetry.lock ./
|
||||||
|
RUN --mount=type=ssh /opt/venv/bin/poetry export --without-hashes ${POETRY_EXPORT_EXTRA_ARGS} > requirements.txt \
|
||||||
|
&& /opt/venv/bin/pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
|
||||||
|
FROM python:3.12-slim AS runtime
|
||||||
|
|
||||||
|
RUN apt update && apt install -y --no-install-recommends netcat-traditional wkhtmltopdf && apt clean
|
||||||
|
|
||||||
|
COPY ./src/ /app
|
||||||
|
|
||||||
|
ENV PATH="/opt/venv/bin:$PATH"
|
||||||
|
ENV VENV_PATH=/opt/venv
|
||||||
|
|
||||||
|
COPY --from=build /opt/venv /opt/venv
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["python", "main.py"]
|
||||||
1329
poetry.lock
generated
Normal file
1329
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
pyproject.toml
Normal file
20
pyproject.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "utility-bot"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["Bulat Kurbanov <kurbanovbul@gmail.com>"]
|
||||||
|
readme = "README.md"
|
||||||
|
packages = [{include = "utility_bot"}]
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.11"
|
||||||
|
tiktok-downloader = "^0.3.5"
|
||||||
|
aiogram = "^3.11.0"
|
||||||
|
aiohttp = "^3.10.2"
|
||||||
|
pydantic = "^2.8.2"
|
||||||
|
pydantic-settings = "^2.4.0"
|
||||||
|
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
10
src/config.py
Normal file
10
src/config.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class Config(BaseSettings):
|
||||||
|
BOT_TOKEN: str
|
||||||
|
BASE_WEBHOOK_URL: str
|
||||||
|
WEBHOOK_SECRET: str
|
||||||
|
|
||||||
|
|
||||||
|
config = Config() # type: ignore
|
||||||
100
src/main.py
Normal file
100
src/main.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
from io import BufferedWriter, BytesIO
|
||||||
|
import re
|
||||||
|
import asyncio
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
from aiogram import Bot, Dispatcher, types
|
||||||
|
from aiogram.enums import ParseMode
|
||||||
|
from aiogram.client.default import DefaultBotProperties
|
||||||
|
from tiktok_downloader import VideoInfo, tikwm, ttdownloader, tikdown, mdown, snaptik, Tikmate
|
||||||
|
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
|
||||||
|
|
||||||
|
from config import config
|
||||||
|
|
||||||
|
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
WEB_SERVER_HOST = "0.0.0.0"
|
||||||
|
WEB_SERVER_PORT = 80
|
||||||
|
|
||||||
|
WEBHOOK_PATH = "/webhook"
|
||||||
|
WEBHOOK_SECRET = config.WEBHOOK_SECRET
|
||||||
|
BASE_WEBHOOK_URL = config.BASE_WEBHOOK_URL
|
||||||
|
|
||||||
|
|
||||||
|
def download(link: str):
|
||||||
|
download_funcs = [
|
||||||
|
VideoInfo.service,
|
||||||
|
tikwm,
|
||||||
|
ttdownloader,
|
||||||
|
tikdown,
|
||||||
|
mdown,
|
||||||
|
snaptik,
|
||||||
|
Tikmate
|
||||||
|
]
|
||||||
|
|
||||||
|
for download_func in download_funcs:
|
||||||
|
try:
|
||||||
|
d = download_func(link)
|
||||||
|
if d:
|
||||||
|
data = d[0].download()
|
||||||
|
if data is not None:
|
||||||
|
return data
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message()
|
||||||
|
async def message_handler(message: types.Message):
|
||||||
|
if message.text is None:
|
||||||
|
await message.answer('This is not a text message')
|
||||||
|
return
|
||||||
|
|
||||||
|
tiktok_link = re.search(r'https://vt.tiktok.com/.+/', message.text)
|
||||||
|
if tiktok_link is None:
|
||||||
|
await message.answer('This is not a TikTok link')
|
||||||
|
return
|
||||||
|
|
||||||
|
await message.answer('Downloading...')
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
data: BytesIO | BufferedWriter | None = await loop.run_in_executor(None, download, tiktok_link.group(0))
|
||||||
|
if data is None:
|
||||||
|
await message.answer('Failed to download the video')
|
||||||
|
return
|
||||||
|
|
||||||
|
filename = 'video.mp4'
|
||||||
|
|
||||||
|
if isinstance(data, bytes):
|
||||||
|
await message.answer_video(types.BufferedInputFile(data, filename=filename))
|
||||||
|
elif isinstance(data, BytesIO):
|
||||||
|
await message.answer_video(types.BufferedInputFile(data.getvalue(), filename=filename))
|
||||||
|
elif isinstance(data, BufferedWriter):
|
||||||
|
temp = BytesIO()
|
||||||
|
data.write(temp.getbuffer())
|
||||||
|
await message.answer_video(types.BufferedInputFile(temp.getvalue(), filename=filename))
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
bot = Bot(token=config.BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML))
|
||||||
|
|
||||||
|
app = web.Application()
|
||||||
|
webhook_requests_handler = SimpleRequestHandler(
|
||||||
|
dispatcher=dp,
|
||||||
|
bot=bot,
|
||||||
|
secret_token=WEBHOOK_SECRET,
|
||||||
|
)
|
||||||
|
|
||||||
|
webhook_requests_handler.register(app, path=WEBHOOK_PATH)
|
||||||
|
|
||||||
|
setup_application(app, dp, bot=bot)
|
||||||
|
|
||||||
|
web.run_app(app, host=WEB_SERVER_HOST, port=WEB_SERVER_PORT)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user