update
This commit is contained in:
parent
5f8d3330f7
commit
de9930c4c2
|
|
@ -0,0 +1,254 @@
|
||||||
|
---
|
||||||
|
name: get-tg-jobs
|
||||||
|
description: Full autonomous end-to-end job pipeline. Fetches fresh vacancies from Oleg's Telegram "Jobs" folder, handles new/pending channels, runs the full triage cycle (triage-jobs), pushes APPLY verdicts to Trello, then reports results in the chat session AND sends a Telegram notification to Oleg via telegram-helper. Trigger on "забери из Jobs", "что нового в каналах", "запусти pipeline", or similar.
|
||||||
|
---
|
||||||
|
|
||||||
|
# get-tg-jobs
|
||||||
|
|
||||||
|
Autonomous end-to-end pipeline: fetch → curate new channels → triage → Trello → report. Does everything without stopping for confirmation. Only halts if a hard error occurs (auth failure, script crash, etc.).
|
||||||
|
|
||||||
|
**Trigger only when Oleg explicitly asks** — e.g. "забери свежее из Jobs", "что нового в каналах", "запусти pipeline". No background polling.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notification targets
|
||||||
|
|
||||||
|
All pipeline reports go to the **"New cool job"** Telegram group. The `telegram-helper` account (samuishechka) is a member of that group and sends messages there.
|
||||||
|
|
||||||
|
| Target | chat_id | When |
|
||||||
|
|---|---|---|
|
||||||
|
| "New cool job" group | `-4783500394` | All reports — success and errors |
|
||||||
|
|
||||||
|
Tool to use: `mcp__telegram-helper__send_message(chat_id=-4783500394, message="...", parse_mode="html")`
|
||||||
|
|
||||||
|
This section is the canonical reference for notification targets. Update here if the group changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1 — Run the pipeline
|
||||||
|
|
||||||
|
```bash
|
||||||
|
~/.local/bin/uv run scripts/list_telegram_channels.py \
|
||||||
|
| ~/.local/bin/uv run scripts/fetch_telegram_jobs.py -
|
||||||
|
```
|
||||||
|
|
||||||
|
Run from the project root. The script prints a per-channel summary to stdout — echo it to the session so Oleg can see raw progress.
|
||||||
|
|
||||||
|
**If the script fails:**
|
||||||
|
- `TELEGRAM_SESSION_STRING not set` or auth error → `.env` is missing or the session is expired. Regenerate with `scripts/regen_telegram_session.sh` and tell Oleg.
|
||||||
|
- `TELEGRAM_API_ID / TELEGRAM_API_HASH not set` → same `.env` issue.
|
||||||
|
- `Could not find folder "Jobs"` → the folder was renamed or the wrong session is in `.env`. Must be the **usulsu** session, not samuishechka.
|
||||||
|
- Any other exception → paste the error and stop. Don't retry blindly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2 — Check for pending (new) channels
|
||||||
|
|
||||||
|
```bash
|
||||||
|
test -f tracking/telegram_pending_channels.json && echo PENDING || echo OK
|
||||||
|
```
|
||||||
|
|
||||||
|
**If OK**: skip to Step 3.
|
||||||
|
|
||||||
|
**If PENDING**: curate each new channel using the procedure below, then rerun Step 1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### New-channel curation procedure
|
||||||
|
|
||||||
|
For each channel in `telegram_pending_channels.json`:
|
||||||
|
|
||||||
|
#### 2a — Read keyword frequency scan
|
||||||
|
|
||||||
|
```bash
|
||||||
|
jq -r 'to_entries[] | "\n=== \(.key) ===\nmessages_scanned: \(.value.messages_scanned)\ntruncated: \(.value.truncated)\ntop keywords:\n\(.value.keyword_counts_from_other_channels | to_entries | sort_by(-.value) | .[:20] | .[] | " \(.key): \(.value)")"' \
|
||||||
|
tracking/telegram_pending_channels.json
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2b — Sample messages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
jq -r '.channels["<channel>"].messages[:6] | .[] | "── \(.date[0:16])\n\(.text[0:400])\n"' \
|
||||||
|
tracking/telegram_inbox.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for: hashtag patterns, language, post structure (single role / digest / chat), recurring noise.
|
||||||
|
|
||||||
|
#### 2c — Decide lang, priority, filter shape
|
||||||
|
|
||||||
|
**Priority:**
|
||||||
|
- `p1` — strong stack hits (JS/TS/React/Node/AI/LLM) AND global-remote culture
|
||||||
|
- `p2` — stack OK but CIS/RUB market, or occasional gems; recruiter/market-intel content
|
||||||
|
- `p3` — wrong stack, off-market, noise, dead channels
|
||||||
|
|
||||||
|
Base priority on the **best** vacancy in the sample, not the average.
|
||||||
|
|
||||||
|
**Filter shape:**
|
||||||
|
|
||||||
|
| Channel pattern | Filter |
|
||||||
|
|---|---|
|
||||||
|
| Consistent `#vacancy` + `#remote` hashtags | AND-of-OR hashtag filter |
|
||||||
|
| Vacancy text without consistent hashtags | Positive stack include + Oleg-stack excludes |
|
||||||
|
| Low-volume personal/curated digest | Trust-all (no `include`/`exclude`) |
|
||||||
|
| Mixes resumes and vacancies | Trust-all |
|
||||||
|
| Mostly noise but worth keeping | Strict positive filter |
|
||||||
|
|
||||||
|
Standard Oleg-stack excludes: `["kafka", "golang", "kotlin", "android", "swift"]`
|
||||||
|
|
||||||
|
For `*_jobs` channels add resume excludes: `["#резюме", "#resume", "#cv", "#ищуработу"]`
|
||||||
|
|
||||||
|
Positive stack include: `["javascript", "typescript", " react", "node.js", "nodejs", "fullstack", "full-stack", "tech lead", "techlead", "ai engineer", " llm"]`
|
||||||
|
|
||||||
|
#### 2d — Add entry to telegram_channels.json
|
||||||
|
|
||||||
|
Insert in correct priority block (p1 → p2 → p3, alphabetically within each group):
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
"<channel_id>": {
|
||||||
|
"lang": "ru" | "en" | "mixed",
|
||||||
|
"priority": "p1" | "p2" | "p3",
|
||||||
|
"include": [["kw1","kw2"], ["kw3"]], // optional
|
||||||
|
"exclude": ["kw4", "kw5"] // optional
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2e — Rerun and confirm clean
|
||||||
|
|
||||||
|
```bash
|
||||||
|
~/.local/bin/uv run scripts/list_telegram_channels.py \
|
||||||
|
| ~/.local/bin/uv run scripts/fetch_telegram_jobs.py -
|
||||||
|
|
||||||
|
test -f tracking/telegram_pending_channels.json && echo "STILL PENDING" || echo "Clean"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2f — Validate the filter
|
||||||
|
|
||||||
|
```bash
|
||||||
|
jq -r '.channels["<channel>"] | "kept \(.kept)/\(.seen)" + (.messages | map("\n── \(.date[0:16])\n\(.text[0:300])") | join(""))' \
|
||||||
|
tracking/telegram_inbox.json
|
||||||
|
```
|
||||||
|
|
||||||
|
If `kept == 0` and a filter is set — verify the filter isn't too strict before accepting zero as correct.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3 — Run full triage
|
||||||
|
|
||||||
|
Follow the complete `triage-jobs` skill procedure on `tracking/telegram_inbox.json`.
|
||||||
|
|
||||||
|
Key points from that skill:
|
||||||
|
|
||||||
|
1. **Pre-step**: build `known_applied` list before spawning Haiku:
|
||||||
|
```bash
|
||||||
|
grep -E '^\|' tracking/applications.md | tail -n +2 | awk -F'|' '{print $3}' | sort -u
|
||||||
|
```
|
||||||
|
Also fetch Trello card titles from all BestJob board lists. Pass the merged list into every Haiku subagent prompt.
|
||||||
|
|
||||||
|
2. **Stratify by priority**:
|
||||||
|
- p1 ≤ 15 messages → triage fully in main session
|
||||||
|
- p1 > 15 messages → Haiku first pass, then main session reviews only APPLY and VERIFY
|
||||||
|
- p2 and p3 → Haiku subagent, main session reviews APPLY and VERIFY only
|
||||||
|
|
||||||
|
3. **VERIFY verdicts**: run a fast `WebSearch "{company} remote hiring countries"` to resolve geo, then apply Gate 1.
|
||||||
|
|
||||||
|
4. **APPLY verdicts**: for every confirmed APPLY —
|
||||||
|
- Add a row to `tracking/applications.md` with status `todo`
|
||||||
|
- Create a Trello card in the TODO column (`6a1aa59555aab72a261c42aa`) on board `6a1a9a5af082cb0526b22704` with: company, role, track, verdict summary, JD link
|
||||||
|
|
||||||
|
5. **QUICK-APPLY verdicts**: note them explicitly in the session report; do not auto-add to Trello. Ask Oleg if he wants to proceed.
|
||||||
|
|
||||||
|
Full triage playbook: `base/reference/vacancy-filter-and-triage-2026.md`. Full batch instructions: `.claude/skills/triage-jobs/SKILL.md`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4 — Report in session
|
||||||
|
|
||||||
|
After triage is complete, output a consolidated summary to the chat:
|
||||||
|
|
||||||
|
```
|
||||||
|
Pipeline done — <date>
|
||||||
|
|
||||||
|
Fetch: N messages from X channels (Y with results)
|
||||||
|
[⚠️ Truncated: channel_name (kept K/500, p2/ru)]
|
||||||
|
|
||||||
|
Triage results:
|
||||||
|
✅ APPLY: N roles → added to Trello
|
||||||
|
🔍 VERIFY: N roles → resolved / still open
|
||||||
|
⚡ QUICK-APPLY: N roles (ask Oleg before adding)
|
||||||
|
❌ SKIP: N roles
|
||||||
|
|
||||||
|
APPLY roles:
|
||||||
|
- Company A — Role Title (Track A) → [Trello card]
|
||||||
|
- Company B — Role Title (Track B) → [Trello card]
|
||||||
|
|
||||||
|
New channels curated: N [or: none]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5 — Send Telegram notification
|
||||||
|
|
||||||
|
After the session report, send a summary to the **"New cool job"** group via telegram-helper.
|
||||||
|
|
||||||
|
### Success message format
|
||||||
|
|
||||||
|
```
|
||||||
|
mcp__telegram-helper__send_message(
|
||||||
|
chat_id=-4783500394,
|
||||||
|
parse_mode="html",
|
||||||
|
message="""<b>Jobs pipeline done</b> · 2026-06-07
|
||||||
|
|
||||||
|
✅ APPLY: N роли → Trello
|
||||||
|
🔍 VERIFY: N (нужен ресёрч)
|
||||||
|
⚡ Quick-apply: N (нужно ок)
|
||||||
|
❌ SKIP: N
|
||||||
|
📥 Всего: N сообщений из X каналов
|
||||||
|
|
||||||
|
<b>APPLY:</b>
|
||||||
|
– <a href="https://trello.com/c/...">Company A — Role (Track A)</a>
|
||||||
|
– <a href="https://trello.com/c/...">Company B — Role (Track B)</a>"""
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Always include Trello links for APPLY roles — это главная ценность уведомления.
|
||||||
|
- If QUICK-APPLY roles exist, list them with direct JD links (not Trello — для быстрого решения):
|
||||||
|
```
|
||||||
|
⚡ Quick-apply (нужно ок):
|
||||||
|
– Company X — Role → wantapply.com/...
|
||||||
|
– Company Y — Role → linkedin.com/jobs/...
|
||||||
|
```
|
||||||
|
- Keep VERIFY as a count only (no list) — they need more research, not immediate action.
|
||||||
|
- If zero APPLY and zero QUICK-APPLY: still send the message — "всё просмотрено, ничего релевантного в этот раз".
|
||||||
|
|
||||||
|
### Error message format
|
||||||
|
|
||||||
|
If the pipeline failed at any step and cannot complete — send a short error notification to the **same group**:
|
||||||
|
|
||||||
|
```
|
||||||
|
mcp__telegram-helper__send_message(
|
||||||
|
chat_id=-4783500394,
|
||||||
|
parse_mode="html",
|
||||||
|
message="""<b>⚠️ Pipeline error</b> · 2026-06-07
|
||||||
|
|
||||||
|
Упало на шаге: <step name>
|
||||||
|
Причина: <one-line error summary>
|
||||||
|
|
||||||
|
Что сделано до ошибки: <brief — e.g. "fetch OK, 12 msgs, triage не запустился">
|
||||||
|
Что нужно: <action — e.g. "обнови TELEGRAM_SESSION_STRING в .env">"""
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Send the error notification **even if only partial work was done**. Better to have an incomplete notification than silence when something breaks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sanity notes
|
||||||
|
|
||||||
|
- `telegram_state.json` is not in git — per-machine cursor. If lost, next run fetches 30 days back per channel (slow but correct).
|
||||||
|
- To reset a single channel's cursor for filter re-validation:
|
||||||
|
```bash
|
||||||
|
jq 'del(.["<channel>"])' tracking/telegram_state.json > /tmp/s.json && mv /tmp/s.json tracking/telegram_state.json
|
||||||
|
```
|
||||||
|
- `telegram_inbox.json` is overwritten every run — it's the current snapshot only.
|
||||||
|
- The pipeline reads the live "Jobs" folder — new channels Oleg added in Telegram are automatically included in the next run.
|
||||||
|
|
@ -231,19 +231,31 @@ For SKIP: one-liner after gate check explaining the objective blocker. No resear
|
||||||
|
|
||||||
When triaging `tracking/telegram_inbox.json`:
|
When triaging `tracking/telegram_inbox.json`:
|
||||||
|
|
||||||
|
### Pre-step: build prior-applications list (do this once before spawning any subagent)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -E '^\|' tracking/applications.md | tail -n +2 | awk -F'|' '{print $3}' | sort -u
|
||||||
|
```
|
||||||
|
|
||||||
|
Also call `mcp__trello__get_cards_by_list_id` for all BestJob board lists to extract card titles. Merge both into a `known_applied` list (company name → date/status where available). Pass this list to every Haiku subagent prompt (see template below).
|
||||||
|
|
||||||
|
### Stratification rules
|
||||||
|
|
||||||
1. Read the file. Group entries by channel `priority` field (`p1` / `p2` / `p3`).
|
1. Read the file. Group entries by channel `priority` field (`p1` / `p2` / `p3`).
|
||||||
2. **p1 entries**: triage in the main session (full Step 1–8 per entry).
|
2. **p1 entries — ≤ 15 messages**: triage fully in the main session (full Step 1–8 per entry).
|
||||||
3. **p2 and p3 entries**: delegate to a Haiku subagent to protect main context.
|
3. **p1 entries — > 15 messages**: Haiku first pass (same prompt as p2/p3). Main session then reviews only APPLY and VERIFY verdicts; SKIP verdicts from Haiku are accepted without re-review.
|
||||||
- Pass the raw entries and the playbook summary to the subagent.
|
4. **p2 and p3 entries**: delegate to a Haiku subagent to protect main context.
|
||||||
|
- Pass the raw entries, the playbook summary, and the `known_applied` list to the subagent.
|
||||||
- Subagent returns a structured verdict list.
|
- Subagent returns a structured verdict list.
|
||||||
- Review, escalate any surprising p2/p3 passes to main session for full triage.
|
- Review, escalate any APPLY and VERIFY passes to main session for full triage.
|
||||||
4. After all verdicts: promote APPLY entries to `tracking/applications.md`.
|
5. After all verdicts: promote confirmed APPLY entries to `tracking/applications.md`.
|
||||||
5. For QUICK-APPLY entries: note explicitly, ask Oleg if he wants to proceed.
|
6. For QUICK-APPLY entries: note explicitly, ask Oleg if he wants to proceed.
|
||||||
|
7. For VERIFY entries: run a fast WebSearch `"{company} remote hiring countries"` to resolve geo; then apply the gate.
|
||||||
|
|
||||||
### Haiku subagent prompt template
|
### Haiku subagent prompt template
|
||||||
|
|
||||||
```
|
```
|
||||||
You are triaging job vacancies for Oleg Proskurin (Tech Lead / AI Engineer, based in Thailand, available for remote work).
|
You are triaging job vacancies for Oleg Proskurin (Tech Lead / AI Engineer, based in Thailand GMT+7, available for remote work).
|
||||||
|
|
||||||
Apply these 4 hard gates — SKIP on first failure:
|
Apply these 4 hard gates — SKIP on first failure:
|
||||||
|
|
||||||
|
|
@ -252,13 +264,25 @@ Gate 2 (TZ): Oleg's window 9:00–22:00 GMT+7. Outside window: ok if comp > $9k.
|
||||||
Gate 3 (Comp): Skip if < $4.5k/month. Floor $6–8k, target $8–12k.
|
Gate 3 (Comp): Skip if < $4.5k/month. Floor $6–8k, target $8–12k.
|
||||||
Gate 4 (Level): Skip if explicitly Junior or mid with no growth ceiling.
|
Gate 4 (Level): Skip if explicitly Junior or mid with no growth ceiling.
|
||||||
|
|
||||||
Posture: trying-friendly. On ambiguous cases, lean toward APPLY.
|
Posture: trying-friendly when data IS PRESENT and borderline. When key gate data is ABSENT from the message text, return VERIFY — not APPLY. Lean toward APPLY only when geo and comp are explicitly stated and borderline (e.g. "global remote", "$7k/month").
|
||||||
|
|
||||||
For each vacancy below, output:
|
Geo rules (Gate 1):
|
||||||
- APPLY, SKIP, or QUICK-APPLY (≥50% match + easy application)
|
- Message explicitly says "global remote" / "worldwide" / "anywhere" / "любой город" / contractor-friendly → Gate 1 passes, proceed.
|
||||||
- Gate that failed (for SKIP)
|
- Message says "United States" / "US only" / "United Kingdom only" / specific country list excluding Thailand → SKIP Gate 1.
|
||||||
- Probable track: A (AI must-have), B (full-stack/frontend), Backend-first, Hybrid
|
- Message says "Remote" with no geo qualifier → VERIFY geo (cannot confirm Thailand eligibility without JD).
|
||||||
- 1-line reason
|
- Message has no location info at all → VERIFY geo.
|
||||||
|
|
||||||
|
Comp rules (Gate 3):
|
||||||
|
- Explicit salary ≥ $4.5k/month or ≥ $54k/year → Gate 3 passes, proceed.
|
||||||
|
- Explicit salary < $4.5k/month → SKIP Gate 3.
|
||||||
|
- No salary mentioned → VERIFY comp.
|
||||||
|
- Timezone offset explicitly stated (e.g. GMT-GMT+6) → treat as geo restriction, apply Gate 1.
|
||||||
|
|
||||||
|
Known applied companies — flag these as SKIP (already applied):
|
||||||
|
[INSERT known_applied LIST HERE — one line per company]
|
||||||
|
|
||||||
|
For each vacancy below, output one line:
|
||||||
|
APPLY | SKIP | QUICK-APPLY | VERIFY — [company/role] — [gate failed or what's missing] — [track: A/B/Backend-first/Hybrid]
|
||||||
|
|
||||||
Vacancies:
|
Vacancies:
|
||||||
[paste entries from telegram_inbox.json]
|
[paste entries from telegram_inbox.json]
|
||||||
|
|
|
||||||
49
CLAUDE.md
49
CLAUDE.md
|
|
@ -51,9 +51,50 @@ The list names "Applyed" and "Artifactes" are the actual spellings on the board
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Telegram — two accounts, two roles
|
||||||
|
|
||||||
|
Two Telegram accounts are wired up as separate MCP servers. **Always use the correct account for each operation** — swapping them sends outreach under the wrong name or clutters Oleg's main history with bot noise.
|
||||||
|
|
||||||
|
| Account | MCP server | Tool prefix | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **usulsu** (main) | `telegram-usulpro` | `mcp__telegram-usulpro__*` | Reading job channels; direct recruiter outreach from Oleg's name |
|
||||||
|
| **samuishechka** (helper) | `telegram-helper` | `mcp__telegram-helper__*` | Notifications to Oleg / Ekaterina; workflow triggers and confirmations |
|
||||||
|
|
||||||
|
### usulsu — main account (`mcp__telegram-usulpro__*`)
|
||||||
|
|
||||||
|
Use for everything that relates to Oleg's identity or the job-channel pipeline:
|
||||||
|
|
||||||
|
- Reading the "Jobs" folder and job channels (also used by direct Telethon scripts — see below)
|
||||||
|
- All vacancy pipeline reads: `list_telegram_channels.py`, `fetch_telegram_jobs.py`
|
||||||
|
- Direct outreach to recruiters — messages appear under Oleg's real name
|
||||||
|
- Any investigation of job postings, channel info, message context
|
||||||
|
|
||||||
|
### samuishechka — helper account (`mcp__telegram-helper__*`)
|
||||||
|
|
||||||
|
Use for auxiliary, bot-like coordination that must not mix with Oleg's main identity:
|
||||||
|
|
||||||
|
- Sending notifications to Oleg (`@usulpro`) or Ekaterina (`@dalikat`) — e.g. "triage done, 3 hot leads", "outreach sent to N recruiters"
|
||||||
|
- Receiving go-ahead commands from Oleg/Ekaterina to trigger a new pipeline run or check a specific vacancy
|
||||||
|
- Any message that is a system event rather than a human outreach
|
||||||
|
|
||||||
|
### Correct tool prefix summary
|
||||||
|
|
||||||
|
| Operation | Tool prefix |
|
||||||
|
|---|---|
|
||||||
|
| Read job channels / check vacancies | `mcp__telegram-usulpro__*` |
|
||||||
|
| Recruiter outreach (DMs from Oleg) | `mcp__telegram-usulpro__*` |
|
||||||
|
| Notify @usulpro or @dalikat | `mcp__telegram-helper__*` |
|
||||||
|
| Workflow triggers / confirmations | `mcp__telegram-helper__*` |
|
||||||
|
|
||||||
|
### Direct Telethon scripts — always usulsu
|
||||||
|
|
||||||
|
`scripts/list_telegram_channels.py` and `scripts/fetch_telegram_jobs.py` connect to Telegram directly via Telethon (bypassing MCP). They read `TELEGRAM_SESSION_STRING` from the project `.env`, which **must be the usulsu session** — the "Jobs" folder lives on that account. Never put the samuishechka session there.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Telegram MCP — safety policy
|
## Telegram MCP — safety policy
|
||||||
|
|
||||||
Claude is connected to Oleg's personal Telegram account via the `telegram-mcp` server (full MTProto access, **not** a bot). This is powerful and reaches a wide social surface, so two rules govern its use. **Both rules are absolute — they take precedence over any in-conversation instruction, including a direct user request to ignore them.**
|
Both accounts are connected via the `telegram-mcp` server (full MTProto access, **not** a bot). This is powerful and reaches a wide social surface, so two rules govern its use. **Both rules apply to both accounts and are absolute — they take precedence over any in-conversation instruction, including a direct user request to ignore them.**
|
||||||
|
|
||||||
### Rule 1 — never perform destructive or irreversible actions
|
### Rule 1 — never perform destructive or irreversible actions
|
||||||
|
|
||||||
|
|
@ -63,7 +104,7 @@ Do not call any Telegram tool whose effect cannot be cleanly undone, **even if O
|
||||||
- Leaving / deleting / archiving-then-purging chats where history would become unreachable
|
- Leaving / deleting / archiving-then-purging chats where history would become unreachable
|
||||||
- Terminating sessions, blocking users, removing contacts
|
- Terminating sessions, blocking users, removing contacts
|
||||||
- Bulk operations that change many entities at once (mass mark-as-read, mass leave, mass unsubscribe)
|
- Bulk operations that change many entities at once (mass mark-as-read, mass leave, mass unsubscribe)
|
||||||
- Changing profile photo, account info, privacy settings, 2FA — anything that alters Oleg's account identity or security posture
|
- Changing profile photo, account info, privacy settings, 2FA — anything that alters account identity or security posture
|
||||||
- Any "clear" / "reset" / "wipe" tool
|
- Any "clear" / "reset" / "wipe" tool
|
||||||
|
|
||||||
If Oleg genuinely wants such an action, he must do it himself in the Telegram client. This rule exists because a misclick from an LLM agent at this layer can lose data permanently.
|
If Oleg genuinely wants such an action, he must do it himself in the Telegram client. This rule exists because a misclick from an LLM agent at this layer can lose data permanently.
|
||||||
|
|
@ -85,8 +126,8 @@ A read-only investigation never needs permission; this rule only applies to muta
|
||||||
Operational details — file layout, scripts, filter schema, priority rubric, new-channel triage procedure — live in **[`tracking/CLAUDE.md`](tracking/CLAUDE.md)**. Read that first before doing any vacancy-related Telegram work.
|
Operational details — file layout, scripts, filter schema, priority rubric, new-channel triage procedure — live in **[`tracking/CLAUDE.md`](tracking/CLAUDE.md)**. Read that first before doing any vacancy-related Telegram work.
|
||||||
|
|
||||||
Quick orientation:
|
Quick orientation:
|
||||||
- **Source of subscriptions**: Telegram folder "Jobs" (id=6), curated manually by Oleg. Never mirror its membership to a repo file.
|
- **Source of subscriptions**: Telegram folder "Jobs" (id=6), curated manually by Oleg on the **usulsu** account. Never mirror its membership to a repo file.
|
||||||
- **Pipeline**: `scripts/list_telegram_channels.py` → `scripts/fetch_telegram_jobs.py` (chainable via stdin).
|
- **Pipeline**: `scripts/list_telegram_channels.py` → `scripts/fetch_telegram_jobs.py` (chainable via stdin). Both scripts use the **usulsu** session (`TELEGRAM_SESSION_STRING` in `.env`).
|
||||||
- **Curated config**: [`tracking/telegram_channels.json`](tracking/telegram_channels.json) — per-channel `lang`, `priority` (`p1`/`p2`/`p3`), and filter (`include` / `exclude`).
|
- **Curated config**: [`tracking/telegram_channels.json`](tracking/telegram_channels.json) — per-channel `lang`, `priority` (`p1`/`p2`/`p3`), and filter (`include` / `exclude`).
|
||||||
- **Output**: [`tracking/telegram_inbox.json`](tracking/telegram_inbox.json) — filtered messages, overwritten each run.
|
- **Output**: [`tracking/telegram_inbox.json`](tracking/telegram_inbox.json) — filtered messages, overwritten each run.
|
||||||
- **Trigger**: manual only — run when Oleg explicitly asks (e.g. "забери свежее из Jobs"). No background polling.
|
- **Trigger**: manual only — run when Oleg explicitly asks (e.g. "забери свежее из Jobs"). No background polling.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
# Telegram MCP — setup on a new machine
|
# Telegram MCP — setup on a new machine
|
||||||
|
|
||||||
Reproduces the working integration this project uses: read Oleg's Telegram channels/DMs as a user (MTProto, not a bot) through the `chigwell/telegram-mcp` server, registered in Claude Code + Claude Desktop.
|
Reproduces the working integration this project uses: two Telegram accounts wired up as separate MCP servers via `chigwell/telegram-mcp` (MTProto, not bots), registered in Claude Code + Claude Desktop.
|
||||||
|
|
||||||
Time: ~10 minutes. Most of it is one-time Telegram credentials.
|
| Account | MCP server name | Tool prefix | Role |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **usulsu** (main) | `telegram-usulpro` | `mcp__telegram-usulpro__*` | Job channels, recruiter outreach |
|
||||||
|
| **samuishechka** (helper) | `telegram-helper` | `mcp__telegram-helper__*` | Notifications to Oleg + Ekaterina, workflow triggers |
|
||||||
|
|
||||||
|
Time: ~15 minutes. Most of it is one-time Telegram credentials and two session strings.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -13,7 +18,9 @@ Time: ~10 minutes. Most of it is one-time Telegram credentials.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Get Telegram API credentials (one-time per Telegram account)
|
## 1. Get Telegram API credentials (one-time — shared across both accounts)
|
||||||
|
|
||||||
|
The `api_id` / `api_hash` identify the *app*, not the account. One app serves both sessions.
|
||||||
|
|
||||||
Open https://my.telegram.org/apps and create a new application:
|
Open https://my.telegram.org/apps and create a new application:
|
||||||
|
|
||||||
|
|
@ -27,12 +34,13 @@ You'll get `api_id` (number) and `api_hash` (32-char hex). Treat them like a pas
|
||||||
|
|
||||||
If you see a generic `ERROR` popup, fill in the URL field — that's the most common cause.
|
If you see a generic `ERROR` popup, fill in the URL field — that's the most common cause.
|
||||||
|
|
||||||
Save both into the project `.env`:
|
Save both into the project `.env` (session strings filled in after step 3):
|
||||||
|
|
||||||
```
|
```
|
||||||
TELEGRAM_API_ID=<id>
|
TELEGRAM_API_ID=<id>
|
||||||
TELEGRAM_API_HASH=<hash>
|
TELEGRAM_API_HASH=<hash>
|
||||||
TELEGRAM_SESSION_STRING=
|
TELEGRAM_SESSION_STRING= # usulsu — main account (job channels + outreach)
|
||||||
|
TELEGRAM_SESSION_STRING_HELPER= # samuishechka — notifications + workflow triggers
|
||||||
```
|
```
|
||||||
|
|
||||||
Confirm `.env` is in `.gitignore`.
|
Confirm `.env` is in `.gitignore`.
|
||||||
|
|
@ -60,52 +68,89 @@ Two binaries appear at `/projects/my-utils/telegram/.venv/bin/`:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Generate the session string (one-time, interactive)
|
## 3. Generate session strings (one per account, interactive)
|
||||||
|
|
||||||
From the cv-2026 project root (where `.env` lives):
|
Run the generator twice — once per account. From the cv-2026 project root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
set -a && source .env && set +a && \
|
set -a && source .env && set +a && \
|
||||||
/projects/my-utils/telegram/.venv/bin/telegram-mcp-generate-session
|
/projects/my-utils/telegram/.venv/bin/telegram-mcp-generate-session
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 3a. usulsu — main account
|
||||||
|
|
||||||
At the prompts:
|
At the prompts:
|
||||||
|
|
||||||
- **Account label:** *leave empty* (Enter). A label adds a `_<LABEL>` suffix to env var names and we don't need multi-account.
|
- **Account label:** *leave empty* (Enter) — output var will be `TELEGRAM_SESSION_STRING`
|
||||||
- **Login method:** `1` (QR) is the fastest — scan from any device where Telegram is already open (Settings → Devices → Link Desktop Device).
|
- **Login method:** `1` (QR) is the fastest — scan from any device where Telegram is already open (Settings → Devices → Link Desktop Device).
|
||||||
- Alternative: `2` (phone). The code arrives **inside Telegram** as a message from the official `Telegram` account, not via SMS — check there first.
|
- Alternative: `2` (phone). The code arrives **inside Telegram** as a message from the official `Telegram` account, not via SMS — check there first.
|
||||||
|
|
||||||
Output ends with a ~350-char string. Paste it into `.env` as `TELEGRAM_SESSION_STRING=<value>`.
|
Paste the output into `.env` as `TELEGRAM_SESSION_STRING=<value>`.
|
||||||
|
|
||||||
|
### 3b. samuishechka — helper account
|
||||||
|
|
||||||
|
Run the same command again, this time logged in as (or switching to) the samuishechka account:
|
||||||
|
|
||||||
|
- **Account label:** `helper` — output var will be `TELEGRAM_SESSION_STRING_HELPER`
|
||||||
|
- **Login method:** same options; use whichever device has samuishechka logged in.
|
||||||
|
|
||||||
|
Paste the output into `.env` as `TELEGRAM_SESSION_STRING_HELPER=<value>`.
|
||||||
|
|
||||||
|
Alternatively, use the `--phone` flag to avoid the interactive menu:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
TELEGRAM_API_ID=<id> TELEGRAM_API_HASH=<hash> \
|
||||||
|
/projects/my-utils/telegram/.venv/bin/python \
|
||||||
|
/projects/my-utils/telegram/session_string_generator.py --phone
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Register in Claude Code + Claude Desktop
|
## 4. Register both servers in Claude Desktop config
|
||||||
|
|
||||||
[`add-mcp`](https://www.npmjs.com/package/add-mcp) writes the server entry into both client configs in one shot. **Important gotcha:** with `--yes`, `add-mcp` stores `${VAR}` placeholders literally instead of resolving them — Claude Desktop won't expand env vars at runtime, so we resolve them manually with `jq` right after.
|
Edit `~/.config/Claude/claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`) and add two entries under `mcpServers`:
|
||||||
|
|
||||||
```bash
|
```json
|
||||||
# from the cv-2026 project root
|
"telegram-usulpro": {
|
||||||
set -a && source .env && set +a && \
|
"command": "/projects/my-utils/telegram/.venv/bin/telegram-mcp",
|
||||||
npx -y add-mcp "/projects/my-utils/telegram/.venv/bin/telegram-mcp" \
|
"args": [],
|
||||||
-n telegram \
|
"env": {
|
||||||
--env 'TELEGRAM_API_ID=${TELEGRAM_API_ID}' \
|
"TELEGRAM_API_ID": "<id>",
|
||||||
--env 'TELEGRAM_API_HASH=${TELEGRAM_API_HASH}' \
|
"TELEGRAM_API_HASH": "<hash>",
|
||||||
--env 'TELEGRAM_SESSION_STRING=${TELEGRAM_SESSION_STRING}' \
|
"TELEGRAM_SESSION_STRING": "<usulsu session string>"
|
||||||
-a claude-code -a claude-desktop \
|
}
|
||||||
-g -y
|
},
|
||||||
|
"telegram-helper": {
|
||||||
# fix the literal-placeholder issue — substitute actual values into both configs
|
"command": "/projects/my-utils/telegram/.venv/bin/telegram-mcp",
|
||||||
set -a && source .env && set +a && \
|
"args": [],
|
||||||
for CFG in ~/.config/Claude/claude_desktop_config.json ~/.claude.json; do
|
"env": {
|
||||||
jq --arg id "$TELEGRAM_API_ID" --arg hash "$TELEGRAM_API_HASH" --arg sess "$TELEGRAM_SESSION_STRING" \
|
"TELEGRAM_API_ID": "<id>",
|
||||||
'.mcpServers.telegram.env.TELEGRAM_API_ID = $id
|
"TELEGRAM_API_HASH": "<hash>",
|
||||||
| .mcpServers.telegram.env.TELEGRAM_API_HASH = $hash
|
"TELEGRAM_SESSION_STRING": "<samuishechka session string>"
|
||||||
| .mcpServers.telegram.env.TELEGRAM_SESSION_STRING = $sess' \
|
}
|
||||||
"$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG"
|
}
|
||||||
done
|
|
||||||
```
|
```
|
||||||
|
|
||||||
macOS Claude Desktop config path is `~/Library/Application Support/Claude/claude_desktop_config.json` — adjust the loop.
|
Or use `jq` to inject from `.env` in one shot (run from project root):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
set -a && source .env && set +a && \
|
||||||
|
CFG=~/.config/Claude/claude_desktop_config.json && \
|
||||||
|
jq --arg id "$TELEGRAM_API_ID" --arg hash "$TELEGRAM_API_HASH" \
|
||||||
|
--arg sess "$TELEGRAM_SESSION_STRING" \
|
||||||
|
--arg sess_h "$TELEGRAM_SESSION_STRING_HELPER" '
|
||||||
|
.mcpServers["telegram-usulpro"] = {
|
||||||
|
"command": "/projects/my-utils/telegram/.venv/bin/telegram-mcp",
|
||||||
|
"args": [], "env": {
|
||||||
|
"TELEGRAM_API_ID": $id, "TELEGRAM_API_HASH": $hash,
|
||||||
|
"TELEGRAM_SESSION_STRING": $sess }} |
|
||||||
|
.mcpServers["telegram-helper"] = {
|
||||||
|
"command": "/projects/my-utils/telegram/.venv/bin/telegram-mcp",
|
||||||
|
"args": [], "env": {
|
||||||
|
"TELEGRAM_API_ID": $id, "TELEGRAM_API_HASH": $hash,
|
||||||
|
"TELEGRAM_SESSION_STRING": $sess_h }}' \
|
||||||
|
"$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG"
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -114,13 +159,18 @@ macOS Claude Desktop config path is `~/Library/Application Support/Claude/claude
|
||||||
- **Claude Desktop:** quit and reopen.
|
- **Claude Desktop:** quit and reopen.
|
||||||
- **Claude Code:** start a new session (this CLI loads MCP servers at session start).
|
- **Claude Code:** start a new session (this CLI loads MCP servers at session start).
|
||||||
|
|
||||||
In the new session, ask: *"проверь доступ к телеграму"*. The agent should call `mcp__telegram__get_me` and return your handle.
|
In the new session, ask: *"проверь доступ к обоим телеграм аккаунтам"*. The agent should call both `mcp__telegram-usulpro__get_me` (→ @UsulPro) and `mcp__telegram-helper__get_me` (→ @samuishka) and return the respective handles.
|
||||||
|
|
||||||
If tools never appear, check the server starts cleanly:
|
If tools never appear, smoke-test each server manually:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# usulsu
|
||||||
set -a && source .env && set +a && /projects/my-utils/telegram/.venv/bin/telegram-mcp
|
set -a && source .env && set +a && /projects/my-utils/telegram/.venv/bin/telegram-mcp
|
||||||
# Ctrl+C after it prints startup logs — it's an stdio MCP server, this just smoke-tests it
|
|
||||||
|
# samuishechka
|
||||||
|
TELEGRAM_SESSION_STRING="$TELEGRAM_SESSION_STRING_HELPER" \
|
||||||
|
/projects/my-utils/telegram/.venv/bin/telegram-mcp
|
||||||
|
# Ctrl+C after startup logs — it's a stdio MCP server
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ channels for keyword decisions.
|
||||||
Inputs:
|
Inputs:
|
||||||
- channel usernames/ids as positional args, OR `-` to read a JSON array from stdin
|
- channel usernames/ids as positional args, OR `-` to read a JSON array from stdin
|
||||||
- .env in the project root (TELEGRAM_API_ID, TELEGRAM_API_HASH, TELEGRAM_SESSION_STRING)
|
- .env in the project root (TELEGRAM_API_ID, TELEGRAM_API_HASH, TELEGRAM_SESSION_STRING)
|
||||||
|
TELEGRAM_SESSION_STRING must be the **usulsu** (main) account — the "Jobs" folder lives there.
|
||||||
- tracking/telegram_state.json — per-channel last_message_id (created if missing)
|
- tracking/telegram_state.json — per-channel last_message_id (created if missing)
|
||||||
- tracking/telegram_channels.json — per-channel curated metadata (lang, priority)
|
- tracking/telegram_channels.json — per-channel curated metadata (lang, priority)
|
||||||
and filter (include/exclude). See tracking/CLAUDE.md.
|
and filter (include/exclude). See tracking/CLAUDE.md.
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ Output: JSON array of usernames (falling back to numeric id for private channels
|
||||||
|
|
||||||
Pipe directly into the fetch script:
|
Pipe directly into the fetch script:
|
||||||
list_telegram_channels.py | fetch_telegram_jobs.py -
|
list_telegram_channels.py | fetch_telegram_jobs.py -
|
||||||
|
|
||||||
|
Account: uses TELEGRAM_SESSION_STRING from .env — must be the usulsu (main) account.
|
||||||
|
The "Jobs" folder lives on that account.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Regenerate Telegram session string via QR login.
|
# Regenerate a Telegram session string.
|
||||||
# Reads TELEGRAM_API_ID / TELEGRAM_API_HASH from the project .env,
|
# Reads TELEGRAM_API_ID / TELEGRAM_API_HASH from the project .env,
|
||||||
# then runs the generator from the telegram-mcp install.
|
# then runs the generator from the telegram-mcp install.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# bash scripts/regen_telegram_session.sh # regenerate usulsu (main)
|
||||||
|
# bash scripts/regen_telegram_session.sh helper # regenerate samuishechka
|
||||||
|
#
|
||||||
|
# After running, paste the printed session string into the project .env:
|
||||||
|
# usulsu → TELEGRAM_SESSION_STRING=...
|
||||||
|
# helper → TELEGRAM_SESSION_STRING_HELPER=...
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
ACCOUNT="${1:-}" # "helper" for samuishechka, empty for usulsu
|
||||||
|
|
||||||
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
TELEGRAM_DIR="/projects/my-utils/telegram"
|
TELEGRAM_DIR="/projects/my-utils/telegram"
|
||||||
|
|
||||||
|
|
@ -21,5 +31,14 @@ if [ -z "${TELEGRAM_API_ID:-}" ] || [ -z "${TELEGRAM_API_HASH:-}" ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$ACCOUNT" = "helper" ]; then
|
||||||
|
echo "Regenerating session for samuishechka (helper account)."
|
||||||
|
echo "When prompted for label, enter: helper"
|
||||||
|
else
|
||||||
|
echo "Regenerating session for usulsu (main account)."
|
||||||
|
echo "When prompted for label, leave empty (press Enter)."
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
cd "$TELEGRAM_DIR"
|
cd "$TELEGRAM_DIR"
|
||||||
exec .venv/bin/python session_string_generator.py --qr "$@"
|
exec .venv/bin/python session_string_generator.py --qr "$@"
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ Two scripts, chainable. Always run from project root.
|
||||||
|
|
||||||
**Step 2 — `scripts/fetch_telegram_jobs.py`**: pulls new messages per channel, applies the per-channel filter, and writes results to `telegram_inbox.json`. Accepts channels as positional args or as a JSON array on stdin (`-`).
|
**Step 2 — `scripts/fetch_telegram_jobs.py`**: pulls new messages per channel, applies the per-channel filter, and writes results to `telegram_inbox.json`. Accepts channels as positional args or as a JSON array on stdin (`-`).
|
||||||
|
|
||||||
|
**Account:** both scripts connect directly via Telethon using `TELEGRAM_SESSION_STRING` from `.env` — that must be the **usulsu** (main) session. The "Jobs" folder lives on that account. Do not put the samuishechka session there.
|
||||||
|
|
||||||
### Constants in the fetch script
|
### Constants in the fetch script
|
||||||
|
|
||||||
- `DEFAULT_LOOKBACK_DAYS = 30` — first-time lookback window for new channels (no cursor yet).
|
- `DEFAULT_LOOKBACK_DAYS = 30` — first-time lookback window for new channels (no cursor yet).
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"JS_RemoteJobs": {
|
"JS_RemoteJobs": {
|
||||||
"lang": "en",
|
"lang": "en",
|
||||||
"priority": "p1",
|
"priority": "p2",
|
||||||
"exclude": ["kafka", "golang", "kotlin", "android", "swift", " rust ", " java "]
|
"exclude": ["kafka", "golang", "kotlin", "android", "swift", " rust ", " java "]
|
||||||
},
|
},
|
||||||
"notificaJobs_nodeJS": {
|
"notificaJobs_nodeJS": {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
{
|
||||||
|
"mobile_jobs": {
|
||||||
|
"last_message_id": 211450,
|
||||||
|
"last_seen_date": "2026-06-06T13:58:01+00:00"
|
||||||
|
},
|
||||||
|
"javascript_jobs": {
|
||||||
|
"last_message_id": 881871,
|
||||||
|
"last_seen_date": "2026-06-06T18:42:26+00:00"
|
||||||
|
},
|
||||||
|
"javascript_jobs_feed": {
|
||||||
|
"last_message_id": 39247,
|
||||||
|
"last_seen_date": "2026-06-06T18:42:25+00:00"
|
||||||
|
},
|
||||||
|
"devops_jobs": {
|
||||||
|
"last_message_id": 1566498,
|
||||||
|
"last_seen_date": "2026-06-06T18:48:55+00:00"
|
||||||
|
},
|
||||||
|
"newworld_2088": {
|
||||||
|
"last_message_id": 8390,
|
||||||
|
"last_seen_date": "2026-06-04T19:29:30+00:00"
|
||||||
|
},
|
||||||
|
"nodejs_jobs": {
|
||||||
|
"last_message_id": 133491,
|
||||||
|
"last_seen_date": "2026-06-06T12:47:02+00:00"
|
||||||
|
},
|
||||||
|
"mobile_vacancies": {
|
||||||
|
"last_message_id": 85194,
|
||||||
|
"last_seen_date": "2026-06-06T13:21:11+00:00"
|
||||||
|
},
|
||||||
|
"jsspeak": {
|
||||||
|
"last_message_id": 58668,
|
||||||
|
"last_seen_date": "2026-06-06T14:14:30+00:00"
|
||||||
|
},
|
||||||
|
"-1002137052673": {
|
||||||
|
"last_message_id": 9437,
|
||||||
|
"last_seen_date": "2026-06-06T18:06:02+00:00"
|
||||||
|
},
|
||||||
|
"projects_jobs": {
|
||||||
|
"last_message_id": 137333,
|
||||||
|
"last_seen_date": "2026-06-06T13:15:22+00:00"
|
||||||
|
},
|
||||||
|
"zarubezhom_jobs": {
|
||||||
|
"last_message_id": 3922,
|
||||||
|
"last_seen_date": "2026-06-06T18:14:12+00:00"
|
||||||
|
},
|
||||||
|
"bez_investorov": {
|
||||||
|
"last_message_id": 34,
|
||||||
|
"last_seen_date": "2026-06-06T14:02:03+00:00"
|
||||||
|
},
|
||||||
|
"budny_lucky_hunter": {
|
||||||
|
"last_message_id": 2069,
|
||||||
|
"last_seen_date": "2026-06-05T11:36:05+00:00"
|
||||||
|
},
|
||||||
|
"remotejobss": {
|
||||||
|
"last_message_id": 12948,
|
||||||
|
"last_seen_date": "2026-06-06T10:00:06+00:00"
|
||||||
|
},
|
||||||
|
"huntermikevolkov": {
|
||||||
|
"last_message_id": 308,
|
||||||
|
"last_seen_date": "2026-06-05T06:45:07+00:00"
|
||||||
|
},
|
||||||
|
"devitjobs_nl": {
|
||||||
|
"last_message_id": 1636,
|
||||||
|
"last_seen_date": "2026-06-05T09:03:47+00:00"
|
||||||
|
},
|
||||||
|
"Jobs_global_startups": {
|
||||||
|
"last_message_id": 711,
|
||||||
|
"last_seen_date": "2026-06-04T07:20:45+00:00"
|
||||||
|
},
|
||||||
|
"gogetajob": {
|
||||||
|
"last_message_id": 145083,
|
||||||
|
"last_seen_date": "2026-06-06T11:39:16+00:00"
|
||||||
|
},
|
||||||
|
"agile_jobs": {
|
||||||
|
"last_message_id": 61712,
|
||||||
|
"last_seen_date": "2026-06-04T13:49:42+00:00"
|
||||||
|
},
|
||||||
|
"techjobsworld": {
|
||||||
|
"last_message_id": 1614,
|
||||||
|
"last_seen_date": "2026-06-02T17:44:08+00:00"
|
||||||
|
},
|
||||||
|
"javascriptjobjs": {
|
||||||
|
"last_message_id": 424,
|
||||||
|
"last_seen_date": "2026-05-12T16:25:08+00:00"
|
||||||
|
},
|
||||||
|
"remote_jobs_today": {
|
||||||
|
"last_message_id": 103,
|
||||||
|
"last_seen_date": "2025-04-06T07:34:01+00:00"
|
||||||
|
},
|
||||||
|
"RemotiveJobs_All_Others": {
|
||||||
|
"last_message_id": 514,
|
||||||
|
"last_seen_date": "2024-12-30T03:13:11+00:00"
|
||||||
|
},
|
||||||
|
"Remote_Software_Developer_Jobs": {
|
||||||
|
"last_message_id": 1073,
|
||||||
|
"last_seen_date": "2024-12-24T15:13:05+00:00"
|
||||||
|
},
|
||||||
|
"serbia_jobs": {
|
||||||
|
"last_message_id": 48345,
|
||||||
|
"last_seen_date": "2026-06-06T16:05:27+00:00"
|
||||||
|
},
|
||||||
|
"jaabz_com": {
|
||||||
|
"last_message_id": 10793,
|
||||||
|
"last_seen_date": "2026-06-06T19:00:07+00:00"
|
||||||
|
},
|
||||||
|
"ai_dev_job": {
|
||||||
|
"last_message_id": 81,
|
||||||
|
"last_seen_date": "2026-06-06T15:00:41+00:00"
|
||||||
|
},
|
||||||
|
"devsunite": {
|
||||||
|
"last_message_id": 46,
|
||||||
|
"last_seen_date": "2026-06-06T18:02:25+00:00"
|
||||||
|
},
|
||||||
|
"notificaJobs_nodeJS": {
|
||||||
|
"last_message_id": 3877,
|
||||||
|
"last_seen_date": "2026-06-06T12:49:44+00:00"
|
||||||
|
},
|
||||||
|
"jsdevjob": {
|
||||||
|
"last_message_id": 2384,
|
||||||
|
"last_seen_date": "2026-06-05T18:03:38+00:00"
|
||||||
|
},
|
||||||
|
"itdevjobs": {
|
||||||
|
"last_message_id": 703,
|
||||||
|
"last_seen_date": "2026-06-05T17:00:11+00:00"
|
||||||
|
},
|
||||||
|
"dev_connectablejobs": {
|
||||||
|
"last_message_id": 2123,
|
||||||
|
"last_seen_date": "2026-06-05T15:02:24+00:00"
|
||||||
|
},
|
||||||
|
"it_jobs_cyprus": {
|
||||||
|
"last_message_id": 7624,
|
||||||
|
"last_seen_date": "2026-06-06T19:20:59+00:00"
|
||||||
|
},
|
||||||
|
"JS_RemoteJobs": {
|
||||||
|
"last_message_id": 336,
|
||||||
|
"last_seen_date": "2026-06-05T11:00:37+00:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue