File size: 10,989 Bytes
a924d18
 
 
 
 
 
 
 
9a3d0db
 
a924d18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8ed08c9
 
 
 
 
fe1a797
a924d18
 
 
 
 
 
 
 
 
 
 
 
 
 
fe1a797
a924d18
 
7e893e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75a85b3
 
 
7e893e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
700070c
 
 
 
 
 
 
 
 
 
 
 
 
acd6d45
 
7c67b0d
acd6d45
 
700070c
 
7c67b0d
700070c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
acd6d45
700070c
 
 
 
 
 
 
 
3cecbba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e893e0
 
 
 
 
a924d18
 
 
 
 
 
 
 
 
 
 
 
2f65926
a924d18
 
 
2f65926
a924d18
 
 
 
 
 
 
 
 
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
import os
import nest_asyncio
import logging
from flask import Flask
from threading import Thread
from pymongo import MongoClient
from telethon import TelegramClient, events



# Enable nested event loops for Flask + Telethon
nest_asyncio.apply()
logging.basicConfig(level=logging.INFO)

# === ENVIRONMENT VARIABLES ===
API_ID = int(os.getenv("API_ID", "3704772"))
API_HASH = os.getenv("API_HASH", "b8e50a035abb851c0dd424e14cac4c06")
BOT_TOKEN = os.getenv("TELEGRAM_TOKEN")  # Bot token, NOT user session
OWNER_ID = int(os.getenv("OWNER_ID"))
MONGO_URI = os.getenv("MONGO_URI")
PORT = int(os.getenv("PORT", 7860))

# === MongoDB ===
client = MongoClient(MONGO_URI)
db = client.teleg4am_reelssss
a_raw = db.raw_links
a_reacted = db.reacted_links
last_alerts = {"raw": -1, "reacted": -1}
# Add indexes to improve performance (run only once on startup)
a_raw.create_index([("link", 1)], unique=True)
a_raw.create_index([("used", 1)])
a_reacted.create_index([("link", 1)], unique=True)
a_reacted.create_index([("used", 1)])
from telethon.sessions import StringSession
# === Flask App ===
app = Flask(__name__)

@app.route("/")
def home():
    return "πŸ€– Bot is running!"

@app.route("/status")
def status():
    raw = a_raw.count_documents({"used": False})
    reacted = a_reacted.count_documents({"used": False})
    return {"raw_links_left": raw, "reacted_links_left": reacted}, 200

# === Telethon Bot Setup (Bot Mode) ===
bot = TelegramClient(StringSession(), API_ID, API_HASH).start(bot_token=BOT_TOKEN)
user_context = {}

# === Helper: Check if sender is owner ===
def is_owner(event):
    return event.sender_id == OWNER_ID

# === Command Handlers ===
@bot.on(events.NewMessage(pattern="/start|/count"))
async def count_handler(event):
    if not is_owner(event): return
    raw = a_raw.count_documents({"used": False})
    reacted = a_reacted.count_documents({"used": False})
    await event.reply(f"πŸ“Š Raw: {raw}\nπŸ“Š Reacted: {reacted}")

@bot.on(events.NewMessage(pattern="/raw"))
async def raw_handler(event):
    if not is_owner(event): return
    user_context[event.sender_id] = {"mode": "raw", "links": []}
    await event.reply("πŸ“₯ Send raw links (one per line). Use /done to save.")

@bot.on(events.NewMessage(pattern="/reacted"))
async def reacted_handler(event):
    if not is_owner(event): return
    user_context[event.sender_id] = {"mode": "reacted", "links": []}
    await event.reply("πŸ“₯ Send reacted links (one per line). Use /done to save.")

@bot.on(events.NewMessage(pattern="/done"))
async def done_handler(event):
    if not is_owner(event): return
    ctx = user_context.pop(event.sender_id, None)
    if not ctx or not ctx["links"]:
        return await event.reply("❌ Nothing to save.")
    
    col = a_raw if ctx["mode"] == "raw" else a_reacted
    inserted = 0
    for link in ctx["links"]:
        if col.count_documents({"link": link}) == 0:
            col.insert_one({"link": link, "used": False})
            inserted += 1

    await event.reply(f"βœ… Saved {inserted} new {ctx['mode']} link(s).")
    await check_and_alert(ctx["mode"], col)

