commit 882bf9ab71e49f490e4c28a754b1c1a67eac6bb3 Author: aNNiMON Date: Sat Aug 10 19:12:42 2024 +0300 First working release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba83e78 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +own-modules +config.own +*.db diff --git a/DanbooruApi.own b/DanbooruApi.own new file mode 100644 index 0000000..7b2bc76 --- /dev/null +++ b/DanbooruApi.own @@ -0,0 +1,90 @@ +use okhttp, types, json, functional + +class DanbooruApi { + def DanbooruApi(auth) { + this.headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0" + } + if auth != "" { + this.headers["Authorization"] = auth + } + } + + def getPosts(params) { + url = "https://danbooru.donmai.us/posts.json" + params + response = okhttp.request() + .headers(this.headers) + .url(url) + .get() + .newCall(okhttp.client) + .execute() + .body() + .string() + if (!length(response)) return [] + posts = jsondecode(response) + if (typeof(posts) != ARRAY) return [] + return map(posts, def(p) = new Post(p)) + } +} + +class Post { + def Post(post) { + this.id = int(post.id) ?? 0 + post.tags = tomap(post.tag_string.split(" "), def(k) = k, def(v) = 1) + this.post = post + } + + def isImage(post) { + ext = post.file_ext + return ext == "jpg" || ext == "png" + } + + def getResolution(post) { + w = int(post.image_width ?? 0) + h = int(post.image_height ?? 0) + if w == 0 || h == 0 return "" + return "" + w + "x" + h + } + + def getImageUrl(post) { + fileSize = post.file_size ?? 0 + if (fileSize >= 9500000) { + return post.large_file_url + } + return post.file_url + } + + def getSampleImageUrl(post) { + return post.large_file_url + } + + def getScore(post) = int(post.score ?? 0) + + def getArtists(post) { + return match int(post.tag_count_artist ?? 0) { + case 0: [] + case 1: [post.tag_string_artist] + case _: post.tag_string_artist.split(" ") + } + } + + def getCharacters(post) { + return match int(post.tag_count_character ?? 0) { + case 0: [] + case 1: [post.tag_string_character] + case _: post.tag_string_character.split(" ") + } + } + + def getSource(post) { + src = post.source + if src.contains("twitter") { + return src.replace("twitter", "x") + } + pixivId = post.pixiv_id ?? 0 + if pixivId { + return "https://www.pixiv.net/en/artworks/" + pixivId + } + return src + } +} diff --git a/config.own.sample b/config.own.sample new file mode 100644 index 0000000..6581972 --- /dev/null +++ b/config.own.sample @@ -0,0 +1,37 @@ +config = { + "telegram": { + "token": "required", + }, + "danbooru": { + // Danbooru user:api_key + "auth": "Basic ZGFuYm9vcnV1c2VyOmFwaWtleQ==", + "params": "?page=20", + "blacklistTags": "comic doujin_cover".split(" "), + }, + "targets": [{ + "name": "Artists", + "chat": "-100123456", + "topic": 0, + "minScore": 0, // minimal post score threshold for filtering + "tags": "daeraeband doraski doushimasho emyo enka_(bcat) guweiz haluka_(aixioo) hamahama houtengeki kagematsuri kcar66t mikuro mocha_(cotton) mohaka_(m_0haka) namyo omelet_tomato rin_yuu sak_(lemondisk) sakiyamama saru saya_pr sion005 zerocat".split(" "), + },{ + "name": "Azur Lane", + "chat": "-100123456", + "topic": 7, + "minScore": 4, + "tags": "azur_lane".split(" "), + },{ + "name": "Blue Archive", + "chat": "-100123456", + "topic": 9, + "minScore": 4, + "tags": "blue_archive".split(" "), + },{ + "name": "Anime", + "chat": "12345", + "topic": 0, + "minScore": 6, + "tags": "bocchi_the_rock!".split(" "), + }] +} + diff --git a/danboo.own b/danboo.own new file mode 100644 index 0000000..b527227 --- /dev/null +++ b/danboo.own @@ -0,0 +1,100 @@ +use std, functional, types, date + +include "own-modules/telegram-bot/TelegramBot.own" +include "config.own" +include "database.own" +include "DanbooruApi.own" + +bot = new TelegramBot(config.telegram.token) +api = new DanbooruApi(config.danbooru.auth) + +// Get posts from API +posts = api.getPosts(config.danbooru.params) +// Skip previously processed +lastPostId = getLastPostId() +skipToPostId = lastPostId +filteredPosts = stream(posts) + .filter(def(p) = p.isImage(p.post)) + .sortBy(def(p) = p.id) + .dropWhile(def(p) = p.id <= skipToPostId) + .peek(def(p) = lastPostId = p.id) + .toArray() +setLastPostId(lastPostId) + +// Process +blacklistTags = config.danbooru.blacklistTags +for post : filteredPosts { + p = post.post + // Check global blacklist + blacklisted = stream(blacklistTags) + .anyMatch(def(tt) = arrayKeyExists(tt, p.tags)) + if (blacklisted) { + continue + } + score = post.getScore(p) + + for target : config.targets { + minScore = target.minScore ?? 0 + if (score < minScore) continue + + matched = stream(target.tags) + .anyMatch(def(tt) = arrayKeyExists(tt, p.tags)) + if (!matched) continue + + url = "https://danbooru.donmai.us/posts/" + p.id + println "%s: %s -> %s".sprintf(newDate(), url, target.name) + + caption = getCaption(post) + buttonText = sprintf("%s 📈%d", post.getResolution(p), score) + markup = jsonencode({"inline_keyboard": [ + [{"text":buttonText, "url":url}] + ]}) + bot.invoke("sendPhoto", { + "chat_id": target.chat, + "message_thread_id": target.topic, + "photo": post.getImageUrl(p), + "parse_mode": "html", + "caption": caption, + "reply_markup": markup + }, def(r) { + if !r.contains("wrong file identifier") return 1 + bot.invoke("sendPhoto", { + "chat_id": target.chat, + "message_thread_id": target.topic, + "photo": post.getSampleImageUrl(p), + "parse_mode": "html", + "caption": "small " + caption, + "reply_markup": markup + }, def(r) = 1) + }) + sleep(2000) + } +} + +closeDB() +thread(def() { + sleep(2000) + exit(0) +}) + +// Helpers +def strToHashtag(str) = + "#" + str.toLowerCase() + .replaceAll("[^a-z_0-9\s]", "") + .replaceAll("\s+", "_") + +def getCaption(post) { + p = post.post + characters = post.getCharacters(p) + if characters.length > 0 { + charactersHashTag = map(characters, ::strToHashtag).joinToString(" ") + } else { + charactersHashTag = "#original" + } + sourceUrl = post.getSource(p) + return "%s | source".sprintf( + charactersHashTag, + sourceUrl + ) +} + diff --git a/database.own b/database.own new file mode 100644 index 0000000..4513e00 --- /dev/null +++ b/database.own @@ -0,0 +1,37 @@ +use jdbc + +conn = getConnection("jdbc:sqlite:danboo.db") + +st = conn.createStatement() +st.executeUpdate( + "CREATE TABLE IF NOT EXISTS meta ( + prop TEXT PRIMARY KEY, + num_value INTEGER, + str_value TEXT + )") +st.executeUpdate( + "INSERT OR IGNORE INTO meta(prop, num_value, str_value) VALUES('last_id', 0, '')") +st.close() + +stGetNumValue = conn.prepareStatement( + "SELECT num_value FROM meta WHERE prop = ?") +stSetNumValue = conn.prepareStatement( + "UPDATE meta SET num_value = ? WHERE prop = ?") + +def getLastPostId() { + stGetNumValue.setString(1, "last_id") + rs = stGetNumValue.executeQuery() + return rs.getInt(1) +} + +def setLastPostId(postId) { + stSetNumValue.setInt(1, postId) + stSetNumValue.setString(2, "last_id") + stSetNumValue.executeUpdate() +} + +def closeDB() { + stGetNumValue.close() + stSetNumValue.close() + conn.close() +} diff --git a/modules.json b/modules.json new file mode 100644 index 0000000..bbd0bc8 --- /dev/null +++ b/modules.json @@ -0,0 +1,6 @@ +[ + { + "id": "18f1894447dfa72000a83011511a817c", + "name": "telegram-bot" + } +] \ No newline at end of file diff --git a/modules.own b/modules.own new file mode 100644 index 0000000..c546f15 --- /dev/null +++ b/modules.own @@ -0,0 +1,39 @@ +use std + +if (ARGS.length >= 2 && ARGS[0] == "owm") { + use files, json, java + + File = newClass("java.io.File") + runtime = newClass("java.lang.Runtime").getRuntime() + + def loadModulesJson(path = "modules.json") { + f = fopen(path, "r") + modules = jsondecode(readText(f)) + fclose(f) + return modules + } + def exec(cmd, dir = ".") = runtime.exec(cmd, null, new File(dir)) + + match (ARGS[1]) { + case "install": { + modulesDir = "own-modules" + if (!exists(modulesDir)) { + mkdir(modulesDir) + } + for module : loadModulesJson() { + print module.name + moduleDir = modulesDir + "/" + module.name + if (!exists(moduleDir)) { + mkdir(moduleDir) + cmd = "git clone https://gist.github.com/" + module.id + ".git " + module.name + exec(cmd, modulesDir) + println " installed" + } else { + exec("git pull origin master", moduleDir) + println " updated" + } + } + } + } +} +