Compare commits

...

3 Commits

Author SHA1 Message Date
26ae4e9589 Add a shards.lock and use HTTPs for new telepathy
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2025-12-25 18:44:17 -08:00
b4e67daa9b [Claude Haiku] Apply old refactors to code
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2025-12-25 17:52:03 -08:00
97668ba0d7 [Claude Haiki] Update to new, working Crystal version
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
2025-12-25 17:35:59 -08:00
6 changed files with 169 additions and 119 deletions

22
shard.lock Normal file
View File

@@ -0,0 +1,22 @@
version: 2.0
shards:
cron_parser:
git: https://github.com/kostya/cron_parser.git
version: 0.4.0
cron_scheduler:
git: https://github.com/kostya/cron_scheduler.git
version: 0.4.0
db:
git: https://github.com/crystal-lang/crystal-db.git
version: 0.14.0
sqlite3:
git: https://github.com/crystal-lang/crystal-sqlite3.git
version: 0.22.0
telepathy:
git: https://dev.danilafe.com/Crystal-Bots/telepathy
version: 0.2.0

View File

@@ -10,12 +10,12 @@ targets:
dependencies: dependencies:
telepathy: telepathy:
git: http://dev.danilafe.com/Crystal-Bots/telepathy git: https://dev.danilafe.com/Crystal-Bots/telepathy
cron_scheduler: cron_scheduler:
github: kostya/cron_scheduler github: kostya/cron_scheduler
sqlite3: sqlite3:
github: crystal-lang/crystal-sqlite3 github: crystal-lang/crystal-sqlite3
crystal: 0.24.1 crystal: 1.0.0
license: MIT license: MIT

View File

@@ -1,112 +1,18 @@
require "./joann-pupper-bot/*" require "./joann-pupper-bot/*"
require "logger" require "option_parser"
require "telepathy"
require "time"
require "sqlite3"
require "cron_scheduler"
config_file = "./config.yaml"
# Chat IDs OptionParser.parse do |parser|
CHATID_JOANN = 215301902_i64 parser.banner = "Usage: joann-pupper-bot [arguments]"
CHATID_DANIEL = 220888832_i64 parser.on("-c", "--config=CONFIG", "Select config file") do |c|
DATABASE_URL = "sqlite3://./data.sqlite" config_file = c
BOT_TOKEN = "599474797:AAEmjQNO32uqurI16blS9FT4OoO7GdUZ6h0" end
EXTENSIONS = ["png", "jpeg", "jpg"] parser.on("-h", "--help", "Show this message") do
LOGGER = Logger.new(STDOUT) puts parser
exit
class BotConfiguration
JSON.mapping(
subreddits: Array(String),
send_cron: String,
refresh_cron: String,
recepients: Array(Int64))
def initialize
@subreddits = [ "rarepuppers" ]
@send_cron = "*/30 8-21 * * *"
@refresh_cron = "*/15 * * * *"
@recepients = [ CHATID_DANIEL ]
end end
end end
class PupperBot botc = PupperBot.new(BotConfiguration.from_yaml(File.open(config_file)))
@db : DB::Database
def initialize(@configuration : BotConfiguration)
@telegram_bot = Telepathy::Bot.new BOT_TOKEN
@db = DB.open DATABASE_URL
initialize_db
update_database
initialize_timers
initialize_telegram
send_broadcast
end
def initialize
initialize BotConfiguration.new
end
private def initialize_db
@db.exec "create table if not exists posts(id integer primary key, title text, url text unique)"
@db.exec "create table if not exists recepient_posts(recepient integer, post integer, foreign key(post) references posts(id))"
end
private def initialize_timers
CronScheduler.define do
at(@configuration.send_cron) { send_broadcast }
at(@configuration.refresh_cron) { update_database }
end
end
private def initialize_telegram
@telegram_bot.command "ping" do |update, args|
@telegram_bot.send_message(update.message.as(Telepathy::Message).chat.id, "pong")
end
@telegram_bot.command "pupper" do |update, args|
command_chatid = update.message.as(Telepathy::Message).chat.id
send_single command_chatid
end
@telegram_bot.poll
end
def send_single(chatid)
unsent_query = "select id, title, url from posts where not exists (select * from recepient_posts where recepient=? and post=id) limit 1"
unless to_send = @db.query_one? unsent_query, chatid, as: { Int64, String, String }
LOGGER.info "Unable to find a post to send to #{chatid}."
return
end
id, title, url = to_send
LOGGER.info "Using URL #{url} for request from #{chatid}"
@db.exec "insert into recepient_posts(recepient, post) values(?, ?)", chatid, id
@telegram_bot.send_photo(chatid, url, title)
end
def send_broadcast
@configuration.recepients.each do |recepient|
send_single recepient
end
end
def update_database
unless response = RedditResponse.from_subreddits(@configuration.subreddits)
LOGGER.info "Unable to find more posts for the database"
return
end
posts = response.data.posts_matching { |post| EXTENSIONS.any? { |it| post.url.ends_with? it } }
posts.each do |post|
LOGGER.info "Trying to save post #{post.title} #{post.url}"
begin
@db.exec "insert into posts(title, url) values(?, ?)", post.title, post.url
rescue
end
end
end
end
botc = PupperBot.new
sleep sleep

