pnvasko/social-job-scanner

AI-powered Telegram job scanner that discovers, deduplicates, filters, classifies, and matches job vacancies against a candidate profile using OpenAI and local LLMs.

Go

1

3 commits

updated Aug 3, 2026

See the code

See what people are saying (1)

SourceMessageScoreDate

Social Job Scanner – an open-source tool for filtering Telegram job posts (r/SideProject)

I built a small open-source project to reduce the time spent browsing job posts across Telegram channels. The tool currently: * imports a list of Telegram channels * downloads messages from the previous 30 days * evaluates whether each channel is relevant to a candidate profile * filters irrelevant…

1

Sep 18, 2026

README

Social Job Scanner

Social Job Scanner is an AI-powered job discovery and candidate-matching platform. The current version searches for software engineering vacancies published in Telegram channels.

The application imports a list of Telegram channels, downloads messages from the previous 30 days, and analyzes their relevance using a configurable candidate profile. The profile is currently defined as a developer-focused prompt, but the matching system can be adapted for other professions and requirements.

During the initial channel analysis, OpenAI classifies detected job postings and ranks each Telegram channel as suitable or unsuitable for continued monitoring.

Approved channels are then monitored automatically. New messages pass through a multi-stage processing pipeline:

  1. Basic duplicate detection
  2. Keyword-based pre-filtering
  3. Local LLM classification
  4. Final candidate-profile matching and vacancy scoring

A lightweight web interface allows users to:

  • Review discovered job vacancies
  • View relevance scores and classification results
  • Open the original vacancy in Telegram
  • Update the application or review status
  • Track processed, rejected, and shortlisted opportunities

The project is currently focused on Telegram, with an architecture intended to support additional job sources such as LinkedIn, job boards, RSS feeds, and company career pages.

This repository is intended to run directly on a local machine. It does not use Docker, Compose, or a reverse proxy.

Key features

  • Telegram channel scanning
  • AI-based job classification
  • Candidate-profile matching
  • OpenAI integration
  • Local LLM pre-classification
  • Channel quality ranking
  • Keyword filtering
  • Job-post deduplication
  • Vacancy relevance scoring
  • Job application status tracking
  • Simple job-review web UI
  • Extensible multi-source ingestion pipeline

Prerequisites

  • Go 1.26 or newer
  • Node.js 24 or newer
  • pnpm 11.18.0 (the version pinned in ui/package.json)
  • A host-compatible llama-server binary
  • A GGUF model; iac/download_models.sh downloads the currently expected model
  • Google Chrome (the MCP web-page service starts it automatically)

To build llama.cpp for the host, run:

./iac/llama_cpp.sh

By default, that installs llama-server under ~/.local/bin.

GPU build (NVIDIA / CUDA)

The default build is CPU-only. On a CPU-only binary, TD_GPU_LAYERS (-ngl) is silently ignored and large models load entirely on the CPU — which can look like a hang. Building the CUDA backend requires the CUDA toolkit (nvcc), not just the driver:

nvcc --version                 # if this fails, install the toolkit first,
                               # e.g. `sudo apt install nvidia-cuda-toolkit`
CUDA=on ./iac/llama_cpp.sh     # `auto` (the default) also builds CUDA when nvcc is found

The script prints the visible GPU devices after installing. Verify your card is listed, then enable offload in .env:

TD_GPU_LAYERS=99                       # layers to offload; 99 = all, 0 = CPU
TD_LLAMA_MMPROJ_PATH=/path/to/mmproj   # optional; only for vision models
llama-server --list-devices            # confirm the GPU is detected

VRAM budget. Model weights + the F16 mmproj (if set) + the KV cache must fit in VRAM. If the model fails to load on the GPU, drop TD_LLAMA_MMPROJ_PATH (job classification is text-only), use a smaller/lower-quant model, or lower TD_GPU_LAYERS. As a reference, a 9B Q5 model with a 32k context is tight on a 12 GB card.

Configuration

Copy the example configuration and replace every placeholder:

cp .env.example .env

The most important local paths are:

TD_DATA_PATH=/absolute/path/to/socialJobScanner/data
TD_LLAMA_SERVER_PATH=/absolute/path/to/llama-server
TD_LLAMA_MODEL_PATH=/absolute/path/to/model.gguf
TD_SOURCES_FILE=/absolute/path/to/sources.md

The backend loads variables with the TD_ prefix. The local runner sources .env before starting the backend. TD_SOURCES_FILE is required only by the estimate-chats command.

Candidate profile

The final-stage OpenAI evaluator (finalStage.md) scores each vacancy against your candidate profile. Create candidate_profile.json in the data directory ($TD_DATA_PATH/candidate_profile.json) before starting the backend — the analyzer loads it at startup and fails fast if it is missing or not valid JSON. It lives under data/ (git-ignored) so your personal details never enter the repository.

The content is free-form JSON injected verbatim into the prompt; use whatever keys describe you. A minimal example:

{
  "seniority": ["senior", "lead"],
  "role_types": ["backend", "full_stack", "platform"],
  "years_experience": "8+",
  "primary_technologies": ["Go", "Python"],
  "additional_technologies": ["TypeScript", "Rust"],
  "databases": ["PostgreSQL", "ClickHouse"],
  "specialties": ["distributed systems", "backend APIs", "ETL"],
  "domains": ["fintech", "logistics"],
  "location": "Remote (EU)",
  "remote_experience": true,
  "languages": { "English": "professional working proficiency" }
}

Editing the file takes effect on the next backend start — it is read at runtime, not embedded, so no rebuild is needed.

Install

Backend dependencies are managed by Go modules. Install UI dependencies once:

pnpm --dir ui install --frozen-lockfile

Run locally

Start the API, MCP web-page service, managed Chrome process, and UI together:

./run-local.sh

Open http://127.0.0.1:8080. Vite proxies /api requests to the Go API at http://127.0.0.1:9092, so no TLS or CORS setup is required.

The runner stops all child processes when any one of them exits or when you press Ctrl+C.

Telegram authentication (headless)

On first run, when a login code is needed the service logs:

stdin is not a terminal; waiting for telegram auth code in file "<DataPath>/telegram_auth_code" ...

Supply the code once Telegram sends it to your account:

echo 12345 > <DataPath>/telegram_auth_code
# or export TELEGRAM_AUTH_CODE=12345 before starting the service

After the first success the DB-backed session storage persists the session, so no code is needed on subsequent runs.

Run individual components

With .env exported into the current shell, start only the backend:

go -C services run ./socials/cmd serve

Start only the UI:

pnpm --dir ui run dev

LLM prompts

The analyzer prompts live in services/socials/internal/analyzer/prompts/ and are embedded into the backend binary at build time (go:embed), so editing a prompt requires a rebuild. Each drives a different stage of the pipeline:

