尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Монголын хөрөнгийн биржид (MSE) бүртгэлтэй компаниудын ханшийн түүх болон санхүүгийн тайланд үндэслэн авах/зарах/хүлээх (BUY/SELL/HOLD) санал өгдөг систем. Next.js (App Router) + MongoDB дээр суурилсан бөгөөд Vercel дээр deploy хийхэд зориулагдсан. Албан ёсны, баримтжуулсан API байхгүй тул систем нь MSE-ийн өөрийн нээлттэй өгөгдлийн порталаас шууд уншиж (scrape) авдаг:檢視完整 README ↗
Монголын хөрөнгийн биржид (MSE) бүртгэлтэй компаниудын ханшийн түүх болон санхүүгийн тайланд үндэслэн авах/зарах/хүлээх (BUY/SELL/HOLD) санал өгдөг систем. Next.js (App Router) + MongoDB дээр суурилсан бөгөөд Vercel дээр deploy хийхэд зориулагдсан.
Албан ёсны, баримтжуулсан API байхгүй тул систем нь MSE-ийн өөрийн нээлттэй
өгөгдлийн порталаас (https://open.mse.mn) шууд уншиж (scrape) авдаг:
/securities — бүртгэлтэй компаниудын жагсаалт (симбол, нэр, ангилал)/securities/{id}/tab/tradeinfo — тухайн компанийн 1999 оноос хойших
өдөр тутмын нээлт/хаалт/дээд/доод үнэ, эзлэхүүн зэрэг арилжааны түүх/securities/{id}/tab/financials — сүүлийн улирлын санхүүгийн тайлан
(P/E, EPS, ROE, ROA, цэвэр ашиг гэх мэт)/securities/{id} — компанийн мэдээ мэдээллийн жагсаалт (AI шинжилгээнд
ашиглагдана)Энэ бол албан ёсоор баримтжуулаагүй, гэхдээ MSE өөрөө олон нийтэд зориулж
нээлттэй тавьсан хуудас тул сайт өөрчлөгдвөл lib/mse/*.ts доторх
parser-үүдийг шинэчлэх шаардлагатай болно.
lib/mse/ — open.mse.mn-с HTML/JSON татаж parse хийх функцүүд
lib/sync.ts — цаг хугацаагаар хязгаарласан (time-boxed) sync batch,
cursor ашиглан компани бүрийг дараалан шинэчилнэ
lib/recommendation.ts — техник (SMA20/50, RSI14, momentum, 52 долоо
хоногийн байрлал) болон фундаментал (P/E зах зээлийн дундажтай харьцуулах,
ROE, EPS) шинжилгээг нэгтгэсэн дүрэмд суурилсан оноон систем — энэ нь
үндсэн, үнэ төлбөргүй, бүх компанид шууд ажилладаг дохио
lib/ai/ — сонголтоор ажилладаг, олон LLM ашигладаг "AI дүн шинжилгээ"
(нэг компанийн гүнзгий шинжилгээ, зорилтот үнэ/stop-loss/эрсдэл бүхий).
Claude (Anthropic), Google Gemini, Groq, OpenRouter — эдгээрээс тохируулсан
бүх түлхүүрийг зэрэг дуудаж, дийлэнх олонхийн (majority vote) саналыг
нэгтгэн "consensus" дүгнэлт гаргана; хариу дээр үйлчилгээ тус бүрийн
дохио/итгэлцлийг тусад нь харуулна. Нэг ч түлхүүр тохируулаагүй бол энэ
хэсэг зүгээр л идэвхгүй харагдана — систем бусад бүх хэсгээрээ бүрэн
ажиллана.
app/settings/ — API түлхүүрүүд (4 LLM провайдер) болон мэдээний эх
сурвалжийн линкүүдийг удирддаг тохиргооны хуудас. Тусдаа нууц үг шаардахгүй
— апп руу нэвтэрсэн хэрэглэгч л хандана ((app) layout аль хэдийн нэвтрээгүй
бол /login руу шилжүүлдэг).
lib/mse/newsSources.ts — Тохиргоо хуудсан дээр нэмсэн мэдээний
сайтуудаас (ямар ч сайт байж болно, зөвхөн MSE биш) текст болон гарчгуудыг
татаж, AI шинжилгээний нэмэлт контекст, мөн компанийн дэлгэрэнгүй хуудасны
холбоотой мэдээ болгон ашиглана. Эх сурвалж бүр статустай буцдаг тул
ажиллахгүй байгаа линкийг Тохиргоо доторх "Эх сурвалжуудыг шалгах" товчоор
шууд харна.
Facebook (lib/mse/facebook.ts): нэвтрээгүй хүнд агуулгаа
харуулдаггүй — www, m, touch, mbasic, page plugin бүгд login руу
шилжүүлэх эсвэл JavaScript-ээр дүүргэдэг хоосон бүрхүүл буцаадаг.
Үнэгүй нийтийн bridge-үүд ч энэ хананд унадаг: RSSHub нь Cloudflare
шалгалт буцаасан, RSS-Bridge-ийн 4 инстанс бүгд Facebook дээрээ
("Unable to find anything useful", "Unable to get the page id")
таслагдсан. Гурван зам үлдэнэ — тохируулахад хялбараас нь эхэлж
дарааллаар оролдоно:
apify/facebook-posts-scraper
actor нь хуудасны постуудыг JSON болгож өгдөг. Өөрийн Facebook
бүртгэлийг огт хөндөхгүй тул хамгийн аюулгүй нь.mbasic.facebook.com-оос уншина —
JavaScript-гүй хувилбар нь постуудыг серверээс бэлэн HTML болгон
өгдөг. Cookie зөвхөн facebook.com руу явна; c_user, xs,
datr, sb, fr-ээс бусдыг хадгалахгүй хаяна. Уг cookie нь
бүртгэлд бүрэн эрх өгдгийг санаж, туслах бүртгэл ашиглах нь зөв.Гурвуулаа байхгүй бол facebook.com эх сурвалж login_required
статустай алгасагдана. Постын огноог Apify-н time талбараас, cookie
замд <abbr data-store>-ийн epoch эсвэл бичвэр хэлбэрээс (жишээ нь
"5 August at 10:03") уншиж, бусад мэдээтэй хамт огнооны дарааллаар
эрэмбэлнэ.
Apify-н үр дүнг facebookSnapshots цуглуулгад 30 минут хадгална:
компани бүрийн мэдээ бүтээх бүрд эх сурвалжуудыг дахин уншдаг тул
кэшгүй бол нэг хуудас үзэхэд арваад ижил scrape ажиллаж, үнэгүй
кредитийг дэмий үрнэ. Хугацаа хэтэрсэн ажиллагаа Apify талд дуусдаг
учир дараагийн оролдлого нь сүүлийн амжилттай run-ы үр дүнг
(runs/last/dataset/items) шинээр кредит зарцуулалгүй авчирна.
RSS/Atom-ыг эрхэмлэнэ (lib/mse/feed.ts): эх сурвалж бүрийн хувьд
эхлээд <link rel="alternate"> заалтыг, дараа нь /rss, /rss.xml,
/feed замуудыг шалгана. Feed олдвол хуудсыг зулгаахын оронд түүнийг
ашиглана — цэс, хөл хэсгийн шуугиангүй, огноо болон хураангуйтай цэвэр
өгөгдөл гарна. Feed-ийн хаягийг шууд эх сурвалж болгон нэмж бас болно.
app/page.tsx, app/stock/[symbol]/page.tsx — dashboard болон
компани тус бүрийн дэлгэрэнгүй хуудас
app/api/sync — MongoDB руу шинэ дата татах endpoint (хуваарийн
дуудлага болон гараар дуудахад зориулагдсан)
/settings хуудсан дээр (эсвэл дараах орчны хувьсагчаар нөөц болгон):
| Провайдер | Тохиргооны талбар | Env var нөөц | Анхны модель |
|---|---|---|---|
| Anthropic (Claude) | Anthropic (Claude) | ANTHROPIC_API_KEY | claude-sonnet-5 |
| Google Gemini | Google Gemini | GEMINI_API_KEY | gemini-2.5-flash |
| Groq | Groq | GROQ_API_KEY | llama-3.3-70b-versatile |
| OpenRouter | OpenRouter | OPENROUTER_API_KEY | openai/gpt-4o-mini |
Дор хаяж нэгийг тохируулахад л AI дүн шинжилгээ идэвхжинэ; хэд хэдийг тохируулсан тусам consensus (олонхийн санал) илүү найдвартай болно. Тохиргоо хуудсан дээрх утга MongoDB-д хадгалагдах бөгөөд env var-аас илүү жин авна (env var зөвхөн нөөц/fallback).
Систем Progressive Web App болгон бүтээгдсэн тул утас эсвэл компьютер дээр жинхэнэ апп мэт нүүр дэлгэц (home screen) дээр суулгах боломжтой:
Суулгасны дараа апп бие даасан цонхонд (address bar-гүй) нээгдэнэ. Лого,
нэр (app/manifest.ts), icon бүгд public/icons/ доторх зургуудаас
үүсдэг.
Профайл дээрх 🔔 товчоор мэдэгдлийг асаана.
Зах зээлийн мэдээ бүх хэрэглэгчид очно — шинэ мэдээ бүр Мэдэгдэл
хуудсанд мөр болж, бүх төхөөрөмж рүү push очно (lib/marketNews.ts).
Компанийн мэдэгдэл зөвхөн тэр хувьцааг эзэмшдэг эсвэл хянадаг хүнд
очно (lib/personalAlerts.ts):
| Юу | Мэдэгдлийн хуудас | Push |
|---|---|---|
| Дохио өөрчлөгдөх | Шилжилт бүр | Тохиргооны "Ямар дохионд мэдэгдэх" дээр сонгосон шилжилтэд |
| Ханш ±5%+ хөдлөх (өдөрт чиглэл тус бүрд нэг удаа) | Тийм | Тийм |
CallPro SMS нь операторын тогтоосон дугаарууд руу бүх зах зээлийн дохиог илгээсээр байна — энэ нь хэрэглэгчийн мэдэгдэл биш.
Push subscription бүр одоо хэн нэвтэрсэн төхөөрөмж болохоо хадгалдаг. Апп нээгдэх бүрд төхөөрөмж subscription-оо дахин илгээж, одоо нэвтэрсэн хэрэглэгч рүү заагдана — гарсан төхөөрөмж хэнд ч хамаарахгүй болно. Тохиргооны "Push" унтраалга эдгээрт мөн үйлчилнэ.
Ажиллуулахын тулд VAPID түлхүүр үүсгэх шаардлагатай:
npx web-push generate-vapid-keys
Гарсан publicKey/privateKey-г VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY,
NEXT_PUBLIC_VAPID_PUBLIC_KEY (públic key-тэй ижил утга) орчны
хувьсагчдад тохируулна (.env.example-г үзнэ үү). Эдгээргүй бол 🔔 товч
харагдахгүй/идэвхгүй байх бөгөөд систем бусад бүх хэсгээрээ хэвийн
ажиллана.
Санамж: push мэдэгдэл зөвхөн жинхэнэ (incognito биш) browser дээр ажиллана — Chrome incognito горимд Push API-г санаатайгаар идэвхгүй болгосон байдаг.
npm install
cp .env.example .env.local # MONGODB_URI-г бөглөнө
npm run dev
Эхний удаад өгөгдлийн сан хоосон байх тул нэг удаа sync хийх шаардлагатай:
curl http://localhost:3000/api/sync?maxMs=250000
Энэ нь компаниудын жагсаалтыг бүрэн, ханш/санхүүгийн мэдээллийг цаг хугацааны хязгаарын дотор аль болох олон компанид татна. 162 идэвхтэй компанийг бүрэн эхний удаад дуусгахын тулд дээрх командыг хэд хэдэн удаа дахин дуудах шаардлагатай байж болно (cursor-ыг санадаг тул үргэлжлээд л явна).
MONGODB_URI — MongoDB Atlas холболтын мөр (заавал)CRON_SECRET — хуваарийн endpoint-уудыг хамгаалах санамсаргүй нууц үг
(санал болгож байна). Хуваарь ажиллуулагч үүнийг
Authorization: Bearer <CRON_SECRET> header-ээр дамжуулна — доорх
"Хуваарь" хэсгийг үзнэ үү. Тохируулаагүй бол /api/sync нээлттэй
болж, нөгөө хоёр нь Vercel-ийн cron user-agent-аар нь таньдаг./settings хуудаснаас нэмэхийг зөвлөж байна (дээрх хүснэгтийг
үзнэ үү) — редеплой хийх шаардлагагүй.VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, NEXT_PUBLIC_VAPID_PUBLIC_KEY,
VAPID_SUBJECT — push мэдэгдэл идэвхжүүлэхэд (сонголт, дээрх Push
мэдэгдэл хэсгийг үзнэ үү)/api/sync-г MONGODB_URI-тэй адил
CRON_SECRET-ийг Authorization: Bearer <CRON_SECRET> header-т дамжуулан
гараар хэд хэдэн удаа дуудаж, бүх компанийн түүхийг эхлүүлнэ (доорх
жишээг үзнэ үү).curl -X POST "https://<your-app>.vercel.app/api/sync?maxMs=200000" \
-H "Authorization: Bearer $CRON_SECRET"
Дөрвөн дуудлага хуваариар ажиллах шаардлагатай. Бүгд GET, POST
хоёуланг дэмждэг ба Authorization: Bearer <CRON_SECRET> header шаарддаг.
| Дуудлага | Давтамж (Улаанбаатарын цагаар) | Юу хийдэг |
|---|---|---|
/api/prices/tick | Ажлын өдөр 09:59–13:01, 2 минут тутам | Ханшийн урсгалыг нэг удаа уншиж, ханш хөдөлсөн бол л цааш ажиллана. Арилжааны цонхны гадна ханш хөдөлдөггүй тул тэр үед дуудах шаардлагагүй |
/api/news/refresh?facebook=skip | 5 минут тутам | Үнэгүй эх сурвалжууд (биржийн мэдэгдэл, мэдээний сайтууд). Шинэ мэдээ бүрийг мэдэгдлээр зарлаж, Зах зээлийн мэдээнд гаргана. Facebook-д хүрэхгүй |
/api/news/refresh | Ажлын өдөр 12:45 | Дээрхийн адил, дээр нь Facebook постыг шинээр татна. Apify-н эрх зарцуулагддаг цорын ганц дуудлага |
/api/sync | Тохируулах шаардлагагүй — vercel.json-д өдөрт хоёр удаа: 18:00 ба 08:00 | Ханш, санхүүгийн тайлан, индекс татаж, дохионы өөрчлөлтийг шалгана. Нэг дуудлага ~3 минут ажиллаж 80 орчим компанийн түүхийг уншина — өдрийн арилжаанд хангалттай |
/api/sync яагаад 13:10-д биш вэБирж тухайн өдрийн хаалтын ханшийг open-data порталдаа хаалтын дараа хэсэг хугацааны дараа нийтэлдэг. 13:10-ын дуудлага түүнээс өмнө ажиллаж, өмнөх өдрийг л олдог байсан бөгөөд дараагийн 13:10 хүртэл дахин ажилладаггүй байсан тул өглөө бүр сүүлийн биш, түүний өмнөх арилжааны өдөр нүүр хуудсанд харагддаг байсан.
Одоо vercel.json дотор хоёр cron байна (Hobby план өдөрт нэг удаа
ажиллах хоёр cron зөвшөөрдөг):
Түр хаалт. Бирж арилжааны хүснэгтдээ (open-data) сессийг клиринг
дууссаны дараа л нэмдэг. 10-р сарын 1-ний орой 22:00 цагт ч нэг ч
компанийн хүснэгт тэр өдрийг нийтлээгүй байсан, харин компанийн хуудасны
толгойд хаалтын үнэ аль хэдийн гарсан байв. Иймээс sync болон компанийн
хуудас хаалтын дараа (13:00-оос хойш, мөн дараагийн нээлт хүртэл) толгойн
үнийг тухайн сессийн түр хаалт (provisional: true) болгон хадгална.
Хүснэгтэд тухайн өдөр гармагц түүгээр нь солигдоно. Түр хаалт нь
хадгалагдсан хамгийн сүүлийн сессийн шууд дараагийнх (толгойн "өмнөх
хаалт" нь яг таарах) үед л бичигдэнэ. Компанийн хуудас шинэ үнэ олбол
жагсаалтын тэр мөр ч шууд шинэчлэгдэнэ.
Гаднах хуваарь ажиллуулагч дээр /api/sync-ийн 13:10-ын дуудлага
тохируулсан бол устгаж болно.
Мэдээ гарсан даруйд нь мэдэгдэл очих ёстой, гэтэл Facebook пост бүрээр төлбөр авдаг. Хоёуланг нэг хуваариар явуулбал аль нэг нь буруу болно: 5 минут тутам Facebook татвал сарын эрх нэг өдөрт барагдана, өдөрт нэг удаа мэдээ шалгавал мэдэгдэл хагас өдөр хоцорно.
Тиймээс ?facebook=skip нь Facebook-ийг огт хөндөхгүй горим. Зөвхөн
Apify-г алгасаад зогсохгүй, cookie болон token замыг ч алгасдаг — тэдгээр нь
Apify-н эрх зарцуулдаггүй ч, операторын өөрийнх нь бүртгэлээс Facebook руу
хэдхэн минут тутам хүсэлт явуулах нь бүртгэл түгжигдэх шалтгаан болдог.
Apify ажлын өдөрт хуудас бүрд нэг удаа, өдөрт 8 хуудас хүртэл ажиллана —
энэ нь кодоор баталгаажсан (lib/mse/facebook.ts, APIFY_PAGES_PER_DAY):
амралтын өдөр огт татахгүй, нэг хуудсыг өнөөдөр аль хэдийн татсан бол дахин
татахгүй (хадгалсныг харуулна), есдэх хуудас хадгалсан постоороо үлдэнэ.
Хуваарь ажиллуулагч нэг өдөр хоёр удаа дуудсан ч зардал нэмэгдэхгүй.
12:45-ын дуудлага run-ыг эхлүүлээд л орхино, дуусахыг хүлээхгүй. Run-ий
id-г тухайн өдрийн бүртгэлд (apifyRuns) хадгалж, дараагийн 5 минут
тутмын шинэчлэлт бүр Apify-аас төлөвийг нь асууж, дууссан бол постыг нь
авна — энэ нь үнэгүй. Тиймээс Facebook пост run дууссанаас хойш 5 минутын
дотор мэдээнд гарна. Өмнө нь run 25 секундээс удвал (ихэнх үед) Apify
талд дуусч төлбөр нь гардаг ч пост нь хэзээ ч хадгалагддаггүй байсан.
Бусад хоёр дүрэм:
Apify өдөрт яг нэг удаа ажиллана. Үүнийг санамсаргүй байдлаар бус,
кодоор баталгаажуулсан: Facebook-ийн горим cached (scrape хийхгүй) ба
fresh (scrape хийнэ) гэсэн хоёрхон утгатай, анхны утга нь cached.
fresh-ийг буцаадаг ганц газар бол /api/news/refresh route бөгөөд тэр нь
cron secret-тэй бөгөөд facebook=skip параметргүй дуудлагад л буцаана —
өөрөөр хэлбэл Даваа–Баасан 12:45-ын job.
Өмнө нь stored гэсэн гурав дахь горим байсан бөгөөд тэр нь анхны утга
байсан: хадгалсан постыг ашиглаад, хангалттай шинэ нь байхгүй бол scrape
хийдэг. Болгоомжтой сонголт мэт харагдавч үнэндээ биш — 26 цаг өнгөрмөгц
төлбөртэй run руу шилждэг. Энэ анхны утгыг 7 дуудагч авдаг байсан
(нүүр хуудас, өдрийн sync, компанийн мэдээ, AI-ийн prompt гэх мэт), тиймээс
нэг өдрийн 12:45 амжилтгүй болбол тэдгээрийн аль нь ч scrape эхлүүлж
болох байсан. Одоо тэр горимыг бүрмөсөн авсан.
Шаардлагатай үед гараар scrape хийх зам хэвээр: cron secret-тэй
/api/news/refresh (эсвэл Actions → App cron → news-facebook).
Энэ горимд хадгалсан постыг насаар нь үл харгалзан авдаг, мөн шинэчлэл хуучин мэдээг устгахгүйн тулд өмнөх жагсаалттайгаа нэгтгэдэг. Эс тэгвэл Facebook мэдээ 5 минут тутам хуудаснаас алга болж, маргааш 12:45-д л эргэж гарах байсан.
Хуваарийг cron-job.org ажиллуулна, GitHub Actions биш. Гурван шалтгаан:
*/2 тэнд тэр давтамжаар ажиллахгүй.The job was not started because your account is locked due to a billing issue гэж бүх
удаад унасан — runner ч аваагүй, log ч үлдээгээгүй. Ажиллаж чадахгүй
хуваарь бол хуваарьгүй байснаас дор, учир нь хуваарь мэт харагддаг..github/workflows/app-cron.yml нь одоо зөвхөн гараар ажиллуулах
хэрэгсэл: Actions → App cron → Run workflow → endpoint сонгоно. Үүнд
APP_CRON_SECRET secret хэрэгтэй бөгөөд утга нь Vercel дээрх CRON_SECRET-
тэй яг ижил байх ёстой. 401 гарвал хоёр secret зөрсөн байна.
cron-job.org дээр timezone-оо Asia/Ulaanbaatar болгоод дараах job-уудыг
үүсгэнэ (Method GET, header Authorization: Bearer <CRON_SECRET>):
| URL | Хуваарь |
|---|---|
/api/prices/tick | Даваа–Баасан, 10–12 цагийн сондгой минут бүр |
/api/prices/tick | Даваа–Баасан 09:59 |
/api/prices/tick | Даваа–Баасан 13:01 |
/api/news/refresh?facebook=skip | 5 минут тутам |
/api/news/refresh | Даваа–Баасан 12:45 |
/api/sync?maxMs=20000 | 13 цагийн 10,15,20,25,30,35,40,45,50,55 минутад |
Ханшийн tick гурван job болсон шалтгаан: cron-job.org-ийн хуваарь нь цаг×минутын үржвэр учир "09:59-өөс 13:01 хүртэл 2 минут тутам" гэдгийг нэг job-оор илэрхийлэх боломжгүй. Дунд нь сондгой минутууд, ирмэгийн хоёр цаг тусдаа.
sync-ийг нэг том дуудлагын оронд 10 удаа давтаж байгаа шалтгаан:
cron-job.org 30 секундын дараа холболтыг тасалдаг. runSyncBatch нь
cursor-тэй — өмнөх удаа хаана зогссоноо syncState-д хадгалж, тэндээсээ
үргэлжлүүлдэг, бас сүүлийн арилжаанаас хоцорсон компаниудыг эхэлж авдаг.
Тиймээс 20 секундын 10 багц нь 250 секундын нэг дуудлагатай ижил ажлыг
30 секундын хязгаарт багтаан гүйцэтгэнэ.
news/refresh нь 30 секундээс хэтэрч болзошгүй. Тэр үед cron-job.org
timeout гэж тэмдэглэнэ, гэхдээ Vercel талд функц үргэлжлэн ажиллаж дуусгадаг
тул ажил нь хийгддэг — зөвхөн хариу нь буцаж очдоггүй.
Хоёуланг зэрэг ажиллуулбал tick давхардана, гэхдээ хор хөнөөлгүй: ханш
хөдлөөгүй бол tick юу ч хийхгүй буцдаг.
JSON API-аар уншдаг сайтууд. Зарим эх сурвалж бүхэлдээ хөтөч дээр зурагддаг тул HTML-ээс нь цэснээс өөр юу ч гардаггүй. Тэднийг өөрсдийнх нь ашигладаг API руу шилжүүлсэн — түлхүүр шаардахгүй:
| Сайт | Эх сурвалж | Модуль |
|---|---|---|
| marketinfo.mn | service.marketinfo.mn/news | lib/marketinfo/news.ts |
| tavanbogdcapital.com | tavanbogdcapital.com/web/api/news/getAllBasic | lib/tavanbogd/news.ts |
| bloombergtv.mn | bloombergtv.mn/api/public/news/* | lib/bloombergtv/news.ts |
bloombergtv.mn нь хайлтын endpoint-той цорын ганц эх сурвалж:
POST /api/public/news/search руу {filterdata: "<компанийн нэр>"}
илгээнэ. Бусад сайтаас татсаныг дараа нь шүүдэг бол энд компанийг
шууд асууна — нүүр хуудасны 10 мэдээнээс хальсан хуучин нийтлэлд
зөвхөн ингэж хүрнэ. Хайлтын хариу огноогүй ирдэг тул өгүүллийн
хуудасны __NEXT_DATA__-аас огноог нь нөхөж (дээд тал нь 6) авна —
эс бөгөөс огноогүй мэдээ жагсаалтын хамгийн доор унана.
Tavan Bogd-ийн хуудас нь Next.js pages-router бөгөөд __NEXT_DATA__
нь хоосон pageProps-той — өгүүллүүд нь дараа нь ачаалагддаг. HTML-ээс
нь ердөө 1,849 тэмдэгт цэс, 1 «гарчиг» (тэр нь цэсний мөр) гардаг
байсныг API руу шилжүүлснээр 40 өгүүлэл, огноо, permalink-тэй болсон.
Апп нь ямар ч эх сурвалж дагалдуулж ирдэггүй — newsSources-ийн
анхны утга хоосон. Жагсаалтыг бүхэлд нь Тохиргоо хуудсаас хэрэглэгч
удирдана; энд ямар ч сайт нэмж, хасаж болно.
Эх сурвалж бүрийн буцаах статусууд:
| Статус | Утга |
|---|---|
ok | Агуулга амжилттай татагдсан (RSS эсвэл HTML-ээс) |
tls_error | Сайтын SSL гинж дутуу — доорх засварыг үз |
timeout | 15 секундэд хариу ирээгүй |
http_error | Сайт алдааны код буцаасан (403/429 бол бот хамгаалалт байж болзошгүй) |
empty | Хуудсаас текст олдоогүй — агуулгаа зөвхөн JavaScript-ээр ачаалдаг |
login_required | Нэвтрэлт шаарддаг (Facebook гэх мэт) |
Аль ч линкийн одоогийн байдлыг Тохиргоо → "Эх сурвалжуудыг шалгах" товчоор шалгана. Ажиллаж байгаа эх сурвалж дээр татсан тэмдэгтийн тоо, гарчгийн тоо, RSS ашигласан эсэх харагдана.
mse.mn-ийг эх сурвалж болгон нэмэх шаардлагагүй: тэр сайт агуулгаа
зөвхөн JavaScript-ээр ачаалдаг бөгөөд МХБ-ийн мэдээг апп аль хэдийн
open.mse.mn-ээс компани тус бүрээр нь шууд татдаг.
SSL сертификатын гинж дутуу (tls_error): зарим сайт (жишээ нь
marketinfo.mn) leaf сертификатаа л илгээж, intermediate-ээ илгээдэггүй.
Хөтөч дутуу гэрчилгээг өөрөө нөхөж татдаг тул хэвийн харагддаг ч Node,
curl, Vercel зэрэг сервер талын клиент "unable to get local issuer
certificate" гэж унана.
Үүнийг апп автоматаар засна. lib/tls/extraCa.ts дахь recoverChain
нь хөтчийн адил ажиллана: сертификат дотор өөрийнх нь гаргагчийг хаанаас
татахыг заасан (AIA "CA Issuers") тул дутуу холбоосыг татаж аваад
итгэмжлэлд нэмнэ. Хэрэглэгч юу ч хийх шаардлагагүй.
Аюулгүй байдал сулраагүй: сертификатыг унших ганц арга нь шалгалтгүй
холболт учир танилцах холболт шалгалтгүй явдаг, гэхдээ түүнээс авсан
цорын ганц зүйл нь "нэр дэвшигч гэрчилгээг хаанаас татах вэ" гэсэн
хаяг юм. Өгөгдөл зөөх жинхэнэ хүсэлт rejectUnauthorized асаалттай
явагддаг тул гинж нь жинхэнэ нийтийн root хүртэл хүрч, хостын нэр
таарах ёстой хэвээр. Туршилтаар баталсан: сэргээсэн гэрчилгээтэй
үед амжилттай, түүнгүйгээр яг адилхан татгалздаг.
Гар аргаар (AIA заалтгүй ховор тохиолдолд) Тохиргоо → "Нэмэлт SSL сертификат" талбарт дутуу гэрчилгээг PEM хэлбэрээр нэмж болно:
openssl s_client -showcerts -connect marketinfo.mn:443 </dev/null