Compare commits

...

9 Commits

5 changed files with 151 additions and 82 deletions

View File

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

View File

@@ -1,67 +1,18 @@
require "./joann-pupper-bot/*" require "./joann-pupper-bot/*"
require "logger" require "option_parser"
require "telepathy"
require "time"
# Chat IDs config_file = "./config.yaml"
chatid_joann = 215301902
chatid_daniel = 220888832
# Configuration OptionParser.parse do |parser|
subreddit = "rarepuppers" parser.banner = "Usage: joann-pupper-bot [arguments]"
chatid = chatid_daniel parser.on("-c", "--config=CONFIG", "Select config file") do |c|
delay = 1.hours config_file = c
active_hours = 7..24 end
bot_token = "599474797:AAEmjQNO32uqurI16blS9FT4OoO7GdUZ6h0" parser.on("-h", "--help", "Show this message") do
puts parser
# Setup exit
completed = [] of String end
logger = Logger.new(STDOUT)
bot = Telepathy::Bot.new bot_token
# Commands.
bot.command "ping" do |update, args|
bot.send_message(update.message.as(Telepathy::Message).chat.id, "pong")
end end
bot.command "pupper" do |update, args| botc = PupperBot.new(BotConfiguration.from_yaml(File.open(config_file)))
url_tuple = get_reddit_post(subreddit, completed) sleep
if url_tuple
url, title = url_tuple
command_chatid = update.message.as(Telepathy::Message).chat.id
logger.info "Using URL #{url} for request from #{command_chatid}"
bot.send_photo(command_chatid, url, title)
else
logger.error "Unable to find a post to send."
end
end
spawn do
loop do
time = Time.now
url_tuple = get_reddit_post(subreddit, completed) if (active_hours.includes? time.hour)
if url_tuple
url, title = url_tuple
logger.info "Sending regular picture to #{chatid}."
bot.send_photo(chatid.to_i64, url, title)
else
logger.error "Unable to find a post to send. (Or it's quiet hours)"
end
sleep delay
end
end
# Code to stop the bot on time.
end_channel = Channel(Nil).new(1)
bot.poll_end do
end_channel.send nil
end
Signal::INT.trap do
logger.info "Shutting down bot..."
bot.end_poll
end
bot.poll
end_channel.receive

View File

@@ -0,0 +1,83 @@
require "logger"
require "telepathy"
require "time"
require "sqlite3"
require "cron_scheduler"
EXTENSIONS = ["png", "jpeg", "jpg"]
LOGGER = Logger.new(STDOUT)
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,11 @@
require "yaml"
class BotConfiguration
YAML.mapping(
token: String,
database: { type: String, default: "./data.sqlite" },
subreddits: { type: Array(String), default: [ "rarepuppers" ] },
send_cron: { type: String, default: "*/30 8-21 * * *" },
refresh_cron: { type: String, default: "*/15 * * * *" },
recipients: Array(Int64))
end

View File

@@ -1,27 +1,47 @@
require "http/client" require "http/client"
require "json" require "json"
def get_reddit_json(subreddit) class RedditWrapper(T)
request_url = "https://www.reddit.com/r/#{subreddit}/hot.json?limit=30" JSON.mapping(
kind: String,
data: T)
end
class RedditChild
JSON.mapping(
url: String,
name: String,
title: String)
end
class RedditResponse
JSON.mapping(
modhash: String,
dist: Int32,
children: Array(RedditWrapper(RedditChild)))
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"
response = HTTP::Client.get(request_url, headers: HTTP::Headers { response = HTTP::Client.get(request_url, headers: HTTP::Headers {
"User-agent" => "Joann-Pupper-Bot" "User-agent" => "Joann-Pupper-Bot"
}) })
response.body?.try { |body| JSON.parse(body) }
end
def filter_reddit_json(json, completed) return nil unless body = response.body?
json["data"]["children"]
.as_a
.map(&.["data"])
.select do |it|
url = it["url"].as_s
name = it["name"].as_s
!completed.includes?(name) && (url.ends_with?(".png") || url.ends_with?(".jpg"))
end
end
def get_reddit_post(subreddit, completed) begin
json = get_reddit_json(subreddit) RedditWrapper(RedditResponse).from_json body
post = json.try { |json| filter_reddit_json(json, completed).first? } rescue e
post.try { |post| completed.push(post["name"].as_s); { post["url"].as_s, post["title"].as_s } } nil
end
end
def self.from_subreddit(subreddit)
from_subreddits [subreddit]
end
def posts_matching(&block)
children
.map(&.data)
.select { |it| yield it }
end
end end