Compare commits
1 Commits
master
...
8d90b052a0
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d90b052a0 |
61
flake.lock
generated
61
flake.lock
generated
@@ -1,61 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"flake-utils": {
|
|
||||||
"inputs": {
|
|
||||||
"systems": "systems"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1731533236,
|
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "flake-utils",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1766736597,
|
|
||||||
"narHash": "sha256-BASnpCLodmgiVn0M1MU2Pqyoz0aHwar/0qLkp7CjvSQ=",
|
|
||||||
"owner": "nixos",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "f560ccec6b1116b22e6ed15f4c510997d99d5852",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nixos",
|
|
||||||
"ref": "nixos-25.11",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-utils": "flake-utils",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"systems": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1681028828,
|
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-systems",
|
|
||||||
"repo": "default",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
||||||
29
flake.nix
29
flake.nix
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
|
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils }:
|
|
||||||
flake-utils.lib.eachDefaultSystem (
|
|
||||||
system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
joann-pupper-bot = pkgs.crystal.buildCrystalPackage {
|
|
||||||
pname = "joann-pupper-bot";
|
|
||||||
version = "0.1.0";
|
|
||||||
src = ./.;
|
|
||||||
lockFile = ./shard.lock;
|
|
||||||
shardsFile = ./shards.nix;
|
|
||||||
format = "shards";
|
|
||||||
buildInputs = [pkgs.sqlite];
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
packages = { inherit joann-pupper-bot; };
|
|
||||||
defaultPackage = joann-pupper-bot;
|
|
||||||
}
|
|
||||||
) // {
|
|
||||||
nixosModules.joann-pupper-bot = import ./module.nix;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
71
module.nix
71
module.nix
@@ -1,71 +0,0 @@
|
|||||||
{ config, lib, pkgs, ... }:
|
|
||||||
with lib;
|
|
||||||
let cfg = config.services.joann-pupper-bot; in {
|
|
||||||
options.services.joann-pupper-bot = {
|
|
||||||
enable = mkEnableOption "Joann's Pupper Bot";
|
|
||||||
user = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "joann-pupper-bot";
|
|
||||||
example = "myuser";
|
|
||||||
description = "Which user should be running Joann's Pupper Bot";
|
|
||||||
};
|
|
||||||
group = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "joann-pupper-bot";
|
|
||||||
example = "mygroup";
|
|
||||||
description = "Which group should be running Joann's Pupper Bot";
|
|
||||||
};
|
|
||||||
stateDir = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "/var/lib/joann-pupper-bot";
|
|
||||||
description = "Which directory the bot should use for storage";
|
|
||||||
};
|
|
||||||
config.token = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = "Bot token used for Telegram";
|
|
||||||
};
|
|
||||||
config.subreddits = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
description = "Which subreddits to draw images from";
|
|
||||||
default = ["rarepuppers"];
|
|
||||||
};
|
|
||||||
config.sendCron = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = "cron-style string determining when images should be sent";
|
|
||||||
default = "*/30 8-21 * * *";
|
|
||||||
};
|
|
||||||
config.refreshCron = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = "cron-style string determining when the bot polls Reddit for new images";
|
|
||||||
default = "*/15 * * * *";
|
|
||||||
};
|
|
||||||
config.recipients = mkOption {
|
|
||||||
type = types.listOf types.int;
|
|
||||||
description = "Telegram chat IDs for each of the image recipients";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
|
||||||
];
|
|
||||||
systemd.services.joann-pupper-bot = {
|
|
||||||
description = "Joann's Pupper Bot";
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
|
|
||||||
preStart =
|
|
||||||
let
|
|
||||||
yml = generators.toYAML {} cfg.config;
|
|
||||||
configFile = pkgs.writeText "config.yaml" yml;
|
|
||||||
in
|
|
||||||
''
|
|
||||||
cp -f '${configFile}' '${cfg.stateDir}'/config.yaml
|
|
||||||
'';
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
ExecStart = "/bin/sh -c :";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
22
shard.lock
22
shard.lock
@@ -1,22 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
@@ -16,6 +16,6 @@ dependencies:
|
|||||||
sqlite3:
|
sqlite3:
|
||||||
github: crystal-lang/crystal-sqlite3
|
github: crystal-lang/crystal-sqlite3
|
||||||
|
|
||||||
crystal: 1.0.0
|
crystal: 0.24.1
|
||||||
|
|
||||||
license: MIT
|
license: MIT
|
||||||
|
|||||||
27
shards.nix
27
shards.nix
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"cron_parser" = {
|
|
||||||
url = "https://github.com/kostya/cron_parser.git";
|
|
||||||
rev = "v0.4.0";
|
|
||||||
sha256 = "17fgg2nvyx99v05l10h6cnxfr7swz8yaxhmnk4l47kg2spi8w90a";
|
|
||||||
};
|
|
||||||
"cron_scheduler" = {
|
|
||||||
url = "https://github.com/kostya/cron_scheduler.git";
|
|
||||||
rev = "v0.4.0";
|
|
||||||
sha256 = "0jd0maw1h87hjgjpqhbwxb4yz83g8shlrwfivyf0sd6x3l5lspns";
|
|
||||||
};
|
|
||||||
"db" = {
|
|
||||||
url = "https://github.com/crystal-lang/crystal-db.git";
|
|
||||||
rev = "v0.14.0";
|
|
||||||
sha256 = "1s67fs5abzgg2yyjsm2la967mk881i91h8jdnal072fb9qh9wr58";
|
|
||||||
};
|
|
||||||
"sqlite3" = {
|
|
||||||
url = "https://github.com/crystal-lang/crystal-sqlite3.git";
|
|
||||||
rev = "v0.22.0";
|
|
||||||
sha256 = "1lc8ixnzjx8nsp96qj9yxmfk7g334id8v87g3h5bwgkhkbx5ghqn";
|
|
||||||
};
|
|
||||||
"telepathy" = {
|
|
||||||
url = "https://dev.danilafe.com/Crystal-Bots/telepathy";
|
|
||||||
rev = "v0.2.0";
|
|
||||||
sha256 = "1zhjlpa31vldgd9f6l1hdfj7a97hlqkxkvxzisfs2zfnvc86aiyc";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
require "log"
|
require "logger"
|
||||||
require "telepathy"
|
require "telepathy"
|
||||||
require "time"
|
require "time"
|
||||||
require "sqlite3"
|
require "sqlite3"
|
||||||
require "cron_scheduler"
|
require "cron_scheduler"
|
||||||
|
|
||||||
EXTENSIONS = ["png", "jpeg", "jpg"]
|
EXTENSIONS = ["png", "jpeg", "jpg"]
|
||||||
LOGGER = Log.for("joann-pupper-bot")
|
LOGGER = Logger.new(STDOUT)
|
||||||
|
|
||||||
class PupperBot
|
class PupperBot
|
||||||
@db : DB::Database
|
@db : DB::Database
|
||||||
|
|
||||||
def initialize(@configuration : BotConfiguration)
|
def initialize(@configuration : BotConfiguration)
|
||||||
@telegram_bot = Telepathy::Bot.new @configuration.token
|
@telegram_bot = Telepathy::Bot.new configuration.token
|
||||||
@db = DB.open "sqlite3://#{@configuration.database}"
|
@db = DB.open "sqlite3://#{configuration.database}"
|
||||||
initialize_db
|
initialize_db
|
||||||
update_database
|
update_database
|
||||||
initialize_timers
|
initialize_timers
|
||||||
@@ -49,12 +49,12 @@ class PupperBot
|
|||||||
unsent_query = "select id, title, url from posts where not exists (select * from recepient_posts where recepient=? and post=id) limit 1"
|
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 }
|
unless to_send = @db.query_one? unsent_query, chatid, as: { Int64, String, String }
|
||||||
LOGGER.info { "Unable to find a post to send to #{chatid}." }
|
LOGGER.info "Unable to find a post to send to #{chatid}."
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
id, title, url = to_send
|
id, title, url = to_send
|
||||||
LOGGER.info { "Using URL #{url} for request from #{chatid}" }
|
LOGGER.info "Using URL #{url} for request from #{chatid}"
|
||||||
@db.exec "insert into recepient_posts(recepient, post) values(?, ?)", chatid, id
|
@db.exec "insert into recepient_posts(recepient, post) values(?, ?)", chatid, id
|
||||||
@telegram_bot.send_photo(chatid, url, title)
|
@telegram_bot.send_photo(chatid, url, title)
|
||||||
end
|
end
|
||||||
@@ -67,13 +67,13 @@ class PupperBot
|
|||||||
|
|
||||||
def update_database
|
def update_database
|
||||||
unless response = RedditResponse.from_subreddits(@configuration.subreddits)
|
unless response = RedditResponse.from_subreddits(@configuration.subreddits)
|
||||||
LOGGER.info { "Unable to find more posts for the database" }
|
LOGGER.info "Unable to find more posts for the database"
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
posts = response.data.posts_matching { |post| EXTENSIONS.any? { |it| post.url.ends_with? it } }
|
posts = response.data.posts_matching { |post| EXTENSIONS.any? { |it| post.url.ends_with? it } }
|
||||||
posts.each do |post|
|
posts.each do |post|
|
||||||
LOGGER.info { "Trying to save post #{post.title} #{post.url}" }
|
LOGGER.info "Trying to save post #{post.title} #{post.url}"
|
||||||
begin
|
begin
|
||||||
@db.exec "insert into posts(title, url) values(?, ?)", post.title, post.url
|
@db.exec "insert into posts(title, url) values(?, ?)", post.title, post.url
|
||||||
rescue
|
rescue
|
||||||
|
|||||||
@@ -1,23 +1,11 @@
|
|||||||
require "yaml"
|
require "yaml"
|
||||||
|
|
||||||
class BotConfiguration
|
class BotConfiguration
|
||||||
include YAML::Serializable
|
YAML.mapping(
|
||||||
|
token: String,
|
||||||
@[YAML::Field(key: "token")]
|
database: { type: String, default: "./data.sqlite" },
|
||||||
property token : String
|
subreddits: { type: Array(String), default: [ "rarepuppers" ] },
|
||||||
|
send_cron: { type: String, default: "*/30 8-21 * * *" },
|
||||||
@[YAML::Field(key: "database")]
|
refresh_cron: { type: String, default: "*/15 * * * *" },
|
||||||
property database : String = "./data.sqlite"
|
recipients: Array(Int64))
|
||||||
|
|
||||||
@[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
|
end
|
||||||
|
|||||||
@@ -2,39 +2,23 @@ require "http/client"
|
|||||||
require "json"
|
require "json"
|
||||||
|
|
||||||
class RedditWrapper(T)
|
class RedditWrapper(T)
|
||||||
include JSON::Serializable
|
JSON.mapping(
|
||||||
|
kind: String,
|
||||||
@[JSON::Field(key: "kind")]
|
data: T)
|
||||||
property kind : String
|
|
||||||
|
|
||||||
@[JSON::Field(key: "data")]
|
|
||||||
property data : T
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class RedditChild
|
class RedditChild
|
||||||
include JSON::Serializable
|
JSON.mapping(
|
||||||
|
url: String,
|
||||||
@[JSON::Field(key: "url")]
|
name: String,
|
||||||
property url : String
|
title: String)
|
||||||
|
|
||||||
@[JSON::Field(key: "name")]
|
|
||||||
property name : String
|
|
||||||
|
|
||||||
@[JSON::Field(key: "title")]
|
|
||||||
property title : String
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class RedditResponse
|
class RedditResponse
|
||||||
include JSON::Serializable
|
JSON.mapping(
|
||||||
|
modhash: String,
|
||||||
@[JSON::Field(key: "modhash")]
|
dist: Int32,
|
||||||
property modhash : String
|
children: Array(RedditWrapper(RedditChild)))
|
||||||
|
|
||||||
@[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"
|
||||||
|
|||||||
Reference in New Issue
Block a user