FileModelUsed byPurpose
estimateJob.mdlocal llama-serverper-message prescreen (EstimateMessage)Fast, high-precision filter: decides whether a single message is a relevant software-engineering vacancy and emits priority, reason, matched_stack, detected_role, detected_level, is_spam.
estimateJob.gbnflocal llama-serversameGBNF grammar that constrains the above output to complete, enum-valid JSON (this build's json_schema response format is broken, so the grammar enforces the shape).
finalStage.mdOpenAI Responses APIfinal pass (EstimateJob)Richer second pass on high-priority messages: compares one vacancy against the full candidate profile and returns a scored JobMatch.
channelQualityEvaluator.mdlocal llama-serverchannel rating (EstimateChannel)Extracts every unique concrete vacancy from a sample of a channel's messages, feeding the channel-quality verdict (the Load Channels / estimate-chats flow).

The candidate profile and the accepted technologies/levels/work-types are substituted into finalStage.md and channelQualityEvaluator.md at runtime (see prompts.go); the {{...}} placeholders in the files are filled before the prompt is sent.

Verify

go -C services build -buildvcs=false ./...
go -C services test -buildvcs=false ./...
pnpm --dir ui run build

License

Released under the MIT License.

  • THIRD_PARTY_NOTICES.md — licenses of bundled/used dependencies and models.
  • DISCLAIMER.md — platform/API compliance and data-protection notes. Using this tool means accepting the terms of every platform, API, and data source it touches.

Contributors

pnvasko

3 commits

pnvasko/social-job-scanner

AI-powered Telegram job scanner that discovers, deduplicates, filters, classifies, and matches job vacancies against a candidate profile using OpenAI and local LLMs.

Go

1

3 commits

updated Aug 3, 2026

See the code

See what people are saying (1)

SourceMessageScoreDate

Social Job Scanner – an open-source tool for filtering Telegram job posts (r/SideProject)

I built a small open-source project to reduce the time spent browsing job posts across Telegram channels. The tool currently: * imports a list of Telegram channels * downloads messages from the previous 30 days * evaluates whether each channel is relevant to a candidate profile * filters irrelevant…

1

Sep 18, 2026

README

Social Job Scanner

Social Job Scanner is an AI-powered job discovery and candidate-matching platform. The current version searches for software engineering vacancies published in Telegram channels.

The application imports a list of Telegram channels, downloads messages from the previous 30 days, and analyzes their relevance using a configurable candidate profile. The profile is currently defined as a developer-focused prompt, but the matching system can be adapted for other professions and requirements.

During the initial channel analysis, OpenAI classifies detected job postings and ranks each Telegram channel as suitable or unsuitable for continued monitoring.

Approved channels are then monitored automatically. New messages pass through a multi-stage processing pipeline:

  1. Basic duplicate detection
  2. Keyword-based pre-filtering
  3. Local LLM classification
  4. Final candidate-profile matching and vacancy scoring

A lightweight web interface allows users to:

  • Review discovered job vacancies
  • View relevance scores and classification results
  • Open the original vacancy in Telegram
  • Update the application or review status
  • Track processed, rejected, and shortlisted opportunities

The project is currently focused on Telegram, with an architecture intended to support additional job sources such as LinkedIn, job boards, RSS feeds, and company career pages.

This repository is intended to run directly on a local machine. It does not use Docker, Compose, or a reverse proxy.

Key features

  • Telegram channel scanning
  • AI-based job classification
  • Candidate-profile matching
  • OpenAI integration
  • Local LLM pre-classification
  • Channel quality ranking
  • Keyword filtering
  • Job-post deduplication
  • Vacancy relevance scoring
  • Job application status tracking
  • Simple job-review web UI
  • Extensible multi-source ingestion pipeline

Prerequisites

  • Go 1.26 or newer
  • Node.js 24 or newer
  • pnpm 11.18.0 (the version pinned in ui/package.json)
  • A host-compatible llama-server binary
  • A GGUF model; iac/download_models.sh downloads the currently expected model
  • Google Chrome (the MCP web-page service starts it automatically)

To build llama.cpp for the host, run:

./iac/llama_cpp.sh

By default, that installs llama-server under ~/.local/bin.

GPU build (NVIDIA / CUDA)

The default build is CPU-only. On a CPU-only binary, TD_GPU_LAYERS (-ngl) is silently ignored and large models load entirely on the CPU — which can look like a hang. Building the CUDA backend requires the CUDA toolkit (nvcc), not just the driver:

nvcc --version                 # if this fails, install the toolkit first,
                               # e.g. `sudo apt install nvidia-cuda-toolkit`
CUDA=on ./iac/llama_cpp.sh     # `auto` (the default) also builds CUDA when nvcc is found

The script prints the visible GPU devices after installing. Verify your card is listed, then enable offload in .env:

TD_GPU_LAYERS=99                       # layers to offload; 99 = all, 0 = CPU
TD_LLAMA_MMPROJ_PATH=/path/to/mmproj   # optional; only for vision models
llama-server --list-devices            # confirm the GPU is detected

VRAM budget. Model weights + the F16 mmproj (if set) + the KV cache must fit in VRAM. If the model fails to load on the GPU, drop TD_LLAMA_MMPROJ_PATH (job classification is text-only), use a smaller/lower-quant model, or lower TD_GPU_LAYERS. As a reference, a 9B Q5 model with a 32k context is tight on a 12 GB card.

Configuration

Copy the example configuration and replace every placeholder:

cp .env.example .env

The most important local paths are:

TD_DATA_PATH=/absolute/path/to/socialJobScanner/data
TD_LLAMA_SERVER_PATH=/absolute/path/to/llama-server
TD_LLAMA_MODEL_PATH=/absolute/path/to/model.gguf
TD_SOURCES_FILE=/absolute/path/to/sources.md

The backend loads variables with the TD_ prefix. The local runner sources .env before starting the backend. TD_SOURCES_FILE is required only by the estimate-chats command.

Candidate profile

The final-stage OpenAI evaluator (finalStage.md) scores each vacancy against your candidate profile. Create candidate_profile.json in the data directory ($TD_DATA_PATH/candidate_profile.json) before starting the backend — the analyzer loads it at startup and fails fast if it is missing or not valid JSON. It lives under data/ (git-ignored) so your personal details never enter the repository.

The content is free-form JSON injected verbatim into the prompt; use whatever keys describe you. A minimal example:

{
  "seniority": ["senior", "lead"],
  "role_types": ["backend", "full_stack", "platform"],
  "years_experience": "8+",
  "primary_technologies": ["Go", "Python"],
  "additional_technologies": ["TypeScript", "Rust"],
  "databases": ["PostgreSQL", "ClickHouse"],
  "specialties": ["distributed systems", "backend APIs", "ETL"],
  "domains": ["fintech", "logistics"],
  "location": "Remote (EU)",
  "remote_experience": true,
  "languages": { "English": "professional working proficiency" }
}

Editing the file takes effect on the next backend start — it is read at runtime, not embedded, so no rebuild is needed.

Install

Backend dependencies are managed by Go modules. Install UI dependencies once:

pnpm --dir ui install --frozen-lockfile

Run locally

Start the API, MCP web-page service, managed Chrome process, and UI together:

./run-local.sh

Open http://127.0.0.1:8080. Vite proxies /api requests to the Go API at http://127.0.0.1:9092, so no TLS or CORS setup is required.

The runner stops all child processes when any one of them exits or when you press Ctrl+C.

Telegram authentication (headless)

On first run, when a login code is needed the service logs:

stdin is not a terminal; waiting for telegram auth code in file "<DataPath>/telegram_auth_code" ...

Supply the code once Telegram sends it to your account:

echo 12345 > <DataPath>/telegram_auth_code
# or export TELEGRAM_AUTH_CODE=12345 before starting the service

After the first success the DB-backed session storage persists the session, so no code is needed on subsequent runs.

Run individual components

With .env exported into the current shell, start only the backend:

go -C services run ./socials/cmd serve

Start only the UI:

pnpm --dir ui run dev

LLM prompts

The analyzer prompts live in services/socials/internal/analyzer/prompts/ and are embedded into the backend binary at build time (go:embed), so editing a prompt requires a rebuild. Each drives a different stage of the pipeline:

FileModelUsed byPurpose
estimateJob.mdlocal llama-serverper-message prescreen (EstimateMessage)Fast, high-precision filter: decides whether a single message is a relevant software-engineering vacancy and emits priority, reason, matched_stack, detected_role, detected_level, is_spam.
estimateJob.gbnflocal llama-serversameGBNF grammar that constrains the above output to complete, enum-valid JSON (this build's json_schema response format is broken, so the grammar enforces the shape).
finalStage.mdOpenAI Responses APIfinal pass (EstimateJob)Richer second pass on high-priority messages: compares one vacancy against the full candidate profile and returns a scored JobMatch.
channelQualityEvaluator.mdlocal llama-serverchannel rating (EstimateChannel)Extracts every unique concrete vacancy from a sample of a channel's messages, feeding the channel-quality verdict (the Load Channels / estimate-chats flow).

The candidate profile and the accepted technologies/levels/work-types are substituted into finalStage.md and channelQualityEvaluator.md at runtime (see prompts.go); the {{...}} placeholders in the files are filled before the prompt is sent.

Verify

go -C services build -buildvcs=false ./...
go -C services test -buildvcs=false ./...
pnpm --dir ui run build

License

Released under the MIT License.

  • THIRD_PARTY_NOTICES.md — licenses of bundled/used dependencies and models.
  • DISCLAIMER.md — platform/API compliance and data-protection notes. Using this tool means accepting the terms of every platform, API, and data source it touches.

Contributors

pnvasko

3 commits

Languages

Go

66.5%

JavaScript

24.5%

Vue

6.4%

Shell

1.6%