View File

@@ -0,0 +1,83 @@
require "log"
require "telepathy"
require "time"
require "sqlite3"
require "cron_scheduler"
EXTENSIONS = ["png", "jpeg", "jpg"]
LOGGER = Log.for("joann-pupper-bot")
class PupperBot
@db : DB::Database
def initialize(@configuration : BotConfiguration)
@telegram_bot = Telepathy::Bot.new @configuration.token
@db = DB.open "sqlite3://#{@configuration.database}"
initialize_db
update_database
initialize_timers
initialize_telegram
send_broadcast
end
private def initialize_db
@db.exec "create table if not exists posts(id integer primary key, title text, url text unique)"
@db.exec "create table if not exists recepient_posts(recepient integer, post integer, foreign key(post) references posts(id))"
end
private def initialize_timers
CronScheduler.define do
at(@configuration.send_cron) { send_broadcast }
at(@configuration.refresh_cron) { update_database }
end
end
private def initialize_telegram
@telegram_bot.command "ping" do |update, args|
@telegram_bot.send_message(update.message.as(Telepathy::Message).chat.id, "pong")
end
@telegram_bot.command "pupper" do |update, args|
command_chatid = update.message.as(Telepathy::Message).chat.id
send_single command_chatid
end
@telegram_bot.poll
end
def send_single(chatid)
unsent_query = "select id, title, url from posts where not exists (select * from recepient_posts where recepient=? and post=id) limit 1"
unless to_send = @db.query_one? unsent_query, chatid, as: { Int64, String, String }
LOGGER.info { "Unable to find a post to send to #{chatid}." }
return
end
id, title, url = to_send
LOGGER.info { "Using URL #{url} for request from #{chatid}" }
@db.exec "insert into recepient_posts(recepient, post) values(?, ?)", chatid, id
@telegram_bot.send_photo(chatid, url, title)
end
def send_broadcast
@configuration.recipients.each do |recepient|
send_single recepient
end
end
def update_database
unless response = RedditResponse.from_subreddits(@configuration.subreddits)
LOGGER.info { "Unable to find more posts for the database" }
return
end
posts = response.data.posts_matching { |post| EXTENSIONS.any? { |it| post.url.ends_with? it } }
posts.each do |post|
LOGGER.info { "Trying to save post #{post.title} #{post.url}" }
begin
@db.exec "insert into posts(title, url) values(?, ?)", post.title, post.url
rescue
end
end
end
end

View File

@@ -0,0 +1,23 @@
require "yaml"
class BotConfiguration
include YAML::Serializable
@[YAML::Field(key: "token")]
property token : String
@[YAML::Field(key: "database")]
property database : String = "./data.sqlite"
@[YAML::Field(key: "subreddits")]
property subreddits : Array(String) = ["rarepuppers"]
@[YAML::Field(key: "send_cron")]
property send_cron : String = "*/30 8-21 * * *"
@[YAML::Field(key: "refresh_cron")]
property refresh_cron : String = "*/15 * * * *"
@[YAML::Field(key: "recipients")]
property recipients : Array(Int64)
end

View File

@@ -2,23 +2,39 @@ require "http/client"
require "json" require "json"
class RedditWrapper(T) class RedditWrapper(T)
JSON.mapping( include JSON::Serializable
kind: String,
data: T) @[JSON::Field(key: "kind")]
property kind : String
@[JSON::Field(key: "data")]
property data : T
end end
class RedditChild class RedditChild
JSON.mapping( include JSON::Serializable
url: String,
name: String, @[JSON::Field(key: "url")]
title: String) property url : String
@[JSON::Field(key: "name")]
property name : String
@[JSON::Field(key: "title")]
property title : String
end end
class RedditResponse class RedditResponse
JSON.mapping( include JSON::Serializable
modhash: String,
dist: Int32, @[JSON::Field(key: "modhash")]
children: Array(RedditWrapper(RedditChild))) property modhash : String
@[JSON::Field(key: "dist")]
property dist : Int32
@[JSON::Field(key: "children")]
property children : Array(RedditWrapper(RedditChild))
def self.from_subreddits(subreddits : Array(String)) def self.from_subreddits(subreddits : Array(String))
request_url = URI.new scheme: "https", host: "www.reddit.com", path: "/r/#{subreddits.join "+"}/hot.json", query: "limit=30" request_url = URI.new scheme: "https", host: "www.reddit.com", path: "/r/#{subreddits.join "+"}/hot.json", query: "limit=30"