Commit 86193bcf authored by Jyrki's avatar Jyrki 🐾

Implement `notify` CLI command for checking for toys and notifying interested watchers

parent f05551ea
require "../../connection/redis"
require "../../bad_dragon/entities/toy"
require "json"
module Repository
# The WatchNotifications repository manages the toy notifications for a
# watcher.
#
# Redis considerations
# --------------------
#
# - Redis key prefix: `ddw:wn:`
# - A redis key for the Telegram chat IDs `3038326` would be e.g. `ddw:wn:3038326`
# - The value is a JSON array containing the toy IDs
class WatchNotifications
KEY_BASE = "ddw:wn"
private macro redis_key(key)
"#{KEY_BASE}:#{{{key}}}"
end
def self.add(user_id : Int64, toy : BadDragon::Entities::Toy)
Application.logger.info "adding toy #{toy.id} (#{toy.sku}/#{toy.size}) for #{user_id} to the notified list"
key = redis_key(user_id)
loop do
Application.logger.debug "[redis] WATCH #{key.inspect}"
redis.watch(key) # optimistic locking
notified_toys = notifications(user_id)
notified_toys << toy.id
notified_toys.uniq!
Application.logger.debug "[redis] SET #{key.inspect} #{notified_toys.to_json.inspect}"
response = redis.multi(&.set(key, notified_toys.to_json))
break unless response.empty?
end
end
def self.notifications(user_id) : Array(Int32)
Application.logger.debug "[redis] GET #{redis_key(user_id).inspect}"
JSON.parse(
redis.get(redis_key(user_id)) || "[]"
).as_a.map(&.as_i)
end
def self.watching?(user_id : Int64, toy)
notifications(user_id).includes?(toy.id)
end
private def self.redis
Connection::Redis.connection
end
end
end
......@@ -66,6 +66,22 @@ module Repository
).as_a.map(&.as_i64)
end
def self.watches : Hash(String, Array(BadDragon::Size))
Application.logger.debug "[redis] KEYS #{KEY_BASE}:*"
toys = redis.keys("#{KEY_BASE}:*").map(&.to_s)
returned_watches = {} of String => Array(BadDragon::Size)
toys.each do |redis_key|
toy_sku = redis_key.sub(/^#{KEY_BASE}:/, "")
Application.logger.debug "[redis] HKEYS #{redis_key}"
returned_watches[toy_sku] = ((redis.hkeys(redis_key).map(&.to_s) || [] of String).map { |x| BadDragon::Size.parse(x) })
end
returned_watches
end
private def self.redis
Connection::Redis.connection
end
......
require "./cli_commands/notify"
require "./cli_commands/start_bot"
require "./cli_commands/help"
......
require "./base"
require "../notify"
require "../../errors"
module UseCase
module CliCommands
class Notify < CliCommands::Base
def self.command_name
"notify"
end
def self.description
"Fetches toys and notifies the watchers"
end
def call(_argv)
check_sanity!
UseCase::Notify.call
end
private def check_sanity!
unless ENV.has_key?("TELEGRAM_API_TOKEN")
raise Errors::ConfigurationError.new("TELEGRAM_API_TOKEN is not set")
end
end
end
end
end
require "./base"
require "../application"
require "../bad_dragon/api/client"
require "../bot"
require "../repository/watch_notifications"
require "../repository/watch_targets"
module UseCase
class Notify < Base
def call
watched_toys = fetch_watches
toys = fetch_toys
notify(toys, watched_toys)
end
private def fetch_toys
Application.logger.info "[notify] fetching all toys"
client = BadDragon::API::Client.new
client.inventory_toys
end
private def fetch_watches
Application.logger.info "[notify] fetching watches"
Repository::WatchTargets.watches
end
private def notify(toys, watched_toys)
filtered_toys = toys.select do |toy|
watched_toys.has_key?(toy.sku) &&
watched_toys[toy.sku].includes?(toy.size)
end
filtered_toys.each do |toy|
watchers = Repository::WatchTargets.watchers(toy.sku, toy.size)
watchers.each do |watcher|
send_notification(watcher, toy)
end
end
end
private def send_notification(watcher, toy)
return if Repository::WatchNotifications.watching?(watcher, toy)
Application.logger.info "[notify] notifying #{watcher} for #{toy.sku}/#{toy.size}"
bot = ::Bot.new
bot.send_message(
chat_id: watcher,
text: "*browses the bad dragon clearance page and notices a #{toy.sku} in #{toy.size}* OwO what's this??\n\nget it while it's hot: https://bad-dragon.com/shop/clearance?sizes[]=#{toy.size.to_s.downcase}&skus[][]=#{toy.sku}"
)
unless toy.images.empty?
toy.images.each do |image|
bot.send_photo(
chat_id: watcher,
photo: image.full_filename
)
end
end
Repository::WatchNotifications.add(watcher, toy)
end
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment