claude-telegram-mirror 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +331 -0
- package/dist/bot/commands.d.ts +41 -0
- package/dist/bot/commands.d.ts.map +1 -0
- package/dist/bot/commands.js +231 -0
- package/dist/bot/commands.js.map +1 -0
- package/dist/bot/formatting.d.ts +62 -0
- package/dist/bot/formatting.d.ts.map +1 -0
- package/dist/bot/formatting.js +295 -0
- package/dist/bot/formatting.js.map +1 -0
- package/dist/bot/telegram.d.ts +93 -0
- package/dist/bot/telegram.d.ts.map +1 -0
- package/dist/bot/telegram.js +378 -0
- package/dist/bot/telegram.js.map +1 -0
- package/dist/bot/types.d.ts +28 -0
- package/dist/bot/types.d.ts.map +1 -0
- package/dist/bot/types.js +5 -0
- package/dist/bot/types.js.map +1 -0
- package/dist/bridge/daemon.d.ts +93 -0
- package/dist/bridge/daemon.d.ts.map +1 -0
- package/dist/bridge/daemon.js +626 -0
- package/dist/bridge/daemon.js.map +1 -0
- package/dist/bridge/index.d.ts +10 -0
- package/dist/bridge/index.d.ts.map +1 -0
- package/dist/bridge/index.js +9 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge/injector.d.ts +97 -0
- package/dist/bridge/injector.d.ts.map +1 -0
- package/dist/bridge/injector.js +289 -0
- package/dist/bridge/injector.js.map +1 -0
- package/dist/bridge/session.d.ts +108 -0
- package/dist/bridge/session.d.ts.map +1 -0
- package/dist/bridge/session.js +381 -0
- package/dist/bridge/session.js.map +1 -0
- package/dist/bridge/socket.d.ts +97 -0
- package/dist/bridge/socket.d.ts.map +1 -0
- package/dist/bridge/socket.js +436 -0
- package/dist/bridge/socket.js.map +1 -0
- package/dist/bridge/types.d.ts +38 -0
- package/dist/bridge/types.d.ts.map +1 -0
- package/dist/bridge/types.js +5 -0
- package/dist/bridge/types.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +332 -0
- package/dist/cli.js.map +1 -0
- package/dist/hooks/handler.d.ts +94 -0
- package/dist/hooks/handler.d.ts.map +1 -0
- package/dist/hooks/handler.js +431 -0
- package/dist/hooks/handler.js.map +1 -0
- package/dist/hooks/index.d.ts +8 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/installer.d.ts +46 -0
- package/dist/hooks/installer.d.ts.map +1 -0
- package/dist/hooks/installer.js +317 -0
- package/dist/hooks/installer.js.map +1 -0
- package/dist/hooks/types.d.ts +88 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +6 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/service/doctor.d.ts +10 -0
- package/dist/service/doctor.d.ts.map +1 -0
- package/dist/service/doctor.js +424 -0
- package/dist/service/doctor.js.map +1 -0
- package/dist/service/manager.d.ts +48 -0
- package/dist/service/manager.d.ts.map +1 -0
- package/dist/service/manager.js +584 -0
- package/dist/service/manager.js.map +1 -0
- package/dist/service/setup.d.ts +10 -0
- package/dist/service/setup.d.ts.map +1 -0
- package/dist/service/setup.js +266 -0
- package/dist/service/setup.js.map +1 -0
- package/dist/utils/chunker.d.ts +24 -0
- package/dist/utils/chunker.d.ts.map +1 -0
- package/dist/utils/chunker.js +123 -0
- package/dist/utils/chunker.js.map +1 -0
- package/dist/utils/config.d.ts +48 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +154 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +28 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +88 -0
- package/postinstall.cjs +76 -0
- package/scripts/claude-wrapper.sh +122 -0
- package/scripts/doctor.sh +433 -0
- package/scripts/get-chat-id.sh +64 -0
- package/scripts/global-hooks.sh +39 -0
- package/scripts/install.sh +831 -0
- package/scripts/start-daemon.sh +49 -0
- package/scripts/telegram-hook.sh +449 -0
- package/scripts/uninstall.sh +261 -0
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Claude Telegram Mirror - Interactive Installer
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# curl -fsSL https://raw.githubusercontent.com/robertelee78/claude-telegram-mirror/master/scripts/install.sh | bash
|
|
7
|
+
#
|
|
8
|
+
# Or run locally:
|
|
9
|
+
# ./scripts/install.sh
|
|
10
|
+
#
|
|
11
|
+
# Supports Linux and macOS
|
|
12
|
+
#
|
|
13
|
+
|
|
14
|
+
set -e
|
|
15
|
+
|
|
16
|
+
# Colors
|
|
17
|
+
RED='\033[0;31m'
|
|
18
|
+
GREEN='\033[0;32m'
|
|
19
|
+
YELLOW='\033[1;33m'
|
|
20
|
+
BLUE='\033[0;34m'
|
|
21
|
+
CYAN='\033[0;36m'
|
|
22
|
+
BOLD='\033[1m'
|
|
23
|
+
NC='\033[0m' # No Color
|
|
24
|
+
|
|
25
|
+
# Configuration
|
|
26
|
+
REPO_URL="https://github.com/robertelee78/claude-telegram-mirror.git"
|
|
27
|
+
INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/share/claude-telegram-mirror}"
|
|
28
|
+
CONFIG_FILE="$HOME/.telegram-env"
|
|
29
|
+
|
|
30
|
+
# State variables (filled during installation)
|
|
31
|
+
BOT_TOKEN=""
|
|
32
|
+
BOT_USERNAME=""
|
|
33
|
+
CHAT_ID=""
|
|
34
|
+
|
|
35
|
+
# ============================================
|
|
36
|
+
# TTY HANDLING FOR curl | bash
|
|
37
|
+
# ============================================
|
|
38
|
+
# When run via "curl ... | bash", stdin is the curl output (the script itself).
|
|
39
|
+
# We need to read user input from /dev/tty instead.
|
|
40
|
+
#
|
|
41
|
+
# This is the standard pattern used by rustup, nvm, and other installers.
|
|
42
|
+
|
|
43
|
+
# Check if we have a TTY available for interactive input
|
|
44
|
+
if [ -t 0 ]; then
|
|
45
|
+
# stdin is a terminal - normal execution
|
|
46
|
+
TTY_INPUT="/dev/stdin"
|
|
47
|
+
else
|
|
48
|
+
# stdin is NOT a terminal (likely piped from curl)
|
|
49
|
+
# Try to use /dev/tty for interactive input
|
|
50
|
+
# We need to verify /dev/tty actually works, not just exists
|
|
51
|
+
if [ -e /dev/tty ] && [ -r /dev/tty ] && [ -w /dev/tty ]; then
|
|
52
|
+
# Test that we can actually read from it
|
|
53
|
+
if (echo "" > /dev/tty) 2>/dev/null; then
|
|
54
|
+
TTY_INPUT="/dev/tty"
|
|
55
|
+
else
|
|
56
|
+
echo ""
|
|
57
|
+
echo -e "${RED}ERROR: This installer requires interactive input.${NC}"
|
|
58
|
+
echo ""
|
|
59
|
+
echo " You're running via 'curl | bash' but /dev/tty is not usable."
|
|
60
|
+
echo ""
|
|
61
|
+
echo " Options:"
|
|
62
|
+
echo " 1. Download and run the script directly:"
|
|
63
|
+
echo " curl -fsSL https://raw.githubusercontent.com/robertelee78/claude-telegram-mirror/master/scripts/install.sh -o install.sh"
|
|
64
|
+
echo " bash install.sh"
|
|
65
|
+
echo ""
|
|
66
|
+
echo " 2. If in a Docker container, run with -it flags:"
|
|
67
|
+
echo " docker run -it ..."
|
|
68
|
+
echo ""
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
else
|
|
72
|
+
echo ""
|
|
73
|
+
echo -e "${RED}ERROR: This installer requires interactive input.${NC}"
|
|
74
|
+
echo ""
|
|
75
|
+
echo " You're running via 'curl | bash' but /dev/tty is not available."
|
|
76
|
+
echo ""
|
|
77
|
+
echo " Options:"
|
|
78
|
+
echo " 1. Download and run the script directly:"
|
|
79
|
+
echo " curl -fsSL https://raw.githubusercontent.com/robertelee78/claude-telegram-mirror/master/scripts/install.sh -o install.sh"
|
|
80
|
+
echo " bash install.sh"
|
|
81
|
+
echo ""
|
|
82
|
+
echo " 2. If in a Docker container, run with -it flags:"
|
|
83
|
+
echo " docker run -it ..."
|
|
84
|
+
echo ""
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# Wrapper function for read that uses the correct input source
|
|
90
|
+
# Usage: prompt_read "prompt text" VARIABLE_NAME
|
|
91
|
+
prompt_read() {
|
|
92
|
+
local prompt="$1"
|
|
93
|
+
local varname="$2"
|
|
94
|
+
local result
|
|
95
|
+
|
|
96
|
+
# Print prompt to stderr (so it shows regardless of stdin redirection)
|
|
97
|
+
# Use -n to avoid newline after prompt
|
|
98
|
+
echo -n "$prompt" >&2
|
|
99
|
+
|
|
100
|
+
# Read from TTY - use || true to prevent set -e from killing us
|
|
101
|
+
# The -r flag prevents backslash interpretation
|
|
102
|
+
if ! read -r result < "$TTY_INPUT"; then
|
|
103
|
+
echo "" >&2
|
|
104
|
+
echo -e "${RED}ERROR: Failed to read input from terminal${NC}" >&2
|
|
105
|
+
echo " TTY_INPUT=$TTY_INPUT" >&2
|
|
106
|
+
exit 1
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# Assign to the named variable
|
|
110
|
+
eval "$varname=\$result"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Wrapper for simple "press enter to continue" prompts
|
|
114
|
+
prompt_continue() {
|
|
115
|
+
local prompt="${1:-Press Enter to continue...}"
|
|
116
|
+
local dummy
|
|
117
|
+
|
|
118
|
+
echo -n "$prompt" >&2
|
|
119
|
+
|
|
120
|
+
if ! read -r dummy < "$TTY_INPUT"; then
|
|
121
|
+
echo "" >&2
|
|
122
|
+
echo -e "${RED}ERROR: Failed to read input from terminal${NC}" >&2
|
|
123
|
+
exit 1
|
|
124
|
+
fi
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# ============================================
|
|
128
|
+
# UTILITY FUNCTIONS
|
|
129
|
+
# ============================================
|
|
130
|
+
|
|
131
|
+
info() {
|
|
132
|
+
echo -e "${BLUE}ℹ${NC} $1"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
success() {
|
|
136
|
+
echo -e "${GREEN}✓${NC} $1"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
warn() {
|
|
140
|
+
echo -e "${YELLOW}⚠${NC} $1"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
error() {
|
|
144
|
+
echo -e "${RED}✗${NC} $1"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
header() {
|
|
148
|
+
echo ""
|
|
149
|
+
echo -e "${BOLD}═══════════════════════════════════════════════${NC}"
|
|
150
|
+
echo -e "${BOLD} $1${NC}"
|
|
151
|
+
echo -e "${BOLD}═══════════════════════════════════════════════${NC}"
|
|
152
|
+
echo ""
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# ============================================
|
|
156
|
+
# STEP 1: PREREQUISITES CHECK
|
|
157
|
+
# ============================================
|
|
158
|
+
|
|
159
|
+
check_prerequisites() {
|
|
160
|
+
header "STEP 1: CHECKING PREREQUISITES"
|
|
161
|
+
|
|
162
|
+
local missing=()
|
|
163
|
+
local os_type=$(uname -s)
|
|
164
|
+
|
|
165
|
+
# Check each required tool
|
|
166
|
+
for cmd in node npm git jq tmux nc curl; do
|
|
167
|
+
if command -v "$cmd" &> /dev/null; then
|
|
168
|
+
if [[ "$cmd" == "node" ]]; then
|
|
169
|
+
local node_version=$(node --version | sed 's/v//' | cut -d. -f1)
|
|
170
|
+
if [[ $node_version -ge 18 ]]; then
|
|
171
|
+
success "$cmd $(node --version)"
|
|
172
|
+
else
|
|
173
|
+
error "$cmd $(node --version) - Need v18+"
|
|
174
|
+
missing+=("node")
|
|
175
|
+
fi
|
|
176
|
+
else
|
|
177
|
+
success "$cmd"
|
|
178
|
+
fi
|
|
179
|
+
else
|
|
180
|
+
error "$cmd not found"
|
|
181
|
+
missing+=("$cmd")
|
|
182
|
+
fi
|
|
183
|
+
done
|
|
184
|
+
|
|
185
|
+
# If anything missing, show install instructions
|
|
186
|
+
if [[ ${#missing[@]} -gt 0 ]]; then
|
|
187
|
+
echo ""
|
|
188
|
+
warn "Missing prerequisites: ${missing[*]}"
|
|
189
|
+
echo ""
|
|
190
|
+
|
|
191
|
+
if [[ "$os_type" == "Darwin" ]]; then
|
|
192
|
+
echo " Install with Homebrew:"
|
|
193
|
+
echo " brew install ${missing[*]}"
|
|
194
|
+
else
|
|
195
|
+
echo " Install on Debian/Ubuntu:"
|
|
196
|
+
echo " sudo apt update && sudo apt install -y ${missing[*]}"
|
|
197
|
+
echo ""
|
|
198
|
+
echo " Install on Fedora/RHEL:"
|
|
199
|
+
echo " sudo dnf install -y ${missing[*]}"
|
|
200
|
+
fi
|
|
201
|
+
|
|
202
|
+
if [[ " ${missing[*]} " =~ " node " ]]; then
|
|
203
|
+
echo ""
|
|
204
|
+
echo " For Node.js 18+, consider using nvm:"
|
|
205
|
+
echo " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash"
|
|
206
|
+
echo " nvm install 18"
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
echo ""
|
|
210
|
+
error "Please install missing prerequisites and run again."
|
|
211
|
+
exit 1
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
success "All prerequisites met!"
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# ============================================
|
|
218
|
+
# STEP 2: BOT TOKEN
|
|
219
|
+
# ============================================
|
|
220
|
+
|
|
221
|
+
get_bot_token() {
|
|
222
|
+
header "STEP 2: CREATE TELEGRAM BOT"
|
|
223
|
+
|
|
224
|
+
echo " You need to create a Telegram bot via @BotFather."
|
|
225
|
+
echo ""
|
|
226
|
+
echo " 1. Open Telegram and search for @BotFather"
|
|
227
|
+
echo " 2. Send /newbot"
|
|
228
|
+
echo " 3. Choose a name (e.g., 'Claude Mirror')"
|
|
229
|
+
echo " 4. Choose a username (must end in 'bot', e.g., 'claude_mirror_bot')"
|
|
230
|
+
echo " 5. Copy the API token"
|
|
231
|
+
echo ""
|
|
232
|
+
|
|
233
|
+
while true; do
|
|
234
|
+
prompt_read "Enter your bot token: " BOT_TOKEN
|
|
235
|
+
|
|
236
|
+
if [[ -z "$BOT_TOKEN" ]]; then
|
|
237
|
+
warn "Token cannot be empty"
|
|
238
|
+
continue
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# Validate token format (should be like: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz)
|
|
242
|
+
if [[ ! "$BOT_TOKEN" =~ ^[0-9]+:[A-Za-z0-9_-]+$ ]]; then
|
|
243
|
+
warn "Token format looks incorrect. Expected format: 123456789:ABCdefGHI..."
|
|
244
|
+
prompt_read "Try again? [Y/n]: " retry
|
|
245
|
+
[[ "$retry" =~ ^[Nn] ]] && exit 1
|
|
246
|
+
continue
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
# Verify token with Telegram API
|
|
250
|
+
info "Verifying token with Telegram..."
|
|
251
|
+
local response=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getMe")
|
|
252
|
+
|
|
253
|
+
if echo "$response" | jq -e '.ok == true' &> /dev/null; then
|
|
254
|
+
BOT_USERNAME=$(echo "$response" | jq -r '.result.username')
|
|
255
|
+
success "Bot verified: @${BOT_USERNAME}"
|
|
256
|
+
break
|
|
257
|
+
else
|
|
258
|
+
local error_msg=$(echo "$response" | jq -r '.description // "Unknown error"')
|
|
259
|
+
error "Token validation failed: $error_msg"
|
|
260
|
+
prompt_read "Try again? [Y/n]: " retry
|
|
261
|
+
[[ "$retry" =~ ^[Nn] ]] && exit 1
|
|
262
|
+
fi
|
|
263
|
+
done
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# ============================================
|
|
267
|
+
# STEP 3: DISABLE PRIVACY MODE
|
|
268
|
+
# ============================================
|
|
269
|
+
|
|
270
|
+
configure_privacy_mode() {
|
|
271
|
+
header "STEP 3: DISABLE PRIVACY MODE"
|
|
272
|
+
|
|
273
|
+
echo " Your bot needs to see all group messages (not just commands)."
|
|
274
|
+
echo ""
|
|
275
|
+
echo " 1. Go back to @BotFather in Telegram"
|
|
276
|
+
echo " 2. Send /mybots"
|
|
277
|
+
echo " 3. Select @${BOT_USERNAME}"
|
|
278
|
+
echo " 4. Click 'Bot Settings'"
|
|
279
|
+
echo " 5. Click 'Group Privacy'"
|
|
280
|
+
echo " 6. Click 'Turn off'"
|
|
281
|
+
echo ""
|
|
282
|
+
echo " You should see: 'Privacy mode is disabled for @${BOT_USERNAME}'"
|
|
283
|
+
echo ""
|
|
284
|
+
|
|
285
|
+
while true; do
|
|
286
|
+
prompt_read "Have you disabled privacy mode? [y/n]: " confirmed
|
|
287
|
+
|
|
288
|
+
if [[ "$confirmed" =~ ^[Yy] ]]; then
|
|
289
|
+
success "Privacy mode configured"
|
|
290
|
+
break
|
|
291
|
+
elif [[ "$confirmed" =~ ^[Nn] ]]; then
|
|
292
|
+
warn "Privacy mode MUST be disabled for the bot to work."
|
|
293
|
+
echo " Please complete this step before continuing."
|
|
294
|
+
prompt_read "Try again? [Y/n]: " retry
|
|
295
|
+
[[ "$retry" =~ ^[Nn] ]] && exit 1
|
|
296
|
+
else
|
|
297
|
+
warn "Please enter 'y' or 'n'"
|
|
298
|
+
fi
|
|
299
|
+
done
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# ============================================
|
|
303
|
+
# STEP 4: CREATE SUPERGROUP WITH TOPICS
|
|
304
|
+
# ============================================
|
|
305
|
+
|
|
306
|
+
setup_telegram_group() {
|
|
307
|
+
header "STEP 4: SETUP SUPERGROUP WITH TOPICS"
|
|
308
|
+
|
|
309
|
+
echo " You need a Telegram supergroup with Topics enabled."
|
|
310
|
+
echo ""
|
|
311
|
+
echo " ${BOLD}Option A: Use an existing supergroup${NC}"
|
|
312
|
+
echo " 1. Add @${BOT_USERNAME} to your existing supergroup"
|
|
313
|
+
echo " 2. Make the bot an admin with 'Manage Topics' permission"
|
|
314
|
+
echo " 3. Send any message in the group (so we can detect it)"
|
|
315
|
+
echo ""
|
|
316
|
+
echo " ${BOLD}Option B: Create a new group${NC}"
|
|
317
|
+
echo " 1. In Telegram, create a new group"
|
|
318
|
+
echo " 2. Add @${BOT_USERNAME} to the group"
|
|
319
|
+
echo " 3. Go to group settings → Enable 'Topics'"
|
|
320
|
+
echo " (This converts it to a supergroup)"
|
|
321
|
+
echo " 4. Make the bot an admin with 'Manage Topics' permission"
|
|
322
|
+
echo " 5. Send any message in the group"
|
|
323
|
+
echo ""
|
|
324
|
+
|
|
325
|
+
prompt_continue "Press Enter when you've completed these steps..."
|
|
326
|
+
|
|
327
|
+
# Try to auto-detect the chat ID
|
|
328
|
+
info "Looking for your group..."
|
|
329
|
+
|
|
330
|
+
local updates=$(curl -s "https://api.telegram.org/bot${BOT_TOKEN}/getUpdates?limit=100")
|
|
331
|
+
|
|
332
|
+
if ! echo "$updates" | jq -e '.ok == true' &> /dev/null; then
|
|
333
|
+
error "Failed to get updates from Telegram API"
|
|
334
|
+
manual_chat_id_entry
|
|
335
|
+
return
|
|
336
|
+
fi
|
|
337
|
+
|
|
338
|
+
# Extract unique supergroups (chat_id starting with -100)
|
|
339
|
+
local groups=$(echo "$updates" | jq -r '
|
|
340
|
+
.result[]
|
|
341
|
+
| .message.chat // .my_chat_member.chat // empty
|
|
342
|
+
| select(.id < 0)
|
|
343
|
+
| select(.id | tostring | startswith("-100"))
|
|
344
|
+
| "\(.id)|\(.title // "Unknown")"
|
|
345
|
+
' | sort -u)
|
|
346
|
+
|
|
347
|
+
if [[ -z "$groups" ]]; then
|
|
348
|
+
warn "No supergroups found. This can happen if:"
|
|
349
|
+
echo " - The bot hasn't seen any messages yet"
|
|
350
|
+
echo " - The group wasn't converted to a supergroup (enable Topics!)"
|
|
351
|
+
echo ""
|
|
352
|
+
manual_chat_id_entry
|
|
353
|
+
return
|
|
354
|
+
fi
|
|
355
|
+
|
|
356
|
+
# Count groups
|
|
357
|
+
local group_count=$(echo "$groups" | wc -l | tr -d ' ')
|
|
358
|
+
|
|
359
|
+
if [[ $group_count -eq 1 ]]; then
|
|
360
|
+
# Only one group found
|
|
361
|
+
CHAT_ID=$(echo "$groups" | cut -d'|' -f1)
|
|
362
|
+
local group_title=$(echo "$groups" | cut -d'|' -f2)
|
|
363
|
+
success "Found group: $group_title ($CHAT_ID)"
|
|
364
|
+
|
|
365
|
+
prompt_read "Is this the correct group? [Y/n]: " confirm
|
|
366
|
+
if [[ "$confirm" =~ ^[Nn] ]]; then
|
|
367
|
+
manual_chat_id_entry
|
|
368
|
+
return
|
|
369
|
+
fi
|
|
370
|
+
else
|
|
371
|
+
# Multiple groups found
|
|
372
|
+
echo ""
|
|
373
|
+
echo " Found multiple groups:"
|
|
374
|
+
echo ""
|
|
375
|
+
|
|
376
|
+
local i=1
|
|
377
|
+
declare -a group_ids
|
|
378
|
+
declare -a group_titles
|
|
379
|
+
|
|
380
|
+
while IFS='|' read -r gid gtitle; do
|
|
381
|
+
echo " $i) $gtitle ($gid)"
|
|
382
|
+
group_ids+=("$gid")
|
|
383
|
+
group_titles+=("$gtitle")
|
|
384
|
+
((i++))
|
|
385
|
+
done <<< "$groups"
|
|
386
|
+
|
|
387
|
+
echo ""
|
|
388
|
+
prompt_read "Select group number (1-$group_count): " selection
|
|
389
|
+
|
|
390
|
+
if [[ "$selection" =~ ^[0-9]+$ ]] && [[ $selection -ge 1 ]] && [[ $selection -le $group_count ]]; then
|
|
391
|
+
CHAT_ID="${group_ids[$((selection-1))]}"
|
|
392
|
+
success "Selected: ${group_titles[$((selection-1))]} ($CHAT_ID)"
|
|
393
|
+
else
|
|
394
|
+
warn "Invalid selection"
|
|
395
|
+
manual_chat_id_entry
|
|
396
|
+
return
|
|
397
|
+
fi
|
|
398
|
+
fi
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
manual_chat_id_entry() {
|
|
402
|
+
echo ""
|
|
403
|
+
echo " Enter the chat ID manually."
|
|
404
|
+
echo " You can find it by:"
|
|
405
|
+
echo " 1. Send a message in the group"
|
|
406
|
+
echo " 2. Visit: https://api.telegram.org/bot${BOT_TOKEN}/getUpdates"
|
|
407
|
+
echo " 3. Look for 'chat':{'id': -100XXXXXXXXXX}"
|
|
408
|
+
echo ""
|
|
409
|
+
|
|
410
|
+
while true; do
|
|
411
|
+
prompt_read "Enter chat ID (starts with -100): " CHAT_ID
|
|
412
|
+
|
|
413
|
+
if [[ "$CHAT_ID" =~ ^-100[0-9]+$ ]]; then
|
|
414
|
+
success "Chat ID format valid"
|
|
415
|
+
break
|
|
416
|
+
else
|
|
417
|
+
warn "Chat ID should start with -100 (supergroup format)"
|
|
418
|
+
prompt_read "Try again? [Y/n]: " retry
|
|
419
|
+
[[ "$retry" =~ ^[Nn] ]] && exit 1
|
|
420
|
+
fi
|
|
421
|
+
done
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
# ============================================
|
|
425
|
+
# STEP 5: VERIFY BOT CAN POST
|
|
426
|
+
# ============================================
|
|
427
|
+
|
|
428
|
+
verify_bot_permissions() {
|
|
429
|
+
header "STEP 5: VERIFY BOT PERMISSIONS"
|
|
430
|
+
|
|
431
|
+
info "Testing if bot can post to the group..."
|
|
432
|
+
|
|
433
|
+
local response=$(curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
|
|
434
|
+
-H "Content-Type: application/json" \
|
|
435
|
+
-d "{\"chat_id\": \"${CHAT_ID}\", \"text\": \"🤖 Claude Telegram Mirror - Installation test successful!\"}")
|
|
436
|
+
|
|
437
|
+
if echo "$response" | jq -e '.ok == true' &> /dev/null; then
|
|
438
|
+
success "Bot can post to the group!"
|
|
439
|
+
echo ""
|
|
440
|
+
echo " Check your Telegram group - you should see a test message."
|
|
441
|
+
prompt_continue "Press Enter to continue..."
|
|
442
|
+
else
|
|
443
|
+
local error_msg=$(echo "$response" | jq -r '.description // "Unknown error"')
|
|
444
|
+
error "Bot cannot post: $error_msg"
|
|
445
|
+
echo ""
|
|
446
|
+
echo " Common fixes:"
|
|
447
|
+
echo " - Make sure the bot is an admin in the group"
|
|
448
|
+
echo " - Ensure 'Post Messages' permission is enabled"
|
|
449
|
+
echo " - Check that 'Manage Topics' permission is enabled"
|
|
450
|
+
echo ""
|
|
451
|
+
prompt_continue "Fix the issue and press Enter to retry, or Ctrl+C to exit..."
|
|
452
|
+
verify_bot_permissions
|
|
453
|
+
fi
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
# ============================================
|
|
457
|
+
# STEP 6: INSTALL PACKAGE
|
|
458
|
+
# ============================================
|
|
459
|
+
|
|
460
|
+
install_package() {
|
|
461
|
+
header "STEP 6: INSTALL PACKAGE"
|
|
462
|
+
|
|
463
|
+
# Check if already installed
|
|
464
|
+
if [[ -d "$INSTALL_DIR" ]]; then
|
|
465
|
+
warn "Installation directory already exists: $INSTALL_DIR"
|
|
466
|
+
prompt_read "Remove and reinstall? [y/N]: " reinstall
|
|
467
|
+
if [[ "$reinstall" =~ ^[Yy] ]]; then
|
|
468
|
+
info "Removing existing installation..."
|
|
469
|
+
rm -rf "$INSTALL_DIR"
|
|
470
|
+
else
|
|
471
|
+
info "Keeping existing installation, updating..."
|
|
472
|
+
cd "$INSTALL_DIR"
|
|
473
|
+
git pull
|
|
474
|
+
npm install
|
|
475
|
+
npm run build
|
|
476
|
+
success "Package updated"
|
|
477
|
+
return
|
|
478
|
+
fi
|
|
479
|
+
fi
|
|
480
|
+
|
|
481
|
+
# Clone repository
|
|
482
|
+
info "Cloning repository..."
|
|
483
|
+
mkdir -p "$(dirname "$INSTALL_DIR")"
|
|
484
|
+
git clone "$REPO_URL" "$INSTALL_DIR"
|
|
485
|
+
|
|
486
|
+
# Install dependencies
|
|
487
|
+
cd "$INSTALL_DIR"
|
|
488
|
+
info "Installing dependencies..."
|
|
489
|
+
npm install
|
|
490
|
+
|
|
491
|
+
# Build
|
|
492
|
+
info "Building..."
|
|
493
|
+
npm run build
|
|
494
|
+
|
|
495
|
+
success "Package installed to $INSTALL_DIR"
|
|
496
|
+
|
|
497
|
+
# Create ctm symlink
|
|
498
|
+
local bin_dir="$HOME/.local/bin"
|
|
499
|
+
mkdir -p "$bin_dir"
|
|
500
|
+
|
|
501
|
+
if [[ ! -f "$bin_dir/ctm" ]]; then
|
|
502
|
+
cat > "$bin_dir/ctm" << EOF
|
|
503
|
+
#!/bin/bash
|
|
504
|
+
node "$INSTALL_DIR/dist/cli.js" "\$@"
|
|
505
|
+
EOF
|
|
506
|
+
chmod +x "$bin_dir/ctm"
|
|
507
|
+
success "Created 'ctm' command in $bin_dir"
|
|
508
|
+
|
|
509
|
+
# Check if bin_dir is in PATH
|
|
510
|
+
if [[ ":$PATH:" != *":$bin_dir:"* ]]; then
|
|
511
|
+
warn "$bin_dir is not in your PATH"
|
|
512
|
+
echo " Add this to your ~/.bashrc or ~/.zshrc:"
|
|
513
|
+
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
|
|
514
|
+
fi
|
|
515
|
+
fi
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
# ============================================
|
|
519
|
+
# STEP 7: CREATE CONFIG
|
|
520
|
+
# ============================================
|
|
521
|
+
|
|
522
|
+
create_config() {
|
|
523
|
+
header "STEP 7: CREATE CONFIGURATION"
|
|
524
|
+
|
|
525
|
+
info "Creating $CONFIG_FILE..."
|
|
526
|
+
|
|
527
|
+
cat > "$CONFIG_FILE" << EOF
|
|
528
|
+
# Claude Telegram Mirror Configuration
|
|
529
|
+
# Generated by install.sh on $(date)
|
|
530
|
+
|
|
531
|
+
export TELEGRAM_BOT_TOKEN="$BOT_TOKEN"
|
|
532
|
+
export TELEGRAM_CHAT_ID="$CHAT_ID"
|
|
533
|
+
export TELEGRAM_MIRROR=true
|
|
534
|
+
|
|
535
|
+
# Optional settings:
|
|
536
|
+
# export TELEGRAM_MIRROR_VERBOSE=true
|
|
537
|
+
# export TELEGRAM_BRIDGE_SOCKET=~/.config/claude-telegram-mirror/bridge.sock
|
|
538
|
+
EOF
|
|
539
|
+
|
|
540
|
+
chmod 600 "$CONFIG_FILE"
|
|
541
|
+
success "Configuration saved to $CONFIG_FILE"
|
|
542
|
+
|
|
543
|
+
# Add to shell profile
|
|
544
|
+
local shell_profile=""
|
|
545
|
+
if [[ -f "$HOME/.bashrc" ]]; then
|
|
546
|
+
shell_profile="$HOME/.bashrc"
|
|
547
|
+
elif [[ -f "$HOME/.zshrc" ]]; then
|
|
548
|
+
shell_profile="$HOME/.zshrc"
|
|
549
|
+
elif [[ -f "$HOME/.profile" ]]; then
|
|
550
|
+
shell_profile="$HOME/.profile"
|
|
551
|
+
fi
|
|
552
|
+
|
|
553
|
+
if [[ -n "$shell_profile" ]]; then
|
|
554
|
+
if ! grep -q "telegram-env" "$shell_profile" 2>/dev/null; then
|
|
555
|
+
echo "" >> "$shell_profile"
|
|
556
|
+
echo "# Claude Telegram Mirror" >> "$shell_profile"
|
|
557
|
+
echo "[[ -f ~/.telegram-env ]] && source ~/.telegram-env" >> "$shell_profile"
|
|
558
|
+
success "Added to $shell_profile"
|
|
559
|
+
else
|
|
560
|
+
info "Already in $shell_profile"
|
|
561
|
+
fi
|
|
562
|
+
fi
|
|
563
|
+
|
|
564
|
+
# Source it now
|
|
565
|
+
source "$CONFIG_FILE"
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
# ============================================
|
|
569
|
+
# STEP 8: INSTALL HOOKS
|
|
570
|
+
# ============================================
|
|
571
|
+
|
|
572
|
+
install_hooks() {
|
|
573
|
+
header "STEP 8: INSTALL CLAUDE CODE HOOKS"
|
|
574
|
+
|
|
575
|
+
cd "$INSTALL_DIR"
|
|
576
|
+
|
|
577
|
+
# Install global hooks
|
|
578
|
+
info "Installing global hooks..."
|
|
579
|
+
node dist/cli.js install-hooks
|
|
580
|
+
success "Global hooks installed to ~/.claude/settings.json"
|
|
581
|
+
|
|
582
|
+
# CRITICAL: Warn about project-level settings
|
|
583
|
+
echo ""
|
|
584
|
+
echo "┌─────────────────────────────────────────────────────────────┐"
|
|
585
|
+
echo "│ ⚠️ IMPORTANT: PROJECT-LEVEL HOOKS │"
|
|
586
|
+
echo "├─────────────────────────────────────────────────────────────┤"
|
|
587
|
+
echo "│ │"
|
|
588
|
+
echo "│ If you use Claude Code in projects that have their own │"
|
|
589
|
+
echo "│ .claude/settings.json file, the GLOBAL hooks we just │"
|
|
590
|
+
echo "│ installed will be IGNORED in those projects. │"
|
|
591
|
+
echo "│ │"
|
|
592
|
+
echo "│ To enable Telegram mirroring in a specific project: │"
|
|
593
|
+
echo "│ │"
|
|
594
|
+
echo "│ cd /path/to/your/project │"
|
|
595
|
+
echo "│ ctm install-hooks --project │"
|
|
596
|
+
echo "│ │"
|
|
597
|
+
echo "│ This adds hooks to that project's .claude/settings.json │"
|
|
598
|
+
echo "│ │"
|
|
599
|
+
echo "└─────────────────────────────────────────────────────────────┘"
|
|
600
|
+
echo ""
|
|
601
|
+
|
|
602
|
+
# Ask if they want to install to a project now
|
|
603
|
+
prompt_read "Do you have a project with .claude/settings.json that needs hooks? [y/N]: " HAS_PROJECT
|
|
604
|
+
|
|
605
|
+
if [[ "$HAS_PROJECT" =~ ^[Yy] ]]; then
|
|
606
|
+
install_project_hooks
|
|
607
|
+
fi
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
install_project_hooks() {
|
|
611
|
+
while true; do
|
|
612
|
+
echo ""
|
|
613
|
+
prompt_read "Enter project path (or 'done' to finish): " PROJECT_PATH
|
|
614
|
+
|
|
615
|
+
[[ "$PROJECT_PATH" == "done" || -z "$PROJECT_PATH" ]] && break
|
|
616
|
+
|
|
617
|
+
# Expand ~ if present
|
|
618
|
+
PROJECT_PATH="${PROJECT_PATH/#\~/$HOME}"
|
|
619
|
+
|
|
620
|
+
# Check if it's a valid project with .claude/
|
|
621
|
+
if [[ -d "$PROJECT_PATH/.claude" ]]; then
|
|
622
|
+
info "Installing hooks to $PROJECT_PATH..."
|
|
623
|
+
|
|
624
|
+
# Save current dir, go to project, install, come back
|
|
625
|
+
pushd "$PROJECT_PATH" > /dev/null
|
|
626
|
+
node "$INSTALL_DIR/dist/cli.js" install-hooks --project
|
|
627
|
+
popd > /dev/null
|
|
628
|
+
|
|
629
|
+
success "Hooks installed to $PROJECT_PATH/.claude/settings.json"
|
|
630
|
+
elif [[ -d "$PROJECT_PATH" ]]; then
|
|
631
|
+
warn "No .claude/ directory in $PROJECT_PATH"
|
|
632
|
+
echo " This project doesn't have custom Claude settings."
|
|
633
|
+
echo " Global hooks will work here - no action needed!"
|
|
634
|
+
else
|
|
635
|
+
warn "Directory not found: $PROJECT_PATH"
|
|
636
|
+
fi
|
|
637
|
+
|
|
638
|
+
prompt_read "Add another project? [y/N]: " ANOTHER
|
|
639
|
+
[[ "$ANOTHER" =~ ^[Yy] ]] || break
|
|
640
|
+
done
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
# ============================================
|
|
644
|
+
# STEP 9: INSTALL SERVICE
|
|
645
|
+
# ============================================
|
|
646
|
+
|
|
647
|
+
install_service() {
|
|
648
|
+
header "STEP 9: INSTALL SYSTEM SERVICE"
|
|
649
|
+
|
|
650
|
+
cd "$INSTALL_DIR"
|
|
651
|
+
|
|
652
|
+
local os_type=$(uname -s)
|
|
653
|
+
|
|
654
|
+
info "Installing service..."
|
|
655
|
+
node dist/cli.js service install
|
|
656
|
+
|
|
657
|
+
info "Starting service..."
|
|
658
|
+
node dist/cli.js service start
|
|
659
|
+
|
|
660
|
+
# Verify service is running
|
|
661
|
+
sleep 2
|
|
662
|
+
|
|
663
|
+
if node dist/cli.js service status 2>/dev/null | grep -q "running\|active"; then
|
|
664
|
+
success "Service is running!"
|
|
665
|
+
else
|
|
666
|
+
warn "Service may not have started correctly"
|
|
667
|
+
|
|
668
|
+
if [[ "$os_type" == "Linux" ]]; then
|
|
669
|
+
echo " Check status: systemctl --user status claude-telegram-mirror"
|
|
670
|
+
echo " View logs: journalctl --user -u claude-telegram-mirror -f"
|
|
671
|
+
echo ""
|
|
672
|
+
echo " You may need to enable user lingering:"
|
|
673
|
+
echo " loginctl enable-linger $USER"
|
|
674
|
+
else
|
|
675
|
+
echo " Check status: launchctl list | grep claude"
|
|
676
|
+
echo " View logs: cat ~/Library/Logs/claude-telegram-mirror.*.log"
|
|
677
|
+
fi
|
|
678
|
+
fi
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
# ============================================
|
|
682
|
+
# STEP 10: COMPLETION
|
|
683
|
+
# ============================================
|
|
684
|
+
|
|
685
|
+
show_completion() {
|
|
686
|
+
echo ""
|
|
687
|
+
echo "╔═════════════════════════════════════════════╗"
|
|
688
|
+
echo "║ ✅ INSTALLATION COMPLETE ║"
|
|
689
|
+
echo "╚═════════════════════════════════════════════╝"
|
|
690
|
+
echo ""
|
|
691
|
+
echo " Bot: @${BOT_USERNAME}"
|
|
692
|
+
echo " Chat: ${CHAT_ID}"
|
|
693
|
+
echo " Config: ~/.telegram-env"
|
|
694
|
+
echo " Install: ${INSTALL_DIR}"
|
|
695
|
+
echo ""
|
|
696
|
+
echo " Commands:"
|
|
697
|
+
echo " ctm start # Start daemon (foreground)"
|
|
698
|
+
echo " ctm status # Show status"
|
|
699
|
+
echo " ctm service status # Service status"
|
|
700
|
+
echo ""
|
|
701
|
+
|
|
702
|
+
local os_type=$(uname -s)
|
|
703
|
+
if [[ "$os_type" == "Linux" ]]; then
|
|
704
|
+
echo " Logs: journalctl --user -u claude-telegram-mirror -f"
|
|
705
|
+
else
|
|
706
|
+
echo " Logs: cat ~/Library/Logs/claude-telegram-mirror.*.log"
|
|
707
|
+
fi
|
|
708
|
+
|
|
709
|
+
echo ""
|
|
710
|
+
echo " ┌─────────────────────────────────────────────────────────┐"
|
|
711
|
+
echo " │ 📌 REMEMBER: Project-specific hooks │"
|
|
712
|
+
echo " │ │"
|
|
713
|
+
echo " │ For projects with .claude/settings.json: │"
|
|
714
|
+
echo " │ cd /path/to/project && ctm install-hooks -p │"
|
|
715
|
+
echo " └─────────────────────────────────────────────────────────┘"
|
|
716
|
+
echo ""
|
|
717
|
+
echo " Next steps:"
|
|
718
|
+
echo " 1. Run 'source ~/.bashrc' or restart terminal"
|
|
719
|
+
echo " 2. Start a Claude Code session in tmux:"
|
|
720
|
+
echo " tmux new -s claude"
|
|
721
|
+
echo " claude"
|
|
722
|
+
echo ""
|
|
723
|
+
echo " Your Claude sessions will now be mirrored to Telegram!"
|
|
724
|
+
echo ""
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
# ============================================
|
|
728
|
+
# CLEANUP ON ERROR
|
|
729
|
+
# ============================================
|
|
730
|
+
|
|
731
|
+
cleanup_on_error() {
|
|
732
|
+
echo ""
|
|
733
|
+
error "Installation failed!"
|
|
734
|
+
echo ""
|
|
735
|
+
echo " Partial installation may exist at:"
|
|
736
|
+
echo " $INSTALL_DIR"
|
|
737
|
+
echo " $CONFIG_FILE"
|
|
738
|
+
echo ""
|
|
739
|
+
echo " To clean up and retry:"
|
|
740
|
+
echo " rm -rf $INSTALL_DIR"
|
|
741
|
+
echo " rm -f $CONFIG_FILE"
|
|
742
|
+
echo ""
|
|
743
|
+
exit 1
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
# ============================================
|
|
747
|
+
# NONINTERACTIVE MODE (existing config)
|
|
748
|
+
# ============================================
|
|
749
|
+
|
|
750
|
+
check_existing_config() {
|
|
751
|
+
if [[ -f "$CONFIG_FILE" ]]; then
|
|
752
|
+
source "$CONFIG_FILE"
|
|
753
|
+
|
|
754
|
+
if [[ -n "$TELEGRAM_BOT_TOKEN" && -n "$TELEGRAM_CHAT_ID" ]]; then
|
|
755
|
+
echo ""
|
|
756
|
+
info "Found existing configuration at $CONFIG_FILE"
|
|
757
|
+
|
|
758
|
+
# Validate existing token
|
|
759
|
+
local response=$(curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMe")
|
|
760
|
+
if echo "$response" | jq -e '.ok == true' &> /dev/null; then
|
|
761
|
+
BOT_USERNAME=$(echo "$response" | jq -r '.result.username')
|
|
762
|
+
BOT_TOKEN="$TELEGRAM_BOT_TOKEN"
|
|
763
|
+
CHAT_ID="$TELEGRAM_CHAT_ID"
|
|
764
|
+
|
|
765
|
+
echo " Bot: @${BOT_USERNAME}"
|
|
766
|
+
echo " Chat: ${CHAT_ID}"
|
|
767
|
+
echo ""
|
|
768
|
+
|
|
769
|
+
prompt_read "Use existing configuration? [Y/n]: " use_existing
|
|
770
|
+
if [[ ! "$use_existing" =~ ^[Nn] ]]; then
|
|
771
|
+
return 0 # Use existing
|
|
772
|
+
fi
|
|
773
|
+
else
|
|
774
|
+
warn "Existing token is invalid"
|
|
775
|
+
fi
|
|
776
|
+
fi
|
|
777
|
+
fi
|
|
778
|
+
|
|
779
|
+
return 1 # Need new config
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
# ============================================
|
|
783
|
+
# MAIN
|
|
784
|
+
# ============================================
|
|
785
|
+
|
|
786
|
+
main() {
|
|
787
|
+
echo ""
|
|
788
|
+
echo "╔═══════════════════════════════════════════════════════════════╗"
|
|
789
|
+
echo "║ ║"
|
|
790
|
+
echo "║ 🤖 Claude Telegram Mirror Installer ║"
|
|
791
|
+
echo "║ ║"
|
|
792
|
+
echo "║ Mirror your Claude Code sessions to Telegram ║"
|
|
793
|
+
echo "║ Control Claude from your phone! ║"
|
|
794
|
+
echo "║ ║"
|
|
795
|
+
echo "╚═══════════════════════════════════════════════════════════════╝"
|
|
796
|
+
echo ""
|
|
797
|
+
|
|
798
|
+
# Set up error handler
|
|
799
|
+
trap cleanup_on_error ERR
|
|
800
|
+
|
|
801
|
+
# Step 1: Prerequisites
|
|
802
|
+
check_prerequisites
|
|
803
|
+
|
|
804
|
+
# Check for existing config
|
|
805
|
+
if check_existing_config; then
|
|
806
|
+
# Skip Telegram setup, go straight to install
|
|
807
|
+
install_package
|
|
808
|
+
install_hooks
|
|
809
|
+
install_service
|
|
810
|
+
show_completion
|
|
811
|
+
exit 0
|
|
812
|
+
fi
|
|
813
|
+
|
|
814
|
+
# Steps 2-5: Telegram setup
|
|
815
|
+
get_bot_token
|
|
816
|
+
configure_privacy_mode
|
|
817
|
+
setup_telegram_group
|
|
818
|
+
verify_bot_permissions
|
|
819
|
+
|
|
820
|
+
# Steps 6-9: Installation
|
|
821
|
+
install_package
|
|
822
|
+
create_config
|
|
823
|
+
install_hooks
|
|
824
|
+
install_service
|
|
825
|
+
|
|
826
|
+
# Step 10: Done!
|
|
827
|
+
show_completion
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
# Run main
|
|
831
|
+
main "$@"
|