Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ bin/setup
coffeeoutside.gemspec
config.example.yaml
lib/coffeeoutside.rb
lib/coffeeoutside/dispatchers/bluesky.rb
lib/coffeeoutside/dispatchers/dispatcher.rb
lib/coffeeoutside/dispatchers/ical.rb
lib/coffeeoutside/dispatchers/json.rb
Expand Down
2 changes: 1 addition & 1 deletion coffeeoutside.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_dependency "http", "= 4.0.0"
spec.add_dependency "http", ">= 5.0"
spec.add_dependency "icalendar", "= 2.11.0"
spec.add_dependency "openweathermap", "= 0.2.3"
spec.add_dependency "rss", "= 0.2.9"
Expand Down
8 changes: 4 additions & 4 deletions config.example.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
production: false
dispatchers:
service:
consumer_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
consumer_key: xxxxxxxxxxxxxxxxxxxxxxxxxx
mastodon:
token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
token_secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
bluesky:
identifier: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
password: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
openweathermap:
city_id: 5913490
api_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Expand Down
6 changes: 4 additions & 2 deletions lib/coffeeoutside.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ def main
}

CoffeeOutside::Dispatchers.constants.each do |d|
dispatch.merge!(config.dispatchers[d.to_s]) if config.dispatchers[d.to_s]
::CoffeeOutside::Dispatchers.const_get(d).new(dispatch).notify
# Get dispatcher info from config.yaml when lowercase
dispatcher_config = config.dispatchers&.fetch(d.to_s.downcase, nil) || config.dispatchers&.fetch(d.to_s, nil) || {}
params = dispatch.merge(dispatcher_config)
::CoffeeOutside::Dispatchers.const_get(d).new(params).notify
end
end
end
104 changes: 104 additions & 0 deletions lib/coffeeoutside/dispatchers/bluesky.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# frozen_string_literal: false

# rbs_inline: enabled

require_relative "dispatcher"
require "json"
require "http"
require "time"

module CoffeeOutside
module Dispatchers
class Bluesky < DispatcherBase
def notify_production
return "Could not find identifier, skipping Bluesky dispatcher" unless @params["identifier"]
return "Could not find password, skipping Bluesky dispatcher" unless @params["password"]

host = @params["host"] || "https://bsky.social"

# Create session to authenticate and get access JWT
session_url = "#{host}/xrpc/com.atproto.server.createSession"
session_resp = HTTP.post(session_url, json: {
identifier: @params["identifier"],
password: @params["password"]
})

raise "Failed to authenticate with Bluesky: #{session_resp.body}" unless session_resp.status.success?

session_data = JSON.parse(session_resp.body.to_s)
access_jwt = session_data["accessJwt"]
did = session_data["did"]

msg = location_skeet_msg
record = {
"$type": "app.bsky.feed.post",
text: msg,
createdAt: Time.now.utc.iso8601
}
f = facets(msg)
record[:facets] = f unless f.empty?

# Post the skeet using the session JWT
post_url = "#{host}/xrpc/com.atproto.repo.createRecord"
post_resp = HTTP.headers({ Authorization: "Bearer #{access_jwt}" })
.post(post_url, json: {
repo: did,
collection: "app.bsky.feed.post",
record: record
})

raise "Failed to post to Bluesky: #{post_resp.body}" unless post_resp.status.success?
end

def notify_debug
puts "Bluesky identifier = #{@params["identifier"]}"
msg = location_skeet_msg
puts msg
f = facets(msg)
puts "Facets: #{JSON.generate(f)}" unless f.empty?
puts "\n"
end

def facets(text)
facets_list = []

# Identify links
text.scan(%r{https?://[^\s,)]+}) do |url|
m = Regexp.last_match
b_start = text[0...m.begin(0)].bytesize
b_end = b_start + url.bytesize
facets_list << {
index: { byteStart: b_start, byteEnd: b_end },
features: [{ "$type": "app.bsky.richtext.facet#link", uri: url }]
}
end

# Identify hashtags
text.scan(/#[a-zA-Z0-9_]+/) do |tag|
m = Regexp.last_match
b_start = text[0...m.begin(0)].bytesize
b_end = b_start + tag.bytesize
facets_list << {
index: { byteStart: b_start, byteEnd: b_end },
features: [{ "$type": "app.bsky.richtext.facet#tag", tag: tag[1..] }]
}
end

facets_list
end

def location_skeet_msg #: String
str = "This week's #CoffeeOutside: #{@location.name}"
str << " (#{@location.location_hint})" if @location.location_hint
if @location.url
str << " #{@location.url}"
elsif @location.map_url
str << " #{@location.map_url}"
end
str << " (#{@location.address})" if @location.address
str << ", see you there! #yycbike"
str
end
end
end
end
Loading