Your automated daily tech news digest, delivered fresh to Slack and/or Telegram every morning β
Features β’ Setup β’ Usage β’ Deployment β’ Configuration
|
|
|
|
Before you begin, ensure you have:
| Requirement | Description |
|---|---|
| Node.js | Version 20.x or higher (Download) |
| Gemini API Key | Get it from Google AI Studio |
| Delivery Target | Configure at least one destination: Slack or Telegram |
| Slack Bot Token | Optional. Create a bot at api.slack.com |
| Slack Channel ID | Optional. The Slack channel where digests will be posted |
| Telegram Bot Token | Optional. Create a bot via @BotFather |
| Telegram Chat ID | Optional. The Telegram channel username or numeric chat ID |
git clone <your-repo-url>
cd techmeme-cron
npm installCreate a .env file in the project root:
# Copy from example template
cp .env.example .envEdit .env with your credentials:
# Google Gemini AI Configuration
GEMINI_API_KEY=your_gemini_api_key_here
GEMINI_MODEL=gemini-2.0-flash-exp # Or gemini-pro-latest
# Configure at least one delivery target.
# Slack Bot Configuration
SLACK_BOT_TOKEN=xoxb-your-slack-bot-token
SLACK_CHANNEL_ID=C1234567890 # Your channel ID
# Telegram Bot Configuration
TELEGRAM_BOT_TOKEN=1234567890:your-telegram-bot-token
TELEGRAM_CHAT_ID=@your_channel_username # Or -1001234567890 for a private channelπ How to get your Slack Channel ID
- Right-click on your Slack channel
- Select "Copy link"
- The channel ID is the last part:
https://yourworkspace.slack.com/archives/C1234567890 - In this example:
C1234567890is your channel ID
π£ How to create a Telegram channel and connect the bot
- Open @BotFather in Telegram.
- Run
/newbot. - Enter a display name for the bot.
- Enter a unique username that ends with
bot. - Copy the bot token that BotFather returns.
- Save that token as
TELEGRAM_BOT_TOKEN.
Example token format:
1234567890:AAExampleTelegramBotTokenHere
- In Telegram, click the new message icon.
- Choose New Channel.
- Enter the channel name and optional description.
- Choose whether the channel should be Public or Private.
- Finish channel creation.
These two environment variables are easy to confuse:
TELEGRAM_BOT_TOKENis the secret token returned by BotFather for your botTELEGRAM_CHAT_IDis the destination where the digest will be posted
TELEGRAM_CHAT_ID is not your bot username.
For example, if your bot username is @Technology_top_news_bot, that value belongs to the bot itself and should not be used as TELEGRAM_CHAT_ID unless you somehow created a channel with that exact public username and want to post there.
In practice, TELEGRAM_CHAT_ID should be one of these:
- a public channel username such as
@techmeme_digest - a numeric channel ID such as
-1001234567890
- Open the new channel.
- Click the channel name at the top.
- Go to Administrators.
- Choose Add Admin.
- Search for your bot by username.
- Grant permission to Post Messages.
For this project, the bot only needs to post messages. You do not need to grant broader moderation permissions.
You have two working options:
If your channel is public and has a username like @techmeme_digest, you can use that directly:
TELEGRAM_CHAT_ID=@techmeme_digestThis is the simplest setup.
How to set the public channel username:
- Open the channel in Telegram.
- Go to the channel profile or edit screen.
- Set a public link or username for the channel.
- Copy that channel username, including the leading
@. - Save that value as
TELEGRAM_CHAT_ID.
Use the channel username, not the bot username.
If the channel is private, use the numeric chat ID.
- Make sure the bot is already an admin in the channel.
- Post at least one message in the channel after adding the bot.
- Open this URL in your browser, replacing the token:
https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/getUpdates
You can also use curl:
curl "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/getUpdates"- Find the
channel_postobject in the JSON response. - Inside it, find
chat.id. - Copy the value, which typically looks like
-1001234567890. - Save it as
TELEGRAM_CHAT_ID.
Example snippet:
{
"channel_post": {
"chat": {
"id": -1001234567890,
"title": "Daily Tech Digest",
"type": "channel"
}
}
}In that case, use:
TELEGRAM_CHAT_ID=-1001234567890If getUpdates returns an empty list, send another fresh message in the channel and try again. The bot only receives channel updates after it has access to the channel.
If you see updates for private chats with the bot but not for your channel, double-check these points:
- The bot was added to the channel as an admin.
- A new message was posted in the channel after the bot was added.
- You are copying
channel_post.chat.id, notmessage.chat.idfrom a private conversation. - The value starts with
-100, which is typical for Telegram channels.
Use a direct Telegram API call to test delivery:
curl -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage" \
-H "Content-Type: application/json" \
-d '{
"chat_id": "'"$TELEGRAM_CHAT_ID"'",
"text": "Techmeme digest bot test message"
}'If that succeeds, the project can post daily digests to the same channel.
If it fails with chat not found or forbidden, the most common causes are:
TELEGRAM_CHAT_IDpoints to the bot username instead of the channel username or numeric channel ID.- The bot is not an admin in the target channel.
- The numeric channel ID was copied from the wrong object in
getUpdates.
GEMINI_API_KEY=your_gemini_api_key_here
GEMINI_MODEL=gemini-2.0-flash-exp
TELEGRAM_BOT_TOKEN=1234567890:AAExampleTelegramBotTokenHere
TELEGRAM_CHAT_ID=@techmeme_digestIf you keep both Slack and Telegram variables in .env, the script will post the same digest to both destinations in a single run.
Run the script manually to verify everything works:
npm start
# or
node index.jsIf both Slack and Telegram are configured but you want to send only to Telegram for a run:
npm run start:telegram
# or
node index.js --telegram-onlyYou should see detailed console output:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π STARTING TECHMEME DIGEST BOT
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Environment variables validated successfully
β Delivery targets configured: Slack, Telegram
β Gemini AI client initialized (Model: gemini-2.0-flash-exp)
β Slack client initialized (Channel: C1234567890)
β Telegram delivery configured (Chat: @techmeme_digest)
π‘ Fetching content from Techmeme...
β Successfully extracted 15 headlines
π€ Generating summary with Gemini AI...
β Received response from Gemini (2.34s)
π€ Posting to Slack...
β
Successfully posted to Slack!
π€ Posting to Telegram...
β
Successfully posted to Telegram!
Automated, serverless, zero maintenance β runs on GitHub's infrastructure for free!
-
Push your code to GitHub (if not already done)
-
Add Repository Secrets:
- Go to your repository β Settings β Secrets and variables β Actions
- Click "New repository secret" and add:
Secret Name Description GEMINI_API_KEYYour Google Gemini API key GEMINI_MODELModel name (e.g., gemini-2.0-flash-exp)SLACK_BOT_TOKENOptional. Your Slack bot token SLACK_CHANNEL_IDOptional. Your Slack channel ID TELEGRAM_BOT_TOKENOptional. Your Telegram bot token TELEGRAM_CHAT_IDOptional. Telegram channel username or numeric chat ID Add the secrets for at least one delivery target.
-
The workflow is already configured!
- Located at .github/workflows/daily-digest.yml
- Runs automatically at 8:00 AM PST (4:00 PM UTC) every day
- Can also be triggered manually from the Actions tab
-
Monitor execution:
- Go to the "Actions" tab in your GitHub repository
- View logs and status of each run
The workflow uses cron syntax to schedule runs:
schedule:
# 8:00 AM PST = 4:00 PM UTC (PST is UTC-8)
- cron: '0 16 * * *'To change the schedule, modify the cron expression in daily-digest.yml.
π Cron Syntax Quick Reference
ββββββββββββββ minute (0-59)
β ββββββββββββββ hour (0-23)
β β ββββββββββββββ day of month (1-31)
β β β ββββββββββββββ month (1-12)
β β β β ββββββββββββββ day of week (0-6, Sunday=0)
β β β β β
* * * * *
Examples:
0 16 * * *β Every day at 4:00 PM UTC0 8 * * 1-5β Weekdays at 8:00 AM UTC0 */6 * * *β Every 6 hours
For running on your local machine or server:
-
Find your Node.js path:
which node # Example output: /usr/local/bin/node -
Open crontab editor:
crontab -e
-
Add the following line (adjust paths to your system):
0 8 * * * cd /Applications/MAMP/htdocs/techmeme-cron && /usr/local/bin/node index.js >> /Applications/MAMP/htdocs/techmeme-cron/cron.log 2>&1
This will:
- Run at 8:00 AM daily (
0 8 * * *) - Navigate to the project directory
- Execute the script with the absolute Node path
- Log output to
cron.logfor debugging
- Run at 8:00 AM daily (
-
Save and exit (in vim:
:wq) -
Verify cron job is active:
crontab -l
Similar to macOS, but cron is typically managed via /etc/crontab or user crontabs.
The bot supports various Gemini models. Choose based on your needs:
| Model | Best For | Speed | Quality |
|---|---|---|---|
gemini-2.0-flash-exp |
Fast daily digests | β‘οΈβ‘οΈβ‘οΈ | βοΈβοΈβοΈ |
gemini-pro-latest |
Balanced performance | β‘οΈβ‘οΈ | βοΈβοΈβοΈβοΈ |
gemini-1.5-pro |
Highest quality | β‘οΈ | βοΈβοΈβοΈβοΈβοΈ |
Set in your .env file:
GEMINI_MODEL=gemini-2.0-flash-expThe AI prompt can be customized in index.js (lines 170-198) to adjust:
- Story selection criteria
- Formatting preferences
- Source prioritization
- Summary length and style
"Missing required environment variables"
Cause: Your .env file is missing or incomplete.
Solution:
- Ensure
.envexists in the project root - Verify
GEMINI_API_KEYis present and that at least one delivery target is fully configured:cat .env
- If using Slack, make sure both
SLACK_BOT_TOKENandSLACK_CHANNEL_IDare set - If using Telegram, make sure both
TELEGRAM_BOT_TOKENandTELEGRAM_CHAT_IDare set - Check for typos in variable names
Telegram returns "chat not found" or "forbidden"
Cause: The bot is not an admin in the channel, or TELEGRAM_CHAT_ID is wrong.
Solution:
- Re-check that the bot was added as a channel administrator
- Verify the chat ID or channel username
- Test manually with
sendMessageusing the curl command shown above - For private channels, re-run
getUpdatesafter posting a fresh message in the channel
"command not found" in cron
Cause: Cron doesn't inherit your shell's PATH.
Solution:
- Always use absolute paths in cron commands
- Find Node path:
which node - Use that full path in your cron entry
"No items fetched from Techmeme"
Cause: Techmeme's HTML structure may have changed.
Solution:
- The script has built-in fallback selectors
- Check
cron.logfor detailed error messages - Verify Techmeme is accessible:
curl -I https://techmeme.com
GitHub Actions not running
Cause: Repository secrets not configured or workflow disabled.
Solution:
- Verify all secrets are added (Settings β Secrets β Actions)
- Check that the workflow is enabled (Actions tab)
- Manually trigger via "Run workflow" button to test
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β π° Techmeme Digest Bot β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββββββββββββββββ
β π Trigger (Cron/Manual/Action) β
ββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββ΄ββββββββββββββββ
βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ
β π‘ Scrape β β βοΈ GitHub β
β Techmeme.com β β Actions β
β (15 stories) β β Scheduler β
βββββββββββββββββββ βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β π€ Gemini AI β
β Analyze & β
β Summarize β
β (Top 10) β
βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β π¬ Format for β
β Slack / β
β Telegram β
βββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β π€ Post to β
β configured β
β destinations β
βββββββββββββββββββ
β
βΌ
β
Success!
- Runtime: Node.js 20.x
- AI/ML: Google Gemini 2.0 Flash / Pro
- Web Scraping: Axios + Cheerio
- Messaging: Slack Web API + Telegram Bot API
- Automation: GitHub Actions / Cron
- Configuration: dotenv
MIT License - feel free to use and modify for your needs!
Contributions are welcome! Feel free to:
- Report bugs or issues
- Suggest new features
- Submit pull requests
- Improve documentation
Made with β and π€ by the power of automation
β Star this repo if you find it useful!