Skip to content

Repository files navigation

هیدرا سندر (Hydra Sender)

English README →

یک قالب برای ربات‌های خزنده و اطلاع‌رسانِ زمان‌بندی‌شده: آن را به یک منبع (یک سایت/API که برای آگهی‌های جدید بررسی می‌کنید) وصل می‌کنید و هر مورد جدید — همراه با جزئیات کامل و هشتگ‌های خودکار — به هر تعداد گیرنده (تلگرام، بله، روبیکا، ایتا یا هرکدام که خودتان اضافه کنید) ارسال می‌شود.

به‌صورت پیش‌فرض یک منبع دیوار کاملاً کاربردی دارد، اما لایهٔ منبع و لایهٔ ارسال‌کننده‌ها از طریق دو رابط سادهٔ برنامه‌نویسی (interface) کاملاً از هم جدا شده‌اند. یعنی می‌توانید این ریپازیتوری را فورک کنید و به یک منبع دیگر وصلش کنید، بدون اینکه لازم باشد منطق ارسال را دست بزنید.

منبع دیوار، نسخهٔ به‌شدت تغییریافته‌ای از debMan/divar-telegram-bot (که خودش از ehcaning/divar-telegram-bot گرفته شده) است. از زمان نوشته‌شدن پروژهٔ اصلی، API غیررسمی دیوار تغییر کرده، بنابراین منطق خزیدن اینجا کاملاً متفاوت است.

امکانات

  • روی گیت‌هاب اکشنز اجرا می‌شود — نیازی به هاست یا سرور جداگانه نیست. یک ورک‌فلوی زمان‌بندی‌شده ربات را هر چند دقیقه یک‌بار اجرا می‌کند.
  • جداسازی منبع از گیرندهmain.py فقط با یک رابط Source و لیستی از Senderها صحبت می‌کند؛ هیچ‌کدام از وجود دیگری خبر ندارند (به بخش معماری نگاه کنید).
  • جست‌وجوی چندشهری (منبع دیوار) — جست‌وجوی هم‌زمان در چند شهر (SEARCH_CITY_IDS).
  • جزئیات کامل آگهی (منبع دیوار) — فیلدهای ساختاریافته‌ای که خود صفحهٔ آگهی در دیوار نشان می‌دهد (متراژ، تعداد اتاق، ظرفیت، نرخ شبانه و امکانات) را می‌گیرد، نه فقط عنوان/قیمت/توضیحات.
  • هشتگ خودکار (منبع دیوار) — ترکیبی از هشتگ‌های مبتنی بر کلیدواژه در متن آگهی و مسیر دسته‌بندی خود دیوار.
  • قالب‌بندی آمادهٔ کانال — عکس‌ها/آلبوم‌ها را با کپشن HTML و یک بلوک تماس/فوتر ثابت ارسال می‌کند، بدون لینک مستقیم خروجی.
  • ارسال چندپیام‌رسانه‌ای — تلگرام عکس/آلبوم کامل دریافت می‌کند؛ بله، روبیکا و ایتا متن + اولین عکس. وضعیت تحویل هر پلتفرم جدا پیگیری می‌شود، پس شکست در یک پلتفرم مانع یا باعث تکرار در بقیه نمی‌شود.

معماری

main.py                 # نقطهٔ ورود - یک Source را به چند Sender وصل می‌کند و یک بار اجرا می‌کند
core/
  models.py              # Item - ساختار عمومی که هر منبع تولید و هر گیرنده مصرف می‌کند
  orchestrator.py         # حلقهٔ اصلی: گرفتن شناسه‌های جدید -> گرفتن هر آیتم -> ارسال -> ذخیرهٔ وضعیت
sources/
  base.py                 # رابط Source: ‎fetch_new_ids(state)‎, ‎fetch_item(id)‎, ‎target_senders‎
  registry.py              # متغیر محیطی SOURCE_TYPE -> نمونهٔ Source
  divar/                   # منبع آماده دیوار (آگهی ساختاریافته -> پیام قالب‌بندی‌شده)
    client.py                # DivarSource - پاسخ API دیوار را به Item تبدیل می‌کند
    _raw_client.py           # فراخوانی‌های سطح‌پایین API دیوار + پارس کردن
    hashtags.py              # تولید هشتگ مخصوص دیوار
  telegram_relay/          # خودِ تلگرام به‌عنوان منبع (به بخش «منبع رله تلگرام» پایین‌تر نگاه کنید)
    client.py                # TelegramRelaySource - با getUpdates گوش می‌دهد و فقط به روبیکا/ایتا رله می‌کند
  website/                 # یک سایت گیت‌هاب‌پیجز به‌عنوان منبع (به بخش «منبع سایت» پایین‌تر نگاه کنید)
    client.py                # WebsiteSource - فایل‌های پوشهٔ submissions/ که فرم سایت کامیت کرده را می‌خواند
  common/                  # ابزارهای مشترک بین منابع رله‌ای (telegram_relay، website)
    quotes.py                 # گرفتن یک نقل‌قول طبیعت فارسی تصادفی
    channel_links.py          # ساخت فوتر «ما را جای دیگر هم دنبال کنید»
senders/
  base.py                  # رابط Sender: ‎enabled()‎, ‎send(item)‎
  registry.py                # لیست همهٔ گیرنده‌های داخلی و فیلتر آن‌هایی که پیکربندی شده‌اند
  formatting.py               # Item -> متن پیام، مشترک بین همهٔ گیرنده‌ها
  telegram.py                  # از طریق python-telegram-bot
  rubika.py                     # از طریق کتابخانهٔ rubka (تماس‌های یک‌بارهٔ async، بدون حلقهٔ polling)
  bale.py, eitaa.py              # HTTP خام - هیچ کتابخانهٔ یک‌بارهٔ مناسبی برای این دو پیدا نشد؛ توضیح پایین‌تر
  http_helpers.py             # ابزارهای مشترک HTTP برای گیرنده‌های شبیه Bot API
  text_utils.py                # ابزارهای تقسیم متن طولانی به چند پیام
storage.py               # وضعیت tokens.json (پیگیری تحویل به تفکیک گیرنده)، مستقل از نوع منبع
config.py                # متغیرهای محیطی و ثابت‌ها
requirements.txt
.github/workflows/run-bots.yml

اضافه کردن یک منبع جدید (مثلاً یک سایت آگهی دیگر، یک فید RSS، جست‌وجوی توییتر): یک sources/<name>/client.py بسازید که یک نمونهٔ SOURCE صادر می‌کند و کلاسش fetch_new_ids(state) و fetch_item(id) -> Item | None را پیاده می‌کند. آن را در sources/registry.py ثبت کنید، سپس SOURCE_TYPE=<name> را تنظیم کنید. هیچ بخش دیگری از ریپازیتوری نیاز به تغییر ندارد — همهٔ گیرنده‌ها از قبل با Item صحبت می‌کنند. اگر منبع شما نیاز به نگه‌داشتن یک نشانگر/آفست بین اجراها دارد (مثل آفست آپدیت‌های telegram_relay)، داخل fetch_new_ids مقدار state["source_state"][self.name] را بخوانید/بنویسید — این مقدار به‌طور خودکار در tokens.json ذخیره می‌شود.

دو نوع محتوای Item: آیتم‌های دیوار داده‌های ساختاریافتهٔ آگهی هستند (قیمت، مشخصات و...) که گیرنده‌ها آن‌ها را در یک قالب می‌ریزند. همهٔ منابع این‌طور نیستند — telegram_relay یک پست از‌پیش‌نوشته‌شدهٔ تلگرام را همان‌طور که هست رله می‌کند. با تنظیم Item.raw_text، گیرنده‌ها همان متن را عیناً ارسال می‌کنند، بدون ساختن قالب دیواری دورش.

محدود کردن مقصد ارسال بر اساس منبع: با تنظیم لیست Source.target_senders (مثلاً ["rubika", "eitaa"]) می‌توانید مشخص کنید که آیتم‌های یک منبع نباید به همهٔ گیرنده‌های پیکربندی‌شده بروند — telegram_relay از همین قابلیت استفاده می‌کند، چون خودِ پست از قبل در تلگرام موجود است. برای ارسال به همهٔ گیرنده‌ها (مثل دیوار) این مقدار را None (پیش‌فرض) بگذارید.

اضافه کردن یک گیرندهٔ جدید (مثلاً دیسکورد، واتساپ، یک وب‌هوک): یک senders/<name>.py بسازید با کلاسی که enabled() و async send(item) -> bool را پیاده می‌کند. یک نمونه از آن را در senders/registry.py ثبت کنید. به‌محض تنظیم متغیرهای محیطی موردنیازش، به‌طور خودکار شناسایی و استفاده می‌شود.

