-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbot.py
More file actions
158 lines (133 loc) · 6.34 KB
/
Copy pathbot.py
File metadata and controls
158 lines (133 loc) · 6.34 KB
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
import os
import requests
import re
import sqlite3
from datetime import datetime
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import ApplicationBuilder, CommandHandler, CallbackQueryHandler, ContextTypes
# --- CONFIGURATION ---
TG_TOKEN = os.getenv('TG_TOKEN')
SOCIALDATA_API = os.getenv('SOCIALDATA_API')
current_data = {"username": "0x_nation"}
DB_PATH = '/app/data/bot_database.db'
# --- DATABASE LOGIC ---
def init_db():
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
# Create the table if it doesn't exist
c.execute('''CREATE TABLE IF NOT EXISTS stats_history
(username TEXT, timestamp DATETIME, following INTEGER, followers INTEGER, posts INTEGER)''')
# SAFETY CHECK: If you are upgrading from the 'verified' version, add the 'posts' column
c.execute("PRAGMA table_info(stats_history)")
columns = [column[1] for column in c.fetchall()]
if 'posts' not in columns:
try:
c.execute("ALTER TABLE stats_history ADD COLUMN posts INTEGER DEFAULT 0")
except sqlite3.OperationalError:
pass # Column already exists or table is empty
conn.commit()
conn.close()
def save_snapshot(username, following, followers, posts):
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
# Explicitly naming columns to avoid conflicts with old 'verified' data
c.execute("INSERT INTO stats_history (username, timestamp, following, followers, posts) VALUES (?, ?, ?, ?, ?)",
(username, datetime.now(), int(following), int(followers), int(posts)))
conn.commit()
conn.close()
def get_last_snapshot(username):
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
# Fetch the last 3 columns (following, followers, posts)
c.execute("SELECT following, followers, posts FROM stats_history WHERE username=? ORDER BY timestamp DESC LIMIT 1 OFFSET 1", (username,))
row = c.fetchone()
conn.close()
return row
# --- UTILS ---
def escape_md(text):
"""Deep escape for MarkdownV2."""
return re.sub(r'([_*\[\]()~`>#+\-=|{}.!])', r'\\\1', str(text))
def get_menu_keyboard():
return InlineKeyboardMarkup([
[InlineKeyboardButton("📊 Analyse Profile", callback_data='analyse')],
[InlineKeyboardButton("⚙️ Set Target User", callback_data='how_to_change')]
])
# --- HANDLERS ---
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
user = escape_md(current_data["username"])
await update.message.reply_text(
f"📊 *Dashboard for @{user}*",
reply_markup=get_menu_keyboard(),
parse_mode='MarkdownV2'
)
async def set_user_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not context.args:
await update.message.reply_text("❌ Usage: `/setuser username`", parse_mode='MarkdownV2')
return
new_name = context.args[0].replace('@', '').strip()
current_data["username"] = new_name
await update.message.reply_text(
f"✅ Target changed to *@{escape_md(new_name)}*",
reply_markup=get_menu_keyboard(),
parse_mode='MarkdownV2'
)
async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
await query.answer()
user_raw = current_data["username"]
user_esc = escape_md(user_raw)
if query.data == 'how_to_change':
await query.edit_message_text(
f"📝 *To change the target:*\nType `/setuser username`",
reply_markup=get_menu_keyboard(),
parse_mode='MarkdownV2'
)
return
if query.data == 'menu':
await query.edit_message_text(f"📊 *Dashboard for @{user_esc}*", reply_markup=get_menu_keyboard(), parse_mode='MarkdownV2')
return
if query.data == 'analyse':
await query.edit_message_text(f"📡 *Analysing @{user_esc}\.\.\.*", parse_mode='MarkdownV2')
try:
res = requests.get(f"https://api.socialdata.tools/twitter/user/{user_raw}",
headers={"Authorization": f"Bearer {SOCIALDATA_API}"})
data = res.json()
# Extract numbers
# Note: 'statuses_count' is the standard X API field for total posts
f_cur = data.get('friends_count') or data.get('public_metrics', {}).get('following_count', 0)
fl_cur = data.get('followers_count') or data.get('public_metrics', {}).get('followers_count', 0)
p_cur = data.get('statuses_count') or data.get('public_metrics', {}).get('tweet_count', 0)
# Ensure they are integers
f_cur = int(f_cur) if not isinstance(f_cur, bool) else 0
fl_cur = int(fl_cur) if not isinstance(fl_cur, bool) else 0
p_cur = int(p_cur) if not isinstance(p_cur, bool) else 0
save_snapshot(user_raw, f_cur, fl_cur, p_cur)
prev = get_last_snapshot(user_raw)
def diff_fmt(cur, old):
d = cur - old
if d > 0: return escape_md(f" (+{d})")
if d < 0: return escape_md(f" ({d})")
return ""
date_str = escape_md(datetime.now().strftime('%Y-%m-%d %H:%M'))
sep = escape_md("───────────────")
report = (
f"👤 *Profile:* @{user_esc}\n"
f"📅 *Update:* {date_str}\n"
f"{sep}\n"
f"📈 *Following:* `{f_cur}`{diff_fmt(f_cur, prev[0]) if prev else ''}\n"
f"👥 *Followers:* `{fl_cur}`{diff_fmt(fl_cur, prev[1]) if prev else ''}\n"
f"📝 *Total Posts:* `{p_cur}`{diff_fmt(p_cur, prev[2]) if prev else ''}\n"
f"{sep}\n"
f"ℹ️ _Tracking changes in posts and audience\._"
)
await query.edit_message_text(text=report, reply_markup=get_menu_keyboard(), parse_mode='MarkdownV2')
except Exception as e:
await query.edit_message_text(f"❌ Error: {escape_md(str(e))}", parse_mode='MarkdownV2')
if __name__ == '__main__':
init_db()
app = ApplicationBuilder().token(TG_TOKEN).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("setuser", set_user_command))
app.add_handler(CallbackQueryHandler(handle_callback))
app.run_polling()