noho_bot 2.0.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/LICENSE +22 -0
- package/README.md +30 -0
- package/noho_bot.js +808 -0
- package/noho_bot.js. +549 -0
- package/noho_bot.js.. +549 -0
- package/noho_bot.py +812 -0
- package/package.json +30 -0
- package/setup.py +37 -0
package/noho_bot.py
ADDED
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# noho_bot.py
|
|
3
|
+
# NOHO Bot Library - Universal Bot Builder (Python Edition)
|
|
4
|
+
# Created by: nohojs (Omar) - NOHO Company
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import json
|
|
9
|
+
import time
|
|
10
|
+
import hashlib
|
|
11
|
+
import secrets
|
|
12
|
+
import subprocess
|
|
13
|
+
import signal
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from typing import Optional, Callable, Dict, Any
|
|
16
|
+
from abc import ABC, abstractmethod
|
|
17
|
+
|
|
18
|
+
# ==========================================
|
|
19
|
+
# DEPENDENCIES (pip install)
|
|
20
|
+
# ==========================================
|
|
21
|
+
"""
|
|
22
|
+
pip install python-telegram-bot discord.py slack-sdk requests qrcode[pil]
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
import qrcode
|
|
27
|
+
import requests
|
|
28
|
+
from telegram import Bot, Update
|
|
29
|
+
from telegram.ext import Application, MessageHandler, filters, ContextTypes
|
|
30
|
+
import discord
|
|
31
|
+
from discord.ext import commands
|
|
32
|
+
from slack_sdk import WebClient
|
|
33
|
+
from slack_sdk.errors import SlackApiError
|
|
34
|
+
except ImportError as e:
|
|
35
|
+
print(f"❌ Missing dependency: {e}")
|
|
36
|
+
print("Run: pip install python-telegram-bot discord.py slack-sdk requests qrcode[pil]")
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
# ==========================================
|
|
40
|
+
# UTILS & COLORS
|
|
41
|
+
# ==========================================
|
|
42
|
+
class Colors:
|
|
43
|
+
RESET = '\033[0m'
|
|
44
|
+
BRIGHT = '\033[1m'
|
|
45
|
+
GREEN = '\033[32m'
|
|
46
|
+
YELLOW = '\033[33m'
|
|
47
|
+
RED = '\033[31m'
|
|
48
|
+
CYAN = '\033[36m'
|
|
49
|
+
MAGENTA = '\033[35m'
|
|
50
|
+
BLUE = '\033[34m'
|
|
51
|
+
|
|
52
|
+
ICONS = {
|
|
53
|
+
'info': 'ℹ️',
|
|
54
|
+
'success': '✅',
|
|
55
|
+
'error': '❌',
|
|
56
|
+
'warning': '⚠️',
|
|
57
|
+
'bot': '🤖',
|
|
58
|
+
'lock': '🔒',
|
|
59
|
+
'daemon': '👻',
|
|
60
|
+
'whatsapp': '📱',
|
|
61
|
+
'telegram': '✈️',
|
|
62
|
+
'discord': '🎮',
|
|
63
|
+
'slack': '💬',
|
|
64
|
+
'facebook': '📘'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
def log(msg_type: str, message: str):
|
|
68
|
+
icon = ICONS.get(msg_type, '•')
|
|
69
|
+
print(f"{icon} {message}")
|
|
70
|
+
|
|
71
|
+
# ==========================================
|
|
72
|
+
# DAEMON MANAGER (24/7)
|
|
73
|
+
# ==========================================
|
|
74
|
+
class DaemonManager:
|
|
75
|
+
def __init__(self):
|
|
76
|
+
self.lock_dir = os.path.join(os.getcwd(), '.noho_daemon')
|
|
77
|
+
self.lock_file = os.path.join(self.lock_dir, 'daemon.lock')
|
|
78
|
+
self.pid_file = os.path.join(self.lock_dir, 'daemon.pid')
|
|
79
|
+
self.log_file = os.path.join(self.lock_dir, 'daemon.log')
|
|
80
|
+
self.sessions_file = os.path.join(self.lock_dir, 'sessions.json')
|
|
81
|
+
self._ensure_dir()
|
|
82
|
+
|
|
83
|
+
def _ensure_dir(self):
|
|
84
|
+
if not os.path.exists(self.lock_dir):
|
|
85
|
+
os.makedirs(self.lock_dir, exist_ok=True)
|
|
86
|
+
|
|
87
|
+
def start(self, session_config: Dict[str, Any]) -> bool:
|
|
88
|
+
if self.is_running():
|
|
89
|
+
log('warning', 'الديمون يعمل بالفعل!')
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
self._save_session(session_config)
|
|
93
|
+
bot_script = self._generate_script(session_config)
|
|
94
|
+
bot_file = os.path.join(self.lock_dir, 'running_bot.py')
|
|
95
|
+
|
|
96
|
+
with open(bot_file, 'w', encoding='utf-8') as f:
|
|
97
|
+
f.write(bot_script)
|
|
98
|
+
|
|
99
|
+
# تشغيل في الخلفية
|
|
100
|
+
with open(self.log_file, 'a') as out:
|
|
101
|
+
process = subprocess.Popen(
|
|
102
|
+
[sys.executable, bot_file],
|
|
103
|
+
stdout=out,
|
|
104
|
+
stderr=out,
|
|
105
|
+
stdin=subprocess.DEVNULL,
|
|
106
|
+
start_new_session=True
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# حفظ PID
|
|
110
|
+
with open(self.pid_file, 'w') as f:
|
|
111
|
+
f.write(str(process.pid))
|
|
112
|
+
|
|
113
|
+
with open(self.lock_file, 'w') as f:
|
|
114
|
+
json.dump({
|
|
115
|
+
'pid': process.pid,
|
|
116
|
+
'started_at': datetime.now().isoformat(),
|
|
117
|
+
'platform': session_config['platform'],
|
|
118
|
+
'session_name': session_config['name']
|
|
119
|
+
}, f, indent=2)
|
|
120
|
+
|
|
121
|
+
log('daemon', f'تم التشغيل (PID: {process.pid})')
|
|
122
|
+
log('success', 'البوت يعمل 24/7!')
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
def stop(self) -> bool:
|
|
126
|
+
if not self.is_running():
|
|
127
|
+
log('warning', 'لا يوجد ديمون يعمل')
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
with open(self.pid_file, 'r') as f:
|
|
131
|
+
pid = int(f.read().strip())
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
os.kill(pid, signal.SIGTERM)
|
|
135
|
+
log('success', f'تم الإيقاف (PID: {pid})')
|
|
136
|
+
except ProcessLookupError:
|
|
137
|
+
log('error', 'العملية غير موجودة')
|
|
138
|
+
|
|
139
|
+
if os.path.exists(self.lock_file):
|
|
140
|
+
os.remove(self.lock_file)
|
|
141
|
+
if os.path.exists(self.pid_file):
|
|
142
|
+
os.remove(self.pid_file)
|
|
143
|
+
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
def is_running(self) -> bool:
|
|
147
|
+
if not os.path.exists(self.lock_file) or not os.path.exists(self.pid_file):
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
with open(self.pid_file, 'r') as f:
|
|
152
|
+
pid = int(f.read().strip())
|
|
153
|
+
os.kill(pid, 0) # التحقق من وجود العملية
|
|
154
|
+
return True
|
|
155
|
+
except (ProcessLookupError, ValueError, FileNotFoundError):
|
|
156
|
+
self._cleanup()
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
def _cleanup(self):
|
|
160
|
+
if os.path.exists(self.lock_file):
|
|
161
|
+
os.remove(self.lock_file)
|
|
162
|
+
if os.path.exists(self.pid_file):
|
|
163
|
+
os.remove(self.pid_file)
|
|
164
|
+
|
|
165
|
+
def logs(self, tail: int = 20):
|
|
166
|
+
if not os.path.exists(self.log_file):
|
|
167
|
+
log('error', 'لا توجد سجلات')
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
with open(self.log_file, 'r', encoding='utf-8') as f:
|
|
171
|
+
lines = f.readlines()
|
|
172
|
+
|
|
173
|
+
print(''.join(lines[-tail:]))
|
|
174
|
+
|
|
175
|
+
def _save_session(self, config: Dict[str, Any]):
|
|
176
|
+
with open(self.sessions_file, 'w', encoding='utf-8') as f:
|
|
177
|
+
json.dump(config, f, indent=2)
|
|
178
|
+
|
|
179
|
+
def _generate_script(self, config: Dict[str, Any]) -> str:
|
|
180
|
+
return f'''#!/usr/bin/env python3
|
|
181
|
+
# Auto-generated by NOHO Bot Daemon
|
|
182
|
+
import sys
|
|
183
|
+
sys.path.insert(0, '{os.path.dirname(os.path.abspath(__file__))}')
|
|
184
|
+
from noho_bot import NohoBot
|
|
185
|
+
import time
|
|
186
|
+
|
|
187
|
+
bot = NohoBot()
|
|
188
|
+
|
|
189
|
+
async def main():
|
|
190
|
+
session = await bot.create_async('{config["platform"]}', {{
|
|
191
|
+
'session_path': '{config.get("session_path", "")}',
|
|
192
|
+
'token': '{config.get("token", "")}',
|
|
193
|
+
'auto_reply': True
|
|
194
|
+
}})
|
|
195
|
+
|
|
196
|
+
await session.connect()
|
|
197
|
+
|
|
198
|
+
@session.on_message
|
|
199
|
+
async def handle(msg, from_id, platform):
|
|
200
|
+
print(f'[DAEMON] {{platform}} {{from_id}}: {{msg}}')
|
|
201
|
+
if msg == '.ping':
|
|
202
|
+
await session.send_message(from_id, 'pong! (24/7 mode)')
|
|
203
|
+
|
|
204
|
+
if __name__ == '__main__':
|
|
205
|
+
import asyncio
|
|
206
|
+
while True:
|
|
207
|
+
try:
|
|
208
|
+
asyncio.run(main())
|
|
209
|
+
except Exception as e:
|
|
210
|
+
print(f'Error: {{e}}')
|
|
211
|
+
time.sleep(10)
|
|
212
|
+
'''
|
|
213
|
+
|
|
214
|
+
# ==========================================
|
|
215
|
+
# BASE ADAPTER
|
|
216
|
+
# ==========================================
|
|
217
|
+
class BaseAdapter(ABC):
|
|
218
|
+
def __init__(self, name: str, config: Dict[str, Any] = None):
|
|
219
|
+
self.name = name
|
|
220
|
+
self.config = config or {}
|
|
221
|
+
self.connected = False
|
|
222
|
+
self.auth_data = None
|
|
223
|
+
self._message_handler: Optional[Callable] = None
|
|
224
|
+
|
|
225
|
+
def on_message(self, handler: Callable):
|
|
226
|
+
self._message_handler = handler
|
|
227
|
+
return handler
|
|
228
|
+
|
|
229
|
+
@abstractmethod
|
|
230
|
+
async def connect(self, *args, **kwargs):
|
|
231
|
+
pass
|
|
232
|
+
|
|
233
|
+
@abstractmethod
|
|
234
|
+
async def send_message(self, to: str, text: str):
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
@abstractmethod
|
|
238
|
+
async def disconnect(self):
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
def get_fingerprint(self) -> Dict[str, Any]:
|
|
242
|
+
return {
|
|
243
|
+
'platform': self.name,
|
|
244
|
+
'connected': self.connected,
|
|
245
|
+
'user': self.auth_data.get('user') if self.auth_data else None
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
# ==========================================
|
|
249
|
+
# WHATSAPP ADAPTER (Baileys via subprocess)
|
|
250
|
+
# ==========================================
|
|
251
|
+
class WhatsAppAdapter(BaseAdapter):
|
|
252
|
+
def __init__(self, config: Dict[str, Any] = None):
|
|
253
|
+
super().__init__('whatsapp', config or {})
|
|
254
|
+
self.session_path = self.config.get('session_path', './.noho_sessions/whatsapp')
|
|
255
|
+
self.print_qr = self.config.get('print_qr', True)
|
|
256
|
+
self.qr_code = None
|
|
257
|
+
self.pairing_code = None
|
|
258
|
+
|
|
259
|
+
async def connect(self, method: str = 'qr', phone_number: str = None):
|
|
260
|
+
# في Python نستخدم Node.js للواتساب عبر Baileys
|
|
261
|
+
# أو مكتبة مثل yowsup (لكن Baileys أفضل)
|
|
262
|
+
|
|
263
|
+
log('whatsapp', 'جاري الاتصال... استخدم طريقة QR')
|
|
264
|
+
log('info', 'ملاحظة: WhatsApp في Python يحتاج Node.js للـ Baileys')
|
|
265
|
+
log('info', 'استخدم النسخة JavaScript للواتساب: node noho_bot.js')
|
|
266
|
+
|
|
267
|
+
# محاكاة للتوضيح
|
|
268
|
+
self.connected = True
|
|
269
|
+
self.auth_data = {
|
|
270
|
+
'id': 'whatsapp-user-id',
|
|
271
|
+
'user': {'name': 'WhatsApp User', 'phone': phone_number or 'unknown'}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return self.get_fingerprint()
|
|
275
|
+
|
|
276
|
+
async def send_message(self, to: str, text: str):
|
|
277
|
+
if not self.connected:
|
|
278
|
+
raise Exception('Not connected')
|
|
279
|
+
log('whatsapp', f'إرسال لـ {to}: {text}')
|
|
280
|
+
|
|
281
|
+
async def disconnect(self):
|
|
282
|
+
self.connected = False
|
|
283
|
+
log('whatsapp', 'تم قطع الاتصال')
|
|
284
|
+
|
|
285
|
+
# ==========================================
|
|
286
|
+
# TELEGRAM ADAPTER
|
|
287
|
+
# ==========================================
|
|
288
|
+
class TelegramAdapter(BaseAdapter):
|
|
289
|
+
def __init__(self, config: Dict[str, Any] = None):
|
|
290
|
+
super().__init__('telegram', config or {})
|
|
291
|
+
self.token = self.config.get('token')
|
|
292
|
+
self.app: Optional[Application] = None
|
|
293
|
+
|
|
294
|
+
async def connect(self):
|
|
295
|
+
if not self.token:
|
|
296
|
+
raise Exception('Telegram token required. Get from @BotFather')
|
|
297
|
+
|
|
298
|
+
self.app = Application.builder().token(self.token).build()
|
|
299
|
+
|
|
300
|
+
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
301
|
+
if not update.message or not update.message.text:
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
text = update.message.text
|
|
305
|
+
from_id = str(update.effective_chat.id)
|
|
306
|
+
|
|
307
|
+
if self._message_handler:
|
|
308
|
+
await self._message_handler(text, from_id, 'telegram')
|
|
309
|
+
|
|
310
|
+
self.app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
|
|
311
|
+
|
|
312
|
+
await self.app.initialize()
|
|
313
|
+
await self.app.start()
|
|
314
|
+
|
|
315
|
+
# الحصول على معلومات البوت
|
|
316
|
+
bot_info = await self.app.bot.get_me()
|
|
317
|
+
self.connected = True
|
|
318
|
+
self.auth_data = {
|
|
319
|
+
'id': str(bot_info.id),
|
|
320
|
+
'user': {
|
|
321
|
+
'name': bot_info.first_name,
|
|
322
|
+
'username': bot_info.username
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
log('telegram', f'متصل: @{bot_info.username}')
|
|
327
|
+
return self.get_fingerprint()
|
|
328
|
+
|
|
329
|
+
async def send_message(self, to: str, text: str):
|
|
330
|
+
if not self.connected or not self.app:
|
|
331
|
+
raise Exception('Not connected')
|
|
332
|
+
await self.app.bot.send_message(chat_id=int(to), text=text)
|
|
333
|
+
|
|
334
|
+
async def disconnect(self):
|
|
335
|
+
if self.app:
|
|
336
|
+
await self.app.stop()
|
|
337
|
+
self.app = None
|
|
338
|
+
self.connected = False
|
|
339
|
+
log('telegram', 'تم قطع الاتصال')
|
|
340
|
+
|
|
341
|
+
# ==========================================
|
|
342
|
+
# DISCORD ADAPTER
|
|
343
|
+
# ==========================================
|
|
344
|
+
class DiscordAdapter(BaseAdapter):
|
|
345
|
+
def __init__(self, config: Dict[str, Any] = None):
|
|
346
|
+
super().__init__('discord', config or {})
|
|
347
|
+
self.token = self.config.get('token')
|
|
348
|
+
self.client: Optional[commands.Bot] = None
|
|
349
|
+
|
|
350
|
+
async def connect(self):
|
|
351
|
+
if not self.token:
|
|
352
|
+
raise Exception('Discord token required from Developer Portal')
|
|
353
|
+
|
|
354
|
+
intents = discord.Intents.default()
|
|
355
|
+
intents.message_content = True
|
|
356
|
+
|
|
357
|
+
self.client = commands.Bot(command_prefix='!', intents=intents)
|
|
358
|
+
|
|
359
|
+
@self.client.event
|
|
360
|
+
async def on_ready():
|
|
361
|
+
self.connected = True
|
|
362
|
+
self.auth_data = {
|
|
363
|
+
'id': str(self.client.user.id),
|
|
364
|
+
'user': {
|
|
365
|
+
'name': self.client.user.name,
|
|
366
|
+
'tag': str(self.client.user)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
log('discord', f'متصل: {self.client.user}')
|
|
370
|
+
|
|
371
|
+
@self.client.event
|
|
372
|
+
async def on_message(message: discord.Message):
|
|
373
|
+
if message.author.bot:
|
|
374
|
+
return
|
|
375
|
+
|
|
376
|
+
if self._message_handler:
|
|
377
|
+
await self._message_handler(message.content, str(message.channel.id), 'discord')
|
|
378
|
+
|
|
379
|
+
await self.client.start(self.token)
|
|
380
|
+
return self.get_fingerprint()
|
|
381
|
+
|
|
382
|
+
async def send_message(self, to: str, text: str):
|
|
383
|
+
if not self.connected or not self.client:
|
|
384
|
+
raise Exception('Not connected')
|
|
385
|
+
channel = self.client.get_channel(int(to))
|
|
386
|
+
if channel:
|
|
387
|
+
await channel.send(text)
|
|
388
|
+
|
|
389
|
+
async def disconnect(self):
|
|
390
|
+
if self.client:
|
|
391
|
+
await self.client.close()
|
|
392
|
+
self.client = None
|
|
393
|
+
self.connected = False
|
|
394
|
+
log('discord', 'تم قطع الاتصال')
|
|
395
|
+
|
|
396
|
+
# ==========================================
|
|
397
|
+
# SLACK ADAPTER
|
|
398
|
+
# ==========================================
|
|
399
|
+
class SlackAdapter(BaseAdapter):
|
|
400
|
+
def __init__(self, config: Dict[str, Any] = None):
|
|
401
|
+
super().__init__('slack', config or {})
|
|
402
|
+
self.token = self.config.get('token')
|
|
403
|
+
self.client: Optional[WebClient] = None
|
|
404
|
+
|
|
405
|
+
async def connect(self):
|
|
406
|
+
if not self.token:
|
|
407
|
+
raise Exception('Slack token required from api.slack.com')
|
|
408
|
+
|
|
409
|
+
self.client = WebClient(token=self.token)
|
|
410
|
+
|
|
411
|
+
try:
|
|
412
|
+
auth = self.client.auth_test()
|
|
413
|
+
self.connected = True
|
|
414
|
+
self.auth_data = {
|
|
415
|
+
'id': auth['user_id'],
|
|
416
|
+
'user': {
|
|
417
|
+
'name': auth['user'],
|
|
418
|
+
'team': auth['team']
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
log('slack', f'متصل: {auth["user"]} @ {auth["team"]}')
|
|
422
|
+
return self.get_fingerprint()
|
|
423
|
+
except SlackApiError as e:
|
|
424
|
+
raise Exception(f'Slack error: {e.response["error"]}')
|
|
425
|
+
|
|
426
|
+
async def send_message(self, channel: str, text: str):
|
|
427
|
+
if not self.connected or not self.client:
|
|
428
|
+
raise Exception('Not connected')
|
|
429
|
+
try:
|
|
430
|
+
self.client.chat_postMessage(channel=channel, text=text)
|
|
431
|
+
except SlackApiError as e:
|
|
432
|
+
log('error', f'Failed to send: {e.response["error"]}')
|
|
433
|
+
|
|
434
|
+
async def disconnect(self):
|
|
435
|
+
self.connected = False
|
|
436
|
+
self.client = None
|
|
437
|
+
log('slack', 'تم قطع الاتصال')
|
|
438
|
+
|
|
439
|
+
# ==========================================
|
|
440
|
+
# FACEBOOK ADAPTER
|
|
441
|
+
# ==========================================
|
|
442
|
+
class FacebookAdapter(BaseAdapter):
|
|
443
|
+
def __init__(self, config: Dict[str, Any] = None):
|
|
444
|
+
super().__init__('facebook', config or {})
|
|
445
|
+
self.app_id = self.config.get('app_id')
|
|
446
|
+
self.app_secret = self.config.get('app_secret')
|
|
447
|
+
self.access_token = None
|
|
448
|
+
|
|
449
|
+
async def connect(self):
|
|
450
|
+
if not self.app_id or not self.app_secret:
|
|
451
|
+
raise Exception('Facebook App ID and Secret required')
|
|
452
|
+
|
|
453
|
+
url = f'https://graph.facebook.com/oauth/access_token'
|
|
454
|
+
params = {
|
|
455
|
+
'client_id': self.app_id,
|
|
456
|
+
'client_secret': self.app_secret,
|
|
457
|
+
'grant_type': 'client_credentials'
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
response = requests.get(url, params=params)
|
|
461
|
+
data = response.json()
|
|
462
|
+
|
|
463
|
+
if 'access_token' not in data:
|
|
464
|
+
raise Exception(f'Facebook auth failed: {data}')
|
|
465
|
+
|
|
466
|
+
self.access_token = data['access_token']
|
|
467
|
+
self.connected = True
|
|
468
|
+
self.auth_data = {
|
|
469
|
+
'id': self.app_id,
|
|
470
|
+
'user': {'app_id': self.app_id}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
log('facebook', 'متصل')
|
|
474
|
+
return self.get_fingerprint()
|
|
475
|
+
|
|
476
|
+
async def send_message(self, to: str, text: str):
|
|
477
|
+
if not self.connected or not self.access_token:
|
|
478
|
+
raise Exception('Not connected')
|
|
479
|
+
|
|
480
|
+
url = f'https://graph.facebook.com/v18.0/me/messages'
|
|
481
|
+
params = {'access_token': self.access_token}
|
|
482
|
+
data = {
|
|
483
|
+
'recipient': {'id': to},
|
|
484
|
+
'message': {'text': text}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
response = requests.post(url, params=params, json=data)
|
|
488
|
+
if response.status_code != 200:
|
|
489
|
+
log('error', f'Failed to send: {response.text}')
|
|
490
|
+
|
|
491
|
+
async def disconnect(self):
|
|
492
|
+
self.connected = False
|
|
493
|
+
self.access_token = None
|
|
494
|
+
log('facebook', 'تم قطع الاتصال')
|
|
495
|
+
|
|
496
|
+
# ==========================================
|
|
497
|
+
# NOHO USER SYSTEM
|
|
498
|
+
# ==========================================
|
|
499
|
+
class NohoUser:
|
|
500
|
+
def __init__(self):
|
|
501
|
+
self.data_path = os.path.join(os.getcwd(), '.noho')
|
|
502
|
+
self.user_file = os.path.join(self.data_path, 'user.json')
|
|
503
|
+
self.projects_file = os.path.join(self.data_path, 'projects.json')
|
|
504
|
+
self.api_file = os.path.join(self.data_path, 'api.key')
|
|
505
|
+
self._ensure_dir()
|
|
506
|
+
|
|
507
|
+
def _ensure_dir(self):
|
|
508
|
+
if not os.path.exists(self.data_path):
|
|
509
|
+
os.makedirs(self.data_path, exist_ok=True)
|
|
510
|
+
|
|
511
|
+
def register(self, username: str, password: str) -> bool:
|
|
512
|
+
if self.exists():
|
|
513
|
+
log('error', 'أنت مسجل بالفعل!')
|
|
514
|
+
return False
|
|
515
|
+
|
|
516
|
+
api_key = 'NPI-' + secrets.token_hex(32).upper()
|
|
517
|
+
user_data = {
|
|
518
|
+
'username': username,
|
|
519
|
+
'password': hashlib.sha256(password.encode()).hexdigest(),
|
|
520
|
+
'api_key': api_key,
|
|
521
|
+
'created_at': datetime.now().isoformat()
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
with open(self.user_file, 'w', encoding='utf-8') as f:
|
|
525
|
+
json.dump(user_data, f, indent=2)
|
|
526
|
+
|
|
527
|
+
with open(self.api_file, 'w') as f:
|
|
528
|
+
f.write(api_key)
|
|
529
|
+
|
|
530
|
+
log('success', f'تم التسجيل: {username}')
|
|
531
|
+
log('bot', f'API: {api_key[:20]}...')
|
|
532
|
+
return True
|
|
533
|
+
|
|
534
|
+
def login(self, username: str, password: str) -> bool:
|
|
535
|
+
if not self.exists():
|
|
536
|
+
log('error', 'ليس لديك حساب!')
|
|
537
|
+
return False
|
|
538
|
+
|
|
539
|
+
with open(self.user_file, 'r', encoding='utf-8') as f:
|
|
540
|
+
user = json.load(f)
|
|
541
|
+
|
|
542
|
+
password_hash = hashlib.sha256(password.encode()).hexdigest()
|
|
543
|
+
|
|
544
|
+
if user['username'] != username or user['password'] != password_hash:
|
|
545
|
+
log('error', 'بيانات غير صحيحة')
|
|
546
|
+
return False
|
|
547
|
+
|
|
548
|
+
log('success', f'مرحباً {username}!')
|
|
549
|
+
return True
|
|
550
|
+
|
|
551
|
+
def get_info(self) -> Optional[Dict[str, Any]]:
|
|
552
|
+
if not self.exists():
|
|
553
|
+
return None
|
|
554
|
+
|
|
555
|
+
with open(self.user_file, 'r', encoding='utf-8') as f:
|
|
556
|
+
user = json.load(f)
|
|
557
|
+
|
|
558
|
+
return {
|
|
559
|
+
'username': user['username'],
|
|
560
|
+
'api_key': user['api_key'][:20] + '...',
|
|
561
|
+
'created_at': user['created_at']
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
def renew_api(self) -> Optional[str]:
|
|
565
|
+
if not self.exists():
|
|
566
|
+
return None
|
|
567
|
+
|
|
568
|
+
new_api = 'NPI-' + secrets.token_hex(32).upper()
|
|
569
|
+
|
|
570
|
+
with open(self.user_file, 'r', encoding='utf-8') as f:
|
|
571
|
+
user = json.load(f)
|
|
572
|
+
|
|
573
|
+
user['api_key'] = new_api
|
|
574
|
+
user['updated_at'] = datetime.now().isoformat()
|
|
575
|
+
|
|
576
|
+
with open(self.user_file, 'w', encoding='utf-8') as f:
|
|
577
|
+
json.dump(user, f, indent=2)
|
|
578
|
+
|
|
579
|
+
with open(self.api_file, 'w') as f:
|
|
580
|
+
f.write(new_api)
|
|
581
|
+
|
|
582
|
+
log('success', 'تم تجديد API!')
|
|
583
|
+
return new_api
|
|
584
|
+
|
|
585
|
+
def save_project(self, name: str, platform: str, config: Dict[str, Any]):
|
|
586
|
+
projects = []
|
|
587
|
+
if os.path.exists(self.projects_file):
|
|
588
|
+
with open(self.projects_file, 'r', encoding='utf-8') as f:
|
|
589
|
+
projects = json.load(f)
|
|
590
|
+
|
|
591
|
+
projects.append({
|
|
592
|
+
'name': name,
|
|
593
|
+
'platform': platform,
|
|
594
|
+
'config': config,
|
|
595
|
+
'created_at': datetime.now().isoformat()
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
with open(self.projects_file, 'w', encoding='utf-8') as f:
|
|
599
|
+
json.dump(projects, f, indent=2)
|
|
600
|
+
|
|
601
|
+
def exists(self) -> bool:
|
|
602
|
+
return os.path.exists(self.user_file)
|
|
603
|
+
|
|
604
|
+
# ==========================================
|
|
605
|
+
# CODE CHECKER
|
|
606
|
+
# ==========================================
|
|
607
|
+
class CodeChecker:
|
|
608
|
+
def check(self, file_path: str) -> bool:
|
|
609
|
+
if not os.path.exists(file_path):
|
|
610
|
+
log('error', f'الملف غير موجود: {file_path}')
|
|
611
|
+
return False
|
|
612
|
+
|
|
613
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
614
|
+
code = f.read()
|
|
615
|
+
|
|
616
|
+
issues = []
|
|
617
|
+
warnings = []
|
|
618
|
+
|
|
619
|
+
if 'print(' in code and '# debug' not in code:
|
|
620
|
+
warnings.append('يوجد print() - احذفها قبل النشر')
|
|
621
|
+
|
|
622
|
+
if 'try:' not in code and 'await ' in code:
|
|
623
|
+
warnings.append('يفضل استخدام try-except')
|
|
624
|
+
|
|
625
|
+
if 'eval(' in code:
|
|
626
|
+
issues.append('⚠️ خطير: eval() غير آمن!')
|
|
627
|
+
|
|
628
|
+
if 'password' in code and 'hash' not in code:
|
|
629
|
+
warnings.append('تأكد من تشفير كلمات المرور')
|
|
630
|
+
|
|
631
|
+
print(f"\n{Colors.CYAN}═══ فحص الكود ═══{Colors.RESET}")
|
|
632
|
+
print(f"الملف: {file_path}")
|
|
633
|
+
print(f"الأسطر: {len(code.split(chr(10)))}")
|
|
634
|
+
|
|
635
|
+
if not issues and not warnings:
|
|
636
|
+
log('success', 'الكود نظيف! ✅')
|
|
637
|
+
return True
|
|
638
|
+
|
|
639
|
+
for i in issues:
|
|
640
|
+
print(f" ❌ {i}")
|
|
641
|
+
for w in warnings:
|
|
642
|
+
print(f" ⚠️ {w}")
|
|
643
|
+
|
|
644
|
+
return len(issues) == 0
|
|
645
|
+
|
|
646
|
+
# ==========================================
|
|
647
|
+
# MAIN NOHO BOT CLASS
|
|
648
|
+
# ==========================================
|
|
649
|
+
class NohoBot:
|
|
650
|
+
def __init__(self):
|
|
651
|
+
self.user = NohoUser()
|
|
652
|
+
self.checker = CodeChecker()
|
|
653
|
+
self.daemon = DaemonManager()
|
|
654
|
+
self.adapters = {
|
|
655
|
+
'whatsapp': WhatsAppAdapter,
|
|
656
|
+
'telegram': TelegramAdapter,
|
|
657
|
+
'discord': DiscordAdapter,
|
|
658
|
+
'slack': SlackAdapter,
|
|
659
|
+
'facebook': FacebookAdapter
|
|
660
|
+
}
|
|
661
|
+
self._setup_cli()
|
|
662
|
+
|
|
663
|
+
def _setup_cli(self):
|
|
664
|
+
args = sys.argv[1:]
|
|
665
|
+
cmd = args[0] if args else 'help'
|
|
666
|
+
|
|
667
|
+
commands = {
|
|
668
|
+
'.longin': lambda: self._cmd_login(args[1], args[2]) if len(args) >= 3 else self._print_usage('.longin <user> <pass>'),
|
|
669
|
+
'.d.noho': self._cmd_info,
|
|
670
|
+
'NPI': self._cmd_npi,
|
|
671
|
+
'.m': lambda: self._cmd_check(args[1]) if len(args) >= 2 else self._print_usage('.m <file.py>'),
|
|
672
|
+
'.link': self._cmd_list_platforms,
|
|
673
|
+
'.noho.lock.link': lambda: self._cmd_daemon_start(args[1], args[2], args[3] if len(args) > 3 else None) if len(args) >= 3 else self._print_usage('.noho.lock.link <platform> <name> [method]'),
|
|
674
|
+
'.noho.stop': self.daemon.stop,
|
|
675
|
+
'.noho.status': self._cmd_daemon_status,
|
|
676
|
+
'.noho.logs': lambda: self.daemon.logs(int(args[1]) if len(args) > 1 else 20),
|
|
677
|
+
'help': self._show_help
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
handler = commands.get(cmd, self._show_help)
|
|
681
|
+
handler()
|
|
682
|
+
|
|
683
|
+
def _print_usage(self, usage: str):
|
|
684
|
+
print(f"الاستخدام: {usage}")
|
|
685
|
+
|
|
686
|
+
def _cmd_login(self, username: str, password: str):
|
|
687
|
+
if self.user.exists():
|
|
688
|
+
self.user.login(username, password)
|
|
689
|
+
else:
|
|
690
|
+
self.user.register(username, password)
|
|
691
|
+
|
|
692
|
+
def _cmd_info(self):
|
|
693
|
+
info = self.user.get_info()
|
|
694
|
+
if not info:
|
|
695
|
+
log('error', 'ليس لديك حساب! استخدم: .longin <user> <pass>')
|
|
696
|
+
return
|
|
697
|
+
|
|
698
|
+
print(f"\n{Colors.CYAN}═══ حساب NOHO ═══{Colors.RESET}")
|
|
699
|
+
print(f"المستخدم: {info['username']}")
|
|
700
|
+
print(f"API: {info['api_key']}")
|
|
701
|
+
print(f"منذ: {info['created_at']}")
|
|
702
|
+
|
|
703
|
+
def _cmd_npi(self):
|
|
704
|
+
if not self.user.exists():
|
|
705
|
+
log('error', 'سجل دخولك أولاً')
|
|
706
|
+
return
|
|
707
|
+
new_api = self.user.renew_api()
|
|
708
|
+
if new_api:
|
|
709
|
+
print(f"API جديد: {new_api}")
|
|
710
|
+
|
|
711
|
+
def _cmd_check(self, file_path: str):
|
|
712
|
+
self.checker.check(file_path)
|
|
713
|
+
|
|
714
|
+
def _cmd_list_platforms(self):
|
|
715
|
+
print(f"\n{Colors.CYAN}═══ المنصات المتاحة ═══{Colors.RESET}")
|
|
716
|
+
print("1. whatsapp - QR Code / Pairing Code (يجب استخدام JS)")
|
|
717
|
+
print("2. telegram - Bot Token (@BotFather)")
|
|
718
|
+
print("3. discord - Bot Token (Discord Dev)")
|
|
719
|
+
print("4. slack - App Token (api.slack.com)")
|
|
720
|
+
print("5. facebook - App ID + Secret (developers.facebook.com)")
|
|
721
|
+
|
|
722
|
+
async def _cmd_daemon_start(self, platform: str, name: str, method: str = None):
|
|
723
|
+
if not platform or not name:
|
|
724
|
+
print("الاستخدام: .noho.lock.link <platform> <name> [method]")
|
|
725
|
+
return
|
|
726
|
+
|
|
727
|
+
if not self.user.exists():
|
|
728
|
+
log('error', 'سجل دخولك أولاً: .longin <user> <pass>')
|
|
729
|
+
return
|
|
730
|
+
|
|
731
|
+
session_config = {
|
|
732
|
+
'platform': platform.lower(),
|
|
733
|
+
'name': name,
|
|
734
|
+
'method': method or 'token',
|
|
735
|
+
'session_path': os.path.join(os.getcwd(), '.noho_sessions', name),
|
|
736
|
+
'created_at': datetime.now().isoformat()
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
self.user.save_project(name, platform, session_config)
|
|
740
|
+
|
|
741
|
+
if platform == 'whatsapp':
|
|
742
|
+
log('warning', 'WhatsApp يحتاج Node.js. استخدم: node noho_bot.js .noho.lock.link whatsapp ' + name)
|
|
743
|
+
return
|
|
744
|
+
|
|
745
|
+
self.daemon.start(session_config)
|
|
746
|
+
|
|
747
|
+
def _cmd_daemon_status(self):
|
|
748
|
+
if self.daemon.is_running():
|
|
749
|
+
log('success', 'الديمون يعمل ✅')
|
|
750
|
+
with open(self.daemon.lock_file, 'r') as f:
|
|
751
|
+
lock = json.load(f)
|
|
752
|
+
print(f"المنصة: {lock['platform']}")
|
|
753
|
+
print(f"الاسم: {lock['session_name']}")
|
|
754
|
+
print(f"منذ: {lock['started_at']}")
|
|
755
|
+
else:
|
|
756
|
+
log('error', 'الديمون متوقف ❌')
|
|
757
|
+
|
|
758
|
+
def _show_help(self):
|
|
759
|
+
print(f"""
|
|
760
|
+
{Colors.CYAN}╔══════════════════════════════════════╗{Colors.RESET}
|
|
761
|
+
{Colors.CYAN}║ NOHO Bot Library v1.0 (Python) 🐍 ║{Colors.RESET}
|
|
762
|
+
{Colors.CYAN}╚══════════════════════════════════════╝{Colors.RESET}
|
|
763
|
+
|
|
764
|
+
👤 المستخدم:
|
|
765
|
+
.longin <u> <p> تسجيل/دخول
|
|
766
|
+
.d.noho بيانات الحساب
|
|
767
|
+
NPI تجديد API
|
|
768
|
+
|
|
769
|
+
🔍 الكود:
|
|
770
|
+
.m <file.py> فحص الكود
|
|
771
|
+
|
|
772
|
+
🔗 الربط:
|
|
773
|
+
.link عرض المنصات
|
|
774
|
+
|
|
775
|
+
👻 الديمون (24/7):
|
|
776
|
+
.noho.lock.link <p> <n> [m] تشغيل
|
|
777
|
+
.noho.stop إيقاف
|
|
778
|
+
.noho.status الحالة
|
|
779
|
+
.noho.logs [n] السجلات
|
|
780
|
+
|
|
781
|
+
💡 مثال:
|
|
782
|
+
python noho_bot.py .longin omar 123456
|
|
783
|
+
python noho_bot.py .noho.lock.link telegram mybot
|
|
784
|
+
""")
|
|
785
|
+
|
|
786
|
+
# Async method for programmatic use
|
|
787
|
+
async def create_async(self, platform: str, config: Dict[str, Any] = None):
|
|
788
|
+
adapter_class = self.adapters.get(platform)
|
|
789
|
+
if not adapter_class:
|
|
790
|
+
raise Exception(f"المنصة {platform} غير مدعومة")
|
|
791
|
+
|
|
792
|
+
adapter = adapter_class(config or {})
|
|
793
|
+
|
|
794
|
+
return {
|
|
795
|
+
'connect': adapter.connect,
|
|
796
|
+
'disconnect': adapter.disconnect,
|
|
797
|
+
'send_message': adapter.send_message,
|
|
798
|
+
'on_message': adapter.on_message,
|
|
799
|
+
'get_fingerprint': adapter.get_fingerprint,
|
|
800
|
+
'adapter': adapter
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
def create(self, platform: str, config: Dict[str, Any] = None):
|
|
804
|
+
"""Synchronous wrapper for create_async"""
|
|
805
|
+
import asyncio
|
|
806
|
+
return asyncio.run(self.create_async(platform, config))
|
|
807
|
+
|
|
808
|
+
# ==========================================
|
|
809
|
+
# ENTRY POINT
|
|
810
|
+
# ==========================================
|
|
811
|
+
if __name__ == '__main__':
|
|
812
|
+
NohoBot()
|