چرا بله و ایتا از HTTP خام استفاده می‌کنند نه یک کتابخانه: کتابخانهٔ python-bale-bot وجود دارد، اما متد Bot.connect() آن قبل از این‌که نشست HTTP قابل‌استفاده شود، یک حلقهٔ polling بی‌نهایت را اجرا می‌کند - این کتابخانه برای رباتی طراحی شده که دائم در حال اجراست، نه یک اجرای یک‌بارهٔ کرون، بنابراین استفاده از آن یعنی وابستگی به جزئیات داخلی مستندنشده. برای ایتا هم هیچ کتابخانهٔ نگه‌داری‌شده‌ای وجود ندارد. در مقابل، کتابخانهٔ rubka برای روبیکا تماس‌های async یک‌باره و بدون مرحلهٔ polling دارد، بنابراین گزینهٔ مناسبی است و در rubika.py استفاده شده.

منبع رله تلگرام (telegram_relay)

با SOURCE_TYPE=telegram_relay کل ایده برعکس می‌شود: به‌جای خزیدن روی یک سایت آگهی، خودِ ربات تلگرام شما منبع است. یک پست برایش بفرستید — یک عکس یا ویدیو همراه با کپشن، چه به‌صورت پیام خصوصی به ربات و چه به‌صورت پست کانالی در کانالی که ربات در آن ادمین است — و همان پست به روبیکا و ایتا رله می‌شود. تلگرام از مقصدهای ارسال حذف می‌شود، چون پست از قبل همان‌جا موجود است.

راه‌اندازی:

  1. از همان رباتِ مرحلهٔ ۱ استفاده کنید (یا یک ربات جداگانه) - در هر صورت باید BOT_TOKEN تنظیم شده باشد.
  2. برای رله کردن پست‌های کانال: ربات را به‌عنوان ادمین کانال اضافه کنید (کانال ← مدیران ← افزودن مدیر). نیازی به دسترسی خاصی فراتر از خواندن پیام‌ها ندارد.
  3. شناسهٔ عددی چت‌هایی که می‌خواهید پست از آن‌ها پذیرفته شود را پیدا کنید:
    • شناسهٔ کاربری خودتان، برای پیام‌دادن مستقیم به ربات — به @userinfobot پیام دهید.
    • شناسهٔ عددی کانال (چیزی شبیه -1001234567890‎) — یک پیام از کانال را به @userinfobot فوروارد کنید، یا بعد از یک‌بار پست کردن، پاسخ getUpdates ربات را بررسی کنید.
  4. مقدار TELEGRAM_RELAY_CHAT_IDS را برابر لیستی از این شناسه‌ها با کاما جدا کنید (مثلاً 123456789,-1001234567890‎). این مقدار الزامی است — بدون آن، منبع هیچ‌چیزی را پردازش نمی‌کند، تا یک پیام خصوصی ناخواسته از یک نفر دیگر به کانال‌های شما رله نشود.
  5. مقدار SOURCE_TYPE=telegram_relay را به‌عنوان یک سیکرت ریپازیتوری تنظیم کنید.

نقل‌قول طبیعت + فوتر لینک کانال‌ها: هر پست رله‌شده یک نقل‌قول تصادفی با موضوع طبیعت دریافت می‌کند (در لحظهٔ اجرا از فایل موضوعی tabiat.json در ریپازیتوری aliaslany/persian-quotes گرفته می‌شود، بدون نیاز به داخل‌ریپو بودن داده‌ها)، به‌همراه یک فوتر «ما را جای دیگر هم دنبال کنید» که به همان محتوا در کانال‌های تلگرام/بله/روبیکا لینک می‌دهد. هر گیرنده این لینک‌ها را در همان قالبی که آن پلتفرم واقعاً پشتیبانی می‌کند رندر می‌کند — لینک واقعاً کلیک‌پذیر در روبیکا (از طریق تبدیل HTML به متادیتای لینک روبیکا)، و متن ساده به شکل برچسب: آدرس در ایتا (چون ایتا از لینک غنی پشتیبانی نمی‌کند). این‌ها را می‌توانید از طریق CHANNEL_LINK_LABEL، TELEGRAM_CHANNEL_URL، BALE_CHANNEL_URL، RUBIKA_CHANNEL_URL و NATURE_QUOTES_URL تنظیم کنید (جدول سیکرت‌ها را پایین‌تر ببینید) — هر کدام از *_CHANNEL_URL را خالی بگذارید تا آن پلتفرم از فوتر حذف شود.

