Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
PASSWORD=somepassword
STREAMLIT_SERVER_PORT=8080
STREAMLIT_SERVER_ADDRESS=0.0.0.0
STREAMLIT_SERVER_HEADLESS=true
STREAMLIT_BROWSER_GATHER_USAGE_STATS=false
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
FROM python:3.10
ENV VENV_PATH="/venv"
ENV PATH="$VENV_PATH/bin:$PATH"
ENV STREAMLIT_SERVER_PORT=8080
ENV STREAMLIT_SERVER_ADDRESS=0.0.0.0
WORKDIR /app
RUN apt-get update && \
apt-get install -y --no-install-recommends apt-utils && \
Expand All @@ -13,5 +15,5 @@ COPY . .
RUN poetry build && \
/venv/bin/pip install --upgrade pip wheel setuptools &&\
/venv/bin/pip install dist/*.whl
EXPOSE 8501
CMD tgcf-web
EXPOSE 8080
CMD ["tgcf-web"]
19 changes: 19 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
services:
tgcf:
build:
context: .
dockerfile: Dockerfile
container_name: tgcf-web
ports:
- "8501:8080"
volumes:
- ./tgcf.config.json:/app/tgcf.config.json
- ./data:/app/data
- ./.env:/app/.env:ro
restart: unless-stopped
networks:
- tgcf-network

networks:
tgcf-network:
driver: bridge
1 change: 1 addition & 0 deletions test_tgcf.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"pid": 31, "theme": "light", "login": {"API_ID": 0, "API_HASH": "", "user_type": 0, "phone_no": 91, "USERNAME": "", "SESSION_STRING": "", "BOT_TOKEN": ""}, "admins": [], "forwards": [], "show_forwarded_from": false, "mode": 0, "live": {"sequential_updates": false, "delete_sync": false, "delete_on_edit": ".deleteMe"}, "past": {"delay": 0}, "auto_run": true, "plugins": {"filter": {"check": false, "users": {"blacklist": [], "whitelist": []}, "files": {"blacklist": [], "whitelist": []}, "text": {"blacklist": [], "whitelist": [], "case_sensitive": false, "regex": false}}, "fmt": {"check": false, "style": "preserve"}, "mark": {"check": false, "image": "image.png", "position": "bottom_right", "frame_rate": 15}, "ocr": {"check": false}, "replace": {"check": false, "text": {}, "text_raw": "", "regex": false}, "caption": {"check": false, "header": "", "footer": ""}, "sender": {"check": false, "user_type": 0, "BOT_TOKEN": "", "SESSION_STRING": ""}}, "bot_messages": {"start": "Hi! I am alive", "bot_help": "For details visit github.com/aahnik/tgcf"}}
1 change: 1 addition & 0 deletions tgcf/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class Config(BaseModel):
mode: int = 0 # 0: live, 1:past
live: LiveSettings = LiveSettings()
past: PastSettings = PastSettings()
auto_run: bool = False # Auto-start tgcf on web UI startup

plugins: PluginConfig = PluginConfig()
bot_messages = BotMessages()
Expand Down
25 changes: 22 additions & 3 deletions tgcf/live.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""The module responsible for operating tgcf in live mode."""

import asyncio
import logging
import os
import sys
from typing import Union

from telethon import TelegramClient, events, functions, types
from telethon.errors.rpcerrorlist import FloodWaitError
from telethon.sessions import StringSession
from telethon.tl.custom.message import Message

Expand Down Expand Up @@ -50,8 +52,17 @@ async def new_message_handler(event: Union[Message, events.NewMessage]) -> None:
for d in dest:
if event.is_reply and r_event_uid in st.stored:
tm.reply_to = st.stored.get(r_event_uid).get(d)
fwded_msg = await send_message(d, tm)
st.stored[event_uid].update({d: fwded_msg})
try:
fwded_msg = await send_message(d, tm)
st.stored[event_uid].update({d: fwded_msg})
except FloodWaitError as fwe:
logging.info(f"Sleeping for {fwe} on forward_messages")
await asyncio.sleep(delay=fwe.seconds)
# Retry after flood wait
fwded_msg = await send_message(d, tm)
st.stored[event_uid].update({d: fwded_msg})
except Exception as err:
logging.exception(f"Failed to forward message to {d}: {err}")
tm.clear()


Expand Down Expand Up @@ -87,7 +98,15 @@ async def edited_message_handler(event) -> None:
dest = config.from_to.get(chat_id)

for d in dest:
await send_message(d, tm)
try:
await send_message(d, tm)
except FloodWaitError as fwe:
logging.info(f"Sleeping for {fwe} on forward_messages")
await asyncio.sleep(delay=fwe.seconds)
# Retry after flood wait
await send_message(d, tm)
except Exception as err:
logging.exception(f"Failed to forward edited message to {d}: {err}")
tm.clear()


Expand Down
1 change: 1 addition & 0 deletions tgcf/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ async def send_message(recipient: EntityLike, tm: "TgcfMessage") -> Message:
client: TelegramClient = tm.client
if CONFIG.show_forwarded_from:
return await client.forward_messages(recipient, tm.message)

if tm.new_file:
message = await client.send_file(
recipient, tm.new_file, caption=tm.text, reply_to=tm.reply_to
Expand Down
22 changes: 22 additions & 0 deletions tgcf/web_ui/0_👋_Hello.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os
import signal
import streamlit as st

from tgcf.web_ui.utils import hide_st, switch_theme
Expand All @@ -13,6 +15,26 @@
switch_theme(st,CONFIG)
st.write("# Welcome to tgcf 👋")

# Check actual service status
def get_service_status():
if CONFIG.pid != 0:
try:
os.kill(CONFIG.pid, signal.SIGCONT)
return "running"
except Exception:
return "stopped"
return "stopped"

service_status = get_service_status()

# Service status indicator
if service_status == "running":
st.success(f"✅ tgcf is running (PID: {CONFIG.pid})")
elif CONFIG.auto_run:
st.warning("⚠️ Auto-start is enabled but tgcf is not running")
else:
st.info("ℹ️ tgcf is not running - Click the Run button to start")

html = """
<p align="center">
<img src = "https://user-images.githubusercontent.com/66209958/115183360-3fa4d500-a0f9-11eb-9c0f-c5ed03a9ae17.png" alt = "tgcf logo" width=120>
Expand Down
52 changes: 52 additions & 0 deletions tgcf/web_ui/auto_start.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Auto-start utilities for tgcf web UI."""

import os
import signal
import subprocess
import time

import streamlit as st

from tgcf.config import CONFIG, write_config


def auto_start_tgcf():
"""Auto-start tgcf if configured and not already running."""
if CONFIG.auto_run and CONFIG.pid == 0:
mode = "live" if CONFIG.mode == 0 else "past"
try:
with open("logs.txt", "w") as logs:
process = subprocess.Popen(
["tgcf", "--loud", mode],
stdout=logs,
stderr=subprocess.STDOUT,
)
CONFIG.pid = process.pid
write_config(CONFIG)
st.success(f"Auto-started tgcf in {mode} mode!")
time.sleep(2)
return True
except Exception as err:
st.error(f"Failed to auto-start tgcf: {err}")
return False
return False


def check_and_auto_start():
"""Check if tgcf should auto-start and trigger it."""
if CONFIG.auto_run:
if CONFIG.pid == 0:
st.info("Auto-start is enabled. Starting tgcf...")
return auto_start_tgcf()
else:
st.info("tgcf is already running (auto-start was previously triggered)")
# Check if process is still alive
try:
os.kill(CONFIG.pid, signal.SIGCONT)
st.success("tgcf is running successfully")
except Exception:
st.warning("Previous tgcf process died. Auto-starting...")
CONFIG.pid = 0
write_config(CONFIG)
return auto_start_tgcf()
return False
9 changes: 7 additions & 2 deletions tgcf/web_ui/pages/5_🏃_Run.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from tgcf.config import CONFIG, read_config, write_config
from tgcf.web_ui.password import check_password
from tgcf.web_ui.utils import hide_st, switch_theme
from tgcf.web_ui.auto_start import auto_start_tgcf

CONFIG = read_config()

Expand Down Expand Up @@ -37,6 +38,10 @@ def termination():
CONFIG.show_forwarded_from = st.checkbox(
"Show 'Forwarded from'", value=CONFIG.show_forwarded_from
)
CONFIG.auto_run = st.checkbox(
"Auto-start on service startup", value=CONFIG.auto_run,
help="Automatically start tgcf when the web UI starts, without needing to click the Run button"
)
mode = st.radio("Choose mode", ["live", "past"], index=CONFIG.mode)
if mode == "past":
CONFIG.mode = 1
Expand Down Expand Up @@ -73,7 +78,7 @@ def termination():
CONFIG.pid = 0
write_config(CONFIG)
time.sleep(1)
st.experimental_rerun()
st.rerun()

stop = st.button("Stop", type="primary")
if stop:
Expand All @@ -100,7 +105,7 @@ def termination():
write_config(CONFIG)
time.sleep(2)

st.experimental_rerun()
st.rerun()

try:
lines = st.slider(
Expand Down
37 changes: 35 additions & 2 deletions tgcf/web_ui/run.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,46 @@
import os
import signal
import subprocess
import time
from importlib import resources

import tgcf.web_ui as wu
from tgcf.config import CONFIG
from tgcf.config import CONFIG, write_config

package_dir = resources.path(package=wu, resource="").__enter__()
package_dir = resources.path(wu, "").__enter__()

def check_and_auto_start_service():
if CONFIG.auto_run and CONFIG.pid == 0:
mode = "live" if CONFIG.mode == 0 else "past"
print(f"Auto-starting tgcf in {mode} mode...")
try:
with open("logs.txt", "w") as logs:
process = subprocess.Popen(
["tgcf", "--loud", mode],
stdout=logs,
stderr=subprocess.STDOUT,
)
CONFIG.pid = process.pid
write_config(CONFIG)
print(f"Successfully auto-started tgcf in {mode} mode with PID {process.pid}")
return True
except Exception as err:
print(f"Failed to auto-start tgcf: {err}")
return False
elif CONFIG.auto_run and CONFIG.pid != 0:
try:
os.kill(CONFIG.pid, signal.SIGCONT)
print(f"tgcf is already running with PID {CONFIG.pid}")
except Exception:
print("Previous tgcf process died. Auto-starting...")
CONFIG.pid = 0
write_config(CONFIG)
return check_and_auto_start_service()
return False

def main():
print(package_dir)
check_and_auto_start_service()
path = os.path.join(package_dir, "0_👋_Hello.py")
os.environ["STREAMLIT_THEME_BASE"] = CONFIG.theme
os.environ["STREAMLIT_BROWSER_GATHER_USAGE_STATS"] = "false"
Expand Down