@bot.on(events.NewMessage(func=lambda e: e.text and e.sender_id in user_context))
async def link_collector(event):
    if not is_owner(event): return
    ctx = user_context[event.sender_id]
    if ctx["mode"] not in ["raw", "reacted"]:
        return  # Skip if it's correction-used or something else

    links = [l.strip() for l in event.text.split() if l.startswith("http")]
    ctx["links"].extend(links)
    await event.reply(f"πŸ”— {len(links)} link(s) added.")

# === Alert System ===
async def check_and_alert(pool: str, col):
    left = col.count_documents({"used": False})
    if 1 <= left <= 5 and last_alerts[pool] != left:
        last_alerts[pool] = left
        try:
            await bot.send_message(OWNER_ID, f"⚠️ Only {left} {pool} link(s) left!")
        except Exception as e:
            logging.error(f"❌ Alert failed: {e}")
import os
import asyncio
import subprocess
from telethon import TelegramClient, events
from telethon.tl.types import DocumentAttributeFilename

@bot.on(events.NewMessage(pattern="/bash"))
async def bash(event):
    cmd = event.message.text.split(None, 1)
    if len(cmd) < 2:
        return await event.reply("❌ Usage: /bash <command>")
    process = await asyncio.create_subprocess_shell(
        cmd[1],
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    stdout, stderr = await process.communicate()

    output = stdout.decode() or "βœ… No output"
    error = stderr.decode()

    await event.reply(f"πŸ“₯ Code:\n`{cmd[1]}`\n\nπŸ“€ Output:\n```\n{output.strip()}\n```"
                      + (f"\n❌ Error:\n```\n{error.strip()}\n```" if error else ""))

@bot.on(events.NewMessage(pattern="/dl"))
async def dl(event):
    reply = await event.get_reply_message()
    if not reply or not reply.media:
        return await event.reply("❌ Reply to a media file to download it.")
    
    path = await bot.download_media(reply)
    await event.reply(f"βœ… File downloaded to `{path}`")

@bot.on(events.NewMessage(pattern="/ul"))
async def ul(event):
    args = event.raw_text.split(maxsplit=1)
    if len(args) != 2:
        return await event.reply("❌ Usage: /ul <full_filename>")

    filepath = args[1].strip()
    if not os.path.exists(filepath):
        return await event.reply("❌ File not found.")

    await bot.send_file(event.chat_id, filepath, caption=f"πŸ“€ Uploaded: `{filepath}`")

@bot.on(events.NewMessage(pattern=r'^/doc(?:\s+(.+))?'))
async def handler_doc(event):
    reply = await event.get_reply_message()
    filename = event.pattern_match.group(1)

    if not reply or not reply.text:
        await event.reply("❌ Please reply to a text message to save as a file.")
        return

    if not filename:
        await event.reply("❌ Usage: `/doc filename.txt`", parse_mode='md')
        return

    try:
        with open(filename, "w", encoding="utf-8") as f:
            f.write(reply.text)

        await bot.send_file(event.chat_id, filename, caption=f"βœ… Saved reply to `{filename}`", force_document=True)
        os.remove(filename)
    except Exception as e:
        await event.reply(f"❌ Error: {e}")
user_context = {}  # make sure this is defined once globally


@bot.on(events.NewMessage(pattern="/cused"))
async def correction_used_handler(event):
    if not is_owner(event): return
    user_context[event.sender_id] = {"mode": "correct_used", "links": []}
    await event.reply("πŸ›  Correction mode ON for used links.\nSend all links (one per line). Use /doneused when finished.")


@bot.on(events.NewMessage(func=lambda e: e.text and e.sender_id in user_context and not e.raw_text.startswith("/doneused")))
async def collect_links(event):
    if not is_owner(event): return
    ctx = user_context.get(event.sender_id)
    if ctx and ctx.get("mode") == "correct_used":
        links = [l.strip() for l in event.text.split() if l.startswith("http")]
        if links:
            ctx["links"].extend(links)
            await event.reply(f"πŸ”— Collected {len(links)} link(s). Send more or use /dused.")
        else:
            await event.reply("⚠️ No valid links detected. Please send links starting with http.")


@bot.on(events.NewMessage(pattern="/dused"))
async def process_correction(event):
    if not is_owner(event): return
    ctx = user_context.get(event.sender_id)
    if not ctx or ctx.get("mode") != "correct_used":
        return await event.reply("⚠️ Not in correction-used mode. Use /cused first.")

    links = ctx.get("links", [])
    updated_raw = updated_reacted = skipped = not_found = 0

    for link in links:
        found = False

        doc_raw = a_raw.find_one({"link": link})
        if doc_raw:
            found = True
            if doc_raw.get("used") is True:
                a_raw.update_one({"_id": doc_raw["_id"]}, {"$set": {"used": False}})
                updated_raw += 1
            else:
                skipped += 1

        doc_reacted = a_reacted.find_one({"link": link})
        if doc_reacted:
            found = True
            if doc_reacted.get("used") is True:
                a_reacted.update_one({"_id": doc_reacted["_id"]}, {"$set": {"used": False}})
                updated_reacted += 1
            else:
                skipped += 1

        if not found:
            not_found += 1

    del user_context[event.sender_id]  # Clear mode after done

    await event.reply(
        f"βœ… Done processing {len(links)} link(s).\n\n"
        f"πŸ“¦ Raw updated: {updated_raw}\n"
        f"🎞 Reacted updated: {updated_reacted}\n"
        f"⏭ Skipped (already unused): {skipped}\n"
        f"❌ Not found in both: {not_found}"
    )
@bot.on(events.NewMessage(pattern="/help"))
async def help_handler(event):
    if not is_owner(event): return
    await event.reply(
        "**πŸ€– Available Commands:**\n\n"
        "**πŸ“Š Status & Counts**\n"
        "`/start` β€” Show total raw/reacted links left\n"
        "`/count` β€” Same as /start (alias)\n"
        "`/status` β€” (Web endpoint) Get JSON of links left\n\n"

        "**πŸ“₯ Add Links**\n"
        "`/raw` β€” Start adding raw links\n"
        "`/reacted` β€” Start adding reacted links\n"
        "`/done` β€” Save links after sending\n\n"

        "**πŸ›  Correct Used Flags**\n"
        "`/cused` β€” Start correction for used links\n"
        "`/dused` β€” Finalize correction and update DB\n\n"

        "**πŸ’» File & Shell Tools**\n"
        "`/bash <command>` β€” Run a terminal command\n"
        "`/dl` β€” Download replied media\n"
        "`/ul <filename>` β€” Upload a file from disk\n"
        "`/doc <filename>` β€” Save replied text to file and upload\n\n"

        "**ℹ️ Notes:**\n"
        "- Only you (owner) can use these commands\n"
        "- All links must start with `http`\n"
        "- You can send multiple links in one message\n"
        "- Correction only flips `used: True β†’ False`, not adding\n",
        parse_mode='md'
    )
    
def run_flask():
    app.run(host="0.0.0.0", port=PORT)

import asyncio

async def auto_alert_loop():
    while True:
        try:
            await check_and_alert("raw", a_raw)
            await check_and_alert("reacted", a_reacted)
        except Exception as e:
            logging.error(f"Auto-alert loop error: {e}")
        await asyncio.sleep(30)  # Check every 30 seconds

async def main():
    if not all([API_ID, API_HASH, BOT_TOKEN, MONGO_URI]):
        raise RuntimeError("❌ Missing ENV vars: API_ID, API_HASH, TELEGRAM_TOKEN, or MONGO_URI")
    Thread(target=run_flask).start()
    # Start background alert loop
    asyncio.create_task(auto_alert_loop())

    # Start Flask in a separate thread (or you can replace with FastAPI if needed

    # Start the bot
    print("βœ… Starting bot...")
    await bot.start(bot_token=BOT_TOKEN)
    await bot.run_until_disconnected()

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())