Spaces:
No application file
No application file
| import gradio as gr | |
| import datetime | |
| import json | |
| import re | |
| import pycountry | |
| from curl_cffi import requests | |
| from lxml import html | |
| # --- كلاس TikTokUser (يبقى كما هو) --- | |
| class TikTokUser: | |
| def __init__(self, data) -> None: | |
| user = data.get("user", {}) | |
| stats = data.get("stats", {}) | |
| self.username = user.get("uniqueId", "N/A") | |
| self.name = user.get("nickname", "N/A") | |
| self.avatar = user.get("avatarMedium", "") | |
| self.bio = user.get("signature", "لا توجد سيرة ذاتية.") | |
| self.created = user.get("createTime", None) | |
| self.verified = user.get("verified", False) | |
| self.private = user.get("privateAccount", False) | |
| self.country_code = user.get("region", "N/A") | |
| self.language_code = user.get("language", "N/A") | |
| self.followers = stats.get("followerCount", 0) | |
| self.following = stats.get("followingCount", 0) | |
| self.hearts = stats.get("heart", 0) | |
| self.video_count = stats.get("videoCount", 0) | |
| self.friends = stats.get("friendCount", 0) | |
| if self.created: | |
| try: | |
| self.created = datetime.datetime.fromtimestamp(self.created).strftime('%Y-%m-%d %H:%M:%S') | |
| except (TypeError, ValueError): | |
| self.created = "غير معروف" | |
| else: | |
| self.created = "غير معروف" | |
| self.country = "غير معروف" | |
| if self.country_code != "N/A": | |
| try: | |
| self.country = pycountry.countries.get(alpha_2=self.country_code.upper()).name | |
| except AttributeError: | |
| self.country = f"رمز ({self.country_code})" | |
| self.language = "غير معروف" | |
| if self.language_code != "N/A": | |
| try: | |
| self.language = pycountry.languages.get(alpha_2=self.language_code.lower()).name | |
| except AttributeError: | |
| self.language = f"رمز ({self.language_code})" | |
| if bio_link := user.get("bioLink", {}): | |
| if link_url := bio_link.get("link"): | |
| self.bio += f"\n\n🔗 رابط السيرة الذاتية: {link_url}" | |
| self.followers_formatted = f"{self.followers:,}" | |
| self.following_formatted = f"{self.following:,}" | |
| self.hearts_formatted = f"{self.hearts:,}" | |
| self.video_count_formatted = f"{self.video_count:,}" | |
| self.friends_formatted = f"{self.friends:,}" | |
| # --- دالة get_user_info (تبقى كما هي، مع تعديل بسيط للتعامل مع الأخطاء لـ Gradio) --- | |
| def get_user_info_from_tiktok(username): | |
| username = re.sub(r"^.*@", "", username).strip() | |
| if not username: | |
| return "خطأ: اسم المستخدم لا يمكن أن يكون فارغاً." | |
| url = f"https://www.tiktok.com/@{username}" | |
| print(f"محاولة جلب البيانات من: {url}") | |
| try: | |
| r = requests.get(url, impersonate="chrome", timeout=15) | |
| r.raise_for_status() | |
| tree = html.fromstring(r.text, parser=html.HTMLParser(encoding="utf8")) | |
| script_tag = tree.xpath('//script[@id="__UNIVERSAL_DATA_FOR_REHYDRATION__"]') | |
| if not script_tag: | |
| return ("خطأ: لم يتم العثور على وسم البيانات في الصفحة. " | |
| "قد يكون هيكل الصفحة قد تغير، أو تم حظر الطلب.") | |
| data = json.loads(script_tag[0].text) | |
| user_info_data = data.get("__DEFAULT_SCOPE__", {})\ | |
| .get("webapp.user-detail", {})\ | |
| .get("userInfo") | |
| if not user_info_data: | |
| return (f"خطأ: لم يتم العثور على معلومات لمستخدم تيك توك: '{username}'. " | |
| "تأكد من صحة اسم المستخدم وأنه حساب عام.") | |
| user = TikTokUser(user_info_data) | |
| # تنسيق المخرجات لـ Gradio | |
| output_html = f""" | |
| <div style="direction: rtl; text-align: right; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; padding: 20px; border-radius: 8px; background-color: #f9f9f9; box-shadow: 0 4px 15px rgba(0,0,0,0.1);"> | |
| <div style="display: flex; align-items: center; gap: 20px; margin-bottom: 20px; flex-wrap: wrap; justify-content: flex-end;"> | |
| <img src="{user.avatar if user.avatar else 'https://via.placeholder.com/120/CCCCCC/000000?text=لا+صورة'}" alt="صورة الملف الشخصي" style="width: 120px; height: 120px; border-radius: 50%; object-fit: cover; border: 3px solid #FE2C55; box-shadow: 0 2px 8px rgba(0,0,0,0.15);"> | |
| <div style="text-align: right; flex-grow: 1;"> | |
| <h2 style="margin: 0; color: #222; font-size: 2em;"> | |
| {user.name} | |
| {"<span style='color: #4CAF50; font-weight: bold; margin-right: 8px;'>✅ (موثق)</span>" if user.verified else ""} | |
| {"<span style='color: #FFC107; font-weight: bold; margin-right: 8px;'>🔒 (خاص)</span>" if user.private else ""} | |
| </h2> | |
| <p style="margin: 5px 0 0 0; color: #555; font-size: 1.1em;">{user.username}</p> | |
| </div> | |
| </div> | |
| <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 15px; margin-top: 20px;"> | |
| <div style="background-color: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); text-align: center;"> | |
| <strong style="display: block; font-size: 1.6em; color: #FE2C55; margin-bottom: 5px;">{user.followers_formatted}</strong> | |
| <span style="font-size: 0.9em; color: #777;">متابع</span> | |
| </div> | |
| <div style="background-color: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); text-align: center;"> | |
| <strong style="display: block; font-size: 1.6em; color: #FE2C55; margin-bottom: 5px;">{user.following_formatted}</strong> | |
| <span style="font-size: 0.9em; color: #777;">يتابع</span> | |
| </div> | |
| <div style="background-color: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); text-align: center;"> | |
| <strong style="display: block; font-size: 1.6em; color: #FE2C55; margin-bottom: 5px;">{user.hearts_formatted}</strong> | |
| <span style="font-size: 0.9em; color: #777;">إعجاب</span> | |
| </div> | |
| <div style="background-color: #fff; padding: 15px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); text-align: center;"> | |
| <strong style="display: block; font-size: 1.6em; color: #FE2C55; margin-bottom: 5px;">{user.video_count_formatted}</strong> | |
| <span style="font-size: 0.9em; color: #777;">فيديو</span> | |
| </div> | |
| </div> | |
| <div style="margin-top: 25px; padding: 15px; background-color: #eef; border-radius: 8px; text-align: right; border-top: 1px solid #e0e0e0;"> | |
| <h3 style="margin-top: 0; color: #444; font-size: 1.3em; border-bottom: 2px solid #FE2C55; padding-bottom: 5px; display: inline-block;">السيرة الذاتية:</h3> | |
| <p style="margin: 10px 0; line-height: 1.6; color: #444; white-space: pre-wrap; word-wrap: break-word;">{user.bio}</p> | |
| </div> | |
| <div style="margin-top: 25px; padding: 15px; background-color: #fdfdfd; border-radius: 8px; text-align: right; border-top: 1px solid #e0e0e0;"> | |
| <h3 style="margin-top: 0; color: #444; font-size: 1.3em; border-bottom: 2px solid #FE2C55; padding-bottom: 5px; display: inline-block;">معلومات إضافية:</h3> | |
| <p style="margin: 8px 0; line-height: 1.5; color: #555;"><strong>الدولة:</strong> {user.country}</p> | |
| <p style="margin: 8px 0; line-height: 1.5; color: #555;"><strong>اللغة:</strong> {user.language}</p> | |
| <p style="margin: 8px 0; line-height: 1.5; color: #555;"><strong>تاريخ الإنشاء:</strong> {user.created}</p> | |
| </div> | |
| <p style="font-size: 0.9em; color: #a00; margin-top: 30px; padding-top: 15px; border-top: 1px dashed #f00; text-align: center; font-weight: bold;"> | |
| ⚠️ ملاحظة هامة: هذا التطبيق يقوم بمحاولة استخلاص البيانات مباشرة من صفحة تيك توك. | |
| <span style="display: block; margin-top: 5px; font-size: 0.8em; color: #c00;">هذه الطريقة غير رسمية وقد تتوقف عن العمل في أي وقت بسبب تغييرات تيك توك.</span> | |
| <span style="display: block; margin-top: 5px; font-size: 0.8em; color: #c00;">كما أنها قد تتعارض مع شروط خدمة تيك توك، وتؤدي إلى حظر عنوان IP الخاص بك.</span> | |
| <span style="display: block; margin-top: 5px; font-size: 0.8em; color: #c00;">استخدم على مسؤوليتك الخاصة ولأغراض تعليمية فقط.</span> | |
| </p> | |
| </div> | |
| """ | |
| return output_html | |
| except Exception as e: | |
| return f"حدث خطأ: {str(e)}. يرجى المحاولة مرة أخرى باسم مستخدم صحيح وتأكد من أن الحساب عام." | |
| # --- واجهة Gradio --- | |
| iface = gr.Interface( | |
| fn=get_user_info_from_tiktok, # الدالة التي ستقوم بالعمل | |
| inputs=gr.Textbox(label="أدخل اسم مستخدم تيك توك (بدون @)", placeholder="مثال: cristiano"), # مدخل نصي | |
| outputs=gr.HTML(label="معلومات مستخدم تيك توك"), # مخرج HTML لعرض المعلومات المنسقة | |
| title="جالب معلومات مستخدم تيك توك (تجريبي)", | |
| description="قم بإدخال اسم مستخدم تيك توك لمحاولة جلب معلوماته. هذه الطريقة تعتمد على الاستخلاص وقد تفشل.", | |
| theme="soft", # يمكنك تغيير الثيم | |
| allow_flagging="never" # لا يسمح بوضع علامات على النتائج | |
| ) | |
| if __name__ == "__main__": | |
| iface.launch() | |
| # local_host=True | |
| # share=True # لإنشاء رابط مشاركة مؤقت إذا كنت تختبر محلياً |