محدودیت شناخته‌شده: تلگرام هر عکس از یک آلبوم چندعکسی را به‌صورت یک آپدیت جداگانه می‌فرستد. این منبع فعلاً هر پیام را یک پست مستقل در نظر می‌گیرد، پس یک آلبوم چندعکسی به چند پست جدا در روبیکا/ایتا تبدیل می‌شود، نه یک آلبوم گروه‌بندی‌شده. برای پست‌های تک‌عکس/تک‌ویدیو مشکلی ندارد؛ اگر آلبوم زیاد پست می‌کنید، گروه‌بندی بر اساس media_group_id قدم بعدی طبیعی خواهد بود.

منبع سایت (website)

با SOURCE_TYPE=website‎، به‌جای تلگرام از فرم گیت‌هاب‌پیجزِ داخل پوشهٔ docs/‎ به‌عنوان منبع استفاده می‌شود. صفحه را باز کنید، یک متن بنویسید، یک عکس یا ویدیو پیوست کنید و ارسال کنید — فرم هر دو فایل را مستقیماً با استفاده از توکن گیت‌هاب خودتان (که در مرورگر وارد می‌شود، هیچ‌جای دیگری ذخیره نمی‌شود، مگر این‌که گزینهٔ به‌خاطرسپاری را در همان مرورگر تیک بزنید) در پوشهٔ submissions/‎ همین ریپازیتوری کامیت می‌کند. چون گیت‌هاب اکشنز همیشه قبل از اجرا کل ریپازیتوری را چک‌اوت می‌کند، اجرای زمان‌بندی‌شدهٔ بعدی فقط همان فایل‌ها را از روی دیسک می‌خواند — نیازی به هیچ API پولینگی در سمت ربات نیست.

برخلاف telegram_relay‎، این محتوا هنوز جایی وجود ندارد، پس target_senders‎ برابر None‎ است — یعنی به همهٔ گیرنده‌هایی که پیکربندی کرده‌اید (تلگرام، بله، روبیکا، ایتا) ارسال می‌شود. همان رفتار نقل‌قول طبیعت + فوتر لینک کانال‌ها که در telegram_relay‎ توضیح داده شد اینجا هم اعمال می‌شود، چون هر دو از همان ابزارهای مشترک در sources/common/‎ استفاده می‌کنند.

