Channel: t.me/noon_hot_deals · Site: ديلز مصر — vette1123.github.io/noon-deals-bot
Auto-posts the best discounted products from noon.com Egypt to a Telegram channel, 6× a day. It is free to run and needs no login.
- Scrapes noon's deal pages using
curl_cffi(Chrome TLS impersonation, no paid API) - Attaches your influencer coupon code to every post as tap-to-copy text
- Posts Arabic-formatted product cards with images to Telegram
- Republishes the same deals as a static Arabic site on GitHub Pages so search traffic earns too
- Optionally crossposts to a Facebook Page
- Runs on GitHub Actions — no server required
How it works#
GitHub Actions (cron, 6×/day)
│
▼
fetch 6 category feeds ── curl_cffi + Chrome fingerprint ──▶ noon.com
│ (rotating ~34 categories × 10 pages)
▼
filter, then rank by expected commission in EGP
│
▼
post to Telegram channel with:
• product image
• Arabic-formatted card
• tap-to-copy coupon code (e.g. gado)
• "Buy now" button → noon.com product URL + affiliate UTMs
│
▼
commit updated state.json / posted.json / deals.json
│
├──▶ crosspost to a Facebook Page (only if secrets are set)
│
▼
rebuild the static site from deals.json ──▶ GitHub PagesAttribution model: two channels, both message-side, neither needs a login. Every "Buy now" link carries the affiliate campaign UTMs, and the message body shows a tap-to-copy coupon code that users paste at noon's checkout.
What gets posted: at least 25% off, at least EGP 150, not rated below 3.5 by 20+ buyers, and not unbranded-and-unreviewed filler. Survivors are ranked by expected commission in pounds — min(cap, category rate × price) times conversion odds — deduplicated by name, capped at 2 per seller. The top 12 go to Telegram; the top 20 are archived for the site.
Rates are per category and payout is capped at AED 50 per item, which makes expensive electronics (laptops 3%, mobiles 2%) the worst thing on the board and mid-priced beauty, toys and small appliances (8%) the best. See docs/MONETIZATION.md for the full table.
Feed cursor: noon's category browse paths are real product lists; each run walks 6 of them and advances through a rotation of ~34 categories × 10 pages (~14,000 products, roughly a 10-day cycle). A SKU already posted is on cooldown for 21 days.
The static site#
Every deal is archived to deals.json and rendered into an Arabic (RTL) site: a front page, one page per deal with Product structured data, a hub per category and per brand, a directory of each, a paginated archive, sitemap.xml, robots.txt and an RSS feed. The pages use no web fonts, CSS files or script bundles. Search traffic arrives on mobile data, and page speed affects ranking. It follows the device's light/dark setting, with a toggle that remembers an explicit choice.
It earns without an audience: a Telegram post reaches today's subscribers, a page that ranks reaches everyone who searches.
It follows two rules:
- Deal pages never 404. A deal that ages out becomes a
noindextombstone linking on to its hubs, because a URL that took four months to rank is the one thing here that cannot be rebuilt. priceValidUntilcomes from when the deal was seen, not from the build, so a rebuild never re-certifies an old price as current.
Enable it once under Settings → Pages → Source: GitHub Actions; the publish-site job does the rest. To move it onto your own domain, set the SITE_DOMAIN repository variable and point the DNS at Pages — worth doing before it ranks, not after.
Getting indexed#
Every build re-emits two ownership files at the site root, and every deploy announces what changed:
- Google Search Console:
google<token>.html, served verbatim. Verify the property once, then submitsitemap.xml. A fork pointsGOOGLE_SITE_VERIFICATIONat its own token. - IndexNow:
<key>.txtplus a POST toapi.indexnow.orgafter the deploy, listing only the pages that run changed: the front page, the new deal pages and the hubs they landed on. Bing, Yandex and Seznam then crawl in minutes instead of weeks. Google does not participate — there,sitemap.xml+lastmodis still the lever. The key is public by design (serving it is the proof of ownership), so it belongs inINDEXNOW_KEYas a variable, never a secret.
Neither can fail a run: a malformed token is dropped with a warning, and a rejected ping is logged and ignored.
Preview it locally:
python site_builder.py && python -m http.server -d public 8000See docs/MONETIZATION.md for what earns, what is built, and what still needs a human.
Project layout#
| File | Purpose |
|---|---|
| main.py | Entry point — orchestrates fetch → filter → post |
| scraper.py | Fetches & parses noon.com catalog pages (inline JS payload + fallbacks) |
| filters.py | Filters out already-posted SKUs, weak discounts and filler; ranks by expected commission |
| categories.py | noon category codes, their commission rates, and their Arabic names |
| telegram_poster.py | Formats & posts product cards to Telegram (includes coupon line) |
| archive.py | Records deals into deals.json (1-year window, then tombstoned) |
| site_builder.py | Renders deals.json into the static site in public/ |
| facebook_poster.py | Optional Facebook Page crosspost (no-op without secrets) |
| indexnow.py | Announces changed URLs to Bing/Yandex/Seznam after each deploy |
| posted.json | {sku: ISO timestamp} — per-SKU repost cooldown |
| state.json | {"next_task": N} — cursor into the category-feed rotation |
| deals.json | The published-deal archive the site is built from |
Local setup#
pip install -r requirements.txt
cp .env.example .env # fill in values
python main.py --dry-run # prints what would be posted, skips Telegram
python main.py # real runnoon.com sits behind Akamai, which can challenge cold requests, so a local run may fail at the fetch step with
Akamai JS challenge. In CI the bot egresses through Cloudflare WARP, which avoids this. To debug parsing locally, save the page HTML from a real browser and feed it toscraper.parse_products_from_html.
Required secrets (GitHub Actions)#
| Secret | Purpose |
|---|---|
TELEGRAM_BOT_TOKEN |
Bot that posts to the channel |
TELEGRAM_CHANNEL_ID |
Channel handle (e.g. @noon_hot_deals) |
NOON_COUPON_CODE |
(optional) Your noon influencer coupon. Defaults to gado. |
FACEBOOK_PAGE_ID |
(optional) Enables the Facebook crosspost. Skipped when unset. |
FACEBOOK_PAGE_TOKEN |
(optional) Long-lived Page token with pages_manage_posts. |
Only the two Telegram secrets are required. There is no scraping API key, noon.partners login, session rotation or OTP flow.
Running tests#
pip install -r requirements-dev.txt
pytestSchedule#
Cron 0 4,8,12,16,20,0 * * * UTC → 6×/day (every 4 hours). Triggered by .github/workflows/bot.yml or manually via workflow_dispatch.
