یک ابزار پخش همزمان یک پست در چند پیامرسان: یک محتوا (متن + عکس/ویدیو) را یکبار میسازید، و به هر تعداد کانال در تلگرام، بله، روبیکا و ایتا — یا هر پیامرسان دیگری که خودتان اضافه کنید — ارسال میشود. وضعیت تحویل هر پلتفرم جدا پیگیری میشود، پس شکست در یک پلتفرم مانع یا باعث تکرار در بقیه نمیشود.
آن پست از کجا میآید کاملاً به شما بستگی دارد و اختیاری است — میتوانید مستقیم از یک فرم پستش کنید، آن را از یک کانال تلگرام رله کنید، یا یک منبع خزنده به یک سایت/API دیگر وصل کنید. بهصورت پیشفرض یک منبع دیوار کاملاً کاربردی همراه ریپازیتوری میآید، اما این فقط یکی از چند گزینهٔ موجود است، نه هستهٔ اصلی پروژه — لایهٔ منبع و لایهٔ ارسالکنندهها از طریق دو رابط سادهٔ برنامهنویسی (interface) کاملاً از هم جدا شدهاند، پس میتوانید این ریپازیتوری را فورک کنید و به یک منبع دیگر (یا هیچکدام، فقط فرم دستی) وصلش کنید، بدون اینکه لازم باشد منطق ارسال را دست بزنید.
منبع دیوار، نسخهٔ بهشدت تغییریافتهای از debMan/divar-telegram-bot (که خودش از ehcaning/divar-telegram-bot گرفته شده) است. از زمان نوشتهشدن پروژهٔ اصلی، API غیررسمی دیوار تغییر کرده، بنابراین منطق خزیدن اینجا کاملاً متفاوت است.
- ارسال چندپیامرسانهای — تلگرام عکس/آلبوم کامل دریافت میکند؛ بله، روبیکا و ایتا متن + اولین عکس.
- منبع محتوا کاملاً اختیاری و قابلتعویض — پست دستی از طریق فرم، رله از تلگرام، یا یک خزندهٔ سفارشی؛ به
main.pyفرقی نمیکند از کدام منبع میآید (به بخش معماری نگاه کنید). - روی گیتهاب اکشنز اجرا میشود — نیازی به هاست یا سرور جداگانه نیست. یک ورکفلوی زمانبندیشده ربات را هر چند دقیقه یکبار اجرا میکند.
- قالببندی آمادهٔ کانال — عکسها/آلبومها را با کپشن 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 - از Worker داخل پوشهٔ worker/ برای پستهای در انتظار پرسوجو میکند
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 # متغیرهای محیطی و ثابتها
docs/index.html # فرم عمومی ثبت پست (گیتهابپیجز) - فقط با worker/ صحبت میکند، هرگز با این ریپازیتوری
worker/ # Cloudflare Worker پشتیبان docs/index.html - برای استقرار به worker/README.md نگاه کنید
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 ذخیره میشود. اگر منبع شما دادههای پشتیبان خودش را دارد که باید فقط پس از تحویل واقعی پاکسازی شوند (مثل پستهای website)، متد on_delivered(item_id, fully_delivered) را بازنویسی کنید — این متد یکبار به ازای هر آیتم، بعد از هر تلاش برای تحویل، صدا زده میشود و fully_delivered فقط زمانی True است که همهٔ گیرندههای هدف آن آیتم، تحویل را با موفقیت ثبت کرده باشند.
دو نوع محتوای Item: آیتمهای دیوار دادههای ساختاریافتهٔ آگهی هستند (قیمت، مشخصات و...) که گیرندهها آنها را در یک قالب میریزند. همهٔ منابع اینطور نیستند — telegram_relay و website یک پست ازپیشنوشتهشده را همانطور که هست رله میکنند. با تنظیم Item.raw_text، گیرندهها همان متن را عیناً ارسال میکنند، بدون ساختن قالب دیواری دورش.
محدود کردن مقصد ارسال بر اساس منبع یا بر اساس هر آیتم: با تنظیم لیست Source.target_senders (مثلاً ["rubika", "eitaa"]) میتوانید مشخص کنید که آیتمهای یک منبع نباید به همهٔ گیرندههای پیکربندیشده بروند — telegram_relay از همین قابلیت استفاده میکند، چون خودِ پست از قبل در تلگرام موجود است. برای تحویلی که بر اساس هر آیتم متفاوت است (نه بر اساس کل منبع) — website به این نیاز دارد، چون هر مشتری مقصد خودش را مشخص میکند — بهجای آن، Item.destination_overrides را برابر یک نگاشت {sender_name: chat_id} قرار دهید؛ یک گیرنده بهجای مقدار پیشفرض خودش از config، همین شناسهٔ چت را استفاده میکند و تحویل فقط به همین گیرندهها محدود میشود. اگر هر دو را خالی/None (پیشفرض) بگذارید، مثل دیوار به همهٔ گیرندههای پیکربندیشده با شناسهٔ چت پیشفرض خودشان ارسال میشود.
اضافه کردن یک گیرندهٔ جدید (مثلاً دیسکورد، واتساپ، یک وبهوک): یک senders/<name>.py بسازید با کلاسی که enabled() و async send(item) -> bool را پیاده میکند. یک نمونه از آن را در senders/registry.py ثبت کنید. بهمحض تنظیم متغیرهای محیطی موردنیازش، بهطور خودکار شناسایی و استفاده میشود. اگر میخواهید این گیرنده از destination_overrides پشتیبانی کند، بهجای استفادهٔ همیشگی از مقدار پیشفرض config، شناسهٔ چت را از item.destination_overrides.get(self.name, config.YOUR_DEFAULT_CHATID) بخوانید — به هر کدام از چهار گیرندهٔ آماده برای الگو نگاه کنید.
چرا بله و ایتا از HTTP خام استفاده میکنند نه یک کتابخانه: کتابخانهٔ python-bale-bot وجود دارد، اما متد Bot.connect() آن قبل از اینکه نشست HTTP قابلاستفاده شود، یک حلقهٔ polling بینهایت را اجرا میکند - این کتابخانه برای رباتی طراحی شده که دائم در حال اجراست، نه یک اجرای یکبارهٔ کرون، بنابراین استفاده از آن یعنی وابستگی به جزئیات داخلی مستندنشده. برای ایتا هم هیچ کتابخانهٔ نگهداریشدهای وجود ندارد. در مقابل، کتابخانهٔ rubka برای روبیکا تماسهای async یکباره و بدون مرحلهٔ polling دارد، بنابراین گزینهٔ مناسبی است و در rubika.py استفاده شده.
با SOURCE_TYPE=telegram_relay کل ایده برعکس میشود: بهجای خزیدن روی یک سایت آگهی، خودِ ربات تلگرام شما منبع است. یک پست برایش بفرستید — یک عکس یا ویدیو همراه با کپشن، چه بهصورت پیام خصوصی به ربات و چه بهصورت پست کانالی در کانالی که ربات در آن ادمین است — و همان پست به روبیکا و ایتا رله میشود. تلگرام از مقصدهای ارسال حذف میشود، چون پست از قبل همانجا موجود است.
راهاندازی:
- از همان رباتِ مرحلهٔ ۱ استفاده کنید (یا یک ربات جداگانه) - در هر صورت باید
BOT_TOKENتنظیم شده باشد. - برای رله کردن پستهای کانال: ربات را بهعنوان ادمین کانال اضافه کنید (کانال ← مدیران ← افزودن مدیر). نیازی به دسترسی خاصی فراتر از خواندن پیامها ندارد.
- شناسهٔ عددی چتهایی که میخواهید پست از آنها پذیرفته شود را پیدا کنید:
- شناسهٔ کاربری خودتان، برای پیامدادن مستقیم به ربات — به
@userinfobotپیام دهید. - شناسهٔ عددی کانال (چیزی شبیه
-1001234567890) — یک پیام از کانال را به@userinfobotفوروارد کنید، یا بعد از یکبار پست کردن، پاسخgetUpdatesربات را بررسی کنید.
- شناسهٔ کاربری خودتان، برای پیامدادن مستقیم به ربات — به
- مقدار
TELEGRAM_RELAY_CHAT_IDSرا برابر لیستی از این شناسهها با کاما جدا کنید (مثلاً123456789,-1001234567890). این مقدار الزامی است — بدون آن، منبع هیچچیزی را پردازش نمیکند، تا یک پیام خصوصی ناخواسته از یک نفر دیگر به کانالهای شما رله نشود. - مقدار
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 قدم بعدی طبیعی خواهد بود.
با SOURCE_TYPE=website، فرم عمومیِ docs/index.html (که از طریق گیتهابپیجز سرو میشود) بهعنوان منبع استفاده میشود. هر کسی که یک کد تبلیغیِ معتبر داشته باشد میتواند یک پست ثبت کند — متن، یک عکس/ویدیوی اختیاری، و شناسهٔ چت مقصد برای هر پلتفرمی که میخواهد پستش در آن منتشر شود. هیچ توکن ربات یا اعتبارنامهٔ گیتهابی از فرستنده گرفته نمیشود.
چرا اینطور ساخته شده: این فرم قبلاً از بازدیدکننده یک Personal Access Token گیتهاب میخواست و پستها را مستقیماً در همین ریپازیتوری کامیت میکرد. این، صرفنظر از اینکه توکن مال چه کسی باشد، یک مشکل امنیتی واقعی است — گیتهاب نمیتواند دسترسی «Contents: write» را فقط به یک پوشه محدود کند، پس همان توکن میتوانست فایلهای .github/workflows/*.yml را هم بازنویسی کند و سکرتهای واقعی رباتهای این ریپازیتوری را در اجرای بعدی ورکفلو بدزدد. یک راهحل کاملاً سمتکلاینت (فراخوانی مستقیم API تلگرام/بله/روبیکا/ایتا از مرورگر فرستنده با توکن ربات خودش) هم ممکن نیست — هیچکدام از این چهار API هدر CORS نمیفرستند، پس مرورگرها این فراخوانیها را کلاً مسدود میکنند. راهحل، یک بکاند کوچک (پوشهٔ worker/، یک Cloudflare Worker) است که تنها چیزی است که فرم عمومی با آن صحبت میکند. این Worker هم هیچ توکن رباتی نگه نمیدارد — فرستندهها فقط یک کد تبلیغی و یک شناسهٔ چت ساده میدهند، که هیچکدام حساس نیستند.
چون هر پست مقصد خودش را مشخص میکند، website بهجای یک لیست ثابت target_senders، برای هر آیتم مقدار Item.destination_overrides را تنظیم میکند — پستی که فقط شناسهٔ چت تلگرام را نام میبرد، فقط به تلگرام تحویل داده میشود، هرگز به کانالهای پیشفرض بله/روبیکا/ایتای خودتان. همان رفتار نقلقول طبیعت + فوتر لینک کانالها که در telegram_relay توضیح داده شد اینجا هم اعمال میشود، چون هر دو از همان ابزارهای مشترک در sources/common/ استفاده میکنند.
راهاندازی:
- Worker را مستقر کنید — مراحل یکبارهٔ
wrangler deployدرworker/README.mdتوضیح داده شده. در پایان یک آدرس مثلhttps://multi-sender-submissions.<subdomain>.workers.devچاپ میشود. - همان آدرس را در ثابت
API_BASEنزدیک ابتدای بخش<script>فایلdocs/index.htmlقرار دهید. - گیتهابپیجز را فعال کنید: Settings → Pages → Source ← «Deploy from a branch» ← شاخهٔ
main، پوشهٔ/docs. یکی دو دقیقه بعد فرم شما در آدرسhttps://<username>.github.io/<repo>/در دسترس خواهد بود. - مقادیر
WEBSITE_API_URL(آدرس Worker) وWEBSITE_API_TOKEN(باید با سکرتAPI_TOKENکه روی Worker تنظیم کردهاید یکی باشد) را بهعنوان سیکرت ریپازیتوری اضافه کنید. - مقدار
SOURCE_TYPE=websiteرا بهعنوان یک سیکرت ریپازیتوری تنظیم کنید. - یک کد تبلیغی بسازید — دستور
curlمربوطه درworker/README.mdهست. کد را به هر کسی که باید بتواند پست ثبت کند بدهید.
اجرای دورهٔ آزمایشی واقعی است، نه صرفاً یک مانع سمتکلاینت: شمارش دورهٔ آزمایشی یک کد تبلیغی از اولین استفادهٔ واقعی آن شروع میشود (این بررسی داخل خود Worker انجام میشود، نه در مرورگر)، پس با پاک کردن کوکی یا localStorage قابل بازنشانی نیست. فعلاً راهی برای فهرست کردن یا لغو کدها بعد از ساختشان، جز ویرایش مستقیم دادهٔ KV در Worker، وجود ندارد.
پاکسازی: برخلاف طراحی قبلی، یک پست فقط زمانی از Worker حذف میشود که تحویل آن به همهٔ پلتفرمهایی که مشتری هدف قرار داده، کاملاً موفق شده باشد (Source.on_delivered با fully_delivered=True صدا زده میشود) — یک شکست جزئی، پست را دستنخورده نگه میدارد تا اجرای بعدی همان پلتفرمهای ناموفق را دوباره امتحان کند، بهجای اینکه محتوا از بین برود.
چون هاست رایگان جایی برای اجرای یک پردازش دائمی نمیدهد، ربات بهطور پیوسته اجرا نمیشود. در عوض، یک ورکفلوی گیتهاب اکشنز آن را طبق زمانبندی (مثلاً هر ۱۰ دقیقه) اجرا میکند. هر اجرا:
- آیتمهای جدید را از منبع پیکربندیشده میگیرد (آگهیهای دیوار، پیامهای تلگرام برای
telegram_relay، یا پستهای ثبتشده از طریق Worker برایwebsite). - هر آیتم جدید را به گیرندههایی که آن منبع (یا خودِ آن آیتم، از طریق
destination_overrides) اجازه میدهد ارسال میکند. - متد
on_deliveredهر منبع را اجرا میکند، سپس وضعیت بهروزشده (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 |
اختیاری | website |
کدام منبع بررسی شود (به sources/registry.py نگاه کنید)؛ پیشفرض website |
TELEGRAM_RELAY_CHAT_IDS |
برای telegram_relay الزامی |
123456789,-1001234567890 |
شناسههای چتی که اجازهٔ پست از طریق رله را دارند (به منبع رله تلگرام نگاه کنید) |
CHANNEL_LINK_LABEL |
اختیاری | دیوار ادز اسیست |
برچسب کلیکپذیر برای هر لینک فوتر «ما را جای دیگر هم دنبال کنید» |
TELEGRAM_CHANNEL_URL |
اختیاری | https://t.me/divaradsassist |
لینک فوتر به کانال تلگرام شما؛ خالی بگذارید تا حذف شود |
BALE_CHANNEL_URL |
اختیاری | (خالی) | لینک فوتر به کانال بله شما؛ خالی بگذارید تا حذف شود |
RUBIKA_CHANNEL_URL |
اختیاری | (خالی) | لینک فوتر به کانال روبیکای شما؛ خالی بگذارید تا حذف شود |
NATURE_QUOTES_URL |
اختیاری | آدرس jsDelivr برای tabiat.json |
برای استفاده از یک دیتاست/موضوع نقلقول دیگر تغییرش دهید |
WEBSITE_API_URL |
برای website الزامی |
https://multi-sender-submissions.<subdomain>.workers.dev |
آدرس Worker مستقرشدهٔ ثبت پست (به منبع سایت نگاه کنید) |
WEBSITE_API_TOKEN |
برای website الزامی |
باید با سکرت API_TOKEN روی Worker (تنظیمشده با wrangler secret put) یکی باشد |
|
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فعلاً هیچ مکانیزم فهرستکردن یا لغوی جز ویرایش مستقیم دادهٔ KV در Worker ندارند. - رسانهٔ
websiteبهصورت base64 در KV ذخیره میشود (سقف ۲۵ مگابایت برای هر مقدار)، پس ویدیوهای بسیار بزرگ (حدود ۲۰ مگابایت به بالا) رد میشوند؛ اگر این محدودیت مشکلساز شد، مهاجرت به R2 آن را برطرف میکند.
به پروژهٔ اصلی بالادستی نگاه کنید — در این فورک مجوز جداگانهای اضافه نشده است.