Spaces:
Paused
Paused
File size: 5,876 Bytes
61d6e2a de342dc 61d6e2a de342dc 2e79f82 65651b1 2e79f82 61d6e2a 2e79f82 de342dc 61d6e2a 2e79f82 848377f 61d6e2a 2e79f82 848377f 109b8ca 2e79f82 b2a4cfc 2e79f82 61d6e2a 848377f 61d6e2a 65651b1 849f335 61d6e2a 65651b1 61d6e2a de342dc 61d6e2a de342dc 2e79f82 de342dc 61d6e2a de342dc 849f335 2e79f82 de342dc 61d6e2a de342dc 2e79f82 61d6e2a de342dc 61d6e2a 2e79f82 de342dc 61d6e2a 19099ec | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | """Channel Post Bot - entry point. Run: python bot.py"""
from __future__ import annotations
import asyncio
import logging
import os
from urllib.parse import unquote, urlsplit
from telethon import TelegramClient, functions, types
from telethon.sessions import StringSession
from app.common import Ctx
from app.config import Config, load_config
from app.db import Database
from app.errorlog import ErrorReporter, TelegramLogHandler
from app.expiry import Expirer
from app.handlers import (
autopost,
backup,
basic,
broadcast,
channels,
checkbtn,
create,
masslinks,
posts,
replace,
repost,
shift,
sync,
)
from app.health import start_health_server
from app.syncer import Syncer
from app.userbot import Userbot
log = logging.getLogger("bot")
COMMANDS = [
("new", "Create a post"),
("posts", "My posts and drafts"),
("autopost", "Post a series of episode download buttons"),
("formatting", "How to format text: bold, links, quotes ... (HTML / Markdown)"),
("addchannel", "Register a channel"),
("channels", "List channels"),
("broadcast", "Send one message to all channels, optionally timed (owner)"),
("replace", "Replace a post's media and caption (owner)"),
("swap", "Swap a username in links (owner)"),
("handleswap", "Swap a @username written in posts (owner)"),
("repost", "Copy a whole channel in order (owner)"),
("shift", "Copy posts from one channel to another (owner)"),
("sync", "Compare channels with My posts now (owner)"),
("undo", "Undo the last replace (owner)"),
("userbot", "Status of the helper account that deletes and edits what the bot can't (owner)"),
("export", "Backup as JSON (owner)"),
("import", "Restore a backup made by /export (owner)"),
("admins", "List admins (owner)"),
("addadmin", "Add an admin by numeric ID (owner)"),
("removeadmin", "Remove an admin by numeric ID (owner)"),
("cancel", "Cancel what I'm doing"),
("help", "Show help"),
]
def build_client(cfg: Config) -> TelegramClient:
if cfg.session_string:
session = StringSession(cfg.session_string)
else:
os.makedirs(os.path.dirname(cfg.session_path) or ".", exist_ok=True)
session = cfg.session_path
client = TelegramClient(session, cfg.api_id, cfg.api_hash)
client.parse_mode = "html"
return client
def register_all(ctx: Ctx) -> None:
for module in (basic, backup, channels, create, posts, masslinks, autopost, broadcast, checkbtn, replace, repost, shift, sync):
module.register(ctx)
async def set_commands(client: TelegramClient) -> None:
try:
await client(
functions.bots.SetBotCommandsRequest(
scope=types.BotCommandScopeDefault(),
lang_code="",
commands=[types.BotCommand(command=c, description=d) for c, d in COMMANDS],
)
)
except Exception as e: # cosmetic only
log.warning("could not set the command menu: %s", e)
def secrets_of(cfg: Config) -> list:
"""Texts that must never appear in the error log."""
out = [cfg.bot_token, cfg.api_hash, cfg.session_string, cfg.userbot_session]
try:
parts = urlsplit(cfg.database_url)
if parts.password:
out += [parts.password, unquote(parts.password)]
except Exception:
pass
return [s for s in out if s]
async def main() -> None:
cfg = load_config()
logging.basicConfig(level=cfg.log_level, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
logging.getLogger("telethon").setLevel(logging.WARNING)
client = build_client(cfg)
reporter = ErrorReporter(client, cfg.error_log_chat_id, bot_token=cfg.bot_token, secrets=secrets_of(cfg))
reporter.start()
logging.getLogger().addHandler(TelegramLogHandler(reporter))
asyncio.get_running_loop().set_exception_handler(reporter.loop_exception_handler)
db = None
ctx = None
userbot = Userbot(client, cfg)
try:
db = Database(cfg.database_url, cfg.db_pool)
await db.init()
ctx = Ctx(cfg=cfg, db=db, client=client, userbot=userbot, reporter=reporter)
ctx.admins = await db.list_admins()
ctx.expirer = Expirer(ctx)
ctx.syncer = Syncer(ctx)
register_all(ctx)
await client.start(bot_token=cfg.bot_token)
me = await client.get_me()
log.info("Logged in as @%s (owners: %s)", me.username, ", ".join(map(str, sorted(cfg.owners))))
await set_commands(client)
if cfg.port:
await start_health_server(cfg.port)
problem = await reporter.check()
if reporter.enabled:
if problem:
log.warning("errors cannot be sent to chat %s yet: %s", cfg.error_log_chat_id, problem)
else:
log.info("errors are sent to chat %s", cfg.error_log_chat_id)
else:
log.info("error log chat: off")
if userbot.enabled:
await userbot.connect() # never fatal: the userbot is only needed for old posts
ctx.expirer.start() # deletes timed broadcasts when their time is up
ctx.syncer.start() # compares the channels with My posts now and then (and notices changes made meanwhile)
await client.run_until_disconnected()
except Exception as e:
await reporter.report_fatal("The bot stopped because of an unhandled exception", e)
raise
finally:
if ctx is not None and ctx.syncer is not None:
await ctx.syncer.stop()
if ctx is not None and ctx.expirer is not None:
await ctx.expirer.stop()
await userbot.close()
if db is not None:
await db.close()
await reporter.stop()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
pass |