Skip to content

Instantly share code, notes, and snippets.

@jgaskins
Last active June 29, 2025 22:17
Show Gist options
  • Save jgaskins/cc6b66c4624d65577f91800b14573dc5 to your computer and use it in GitHub Desktop.
Save jgaskins/cc6b66c4624d65577f91800b14573dc5 to your computer and use it in GitHub Desktop.
Mastodon weather bot written in Crystal
# Replace the domain with your Mastodon server
API_URL="https://your.server/api/v1/statuses"
# On your Mastodon server, go to `/settings/applications` and create an
# application, then get the access token from it and place it here.
API_KEY=
# Comma-separated list of URLs. Use RSS links from the NWS website, found here:
# https://forecast.weather.gov/xml/current_obs/seek.php?state=KS
WEATHER_FEEDS=
name: weather_bot
version: 0.1.0
authors:
- Jamie Gaskins <[email protected]>
targets:
weather_bot:
main: weather_bot.cr
dependencies:
rss:
github: ruivieira/rss
sqlite3:
github: crystal-lang/crystal-sqlite3
dotenv:
github: gdotdesign/cr-dotenv
crystal: '>= 1.16.3'
license: MIT
require "rss"
require "sqlite3"
require "dotenv"
Dotenv.load?
api_url = ENV["API_URL"]
api_key = ENV["API_KEY"]
urls = ENV["WEATHER_FEEDS"].split(',')
log = Log.for("weather-bot")
running = true
Signal::TERM.trap { running = false }
db = DB.open("sqlite3://#{ENV.fetch("DB_PATH", "db.sqlite3")}")
provision db
while running
urls.each do |url|
feed = RSS.parse url
feed.items.each do |e|
result = db.exec <<-SQL, url.to_s, e.guid, e.title, e.description
INSERT INTO feed_items (source, guid, title, description)
VALUES (?, ?, ?, ?)
ON CONFLICT (guid) DO NOTHING
SQL
begin
if result.rows_affected > 0
response = HTTP::Client.post api_url,
headers: HTTP::Headers{
"Authorization" => "Bearer #{api_key}",
"Idempotency-Key" => "#{url} #{e.guid}",
},
form: {"status" => e.description.strip.lines[1..].join("\n")}
log.info &.emit "posted",
status: response.status.to_s,
response: response.body
end
rescue ex
log.error &.emit "Error occurred in talking to the Mastodon API", error: ex.message || "[no message provided]"
end
rescue ex
log.error &.emit "Error occurred in saving the feed item", error: ex.message || "[no message provided]"
end
rescue ex
log.error &.emit "Error occurred in fetching or parsing the feed", error: ex.message || "[no message provided]"
end
sleep 1.minute
end
def provision(db)
db.exec <<-SQL
CREATE TABLE IF NOT EXISTS feed_items(
source TEXT,
guid TEXT UNIQUE,
title TEXT,
description TEXT
)
SQL
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment