-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbot.py
More file actions
199 lines (159 loc) · 6.17 KB
/
bot.py
File metadata and controls
199 lines (159 loc) · 6.17 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
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
# bot.py — Your Telegram Bot
# Uses only Python's standard library. Zero external packages.
import urllib.request
import urllib.parse
import json
import time
import os
# --- LOAD SECRETS FROM .env FILE ---
def load_env(filepath=".env"):
"""
Reads a .env file and loads each KEY=VALUE pair into the
environment (os.environ), so the rest of our code can access
them with os.environ.get() just like any other env variable.
This is what packages like 'python-dotenv' do internally —
we're just doing it ourselves to avoid needing pip.
We skip blank lines and lines starting with # (comments).
"""
try:
with open(filepath, "r") as f:
for line in f:
line = line.strip()
# Skip empty lines and comment lines
if not line or line.startswith("#"):
continue
# Split on the FIRST '=' only, so values can contain '=' safely
if "=" in line:
key, value = line.split("=", 1)
key = key.strip()
value = value.strip()
# Only set the variable if it isn't already set by the system.
# This means Railway's dashboard variables take priority over
# the .env file, which is exactly the behavior we want.
if key and not os.environ.get(key):
os.environ[key] = value
except FileNotFoundError:
# On Railway, there's no .env file — that's totally fine.
# Railway injects variables directly into the environment.
pass
# Load the .env file FIRST, before reading any variables
load_env()
# --- CONFIGURATION ---
# Now TOKEN is safely read from the environment.
# It comes from .env when running locally, and from Railway's
# Variables dashboard when running in the cloud.
TOKEN = os.environ.get("BOT_TOKEN", "")
BASE_URL = f"https://api.telegram.org/bot{TOKEN}"
# --- CORE FUNCTION: Talk to Telegram ---
def make_request(method, params=None):
"""
The engine of the bot. Builds and sends an HTTP request to
Telegram's API and returns the parsed JSON response.
"""
url = f"{BASE_URL}/{method}"
if params:
data = urllib.parse.urlencode(params).encode("utf-8")
req = urllib.request.Request(url, data=data)
else:
req = urllib.request.Request(url)
with urllib.request.urlopen(req, timeout=35) as response:
result = json.loads(response.read().decode("utf-8"))
return result
# --- SEND A MESSAGE ---
def send_message(chat_id, text):
"""Sends a text message to a specific chat ID."""
return make_request("sendMessage", {
"chat_id": chat_id,
"text": text,
"parse_mode": "HTML"
})
# --- GET NEW MESSAGES ---
def get_updates(offset=None):
"""
Asks Telegram for new messages using long polling.
'timeout=30' means Telegram holds the connection open for up to
30 seconds if there are no new messages, which is efficient
compared to constantly hammering the API every second.
"""
params = {"timeout": 30}
if offset is not None:
params["offset"] = offset
return make_request("getUpdates", params)
# --- HANDLE INCOMING MESSAGES ---
def handle_message(message):
"""
Defines your bot's behavior. Every incoming message is routed here.
Add or modify commands by adding elif blocks below.
"""
chat_id = message["chat"]["id"]
text = message.get("text", "")
user_name = message["from"].get("first_name", "friend")
if text == "/start":
send_message(chat_id,
f"👋 Hello, <b>{user_name}</b>! I'm your first Telegram bot.\n\n"
"Running on Railway, deployed from GitHub, coded in Termux. 🚀\n\n"
"Type /help to see what I can do."
)
elif text == "/help":
send_message(chat_id,
"🤖 <b>Available Commands:</b>\n\n"
"/start — Greet me\n"
"/help — Show this help message\n"
"/echo <text> — I'll repeat what you say\n"
"/about — Learn about this bot\n\n"
"Or just send me any message and I'll reply!"
)
elif text.startswith("/echo"):
echo_text = text[5:].strip()
if echo_text:
send_message(chat_id, f"🔁 {echo_text}")
else:
send_message(chat_id, "Please write something after /echo\nExample: /echo Hello world")
elif text == "/about":
send_message(chat_id,
"ℹ️ <b>About This Bot</b>\n\n"
"Built with: Python (stdlib only)\n"
"Coded on: Termux (Android)\n"
"Code hosted on: GitHub\n"
"Running on: Railway\n\n"
"Zero external packages. Pure Python. 💪"
)
elif text.startswith("/"):
send_message(chat_id, f"❓ Unknown command: {text}\nType /help to see what I can do.")
else:
send_message(chat_id,
f"You said: <i>{text}</i>\n\n"
"Type /help to see what I can do! 😊"
)
# --- MAIN LOOP ---
def main():
"""
Runs the bot forever. Continuously polls Telegram for new
messages and hands each one to handle_message().
"""
if not TOKEN:
print("❌ ERROR: BOT_TOKEN is not set!")
print(" For local use: add BOT_TOKEN=your_token to your .env file")
print(" For Railway: add BOT_TOKEN in the Variables tab")
return
print("✅ Bot is starting...")
print("🔄 Listening for messages. Press Ctrl+C to stop.\n")
offset = None
while True:
try:
response = get_updates(offset)
updates = response.get("result", [])
for update in updates:
offset = update["update_id"] + 1
if "message" in update:
handle_message(update["message"])
print(f"✉️ Processed update ID: {update['update_id']}")
except KeyboardInterrupt:
print("\n🛑 Bot stopped.")
break
except Exception as e:
print(f"⚠️ Error: {e}")
print(" Retrying in 5 seconds...")
time.sleep(5)
if __name__ == "__main__":
main()