Skip to content

Repository files navigation

MTC Job Alert - Slack Bot

Made by: Heba Alazzeh

Checks a set of tech-internship and full-time new-grad sources every hour, skips anything it's already posted, filters out defense-industry employers, and drops a concise alert into Slack with role, company, location, posting date, and a direct apply link.

  • All job alerts — internships and full-time — post to one channel, each message labeled [Internship] or [Full-Time] so they're easy to tell apart in a single feed.

(Set in config.py as SLACK_CHANNEL, overridable via env var.)

Default sources (edit in config.py):

These are actively-maintained, scrape-friendly public lists rather than scraping LinkedIn/Indeed directly, which is more reliable and avoids ToS issues. You can add more sources — see "Adding a source" below.

Defense-industry exclusion

Any listing whose company name matches the blocklist in config.py (DEFENSE_COMPANY_BLOCKLIST) is dropped entirely — never posted at all. Currently blocked: Raytheon/RTX, Lockheed Martin, Northrop Grumman, Boeing, General Dynamics, L3Harris, BAE Systems, Leonardo, Thales, Anduril, Palantir, Shield AI, Saronic, Skydio, Epirus, Firestorm Labs, Helsing, Rebellion Defense, Govini, Rocket Lab, and Apex Space.

Matching is a case-insensitive substring check on the company name, so it's intentionally broad. A couple of names are generic or dual-use (e.g. "Boeing" also does commercial aviation, "Leonardo" is a common name, "Rocket Lab"/"Apex Space" do civil space work too) — that's a deliberate trade-off toward not letting anything defense-related slip through, at the cost of occasionally also filtering an unrelated listing that happens to share the name. To extend the list without touching code, set DEFENSE_COMPANY_BLOCKLIST_EXTRA as a comma-separated env var (e.g. "kratos,leidos") — it adds to, rather than replaces, the defaults.

Keeping this safe as a public repo

This repo is designed to be safe to make public — the token is never stored in code, only in GitHub Actions secrets. Before publishing:

  • SLACK_BOT_TOKEN must never be committed. It only ever lives in the repo's Settings → Secrets and variables → Actions → Secrets (encrypted, not visible even to you after saving) or in a local .env file — which .gitignore already excludes.
  • .env.example is safe to commit (no real values); .env is not and is gitignored.
  • seen_listings.json only contains job listing IDs and a timestamp — no secrets, safe to be public and to get committed back by the workflow.
  • If a token ever does leak (e.g. pasted somewhere by accident), revoke it immediately in the Slack app's OAuth & Permissions page (reinstall to get a new one) — a leaked bot token only allows posting messages as this bot (chat:write scope), not account takeover, but it should still be rotated right away.

Configuration

All tunable settings live in config.py / environment variables:

Setting Purpose Default
SLACK_BOT_TOKEN Bot token (required unless DRY_RUN)
DRY_RUN If true, log what would post instead of calling Slack; no token needed false
SLACK_CHANNEL Channel ID all alerts post to C0BPUEWSEM6
MAX_LISTING_AGE_DAYS Ignore listings older than this 14
ROLE_INCLUDE_KEYWORDS Comma-separated; only post if role title matches one (none — allow all)
ROLE_EXCLUDE_KEYWORDS Comma-separated; skip if role title matches one (none)
DEFENSE_COMPANY_BLOCKLIST_EXTRA Comma-separated; extra companies to block, added to the built-in list (none)
SEEN_STORE_PATH Where dedupe state is saved seen_listings.json

Example: only software/data roles, skip anything requiring an advanced degree:

export ROLE_INCLUDE_KEYWORDS="software,swe,backend,frontend,full-stack,data,ml,machine learning"
export ROLE_EXCLUDE_KEYWORDS="phd,mba"

How de-duping works

Every listing gets a stable ID (the source's own ID when available, otherwise a hash of company+role+link). seen_listings.json tracks every ID the bot has ever fetched — not just ones it posted — so a listing that gets filtered out today (e.g. too old, wrong keyword) won't be re-evaluated and possibly posted later once it "ages into" being new. Only truly new IDs trigger a Slack message.

Message format

Each alert is its own Slack message, labeled with its job type since both post to the same channel:

🧑‍🎓 Internship Software Engineer Intern @ Acme Corp 📍 San Francisco, CA    📅 Posted Aug 05, 2026 Apply here Source: SimplifyJobs Summer2027-Internships

💼 Full-Time Software Engineer @ Stripe 📍 San Francisco, CA    📅 Posted Aug 05, 2026 Apply here Source: SimplifyJobs New-Grad-Positions

Adding a source

Most internship-list GitHub repos that follow the SimplifyJobs listings.json format work out of the box — just add a new entry to SOURCES in config.py with "type": "github_json" and the raw file URL.

For a different shape of source (an RSS feed, a different JSON API, a company careers page with a public API), write a fetch_<type>(source) function in sources.py that returns the normalized listing dict shape documented at the top of that file, then register it in FETCHERS.

Notes & limits

  • Scraping LinkedIn, Handshake, or Indeed directly isn't included here — those sites actively block automated scraping and it would violate their Terms of Service. The GitHub-list + RemoteOK approach avoids that while still surfacing the same roles (Simplify's own bot scrapes company career pages hourly and feeds these lists).
  • Slack's API rate limits chat.postMessage to roughly 1 message/second per channel; slack_notifier.post_all paces requests accordingly.
  • If a source's format changes upstream, that source will log an error and be skipped for that run — it won't crash the whole job.

About

A Slackbot that updates the MTC berkeley slack with internship and fulltime job opportunties

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages