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/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()