راه‌اندازی:

  1. گیت‌هاب‌پیجز را فعال کنید: Settings → Pages → Source ← «Deploy from a branch» ← شاخهٔ main‎، پوشهٔ /docs‎. یک دقیقه صبر کنید تا اولین استقرار انجام شود، سپس فرم شما در آدرس https://<username>.github.io/<repo>/‎ در دسترس خواهد بود.
  2. مقدار SOURCE_TYPE=website‎ را به‌عنوان یک سیکرت ریپازیتوری تنظیم کنید.
  3. هر بار که از فرم استفاده می‌کنید، یک توکن دسترسی شخصی fine-grained بسازید که فقط به همین ریپازیتوری محدود باشد و دسترسی Contents: Read and write داشته باشد (https://github.com/settings/tokens?type=beta‎) و آن را در فیلد توکن فرم وارد کنید. این توکن فقط برای همان دو فراخوانی API که پست شما را کامیت می‌کند استفاده می‌شود — به جای دیگری ارسال نمی‌شود.

نکته دربارهٔ پاک‌سازی: فایل‌های هر پست بلافاصله بعد از خوانده‌شدن حذف می‌شوند، قبل از این‌که تحویل موفق به همهٔ پلتفرم‌ها تأیید شده باشد. اگر ارسال در وسط راه شکست بخورد، محتوا از بین می‌رود و به‌طور خودکار دوباره امتحان نمی‌شود — این قابل‌قبول است چون برخلاف یک آگهی دیوار، می‌توانید دوباره از طریق فرم ارسالش کنید. مرحلهٔ کامیت وضعیت در ورک‌فلو، این حذف‌ها را هم همراه با tokens.json‎ به ریپازیتوری پوش می‌کند.

نحوهٔ کار

چون هاست رایگان جایی برای اجرای یک پردازش دائمی نمی‌دهد، ربات به‌طور پیوسته اجرا نمی‌شود. در عوض، یک ورک‌فلوی گیت‌هاب اکشنز آن را طبق زمان‌بندی (مثلاً هر ۱۰ دقیقه) اجرا می‌کند. هر اجرا:

  1. آیتم‌های جدید را از منبع پیکربندی‌شده می‌گیرد (آگهی‌های دیوار، یا پیام‌های تلگرام برای telegram_relay).
  2. هر آیتم جدید را به هر گیرنده‌ای که آن منبع اجازه می‌دهد ارسال می‌کند (به Source.target_senders نگاه کنید).
  3. وضعیت به‌روزشده (tokens.json) را به ریپازیتوری کامیت می‌کند تا اجرای بعدی از همان‌جا ادامه دهد.

راه‌اندازی

۱. ساخت ربات

@BotFather را در تلگرام باز کنید، یک ربات بسازید و توکن آن را یادداشت کنید.

۲. گرفتن شناسهٔ چت/کانال

  • چت خصوصی: به ربات پیام بدهید، سپس آدرس https://api.telegram.org/bot<TOKEN>/getUpdates را باز کنید و مقدار chat.id را بخوانید.
  • کانال عمومی: می‌توانید مستقیماً از @username‎ آن به‌عنوان شناسهٔ چت استفاده کنید.
  • کانال/گروه خصوصی: ربات را به‌عنوان ادمین با دسترسی «ارسال پیام» اضافه کنید، یک پیام در آن بفرستید، سپس getUpdates را همان‌طور بررسی کنید — شناسه یک عدد منفی بزرگ خواهد بود.

۳. پیدا کردن شناسهٔ شهر(ها) و اسلاگ دسته‌بندی

به divar.ir بروید، شهر و دسته‌بندی موردنظر را انتخاب کنید و در حین مرور نتایج جست‌وجو، تب Network مرورگر (DevTools) را باز کنید. مقادیر city_ids و category را در درخواست ارسالی به api.divar.ir/v8/postlist/w/search پیدا کنید. به‌طور جایگزین، آدرسی که هنگام مرور divar.ir/s/...‎ نشان داده می‌شود اغلب همان اسلاگ دسته‌بندی را نشان می‌دهد (مثل real-estate‎, villa‎, temporary-rent‎).

۴. فورک این ریپازیتوری، سپس افزودن سیکرت‌ها

در فورک خودتان به Settings → Secrets and variables → Actions بروید و موارد زیر را اضافه کنید:

سیکرت الزامی؟ مثال توضیح
BOT_TOKEN اختیاری 123456:ABC-DEF...‎ توکن ربات تلگرام از BotFather
BOT_CHATID اختیاری -1001234567890‎ یا @mychannel‎ چت/کانال مقصد در تلگرام
BALE_BOT_TOKEN اختیاری توکن ربات بله
BALE_CHATID اختیاری چت/کانال مقصد در بله
RUBIKA_BOT_TOKEN اختیاری توکن ربات روبیکا
RUBIKA_CHATID اختیاری چت/کانال مقصد در روبیکا
EITAA_TOKEN اختیاری توکن API ایتایار
EITAA_CHATID اختیاری چت/کانال مقصد در ایتا
SOURCE_TYPE اختیاری divar‎ کدام منبع بررسی شود (به sources/registry.py نگاه کنید)؛ پیش‌فرض divar‎
TELEGRAM_RELAY_CHAT_IDS برای telegram_relay‎ الزامی 123456789,-1001234567890‎ شناسه‌های چتی که اجازهٔ پست از طریق رله را دارند (به منبع رله تلگرام نگاه کنید)
CHANNEL_LINK_LABEL اختیاری طبیعت+‎ برچسب کلیک‌پذیر برای هر لینک فوتر «ما را جای دیگر هم دنبال کنید»
TELEGRAM_CHANNEL_URL اختیاری https://t.me/nature_plus‎ لینک فوتر به کانال تلگرام شما؛ خالی بگذارید تا حذف شود
BALE_CHANNEL_URL اختیاری https://ble.ir/natureplus‎ لینک فوتر به کانال بله شما؛ خالی بگذارید تا حذف شود
RUBIKA_CHANNEL_URL اختیاری https://rubika.ir/natureplus1‎ لینک فوتر به کانال روبیکای شما؛ خالی بگذارید تا حذف شود
NATURE_QUOTES_URL اختیاری آدرس jsDelivr برای tabiat.json‎ برای استفاده از یک دیتاست/موضوع نقل‌قول دیگر تغییرش دهید
GITHUB_REPO در اکشنز خودکار تنظیم می‌شود aliaslany/Multi_sender‎ برای ساخت آدرس فایل خام در منبع website‎ استفاده می‌شود؛ فقط برای توسعهٔ محلی خارج از اکشنز نیاز به تنظیم دستی دارد
SEARCH_CITY_IDS 823,1996,1999‎ شناسه‌های عددی شهر با کاما جدا (فقط منبع دیوار)
SEARCH_CATEGORY real-estate‎ اسلاگ دسته‌بندی دیوار
PROXY_URL اختیاری فقط اگر ران‌ر شما نمی‌تواند مستقیم به دیوار/تلگرام دسترسی داشته باشد

حداقل یک جفت توکن/شناسهٔ چت را تنظیم کنید. تلگرام ارسال کامل عکس یا آلبوم را حفظ می‌کند؛ بله اولین عکس آگهی و سپس متن قالب‌بندی‌شده را می‌فرستد؛ روبیکا و ایتا آگهی قالب‌بندی‌شده را به‌صورت متن دریافت می‌کنند. کلاینت‌های غیر از تلگرام از اندپوینت‌های سازگار با Bot API استفاده می‌کنند و با متغیرهای محیطی اختیاری BALE_API_BASE_URL، RUBIKA_API_BASE_URL یا EITAA_API_BASE_URL می‌توان آن‌ها را به گیت‌وی‌های جایگزین وصل کرد.

tokens.json اکنون تحویل هر پلتفرم را جداگانه ثبت می‌کند. اگر یک پلتفرم شکست بخورد، اجرای بعدی فقط همان پلتفرم را دوباره امتحان می‌کند و از پست تکراری در پلتفرم‌هایی که موفق بوده‌اند جلوگیری می‌شود.

۵. فعال‌سازی دسترسی نوشتن اکشنز

Settings → Actions → General → Workflow permissions ← گزینهٔ «Read and write permissions» را انتخاب کنید (لازم است تا ورک‌فلو بتواند tokens.json را به ریپازیتوری کامیت کند).

۶. اجرای آن

به تب Actions بروید ← ورک‌فلو را انتخاب کنید ← Run workflow. پس از موفقیت، طبق زمان‌بندی تعریف‌شده در .github/workflows/run-bots.yml‎ به‌طور خودکار اجرا می‌شود.

توسعهٔ محلی

git clone https://github.com/aliaslany/Multi_sender.git
cd Multi_sender
pip install -r requirements.txt
cp .env.example .env
# مقادیر .env را با توکن‌ها/شناسه‌های واقعی خودتان پر کنید
export $(grep -v '^#' .env | xargs)
echo '{}' > tokens.json
python main.py

یا با داکر:

docker compose up --build

محدودیت‌های شناخته‌شده

  • این پروژه از API غیررسمی دیوار (همان چیزی که خودِ divar.ir صدا می‌زند) استفاده می‌کند که از ترافیک مرورگر مهندسی معکوس شده است. اگر دیوار هدرها، اندپوینت‌ها یا ساختار پاسخ را تغییر دهد، ممکن است دوباره بشکند.
  • تشخیص هشتگ بر اساس کلیدواژه/زیررشته است، پس عبارت‌های غیرمعمول در متن آگهی ممکن است دیده نشوند.
  • telegram_relay‎ هر پیام تلگرام را یک پست مستقل در نظر می‌گیرد، پس یک آلبوم چندعکسی به چند پست جدا در پلتفرم‌های مقصد تبدیل می‌شود، نه یک آلبوم گروه‌بندی‌شده.
  • website‎ فایل‌های هر پست را بلافاصله بعد از خواندن حذف می‌کند، قبل از تأیید تحویل موفق به همهٔ پلتفرم‌ها — یک ارسال ناموفق باعث از‌دست‌رفتن محتوا می‌شود، نه تلاش خودکار دوباره.

مجوز

به پروژهٔ اصلی بالادستی نگاه کنید — در این فورک مجوز جداگانه‌ای اضافه نشده است.

About

ربات چندپیام‌رسانه ماژولار: منبع‌ها (دیوار، ریلی تلگرام) را به تلگرام، بله، روبیکا و ایتا ارسال می‌کند.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages