diff options
| author | Maria Lisina <sekoohaka.sarisan@gmail.com> | 2025-04-14 05:29:28 +0300 |
|---|---|---|
| committer | Maria Lisina <sekoohaka.sarisan@gmail.com> | 2025-04-14 05:29:28 +0300 |
| commit | 570cd31efa5194c57bc9ed59018a345d667cb075 (patch) | |
| tree | 153e1d28578f3138e3681fad43b76d241ef46388 | |
| parent | 290c81bbd47f4ec35f1b3990305e4a4b645959cf (diff) | |
bot: Add initial SauceNAO support
Signed-off-by: Maria Lisina <sekoohaka.sarisan@gmail.com>
| -rw-r--r-- | README.md | 3 | ||||
| -rw-r--r-- | files/help.txt.default | 2 | ||||
| -rw-r--r-- | modules/commands.sh | 3 | ||||
| -rw-r--r-- | submodules/callback_stop.sh | 2 | ||||
| -rw-r--r-- | submodules/command_saucenao.sh | 35 | ||||
| -rw-r--r-- | units/sn_auth.sh | 61 | ||||
| -rw-r--r-- | units/sn_get.sh | 77 | ||||
| -rw-r--r-- | units/sn_result.sh | 82 | ||||
| -rw-r--r-- | units/sn_search.sh | 63 |
9 files changed, 327 insertions, 1 deletions
@@ -16,6 +16,7 @@ Telegram Image Board Bot written in POSIX Shell (dash) * Post information view * Post tags view * MD5 hash lookup +* SauceNAO reverse image search * Inline shortcuts * Inline shortcuts saving * Inline shortcuts search @@ -102,6 +103,7 @@ Caching modes: * `[md5]` - MD5 hash * `[login]` - Image Board login or username * `[key]` - Image Board API key or password +* `[snkey]` - SauceNAO API Key ### Inline search @@ -134,6 +136,7 @@ Caching modes: * `/original [b] [id/md5]` - Get original file of post * `/post [b] [id/md5]` - Get information of post * `/hash [md5]` - Get posts IDs by hash lookup +* `/saucenao [snkey]` - Reverse search image * `/short [query]` - Create inline shortcut * `/shorts` - Manage saved shortcuts * `/stop` - Remove all user data diff --git a/files/help.txt.default b/files/help.txt.default index 30b644d..427b644 100644 --- a/files/help.txt.default +++ b/files/help.txt.default @@ -12,6 +12,7 @@ [md5] - MD5 hash [login] - Image Board login or username [key] - Image Board API key or password +[snkey] - SauceNAO API Key <b>Inline search:</b> <code>pools</code> [b] [page] [options] [name] - Pools search @@ -40,6 +41,7 @@ /original [b] [id/md5] - Get original file of post /post [b] [id/md5] - Get information of post /hash [md5] - Get posts IDs by hash lookup +/saucenao [snkey] - Reverse search image /short [query] - Create inline shortcut /shorts - Manage saved shortcuts /stop - Remove all your data diff --git a/modules/commands.sh b/modules/commands.sh index e1f7f38..8457321 100644 --- a/modules/commands.sh +++ b/modules/commands.sh @@ -50,6 +50,9 @@ case "${command}" in ("/prpr" | "/prpr@${username}") . "${submodules}/command_prpr.sh" ;; + ("/saucenao" | "/saucenao@${username}") + . "${submodules}/command_saucenao.sh" + ;; ("/short" | "/short@${username}") . "${submodules}/command_short.sh" ;; diff --git a/submodules/callback_stop.sh b/submodules/callback_stop.sh index 311cd77..dc3e9c5 100644 --- a/submodules/callback_stop.sh +++ b/submodules/callback_stop.sh @@ -8,7 +8,7 @@ then return 0 fi -locks="auth short" +locks="auth saucenao short" for lock in ${locks} do diff --git a/submodules/command_saucenao.sh b/submodules/command_saucenao.sh new file mode 100644 index 0000000..471d8ca --- /dev/null +++ b/submodules/command_saucenao.sh @@ -0,0 +1,35 @@ +# Copyright (C) 2024-2025 Maria Lisina +# Copyright (C) 2024-2025 Danil Lisin +# SPDX-License-Identifier: Apache-2.0 + +until mkdir "${user_config}_saucenao.lock" +do + sleep 1 +done + +. "${units}/sn_auth.sh" + +if [ -n "${output_text}" ] +then + rm -fr "${user_config}_saucenao.lock" + return 0 +fi + +. "${units}/sn_get.sh" + +if [ -n "${output_text}" ] +then + rm -fr "${user_config}_saucenao.lock" + return 0 +fi + +. "${units}/sn_search.sh" + +if [ -n "${output_text}" ] +then + rm -fr "${user_config}_saucenao.lock" + return 0 +fi + +. "${units}/sn_result.sh" +rm -fr "${user_config}_saucenao.lock" diff --git a/units/sn_auth.sh b/units/sn_auth.sh new file mode 100644 index 0000000..0d595ac --- /dev/null +++ b/units/sn_auth.sh @@ -0,0 +1,61 @@ +# Copyright (C) 2024-2025 Maria Lisina +# Copyright (C) 2024-2025 Danil Lisin +# SPDX-License-Identifier: Apache-2.0 + +if [ -n "${1}" ] +then + sn_key="${1}" +elif ! [ -s "${user_config}/saucenao" ] +then + output_text="You must provide your SauceNAO API Key before you can use SauceNAO" + return 0 +else + return 0 +fi + +sn_file="${cache}/${update_id}_search.json" +dump="${dump} ${sn_file##*/}" + +if ! curl --data-urlencode "output_type=2" \ + --data-urlencode "api_key=${sn_key}" \ + --get \ + --max-time ${external_timeout} \ + --output "${sn_file}" \ + --proxy "${external_proxy}" \ + --silent \ + --user-agent "Sekoohaka" \ + "https://saucenao.com/search.php" +then + output_text="Failed to process request" + log_text="sn_auth (${update_id}): ${output_text}" + + . "${units}/log.sh" + . "${units}/dump.sh" + + return 0 +fi + +if ! jq -e '.' "${sn_file}" > /dev/null +then + output_text="An unknown error occurred" + log_text="sn_auth (${update_id}): ${output_text}" + + . "${units}/log.sh" + . "${units}/dump.sh" + + return 0 +fi + +if [ "$(jq -r '.header.user_id' "${sn_file}")" = "null" ] +then + output_text="Invalid API Key" + log_text="sn_auth (${update_id}): ${output_text}" + + . "${units}/log.sh" + . "${units}/dump.sh" + + return 0 +fi + +output_text="Authorized successfully" +printf "%s\n" "${sn_key}" > "${user_config}/saucenao" diff --git a/units/sn_get.sh b/units/sn_get.sh new file mode 100644 index 0000000..88a5da8 --- /dev/null +++ b/units/sn_get.sh @@ -0,0 +1,77 @@ +# Copyright (C) 2024-2025 Maria Lisina +# Copyright (C) 2024-2025 Danil Lisin +# SPDX-License-Identifier: Apache-2.0 + +message_id="$(jq -r '.message.reply_to_message.message_id' "${update}")" +file_id="$(jq -r '.message.reply_to_message.photo.[1].file_id' "${update}")" + +if [ "${message_id}" = "null" ] +then + output_text="You must use this command in reply to an image" + return 0 +fi + +if [ "${file_id}" = "null" ] +then + output_text="Could not find an image in replied message" + return 0 +fi + +output_file="${cache}/${update_id}_getFile.json" +dump="${dump} ${output_file##*/}" + +if ! curl --data-urlencode "file_id=${file_id}" \ + --get \ + --max-time ${internal_timeout} \ + --output "${output_file}" \ + --proxy "${internal_proxy}" \ + --silent \ + "${api_address}/bot${api_token}/getFile" +then + output_text="An unknown error occurred" + log_text="getFile (${update_id}): An unknown error occurred" + + . "${units}/log.sh" + . "${units}/dump.sh" + + return 0 +fi + +if ! jq -e '.' "${output_file}" > /dev/null +then + output_text="An unknown error occurred" + log_text="getFile (${update_id}): An unknown error occurred" + + . "${units}/log.sh" + . "${units}/dump.sh" + + return 0 +fi + +if [ "$(jq -r '.ok' "${output_file}")" != "true" ] +then + output_text="An unknown error occurred" + error_description="$(jq -r '.description' "${output_file}")" + + if [ "${error_description}" != "null" ] + then + log_text="getFile (${update_id}): ${error_description}" + else + log_text="getFile (${update_id}): An unknown error occurred" + fi + + . "${units}/log.sh" + . "${units}/dump.sh" + + return 0 +fi + +file_path="$(jq -r '.result.file_path' "${output_file}")" + +if [ "${api_address}" = "127.0.0.1:8081" ] && ! ls "${file_path}" > /dev/null +then + output_text="This command cannot be used, contact bot deployer" + log_text="Error: sn_get: Cannot access Bot API working directory" + + . "${units}/log.sh" +fi diff --git a/units/sn_result.sh b/units/sn_result.sh new file mode 100644 index 0000000..84f429c --- /dev/null +++ b/units/sn_result.sh @@ -0,0 +1,82 @@ +# Copyright (C) 2024-2025 Maria Lisina +# Copyright (C) 2024-2025 Danil Lisin +# SPDX-License-Identifier: Apache-2.0 + +minimum_similarity="$(jq -r ".header.index.minimum_similarity" "${sn_file}" | sed 's/\.//')" +highest_similarity=0 +highest_index=0 +array_count=0 + +while [ -n "${array_count}" ] +do + similarity="$(jq -r ".results.[${array_count}].header.similarity" "${sn_file}" | sed 's/\.//')" + + if [ "${similarity}" = "null" ] + then + break + fi + + if [ ${highest_similarity} -lt ${similarity} ] + then + highest_similarity=${similarity} + highest_index=${array_count} + fi + + array_count=$((array_count + 1)) +done + +if [ ${highest_similarity} -lt ${minimum_similarity} ] +then + output_text="No results found" + return 0 +fi + +thumbnail="$(jq -r ".results.[${highest_index}].header.thumbnail" "${sn_file}")" +similarity="$(printf "%.2f" "$(printf "%s" "${highest_similarity} / 100" | bc -l)")" + +link_preview_options="$(jq --null-input --compact-output \ + --arg url "${thumbnail}" \ + '{"url": $url, "prefer_small_media": true, "show_above_text": true}')" + +danbooru_id="$(jq -r ".results.[${highest_index}].data.danbooru_id" "${sn_file}")" +gelbooru_id="$(jq -r ".results.[${highest_index}].data.gelbooru_id" "${sn_file}")" +idol_id="$(jq -r ".results.[${highest_index}].data.idol_id" "${sn_file}")" +konachan_id="$(jq -r ".results.[${highest_index}].data.konachan_id" "${sn_file}")" +sankaku_id="$(jq -r ".results.[${highest_index}].data.sankaku_id" "${sn_file}")" +yandere_id="$(jq -r ".results.[${highest_index}].data.yandere_id" "${sn_file}")" + +output_text="<b>SauceNAO</b>" +output_text="$(printf "%s\n<b>Similarity:</b> %s%%" "${output_text}" "${similarity}")" + +if [ "${danbooru_id}" != "null" ] +then + output_text="$(printf "%s\n<b>Danbooru ID:</b> <code>%s</code>" "${output_text}" "${danbooru_id}")" +fi + +if [ "${gelbooru_id}" != "null" ] +then + output_text="$(printf "%s\n<b>Gelbooru ID:</b> <code>%s</code>" "${output_text}" "${gelbooru_id}")" +fi + +if [ "${idol_id}" != "null" ] +then + idol_md5="$(jq -r ".results.[${highest_index}].header.index_name" "${sn_file}" | parameter 6 | cut -d '_' -f 1)" + + output_text="$(printf "%s\n<b>Idol Complex ID:</b> <code>%s</code>" "${output_text}" "${idol_id}")" + output_text="$(printf "%s\n<b>Idol Complex MD5:</b> <code>%s</code>" "${output_text}" "${idol_md5}")" +fi + +if [ "${konachan_id}" != "null" ] +then + output_text="$(printf "%s\n<b>Konachan.com ID:</b> <code>%s</code>" "${output_text}" "${konachan_id}")" +fi + +if [ "${sankaku_id}" != "null" ] +then + output_text="$(printf "%s\n<b>Sankaku Channel ID:</b> <code>%s</code>" "${output_text}" "${sankaku_id}")" +fi + +if [ "${yandere_id}" != "null" ] +then + output_text="$(printf "%s\n<b>yande.re ID:</b> <code>%s</code>" "${output_text}" "${yandere_id}")" +fi diff --git a/units/sn_search.sh b/units/sn_search.sh new file mode 100644 index 0000000..4070ed4 --- /dev/null +++ b/units/sn_search.sh @@ -0,0 +1,63 @@ +# Copyright (C) 2024-2025 Maria Lisina +# Copyright (C) 2024-2025 Danil Lisin +# SPDX-License-Identifier: Apache-2.0 + +if [ "${api_address}" = "127.0.0.1:8081" ] +then + sn_query="file=@${file_path}" +else + sn_query="url=${api_address}/file/bot${api_token}/${file_path}" +fi + +sn_key="$(cat "${user_config}/saucenao")" +sn_file="${cache}/${update_id}_search.json" +dump="${dump} ${sn_file##*/}" + +if ! curl --form "output_type=2" \ + --form "api_key=${sn_key}" \ + --form "dbs[]=9" \ + --form "dbs[]=12" \ + --form "dbs[]=25" \ + --form "dbs[]=26" \ + --form "dbs[]=27" \ + --form "dbs[]=30" \ + --form "dedupe=2" \ + --form "${sn_query}" \ + --get \ + --max-time ${external_timeout} \ + --output "${sn_file}" \ + --proxy "${external_proxy}" \ + --silent \ + --user-agent "Sekoohaka" \ + "https://saucenao.com/search.php" +then + output_text="Failed to process request" + log_text="sn_search (${update_id}): ${output_text}" + + . "${units}/log.sh" + . "${units}/dump.sh" + + return 0 +fi + +if ! jq -e '.' "${sn_file}" > /dev/null +then + output_text="An unknown error occurred" + log_text="sn_search (${update_id}): ${output_text}" + + . "${units}/log.sh" + . "${units}/dump.sh" + + return 0 +fi + +if [ "$(jq -r '.header.status' "${sn_file}")" != "0" ] +then + output_text="Error: $(jq -r '.header.message' "${sn_file}")" + log_text="sn_search (${update_id}): $(jq -r '.header.status' "${sn_file}")" + + . "${units}/log.sh" + . "${units}/dump.sh" + + return 0 +fi |
