aboutsummaryrefslogtreecommitdiff
path: root/operators
diff options
context:
space:
mode:
authorMaria Lisina <sekoohaka.sarisan@gmail.com>2026-02-06 04:20:11 +0300
committerMaria Lisina <sekoohaka.sarisan@gmail.com>2026-02-06 04:20:11 +0300
commit0671dc134a9e122ce2c0348f3f4deeb2d274d524 (patch)
treef9bb88f626c1d68be98216e32b3e02f08701dac3 /operators
parent1e79ebd5f9a288563c8bb25acacd37bd55abc2d4 (diff)
*/*: Rename agents to operators for alphabetical order
Signed-off-by: Maria Lisina <sekoohaka.sarisan@gmail.com>
Diffstat (limited to 'operators')
-rw-r--r--operators/callback_delete.zsh54
-rw-r--r--operators/callback_post.zsh61
-rw-r--r--operators/callback_short.zsh54
-rw-r--r--operators/callback_stop.zsh70
-rw-r--r--operators/callback_tags.zsh144
-rw-r--r--operators/command_authorize.zsh40
-rw-r--r--operators/command_donate.zsh10
-rw-r--r--operators/command_export.zsh29
-rw-r--r--operators/command_hash.zsh75
-rw-r--r--operators/command_help.zsh10
-rw-r--r--operators/command_original.zsh154
-rw-r--r--operators/command_ping.zsh112
-rw-r--r--operators/command_post.zsh67
-rw-r--r--operators/command_prpr.zsh5
-rw-r--r--operators/command_short.zsh46
-rw-r--r--operators/command_shorts.zsh53
-rw-r--r--operators/command_source.zsh99
-rw-r--r--operators/command_stop.zsh24
-rw-r--r--operators/inline_donate.zsh22
-rw-r--r--operators/inline_help.zsh16
-rw-r--r--operators/inline_none.zsh24
-rw-r--r--operators/inline_original.zsh90
-rw-r--r--operators/inline_post.zsh82
-rw-r--r--operators/inline_search.zsh151
-rw-r--r--operators/inline_search_pools.zsh74
-rw-r--r--operators/inline_search_posts.zsh134
-rw-r--r--operators/inline_search_tags.zsh59
-rw-r--r--operators/inline_short.zsh57
-rw-r--r--operators/inline_shorts.zsh112
-rw-r--r--operators/timer_uncache.zsh26
-rw-r--r--operators/timer_unlock.zsh14
-rw-r--r--operators/url_parser.zsh125
32 files changed, 2093 insertions, 0 deletions
diff --git a/operators/callback_delete.zsh b/operators/callback_delete.zsh
new file mode 100644
index 0000000..a2334bf
--- /dev/null
+++ b/operators/callback_delete.zsh
@@ -0,0 +1,54 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+chat_id="$(jq -r '.callback_query.message.chat.id' <<< "${update}")"
+message_id="$(jq -r '.callback_query.message.message_id' <<< "${update}")"
+
+if ! output_data="$(
+ curl --connect-timeout ${connrefused_timeout} \
+ --data-urlencode "chat_id=${chat_id}" \
+ --data-urlencode "message_id=${message_id}" \
+ --get \
+ --max-time ${internal_timeout} \
+ --proxy "${internal_proxy}" \
+ --retry 1 \
+ --retry-connrefused \
+ --retry-max-time $((internal_timeout - connrefused_timeout)) \
+ --silent \
+ --user-agent "${useragent}" \
+ "${api_address}/bot${api_token}/deleteMessage"
+)"
+then
+ notification_text="Failed to delete message"
+
+ log_text="deleteMessage (${update_id}): Failed to access Telegram Bot API"
+ source "${units}/log.zsh"
+
+ return
+fi
+
+if ! jq -e '.' <<< "${output_data}" > /dev/null
+then
+ notification_text="An unknown error occurred"
+
+ log_text="deleteMessage (${update_id}): An unknown error occurred"
+ source "${units}/log.zsh"
+
+ return
+fi
+
+if [[ "$(jq -r '.ok' <<< "${output_data}")" != "true" ]]
+then
+ notification_text="Failed to delete message"
+ error_description="$(jq -r '.description' "${output_file}")"
+
+ if [[ "${error_description}" != "null" ]]
+ then
+ log_text="deleteMessage (${update_id}): ${error_description}"
+ else
+ log_text="deleteMessage (${update_id}): An unknown error occurred"
+ fi
+
+ source "${units}/log.zsh"
+fi
diff --git a/operators/callback_post.zsh b/operators/callback_post.zsh
new file mode 100644
index 0000000..301001f
--- /dev/null
+++ b/operators/callback_post.zsh
@@ -0,0 +1,61 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+ib_lock=0
+source "${units}/ib_common.zsh"
+
+if [[ -n "${notification_text}" ]]
+then
+ return
+fi
+
+ib_hash="$(sha1sum <<< "${user_id}${ib_board}${ib_query}" | cut -d ' ' -f 1)"
+ib_file="${cache}/${ib_hash}.json"
+
+until mkdir "${cache}/${ib_hash}.lock"
+do
+ sleep 1
+done
+
+source "${units}/ib_file.zsh"
+
+if [[ -n "${notification_text}" ]]
+then
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+source "${units}/ib_post.zsh"
+
+link_preview_options="$(
+ jq --null-input --compact-output \
+ --arg url "${ib_sample_url}" \
+ '{"url": $url, "prefer_small_media": true, "show_above_text": true}'
+)"
+
+keyboard_text1="Post link"
+keyboard_url1="${ib_url}$(urlencode <<< "${ib_id}")"
+
+reply_markup="$(
+ jq --null-input --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg url1 "${keyboard_url1}" \
+ '{"inline_keyboard": [[{"text": $text1, "url": $url1}]]}'
+)"
+
+if [[ ${#ib_tags} -gt 0 ]]
+then
+ keyboard_text1="Tags (${#ib_tags})"
+ keyboard_data1="tags ${ib_board} ${ib_post_id}"
+
+ reply_markup="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg data1 "${keyboard_data1}" \
+ '.inline_keyboard[0] += [{"text": $text1, "callback_data": $data1}]' \
+ <<< "${reply_markup}"
+ )"
+fi
+
+rmdir "${cache}/${ib_hash}.lock"
diff --git a/operators/callback_short.zsh b/operators/callback_short.zsh
new file mode 100644
index 0000000..a48a27c
--- /dev/null
+++ b/operators/callback_short.zsh
@@ -0,0 +1,54 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+until mkdir "${user_config}_shorts.lock"
+do
+ sleep 1
+done
+
+if [[ -n "${1}" ]]
+then
+ short_query="${@}"
+ shift ${#}
+else
+ notification_text="You must specify query"
+
+ rmdir "${user_config}_shorts.lock"
+ return
+fi
+
+shorts_config="${user_config}/shorts"
+
+if ! mkdir -p "${shorts_config}"
+then
+ notification_text="Failed to create user config"
+
+ rmdir "${user_config}_shorts.lock"
+ return
+fi
+
+short_hash="$(sha1sum <<< "${short_query}" | cut -d ' ' -f 1)"
+short="${shorts_config}/${short_hash}"
+shorts=($(ls -1 "${shorts_config}"))
+
+if [[ -f "${short}" ]]
+then
+ rm -f "${short}"
+ notification_text="Removed shortcut"
+elif [[ ${#shorts} -le ${shorts_limit} ]]
+then
+ printf "%s\n" "${short_query}" > "${short}"
+ notification_text="Saved shortcut"
+else
+ notification_text="Too many shortcuts"
+fi
+
+shorts=($(ls -1 "${shorts_config}"))
+
+if [[ ${#shorts} -eq 0 ]]
+then
+ rmdir "${shorts_config}"
+fi
+
+rmdir "${user_config}_shorts.lock"
diff --git a/operators/callback_stop.zsh b/operators/callback_stop.zsh
new file mode 100644
index 0000000..d2eab2c
--- /dev/null
+++ b/operators/callback_stop.zsh
@@ -0,0 +1,70 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+if [[ -n "${1}" ]]
+then
+ data_name="${1}"
+ check_table=(${data_table[@]})
+ shift
+fi
+
+while [[ ${#check_table} -ge 2 ]]
+do
+ if [[ "${data_name}" == "${check_table[1]}" ]]
+ then
+ break
+ elif [[ "${check_table[1]}" == "${check_table[-2]}" ]]
+ then
+ notification_text="No requested data found"
+ return
+ fi
+
+ shift 2 check_table
+done
+
+if mkdir "${user_config}_stop.lock"
+then
+ notification_text="Click again to confirm data removal"
+ return
+fi
+
+for lock in ${user_locks[@]}
+do
+ until mkdir "${user_config}_${lock}.lock"
+ do
+ sleep 1
+ done
+done
+
+if [[ -n "${data_name}" ]]
+then
+ if ! rm -fr "${user_config}/${check_table[1]}"
+ then
+ notification_text="Something went wrong, try again later"
+ fi
+else
+ if ! rm -fr "${user_config}"
+ then
+ notification_text="Something went wrong, try again later"
+ fi
+fi
+
+if [[ -d "${user_config}" ]]
+then
+ user_data=($(ls -1 "${user_config}"))
+
+ if [[ ${#user_data} -eq 0 ]]
+ then
+ rmdir "${user_config}"
+ fi
+fi
+
+source "${units}/stop.zsh"
+
+for lock in ${user_locks[@]}
+do
+ rmdir "${user_config}_${lock}.lock"
+done
+
+rmdir "${user_config}_stop.lock"
diff --git a/operators/callback_tags.zsh b/operators/callback_tags.zsh
new file mode 100644
index 0000000..803c0dd
--- /dev/null
+++ b/operators/callback_tags.zsh
@@ -0,0 +1,144 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+ib_lock=0
+source "${units}/ib_common.zsh"
+
+if [[ -n "${notification_text}" ]]
+then
+ return
+fi
+
+ib_hash="$(sha1sum <<< "${user_id}${ib_board}${ib_query}" | cut -d ' ' -f 1)"
+ib_file="${cache}/${ib_hash}.json"
+
+until mkdir "${cache}/${ib_hash}.lock"
+do
+ sleep 1
+done
+
+source "${units}/ib_file.zsh"
+
+if [[ -n "${notification_text}" ]]
+then
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+ib_file_size="$(jq -r ".${ib_iarray}[0].${ib_isize}" "${ib_file}")"
+ib_file_url="$(jq -r ".${ib_iarray}[0].${ib_ifile}" "${ib_file}")"
+ib_sample_url="$(jq -r ".${ib_iarray}[0].${ib_isample}" "${ib_file}")"
+ib_preview_url="$(jq -r ".${ib_iarray}[0].${ib_ipreview}" "${ib_file}")"
+ib_width="$(jq -r ".${ib_iarray}[0].${ib_iwidth}" "${ib_file}")"
+ib_height="$(jq -r ".${ib_iarray}[0].${ib_iheight}" "${ib_file}")"
+ib_tags=($(jq -r ".${ib_iarray}[0].${ib_itags}" "${ib_file}"))
+ib_groups_offset=${1:-0}
+ib_tags_offset=${1:-0}
+
+source "${units}/ib_size.zsh"
+source "${units}/ib_meta.zsh"
+
+if [[ -z "${ib_tags}" || "${ib_tags}" == "null" ]]
+then
+ notification_text="Failed to get tags"
+
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+while [[ ${#ib_groups} -ge 2 ]]
+do
+ ib_group_tags=($(jq -r ".${ib_iarray}[0].${ib_groups[1]}" "${ib_file}" | htmlescape))
+ ib_group_name="${ib_groups[2]}"
+
+ if [[ -z "${ib_group_tags}" || "${ib_group_tags}" == "null" ]]
+ then
+ shift 2 ib_groups
+ continue
+ fi
+
+ if [[ ${#ib_group_tags} -gt ${ib_groups_offset} ]]
+ then
+ shift ${ib_groups_offset} ib_group_tags
+ ib_groups_offset=0
+ else
+ ib_groups_offset=$((ib_groups_offset - $#ib_group_tags))
+ shift 2 ib_groups
+ continue
+ fi
+
+ ib_group_text="$(printf "%s\n<b>%s:</b>" "${output_text}" "${ib_group_name}")"
+
+ while [[ ${#ib_group_tags} -gt 0 ]]
+ do
+ ib_tag="<code>${ib_group_tags[1]}</code>"
+
+ if [[ $(($#ib_group_text + $#ib_tag)) -gt 2048 ]]
+ then
+ break 2
+ fi
+
+ ib_group_text="${ib_group_text} ${ib_tag}"
+ ib_tags_count=$((ib_tags_count + 1))
+
+ output_text="${ib_group_text}"
+ shift 1 ib_group_tags
+ done
+
+ shift 2 ib_groups
+done
+
+if [[ -z "${output_text}" ]]
+then
+ notification_text="No tags found"
+
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+link_preview_options="$(
+ jq --null-input --compact-output \
+ --arg url "${ib_sample_url}" \
+ '{"url": $url, "prefer_small_media": true, "show_above_text": true}'
+)"
+
+keyboard_text1="Back"
+keyboard_data1="post ${ib_board} ${ib_post_id}"
+
+reply_markup="$(
+ jq --null-input --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg data1 "${keyboard_data1}" \
+ '{"inline_keyboard": [[{"text": $text1, "callback_data": $data1}]]}'
+)"
+
+if [[ ${ib_tags_offset} -gt 0 ]]
+then
+ keyboard_text1="Over"
+ keyboard_data1="tags ${ib_board} ${ib_post_id}"
+
+ reply_markup="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg data1 "${keyboard_data1}" \
+ '.inline_keyboard[0] += [{"text": $text1, "callback_data": $data1}]' \
+ <<< "${reply_markup}"
+ )"
+fi
+
+if [[ $(($#ib_tags - ib_tags_offset - ib_tags_count)) -gt 0 ]]
+then
+ keyboard_text1="Next"
+ keyboard_data1="tags ${ib_board} ${ib_post_id} $((ib_tags_offset + ib_tags_count))"
+
+ reply_markup="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg data1 "${keyboard_data1}" \
+ '.inline_keyboard[0] += [{"text": $text1, "callback_data": $data1}]' \
+ <<< "${reply_markup}"
+ )"
+fi
+
+rmdir "${cache}/${ib_hash}.lock"
diff --git a/operators/command_authorize.zsh b/operators/command_authorize.zsh
new file mode 100644
index 0000000..a8be540
--- /dev/null
+++ b/operators/command_authorize.zsh
@@ -0,0 +1,40 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+if [[ -n "${1}" ]]
+then
+ ib_board="${1}"
+ source "${units}/ib_config.zsh"
+ source "${units}/ib_authconfig.zsh"
+
+ if [[ -z "${ib_auth}" ]]
+ then
+ output_text="Unsupported Image Board"
+ return
+ fi
+
+ shift
+else
+ output_text="You must specify Image Board"
+ return
+fi
+
+if [[ -n "${1}" ]]
+then
+ ib_login="${1}"
+ shift
+fi
+
+if [[ -n "${1}" ]]
+then
+ ib_key="${1}"
+ shift
+fi
+
+source "${units}/ib_lock.zsh"
+
+if [[ -z "${output_text}" ]]
+then
+ output_text="Authorized successfully"
+fi
diff --git a/operators/command_donate.zsh b/operators/command_donate.zsh
new file mode 100644
index 0000000..fa7518c
--- /dev/null
+++ b/operators/command_donate.zsh
@@ -0,0 +1,10 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+if [[ -n "${nocommand_donate}" ]]
+then
+ exit 0
+fi
+
+output_text="${donate_content}"
diff --git a/operators/command_export.zsh b/operators/command_export.zsh
new file mode 100644
index 0000000..fe68802
--- /dev/null
+++ b/operators/command_export.zsh
@@ -0,0 +1,29 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+if [[ "${chat_id}" != "${target_user}" ]]
+then
+ output_text="For security reasons you can execute this command only in direct messages"
+ return
+fi
+
+for lock in ${user_locks[@]}
+do
+ until mkdir "${user_config}_${lock}.lock"
+ do
+ sleep 1
+ done
+done
+
+source "${units}/export.zsh"
+
+for lock in ${user_locks[@]}
+do
+ rmdir "${user_config}_${lock}.lock"
+done
+
+if [[ -z "${output_text}" ]]
+then
+ exit 0
+fi
diff --git a/operators/command_hash.zsh b/operators/command_hash.zsh
new file mode 100644
index 0000000..d8bb677
--- /dev/null
+++ b/operators/command_hash.zsh
@@ -0,0 +1,75 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+if [[ -n "${1}" ]]
+then
+ ib_post_md5="${1}"
+ shift
+else
+ output_text="You must specify MD5 hash"
+ return
+fi
+
+if ! [[ ${#ib_post_md5} -eq 32 ]]
+then
+ output_text="Invalid MD5 hash"
+ return
+fi
+
+ib_post="${cache}/${update_id}_id.txt"
+ib_sample="${cache}/${update_id}_sample.txt"
+
+for lock in "${ib_post}" "${ib_sample}"
+do
+ until mkdir "${lock%.*}.lock"
+ do
+ sleep 1
+ done
+done
+
+for ib_board in {a..z}
+do
+ source "${units}/ib_config.zsh"
+ source "${units}/ib_hash.zsh" &
+ wait
+
+ if ! [[ -f "${ib_post}" ]]
+ then
+ continue
+ fi
+
+ ib_id="$(< "${ib_post}")"
+
+ keyboard_text1="${ib_name}"
+ keyboard_query1="post ${ib_board} ${ib_id}"
+
+ reply_markup="$(
+ jq --compact-output \
+ --argjson offset "$(printf "%u" "${keyboard_offset}")" \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ '.inline_keyboard[$offset] += [{"text": $text1, "switch_inline_query_current_chat": $query1}]' \
+ <<< "${reply_markup:-"{}"}"
+ )"
+
+ keyboard_offset=$((keyboard_offset + 0.5))
+done
+
+if [[ -f "${ib_sample}" ]]
+then
+ output_text="$(printf "<b>MD5:</b> <code>%s</code>" "${ib_post_md5}")"
+
+ link_preview_options="$(
+ jq --null-input --compact-output \
+ --arg url "$(< "${ib_sample}")" \
+ '{"url": $url, "prefer_small_media": true, "show_above_text": true}'
+ )"
+else
+ output_text="No results found"
+fi
+
+for lock in "${ib_post}" "${ib_sample}"
+do
+ rmdir "${lock%.*}.lock"
+done
diff --git a/operators/command_help.zsh b/operators/command_help.zsh
new file mode 100644
index 0000000..2d86678
--- /dev/null
+++ b/operators/command_help.zsh
@@ -0,0 +1,10 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+output_text="${help_content}"
+
+link_preview_options="$(
+ jq --null-input --compact-output \
+ '{"is_disabled": true}'
+)"
diff --git a/operators/command_original.zsh b/operators/command_original.zsh
new file mode 100644
index 0000000..adeebdf
--- /dev/null
+++ b/operators/command_original.zsh
@@ -0,0 +1,154 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+source "${units}/ib_name.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ return
+fi
+
+source "${units}/ib_common.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ return
+fi
+
+ib_hash="$(sha1sum <<< "${user_id}${ib_board}${ib_query}" | cut -d ' ' -f 1)"
+ib_file="${cache}/${ib_hash}.json"
+
+until mkdir "${cache}/${ib_hash}.lock"
+do
+ sleep 1
+done
+
+source "${units}/ib_file.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+source "${units}/ib_original.zsh"
+
+if [[ -n "${ib_file_url}" && "${ib_file_url}" != "null" ]]
+then
+ keyboard_text1="Original file link"
+ keyboard_url1="${ib_file_url}"
+
+ reply_markup="$(
+ jq --null-input --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg url1 "${keyboard_url1}" \
+ '{"inline_keyboard": [[{"text": $text1, "url": $url1}]]}'
+ )"
+fi
+
+if [[ -n "${output_text}" ]]
+then
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+if ! output_data="$(
+ curl --connect-timeout ${connrefused_timeout} \
+ --data-urlencode "chat_id=${chat_id}" \
+ --data-urlencode "message_thread_id=${message_thread_id}" \
+ --data-urlencode "action=upload_document" \
+ --get \
+ --max-time ${internal_timeout} \
+ --proxy "${internal_proxy}" \
+ --retry 1 \
+ --retry-connrefused \
+ --retry-max-time $((internal_timeout - connrefused_timeout)) \
+ --silent \
+ --user-agent "${useragent}" \
+ "${api_address}/bot${api_token}/sendChatAction"
+)"
+then
+ log_text="sendChatAction (${update_id}): Failed to access Telegram Bot API"
+ source "${units}/log.zsh"
+fi
+
+if [[ -z "${log_text}" ]] && ! jq -e '.' <<< "${output_data}" > /dev/null
+then
+ log_text="sendChatAction (${update_id}): An unknown error occurred"
+ source "${units}/log.zsh"
+fi
+
+if [[ -z "${log_text}" && "$(jq -r '.ok' <<< "${output_data}")" != "true" ]]
+then
+ error_description="$(jq -r '.description' <<< "${output_data}")"
+
+ if [[ "${error_description}" != "null" ]]
+ then
+ log_text="sendChatAction (${update_id}): ${error_description}"
+ else
+ log_text="sendChatAction (${update_id}): An unknown error occurred"
+ fi
+
+ source "${units}/log.zsh"
+fi
+
+if ! output_data="$(
+ curl --connect-timeout ${connrefused_timeout} \
+ --data-urlencode "chat_id=${chat_id}" \
+ --data-urlencode "message_thread_id=${message_thread_id}" \
+ --data-urlencode "document=${ib_file_url}" \
+ --data-urlencode "thumbnail=${ib_preview_url}" \
+ --data-urlencode "reply_parameters=${reply_parameters}" \
+ --data-urlencode "reply_markup=${reply_markup}" \
+ --get \
+ --max-time ${internal_timeout} \
+ --proxy "${internal_proxy}" \
+ --retry 1 \
+ --retry-connrefused \
+ --retry-max-time $((internal_timeout - connrefused_timeout)) \
+ --silent \
+ --user-agent "${useragent}" \
+ "${api_address}/bot${api_token}/sendDocument"
+)"
+then
+ output_text="Failed to send original file"
+
+ log_text="sendDocument (${update_id}): Failed to access Telegram Bot API"
+ source "${units}/log.zsh"
+
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+if ! jq -e '.' <<< "${output_data}" > /dev/null
+then
+ output_text="An unknown error occurred"
+
+ log_text="sendDocument (${update_id}): An unknown error occurred"
+ source "${units}/log.zsh"
+
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+if [[ "$(jq -r '.ok' <<< "${output_data}")" != "true" ]]
+then
+ output_text="Failed to send original file"
+ error_description="$(jq -r '.description' <<< "${output_data}")"
+
+ if [[ "${error_description}" != "null" ]]
+ then
+ log_text="sendDocument (${update_id}): ${error_description}"
+ else
+ log_text="sendDocument (${update_id}): An unknown error occurred"
+ fi
+
+ source "${units}/log.zsh"
+
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+rmdir "${cache}/${ib_hash}.lock"
+exit 0
diff --git a/operators/command_ping.zsh b/operators/command_ping.zsh
new file mode 100644
index 0000000..83ffb3b
--- /dev/null
+++ b/operators/command_ping.zsh
@@ -0,0 +1,112 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+output_text="Measuring..."
+latency_init=$(strftime %s%N)
+
+if ! output_data="$(
+ curl --connect-timeout ${connrefused_timeout} \
+ --data-urlencode "chat_id=${chat_id}" \
+ --data-urlencode "message_thread_id=${message_thread_id}" \
+ --data-urlencode "text=${output_text}" \
+ --data-urlencode "reply_parameters=${reply_parameters}" \
+ --get \
+ --max-time ${internal_timeout} \
+ --proxy "${internal_proxy}" \
+ --retry 1 \
+ --retry-connrefused \
+ --retry-max-time $((internal_timeout - connrefused_timeout)) \
+ --silent \
+ --user-agent "${useragent}" \
+ "${api_address}/bot${api_token}/sendMessage"
+)"
+then
+ output_text="Failed to measure latency"
+
+ log_text="sendMessage (${update_id}): Failed to access Telegram Bot API"
+ source "${units}/log.zsh"
+
+ return
+fi
+
+latency_fin=$(strftime %s%N)
+
+if ! jq -e '.' <<< "${output_data}" > /dev/null
+then
+ output_text="An unknown error occurred"
+
+ log_text="sendMessage (${update_id}): An unknown error occurred"
+ source "${units}/log.zsh"
+
+ return
+fi
+
+if [[ "$(jq -r '.ok' <<< "${output_data}")" != "true" ]]
+then
+ output_text="Failed to measure latency"
+ error_description="$(jq -r '.description' <<< "${output_data}")"
+
+ if [[ "${error_description}" != "null" ]]
+ then
+ log_text="sendMessage (${update_id}): ${error_description}"
+ else
+ log_text="sendMessage (${update_id}): An unknown error occurred"
+ fi
+
+ source "${units}/log.zsh"
+ return
+fi
+
+chat_id="$(jq -r '.result.chat.id' <<< "${output_data}")"
+message_id="$(jq -r '.result.message_id' <<< "${output_data}")"
+
+latency=$(((latency_fin - latency_init) / 1000000))
+output_text="$(printf "<b>Latency:</b> %ums" "${latency}")"
+
+if ! output_data="$(
+ curl --connect-timeout ${connrefused_timeout} \
+ --data-urlencode "chat_id=${chat_id}" \
+ --data-urlencode "message_id=${message_id}" \
+ --data-urlencode "text=${output_text}" \
+ --data-urlencode "parse_mode=HTML" \
+ --get \
+ --max-time ${internal_timeout} \
+ --proxy "${internal_proxy}" \
+ --retry 1 \
+ --retry-connrefused \
+ --retry-max-time $((internal_timeout - connrefused_timeout)) \
+ --silent \
+ --user-agent "${useragent}" \
+ "${api_address}/bot${api_token}/editMessageText"
+)"
+then
+ log_text="editMessageText (${update_id}): Failed to access Telegram Bot API"
+ source "${units}/log.zsh"
+
+ exit 0
+fi
+
+if ! jq -e '.' <<< "${output_data}" > /dev/null
+then
+ log_text="editMessageText (${update_id}): An unknown error occurred"
+ source "${units}/log.zsh"
+
+ exit 0
+fi
+
+if [[ "$(jq -r '.ok' <<< "${output_data}")" != "true" ]]
+then
+ error_description="$(jq -r '.description' <<< "${output_data}")"
+
+ if [[ "${error_description}" != "null" ]]
+ then
+ log_text="editMessageText (${update_id}): ${error_description}"
+ else
+ log_text="editMessageText (${update_id}): An unknown error occurred"
+ fi
+
+ source "${units}/log.zsh"
+fi
+
+exit 0
diff --git a/operators/command_post.zsh b/operators/command_post.zsh
new file mode 100644
index 0000000..2ead112
--- /dev/null
+++ b/operators/command_post.zsh
@@ -0,0 +1,67 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+source "${units}/ib_name.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ return
+fi
+
+source "${units}/ib_common.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ return
+fi
+
+ib_hash="$(sha1sum <<< "${user_id}${ib_board}${ib_query}" | cut -d ' ' -f 1)"
+ib_file="${cache}/${ib_hash}.json"
+
+until mkdir "${cache}/${ib_hash}.lock"
+do
+ sleep 1
+done
+
+source "${units}/ib_file.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+source "${units}/ib_post.zsh"
+
+link_preview_options="$(
+ jq --null-input --compact-output \
+ --arg url "${ib_sample_url}" \
+ '{"url": $url, "prefer_small_media": true, "show_above_text": true}'
+)"
+
+keyboard_text1="Post link"
+keyboard_url1="${ib_url}$(urlencode <<< "${ib_id}")"
+
+reply_markup="$(
+ jq --null-input --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg url1 "${keyboard_url1}" \
+ '{"inline_keyboard": [[{"text": $text1, "url": $url1}]]}'
+)"
+
+if [[ ${#ib_tags} -gt 0 ]]
+then
+ keyboard_text1="Tags (${#ib_tags})"
+ keyboard_data1="tags ${ib_board} ${ib_post_id}"
+
+ reply_markup="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg data1 "${keyboard_data1}" \
+ '.inline_keyboard[0] += [{"text": $text1, "callback_data": $data1}]' \
+ <<< "${reply_markup}"
+ )"
+fi
+
+rmdir "${cache}/${ib_hash}.lock"
diff --git a/operators/command_prpr.zsh b/operators/command_prpr.zsh
new file mode 100644
index 0000000..a6db455
--- /dev/null
+++ b/operators/command_prpr.zsh
@@ -0,0 +1,5 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+output_text="ฅ^•ﻌ•^ฅ"
diff --git a/operators/command_short.zsh b/operators/command_short.zsh
new file mode 100644
index 0000000..cc551e3
--- /dev/null
+++ b/operators/command_short.zsh
@@ -0,0 +1,46 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+if [[ -n "${1}" ]]
+then
+ short_query="${@}"
+ shift ${#}
+else
+ output_text="You must specify query"
+ return
+fi
+
+if [[ ${#short_query} -gt 256 ]]
+then
+ output_text="Query is too long"
+ return
+fi
+
+output_text="<b>Shortcut:</b> <code>$(htmlescape <<< "${short_query}")</code>"
+
+keyboard_text1="Open inline"
+keyboard_query1="${short_query}"
+
+reply_markup="$(
+ jq --null-input --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ '{"inline_keyboard": [[{"text": $text1, "switch_inline_query_current_chat": $query1}]]}'
+)"
+
+short_query="short ${short_query}"
+
+if [[ ${#short_query} -le 64 ]]
+then
+ keyboard_text1="Manage"
+ keyboard_data1="${short_query}"
+
+ reply_markup="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg data1 "${keyboard_data1}" \
+ '.inline_keyboard[0] += [{"text": $text1, "callback_data": $data1}]' \
+ <<< "${reply_markup}"
+ )"
+fi
diff --git a/operators/command_shorts.zsh b/operators/command_shorts.zsh
new file mode 100644
index 0000000..f52b531
--- /dev/null
+++ b/operators/command_shorts.zsh
@@ -0,0 +1,53 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+until mkdir "${user_config}_shorts.lock"
+do
+ sleep 1
+done
+
+shorts_config="${user_config}/shorts"
+
+if [[ -d "${shorts_config}" ]]
+then
+ shorts=($(ls -1 "${shorts_config}"))
+fi
+
+if [[ ${#shorts} -eq 0 ]]
+then
+ output_text="You have no saved shortcuts yet"
+
+ rmdir "${user_config}_shorts.lock"
+ return
+fi
+
+output_text="<b>Saved shortcuts:</b> ${#shorts} / ${shorts_limit}"
+
+keyboard_text1="Open inline"
+keyboard_query1="shorts "
+
+reply_markup="$(
+ jq --null-input --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ --arg text2 "${keyboard_text2}" \
+ --arg data2 "${keyboard_data2}" \
+ '{"inline_keyboard": [[{"text": $text1, "switch_inline_query_current_chat": $query1}]]}'
+)"
+
+if [[ "${chat_id}" == "${target_user}" ]]
+then
+ keyboard_text1="Remove all"
+ keyboard_data1="stop shorts"
+
+ reply_markup="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg data1 "${keyboard_data1}" \
+ '.inline_keyboard[0] += [{"text": $text1, "callback_data": $data1}]' \
+ <<< "${reply_markup}"
+ )"
+fi
+
+rmdir "${user_config}_shorts.lock"
diff --git a/operators/command_source.zsh b/operators/command_source.zsh
new file mode 100644
index 0000000..9553bc6
--- /dev/null
+++ b/operators/command_source.zsh
@@ -0,0 +1,99 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+if [[ -n "${nocommand_source}" ]]
+then
+ exit 0
+fi
+
+until mkdir "${user_config}_source.lock"
+do
+ sleep 1
+done
+
+source "${units}/sn_auth.zsh"
+
+rmdir "${user_config}_source.lock"
+
+if [[ -n "${output_text}" ]]
+then
+ return
+fi
+
+source "${units}/sn_get.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ return
+fi
+
+source_table=(
+ danbooru_id d "Danbooru"
+ gelbooru_id g "Gelbooru"
+ idol_id i "Idol Complex"
+ konachan_id k "Konachan.com"
+ sankaku_id s "Sankaku Channel"
+ yandere_id y "yande.re"
+)
+
+minimum_similarity="$(jq -r ".header.minimum_similarity" <<< "${sn_data}")"
+
+for ((idx = 0; idx >= 0; idx++))
+do
+ similarity="$(jq -r ".results.[${idx}].header.similarity" <<< "${sn_data}")"
+
+ if [[ "${similarity}" == "null" ]]
+ then
+ break
+ fi
+
+ if [[ ${highest_similarity} -lt ${similarity} ]]
+ then
+ highest_similarity=${similarity}
+ highest_index=${idx}
+ fi
+done
+
+if [[ ${highest_similarity} -lt ${minimum_similarity} || ${highest_similarity} -lt 70.00 ]]
+then
+ output_text="No results found"
+ return
+fi
+
+thumbnail="$(jq -r ".results.[${highest_index}].header.thumbnail" <<< "${sn_data}")"
+link_preview_options="$(
+ jq --null-input --compact-output \
+ --arg url "${thumbnail}" \
+ '{"url": $url, "prefer_small_media": true, "show_above_text": true}'
+)"
+
+index_name="$(jq -r ".results.[${highest_index}].header.index_name" <<< "${sn_data}")"
+index_md5="$(cut -d '_' -f 1 <<< "${index_name##* }")"
+
+output_text="$(printf "<b>SauceNAO</b>\n<b>Similarity:</b> %.2f%%" "${highest_similarity}")"
+output_text="$(printf "%s\n<b>MD5:</b> <code>%s</code>" "${output_text}" "${index_md5}")"
+
+while [[ ${#source_table} -ge 3 ]]
+do
+ if ! jq -e ".results.[${highest_index}].data|has(\"${source_table[1]}\")" <<< "${sn_data}" > /dev/null
+ then
+ shift 3 source_table
+ continue
+ fi
+
+ keyboard_text1="${source_table[3]}"
+ keyboard_query1="post ${source_table[2]} ${index_md5}"
+
+ reply_markup="$(
+ jq --compact-output \
+ --argjson offset "$(printf "%u" "${keyboard_offset}")" \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ '.inline_keyboard[$offset] += [{"text": $text1, "switch_inline_query_current_chat": $query1}]' \
+ <<< "${reply_markup:-"{}"}"
+ )"
+
+ keyboard_offset=$((keyboard_offset + 0.5))
+ shift 3 source_table
+done
diff --git a/operators/command_stop.zsh b/operators/command_stop.zsh
new file mode 100644
index 0000000..1ddf10c
--- /dev/null
+++ b/operators/command_stop.zsh
@@ -0,0 +1,24 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+if [[ "${chat_id}" != "${target_user}" ]]
+then
+ output_text="For security reasons you can execute this command only in direct messages"
+ return
+fi
+
+for lock in ${user_locks[@]}
+do
+ until mkdir "${user_config}_${lock}.lock"
+ do
+ sleep 1
+ done
+done
+
+source "${units}/stop.zsh"
+
+for lock in ${user_locks[@]}
+do
+ rmdir "${user_config}_${lock}.lock"
+done
diff --git a/operators/inline_donate.zsh b/operators/inline_donate.zsh
new file mode 100644
index 0000000..a823311
--- /dev/null
+++ b/operators/inline_donate.zsh
@@ -0,0 +1,22 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+if [[ -n "${nocommand_donate}" ]]
+then
+ source "${operators}/inline_help.zsh"
+ return
+fi
+
+output_title="Donate"
+output_text="${donate_content}"
+output_description="Click to send details"
+
+results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg description "${output_description}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text, "parse_mode": "HTML"}, "description": $description}]'
+)"
diff --git a/operators/inline_help.zsh b/operators/inline_help.zsh
new file mode 100644
index 0000000..2ac9455
--- /dev/null
+++ b/operators/inline_help.zsh
@@ -0,0 +1,16 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+output_title="Sekoohaka Bot"
+output_text="${help_content}"
+output_description="Click to send help message"
+
+results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg description "${output_description}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text, "parse_mode": "HTML", "link_preview_options": {"is_disabled": true}}, "description": $description}]'
+)"
diff --git a/operators/inline_none.zsh b/operators/inline_none.zsh
new file mode 100644
index 0000000..c6b0a2e
--- /dev/null
+++ b/operators/inline_none.zsh
@@ -0,0 +1,24 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+until mkdir "${user_config}_shorts.lock"
+do
+ sleep 1
+done
+
+shorts_config="${user_config}/shorts"
+
+if [[ -d "${shorts_config}" ]]
+then
+ shorts=($(ls -1 "${shorts_config}"))
+fi
+
+if [[ ${#shorts} -gt 0 ]]
+then
+ command="shorts"
+else
+ command="help"
+fi
+
+rmdir "${user_config}_shorts.lock"
diff --git a/operators/inline_original.zsh b/operators/inline_original.zsh
new file mode 100644
index 0000000..5e9caac
--- /dev/null
+++ b/operators/inline_original.zsh
@@ -0,0 +1,90 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+ib_lock=0
+source "${units}/ib_common.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg description "${output_text}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text}, "description": $description}]'
+ )"
+
+ return
+fi
+
+ib_hash="$(sha1sum <<< "${user_id}${ib_board}${ib_query}" | cut -d ' ' -f 1)"
+ib_file="${cache}/${ib_hash}.json"
+
+until mkdir "${cache}/${ib_hash}.lock"
+do
+ sleep 1
+done
+
+source "${units}/ib_file.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg description "${output_text}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text}, "description": $description}]'
+ )"
+
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+source "${units}/ib_original.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ output_title="An error occurred"
+
+ results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg description "${output_text}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text}, "description": $description}]'
+ )"
+else
+ output_title="Original file of post ${ib_post_id}"
+ output_description="Click to send original file"
+
+ results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg document_url "${ib_file_url}" \
+ --arg description "${output_description}" \
+ --arg thumbnail_url "${ib_preview_url}" \
+ '[{"type": "document", "id": $id, "title": $title, "document_url": $document_url, "mime_type": "application/zip", "description": $description, "thumbnail_url", $thumbnail_url}]'
+ )"
+fi
+
+if [[ -n "${ib_file_url}" && "${ib_file_url}" != "null" ]]
+then
+ keyboard_text1="Original file link"
+ keyboard_url1="${ib_file_url}"
+
+ results="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg url1 "${keyboard_url1}" \
+ '.[0] += {"reply_markup": {"inline_keyboard": [[{"text": $text1, "url": $url1}]]}}' \
+ <<< "${results}"
+ )"
+fi
+
+rmdir "${cache}/${ib_hash}.lock"
diff --git a/operators/inline_post.zsh b/operators/inline_post.zsh
new file mode 100644
index 0000000..92d7199
--- /dev/null
+++ b/operators/inline_post.zsh
@@ -0,0 +1,82 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+ib_lock=0
+source "${units}/ib_common.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg description "${output_text}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text}, "description": $description}]'
+ )"
+
+ return
+fi
+
+ib_hash="$(sha1sum <<< "${user_id}${ib_board}${ib_query}" | cut -d ' ' -f 1)"
+ib_file="${cache}/${ib_hash}.json"
+
+until mkdir "${cache}/${ib_hash}.lock"
+do
+ sleep 1
+done
+
+source "${units}/ib_file.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg description "${output_text}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text}, "description": $description}]'
+ )"
+
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+source "${units}/ib_post.zsh"
+
+output_title="Information of post ${ib_post_id}"
+output_description="Click to send post information"
+
+keyboard_text1="Post link"
+keyboard_url1="${ib_url}$(urlencode <<< "${ib_id}")"
+
+results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg url "${ib_sample_url}" \
+ --arg text1 "${keyboard_text1}" \
+ --arg url1 "${keyboard_url1}" \
+ --arg description "${output_description}" \
+ --arg thumbnail_url "${ib_preview_url}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text, "parse_mode": "HTML", "link_preview_options": {"url": $url, "prefer_small_media": true, "show_above_text": true}}, "reply_markup": {"inline_keyboard": [[{"text": $text1, "url": $url1}]]}, "description": $description, "thumbnail_url": $thumbnail_url}]'
+)"
+
+if [[ ${#ib_tags} -gt 0 ]]
+then
+ keyboard_text1="Tags (${#ib_tags})"
+ keyboard_data1="tags ${ib_board} ${ib_post_id}"
+
+ results="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg data1 "${keyboard_data1}" \
+ '.[0].reply_markup.inline_keyboard[0] += [{"text": $text1, "callback_data": $data1}]' \
+ <<< "${results}"
+ )"
+fi
+
+rmdir "${cache}/${ib_hash}.lock"
diff --git a/operators/inline_search.zsh b/operators/inline_search.zsh
new file mode 100644
index 0000000..93133fc
--- /dev/null
+++ b/operators/inline_search.zsh
@@ -0,0 +1,151 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+case "${command}" in
+ ("pools" | l)
+ ib_mode="l"
+ inline_options="aq"
+ ;;
+ ("posts" | p)
+ ib_mode="p"
+ inline_options="ampq"
+ ;;
+ ("tags" | t)
+ ib_mode="t"
+ inline_options="aq"
+ ;;
+ (*)
+ set -- ${inline_query[@]}
+ source "${operators}/inline_none.zsh"
+
+ case "${command}" in
+ ("shorts")
+ source "${operators}/inline_shorts.zsh"
+ ;;
+ ("help")
+ source "${operators}/inline_help.zsh"
+ ;;
+ esac
+
+ return
+ ;;
+esac
+
+ib_lock=0
+source "${units}/ib_search.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ keyboard_text1="Resume"
+ keyboard_query1="${command} ${ib_board} ${inline_page}"
+
+ if [[ -n "${search_query}" ]]
+ then
+ keyboard_query1="${keyboard_query1} ${search_query}"
+ fi
+
+ results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ --arg description "${output_text}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text}, "reply_markup": {"inline_keyboard": [[{"text": $text1, "switch_inline_query_current_chat": $query1}]]}, "description": $description}]'
+ )"
+
+ return
+fi
+
+ib_hash="$(sha1sum <<< "${user_id}${ib_mode}${ib_board}${ib_page}${ib_query}" | cut -d ' ' -f 1)"
+ib_file="${cache}/${ib_hash}.json"
+
+until mkdir "${cache}/${ib_hash}.lock"
+do
+ sleep 1
+done
+
+source "${units}/ib_file.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ keyboard_text1="Resume"
+ keyboard_query1="${command} ${ib_board} ${inline_page}"
+
+ if [[ -n "${search_query}" ]]
+ then
+ keyboard_query1="${keyboard_query1} ${search_query}"
+ fi
+
+ results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ --arg description "${output_text}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text}, "reply_markup": {"inline_keyboard": [[{"text": $text1, "switch_inline_query_current_chat": $query1}]]}, "description": $description}]'
+ )"
+
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+ib_ids=("${(@f)$(jq -r ".${ib_iarray}[].${ib_iid}" "${ib_file}")}")
+
+case "${ib_mode}" in
+ (l)
+ ib_created_ats=("${(@f)$(jq -r ".${ib_iarray}[].${ib_icreated}" "${ib_file}")}")
+ ib_pools=("${(@f)$(jq -r ".${ib_iarray}[].${ib_ipool}" "${ib_file}")}")
+ ib_counts=("${(@f)$(jq -r ".${ib_iarray}[].${ib_icount}" "${ib_file}")}")
+ ;;
+ (p)
+ ib_created_ats=("${(@f)$(jq -r ".${ib_iarray}[].${ib_icreated}" "${ib_file}")}")
+ ib_file_sizes=("${(@f)$(jq -r ".${ib_iarray}[].${ib_isize}" "${ib_file}")}")
+ ib_file_urls=("${(@f)$(jq -r ".${ib_iarray}[].${ib_ifile}" "${ib_file}")}")
+ ib_sample_urls=("${(@f)$(jq -r ".${ib_iarray}[].${ib_isample}" "${ib_file}")}")
+ ib_preview_urls=("${(@f)$(jq -r ".${ib_iarray}[].${ib_ipreview}" "${ib_file}")}")
+ ib_widths=("${(@f)$(jq -r ".${ib_iarray}[].${ib_iwidth}" "${ib_file}")}")
+ ib_heights=("${(@f)$(jq -r ".${ib_iarray}[].${ib_iheight}" "${ib_file}")}")
+ ;;
+ (t)
+ ib_tags=("${(@f)$(jq -r ".${ib_iarray}[].${ib_itag}" "${ib_file}")}")
+ ib_counts=("${(@f)$(jq -r ".${ib_iarray}[].${ib_icount}" "${ib_file}")}")
+ ;;
+esac
+
+for ((idx = 1; idx > 0; idx++))
+do
+ ib_id="${ib_ids[idx]}"
+
+ if [[ -z "${ib_id}" || "${ib_id}" == "null" ]]
+ then
+ break
+ fi
+
+ case "${ib_mode}" in
+ (l)
+ source "${operators}/inline_search_pools.zsh"
+ ;;
+ (p)
+ source "${operators}/inline_search_posts.zsh"
+ ;;
+ (t)
+ source "${operators}/inline_search_tags.zsh"
+ ;;
+ esac
+
+ results+=(${result})
+done
+
+results="$(jq -sc <<< ${results[@]})"
+
+if [[ -n "${ib_autopaging}" ]]
+then
+ next_offset=$((inline_page + 1))
+fi
+
+rmdir "${cache}/${ib_hash}.lock"
diff --git a/operators/inline_search_pools.zsh b/operators/inline_search_pools.zsh
new file mode 100644
index 0000000..476dd9a
--- /dev/null
+++ b/operators/inline_search_pools.zsh
@@ -0,0 +1,74 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+ib_created_at="${ib_created_ats[idx]}"
+ib_pool="${ib_pools[idx]}"
+ib_count="${ib_counts[idx]}"
+
+unset ib_date_text
+source "${units}/ib_date.zsh"
+
+if [[ -n "${ib_ispace}" ]]
+then
+ ib_pool="$(tr "${ib_ispace}" ' ' <<< "${ib_pool}")"
+fi
+
+if [[ ${#ib_pool} -gt 1024 ]]
+then
+ ib_pool="$(cut -c 1-1024 <<< "${ib_pool}")..."
+fi
+
+output_title="${ib_pool:- }"
+output_text="$(printf "<b>%s</b>\n<b>ID:</b> <code>%s</code>" "${ib_name}" "${ib_id}")"
+
+if [[ -n "${ib_date_text}" ]]
+then
+ output_text="$(printf "%s\n<b>Date:</b> <code>%s</code>" "${output_text}" "${ib_date_text}")"
+fi
+
+output_text="$(printf "%s\n<b>Name:</b> %s" "${output_text}" "$(htmlescape <<< "${ib_pool}")")"
+output_text="$(printf "%s\n<b>Post count:</b> %s" "${output_text}" "${ib_count}")"
+output_description="Click to send pool information"
+
+keyboard_text1="Pool link"
+keyboard_url1="${ib_url}$(urlencode <<< "${ib_id}")"
+keyboard_text2="Resume"
+keyboard_query2="${command} ${ib_board} ${inline_page}"
+
+if [[ -n "${search_query}" ]]
+then
+ keyboard_query2="${keyboard_query2} ${search_query}"
+fi
+
+result="$(
+ jq --null-input --compact-output \
+ --arg id "${ib_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg description "${output_description}" \
+ --arg text1 "${keyboard_text1}" \
+ --arg url1 "${keyboard_url1}" \
+ --arg text2 "${keyboard_text2}" \
+ --arg query2 "${keyboard_query2}" \
+ '{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text, "parse_mode": "HTML"}, "description": $description, "reply_markup": {"inline_keyboard": [[{"text": $text1, "url": $url1}, {"text": $text2, "switch_inline_query_current_chat": $query2}]]}}'
+)"
+
+if [[ -n "${ib_quick}" ]]
+then
+ keyboard_text1="Search posts"
+ keyboard_query1="posts ${ib_board} pool:${ib_id}"
+
+ if [[ -n "${ib_iorder}" ]]
+ then
+ keyboard_query1="${keyboard_query1} ${ib_iorder}"
+ fi
+
+ result="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ '.reply_markup.inline_keyboard += [[{"text": $text1, "switch_inline_query_current_chat": $query1}]]' \
+ <<< "${result}"
+ )"
+fi
diff --git a/operators/inline_search_posts.zsh b/operators/inline_search_posts.zsh
new file mode 100644
index 0000000..1af741d
--- /dev/null
+++ b/operators/inline_search_posts.zsh
@@ -0,0 +1,134 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+ib_created_at="${ib_created_ats[idx]}"
+ib_file_size="${ib_file_sizes[idx]}"
+ib_file_url="${ib_file_urls[idx]}"
+ib_sample_url="${ib_sample_urls[idx]}"
+ib_preview_url="${ib_preview_urls[idx]}"
+ib_width="${ib_widths[idx]}"
+ib_height="${ib_heights[idx]}"
+
+unset ib_date_text ib_resolution_text ib_type_text ib_size_text
+source "${units}/ib_date.zsh"
+source "${units}/ib_meta.zsh"
+
+if [[ -n "${ib_preview}" ]]
+then
+ case "${ib_type}" in
+ (gif | video)
+ ib_sample_url="${ib_preview_url}"
+ ib_type="photo"
+ ;;
+ esac
+fi
+
+output_text="$(printf "<b>%s</b>\n<b>ID:</b> <code>%s</code>" "${ib_name}" "${ib_id}")"
+
+if [[ -n "${ib_date_text}" ]]
+then
+ output_text="$(printf "%s\n<b>Date:</b> <code>%s</code>" "${output_text}" "${ib_date_text}")"
+fi
+
+if [[ -n "${ib_metadata}" ]]
+then
+ if [[ -n "${ib_resolution_text}" ]]
+ then
+ output_text="$(printf "%s\n<b>Resolution:</b> %s" "${output_text}" "${ib_resolution_text}")"
+ fi
+
+ if [[ -n "${ib_size_text}" ]]
+ then
+ output_text="$(printf "%s\n<b>Size:</b> %s" "${output_text}" "${ib_size_text}")"
+ fi
+
+ if [[ -n "${ib_type_text}" ]]
+ then
+ output_text="$(printf "%s\n<b>Type:</b> %s" "${output_text}" "${ib_type_text}")"
+ fi
+fi
+
+keyboard_text1="Post link"
+keyboard_url1="${ib_url}$(urlencode <<< "${ib_id}")"
+keyboard_text2="Resume"
+keyboard_query2="${command} ${ib_board} ${inline_page}"
+
+if [[ -n "${search_query}" ]]
+then
+ keyboard_query2="${keyboard_query2} ${search_query}"
+fi
+
+case "${ib_type}" in
+ (photo)
+ result="$(
+ jq --null-input --compact-output \
+ --arg id "${ib_id}" \
+ --arg photo_url "${ib_sample_url}" \
+ --arg thumbnail_url "${ib_preview_url}" \
+ --arg photo_width "${ib_width}" \
+ --arg photo_height "${ib_height}" \
+ --arg caption "${output_text}" \
+ --arg text1 "${keyboard_text1}" \
+ --arg url1 "${keyboard_url1}" \
+ --arg text2 "${keyboard_text2}" \
+ --arg query2 "${keyboard_query2}" \
+ '{"type": "photo", "id": $id, "photo_url": $photo_url, "thumbnail_url": $thumbnail_url, "photo_width": $photo_width, "photo_height": $photo_height, "caption": $caption, "parse_mode": "HTML", "reply_markup": {"inline_keyboard": [[{"text": $text1, "url": $url1}, {"text": $text2, "switch_inline_query_current_chat": $query2}]]}}'
+ )"
+ ;;
+ (gif)
+ result="$(
+ jq --null-input --compact-output \
+ --arg id "${ib_id}" \
+ --arg gif_url "${ib_file_url}" \
+ --arg thumbnail_url "${ib_preview_url}" \
+ --arg gif_width "${ib_width}" \
+ --arg gif_height "${ib_height}" \
+ --arg caption "${output_text}" \
+ --arg text1 "${keyboard_text1}" \
+ --arg url1 "${keyboard_url1}" \
+ --arg text2 "${keyboard_text2}" \
+ --arg query2 "${keyboard_query2}" \
+ '{"type": "gif", "id": $id, "gif_url": $gif_url, "thumbnail_url": $thumbnail_url, "gif_width": $gif_width, "gif_height": $gif_height, "caption": $caption, "parse_mode": "HTML", "reply_markup": {"inline_keyboard": [[{"text": $text1, "url": $url1}, {"text": $text2, "switch_inline_query_current_chat": $query2}]]}}'
+ )"
+ ;;
+ (video)
+ result="$(
+ jq --null-input --compact-output \
+ --arg id "${ib_id}" \
+ --arg mpeg4_url "${ib_file_url}" \
+ --arg thumbnail_url "${ib_preview_url}" \
+ --arg mpeg4_width "${ib_width}" \
+ --arg mpeg4_height "${ib_height}" \
+ --arg caption "${output_text}" \
+ --arg text1 "${keyboard_text1}" \
+ --arg url1 "${keyboard_url1}" \
+ --arg text2 "${keyboard_text2}" \
+ --arg query2 "${keyboard_query2}" \
+ '{"type": "mpeg4_gif", "id": $id, "mpeg4_url": $mpeg4_url, "thumbnail_url": $thumbnail_url, "mpeg4_width": $mpeg4_width, "mpeg4_height": $mpeg4_height, "caption": $caption, "parse_mode": "HTML", "reply_markup": {"inline_keyboard": [[{"text": $text1, "url": $url1}, {"text": $text2, "switch_inline_query_current_chat": $query2}]]}}'
+ )"
+ ;;
+esac
+
+if [[ -n "${ib_quick}" ]]
+then
+ keyboard_text1="Original"
+ keyboard_query1="original ${ib_board} ${ib_id}"
+ keyboard_text2="Post"
+ keyboard_query2="post ${ib_board} ${ib_id}"
+
+ result="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ --arg text2 "${keyboard_text2}" \
+ --arg query2 "${keyboard_query2}" \
+ '.reply_markup.inline_keyboard += [[{"text": $text1, "switch_inline_query_current_chat": $query1}, {"text": $text2, "switch_inline_query_current_chat": $query2}]]' \
+ <<< "${result}"
+ )"
+fi
+
+if [[ "${cache_mode}" == "advanced" ]]
+then
+ source "${units}/ib_cache.zsh" &
+fi
diff --git a/operators/inline_search_tags.zsh b/operators/inline_search_tags.zsh
new file mode 100644
index 0000000..aea0184
--- /dev/null
+++ b/operators/inline_search_tags.zsh
@@ -0,0 +1,59 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+ib_tag="${ib_tags[idx]}"
+ib_count="${ib_counts[idx]}"
+
+if [[ -n "${ib_irecode}" ]] && ib_tag_recode="$(recode ${ib_irecode}..ASCII <<< "${ib_tag}")"
+then
+ ib_tag="${ib_tag_recode}"
+fi
+
+if [[ ${#ib_tag} -gt 1024 ]]
+then
+ ib_tag="$(cut -c 1-1024 <<< "${ib_tag}")..."
+fi
+
+output_title="${ib_tag:- }"
+output_text="$(printf "<b>%s</b>\n<b>ID:</b> <code>%s</code>" "${ib_name}" "${ib_id}")"
+output_text="$(printf "%s\n<b>Name:</b> <code>%s</code>" "${output_text}" "$(htmlescape <<< "${ib_tag}")")"
+output_text="$(printf "%s\n<b>Post count:</b> %s" "${output_text}" "${ib_count}")"
+output_description="Click to send tag information"
+
+keyboard_text1="Wiki link"
+keyboard_url1="${ib_url}$(urlencode <<< "${ib_tag}")"
+keyboard_text2="Resume"
+keyboard_query2="${command} ${ib_board} ${inline_page}"
+
+if [[ -n "${search_query}" ]]
+then
+ keyboard_query2="${keyboard_query2} ${search_query}"
+fi
+
+result="$(
+ jq --null-input --compact-output \
+ --arg id "${ib_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg description "${output_description}" \
+ --arg text1 "${keyboard_text1}" \
+ --arg url1 "${keyboard_url1}" \
+ --arg text2 "${keyboard_text2}" \
+ --arg query2 "${keyboard_query2}" \
+ '{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text, "parse_mode": "HTML"}, "description": $description, "reply_markup": {"inline_keyboard": [[{"text": $text1, "url": $url1}, {"text": $text2, "switch_inline_query_current_chat": $query2}]]}}'
+)"
+
+if [[ -n "${ib_quick}" ]]
+then
+ keyboard_text1="Search posts"
+ keyboard_query1="posts ${ib_board} ${ib_tag}"
+
+ result="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ '.reply_markup.inline_keyboard += [[{"text": $text1, "switch_inline_query_current_chat": $query1}]]' \
+ <<< "${result}"
+ )"
+fi
diff --git a/operators/inline_short.zsh b/operators/inline_short.zsh
new file mode 100644
index 0000000..84dd3d4
--- /dev/null
+++ b/operators/inline_short.zsh
@@ -0,0 +1,57 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+if [[ -n "${1}" ]]
+then
+ short_query="${@}"
+ shift ${#}
+else
+ output_title="Invalid arguments"
+ output_text="You must specify query"
+
+ results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg description "${output_text}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text}, "description": $description}]'
+ )"
+
+ return
+fi
+
+output_title="Shortcut"
+output_text="<b>Shortcut:</b> <code>$(htmlescape <<< "${short_query}")</code>"
+output_description="${short_query}"
+
+keyboard_text1="Open inline"
+keyboard_query1="${short_query}"
+
+results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ --arg description "${output_description}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text, "parse_mode": "HTML"}, "reply_markup": {"inline_keyboard": [[{"text": $text1, "switch_inline_query_current_chat": $query1}]]}, "description": $description}]'
+)"
+
+short_query="short ${short_query}"
+
+if [[ ${#short_query} -le 64 ]]
+then
+ keyboard_text1="Manage"
+ keyboard_data1="${short_query}"
+
+ results="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg data1 "${keyboard_data1}" \
+ '.[0].reply_markup.inline_keyboard[0] += [{"text": $text1, "callback_data": $data1}]' \
+ <<< "${results}"
+ )"
+fi
diff --git a/operators/inline_shorts.zsh b/operators/inline_shorts.zsh
new file mode 100644
index 0000000..ea40e95
--- /dev/null
+++ b/operators/inline_shorts.zsh
@@ -0,0 +1,112 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+until mkdir "${user_config}_shorts.lock"
+do
+ sleep 1
+done
+
+shorts_config="${user_config}/shorts"
+source "${units}/shorts_search.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ results="$(
+ jq --null-input --compact-output \
+ --arg id "${query_id}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg description "${output_text}" \
+ '[{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text}, "description": $description}]'
+ )"
+
+ if [[ "${chat_type}" == "sender" ]]
+ then
+ keyboard_text1="Resume"
+ keyboard_query1="${command} ${inline_page}"
+
+ if [[ -n "${search_query}" ]]
+ then
+ keyboard_query1="${keyboard_query1} ${search_query}"
+ fi
+
+ results="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ '.[0] += {"reply_markup": {"inline_keyboard": [[{"text": $text1, "switch_inline_query_current_chat": $query1}]]}}' \
+ <<< "${results}"
+ )"
+ fi
+
+ rmdir "${user_config}_shorts.lock"
+ return
+fi
+
+for ((idx = 1; idx <= ${inline_limit} && idx <= ${#shorts}; idx++))
+do
+ short_query="$(< "${shorts_config}/${shorts[idx]}")"
+
+ output_title="Shortcut"
+ output_text="<b>Shortcut:</b> <code>$(htmlescape <<< "${short_query}")</code>"
+ output_description="${short_query}"
+
+ keyboard_text1="Open inline"
+ keyboard_query1="${short_query}"
+
+ result="$(
+ jq --null-input --compact-output \
+ --arg id "${shorts[idx]}" \
+ --arg title "${output_title}" \
+ --arg text "${output_text}" \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ --arg description "${output_description}" \
+ '{"type": "article", "id": $id, "title": $title, "input_message_content": {"message_text": $text, "parse_mode": "HTML"}, "reply_markup": {"inline_keyboard": [[{"text": $text1, "switch_inline_query_current_chat": $query1}]]}, "description": $description}'
+ )"
+
+ if [[ "${chat_type}" == "sender" ]]
+ then
+ keyboard_text1="Resume"
+ keyboard_query1="${command} ${inline_page}"
+
+ if [[ -n "${search_query}" ]]
+ then
+ keyboard_query1="${keyboard_query1} ${search_query}"
+ fi
+
+ result="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg query1 "${keyboard_query1}" \
+ '.reply_markup.inline_keyboard[0] += [{"text": $text1, "switch_inline_query_current_chat": $query1}]' \
+ <<< "${result}"
+ )"
+ fi
+
+ if [[ -n "${shorts_quick}" ]]
+ then
+ keyboard_text1="Manage"
+ keyboard_data1="short ${short_query}"
+
+ result="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg data1 "${keyboard_data1}" \
+ '.reply_markup.inline_keyboard += [[{"text": $text1, "callback_data": $data1}]]' \
+ <<< "${result}"
+ )"
+ fi
+
+ results+=(${result})
+done
+
+results="$(jq -sc <<< "${results[@]}")"
+
+if [[ -n "${shorts_autopaging}" ]]
+then
+ next_offset=$((inline_page + 1))
+fi
+
+rmdir "${user_config}_shorts.lock"
diff --git a/operators/timer_uncache.zsh b/operators/timer_uncache.zsh
new file mode 100644
index 0000000..4af2543
--- /dev/null
+++ b/operators/timer_uncache.zsh
@@ -0,0 +1,26 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+if [[ -n "${no_clear}" ]]
+then
+ return
+fi
+
+for file in $(find "${cache}" -follow -type f)
+do
+ if ! mkdir "${file%.*}.lock"
+ then
+ continue
+ fi
+
+ file_ctime=$(strftime %s)
+ file_mtime=$(stat +mtime "${file}")
+
+ if [[ $((file_ctime - file_mtime)) -gt ${cache_time} ]]
+ then
+ rm -f "${file}"
+ fi
+
+ rmdir "${file%.*}.lock"
+done
diff --git a/operators/timer_unlock.zsh b/operators/timer_unlock.zsh
new file mode 100644
index 0000000..9736116
--- /dev/null
+++ b/operators/timer_unlock.zsh
@@ -0,0 +1,14 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+for dir in $(find "${cache}" "${users}" -follow -name "*.lock")
+do
+ dir_ctime=$(strftime %s)
+ dir_mtime=$(stat +mtime "${dir}")
+
+ if [[ $((dir_ctime - dir_mtime)) -gt 30 ]]
+ then
+ rmdir "${dir}"
+ fi
+done
diff --git a/operators/url_parser.zsh b/operators/url_parser.zsh
new file mode 100644
index 0000000..b24c1bf
--- /dev/null
+++ b/operators/url_parser.zsh
@@ -0,0 +1,125 @@
+# Copyright (C) 2024-2026 Maria Lisina
+# Copyright (C) 2024-2026 Danil Lisin
+# SPDX-License-Identifier: Apache-2.0
+
+url_table=(
+ a / 5 "https://safebooru.donmai.us/posts/.*"
+ a / 3 "safebooru.donmai.us/posts/.*"
+ d / 5 "https://danbooru.donmai.us/posts/.*"
+ d / 3 "danbooru.donmai.us/posts/.*"
+ g = 4 "https://gelbooru.com/index.php?page=post&s=view&id=.*"
+ g = 4 "gelbooru.com/index.php?page=post&s=view&id=.*"
+ i / 6 "https://www.idolcomplex.com/.*/posts/.*"
+ i / 4 "www.idolcomplex.com/.*/posts/.*"
+ i / 4 "idolcomplex.com/.*/posts/.*"
+ i / 5 "https://www.idolcomplex.com/posts/.*"
+ i / 3 "www.idolcomplex.com/posts/.*"
+ i / 3 "idolcomplex.com/posts/.*"
+ k / 6 "https://konachan.com/post/show/.*"
+ k / 4 "konachan.com/post/show/.*"
+ s / 6 "https://www.sankakucomplex.com/.*/posts/.*"
+ s / 4 "www.sankakucomplex.com/.*/posts/.*"
+ i / 4 "sankakucomplex.com/.*/posts/.*"
+ s / 5 "https://www.sankakucomplex.com/posts/.*"
+ s / 3 "www.sankakucomplex.com/posts/.*"
+ i / 3 "sankakucomplex.com/posts/.*"
+ y / 6 "https://yande.re/post/show/.*"
+ y / 4 "yande.re/post/show/.*"
+)
+
+ib_mode="p"
+
+while [[ ${#url_table} -ge 4 ]]
+do
+ if grep -qx "${url_table[4]}" <<< ${command}
+ then
+ ib_board="${url_table[1]}"
+ ib_post_id="$(cut -d ${url_table[2]} -f ${url_table[3]} <<< ${command} | cut -d '?' -f 1)"
+
+ break
+ fi
+
+ shift 4 url_table
+done
+
+if [[ -z "${ib_post_id}" ]]
+then
+ exit 0
+fi
+
+source "${units}/ib_config.zsh"
+source "${units}/ib_authconfig.zsh"
+
+if [[ ${#ib_post_id} -gt 32 ]]
+then
+ exit 0
+elif [[ ${#ib_post_id} -eq 32 ]]
+then
+ ib_query="md5:${ib_post_id}"
+else
+ ib_query="id:${ib_post_id}"
+fi
+
+ib_hash="$(sha1sum <<< "${user_id}${ib_board}${ib_query}" | cut -d ' ' -f 1)"
+ib_file="${cache}/${ib_hash}.json"
+
+until mkdir "${cache}/${ib_hash}.lock"
+do
+ sleep 1
+done
+
+source "${units}/ib_file.zsh"
+
+if [[ -n "${output_text}" ]]
+then
+ keyboard_text1="Delete"
+ keyboard_data1="delete"
+
+ reply_markup="$(
+ jq --null-input --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg data1 "${keyboard_data1}" \
+ '{"inline_keyboard": [[{"text": $text1, "callback_data": $data1}]]}'
+ )"
+
+ rmdir "${cache}/${ib_hash}.lock"
+ return
+fi
+
+source "${units}/ib_post.zsh"
+
+link_preview_options="$(
+ jq --null-input --compact-output \
+ --arg url "${ib_sample_url}" \
+ '{"url": $url, "prefer_small_media": true, "show_above_text": true}'
+)"
+
+keyboard_text1="Post link"
+keyboard_url1="${ib_url}$(urlencode <<< "${ib_id}")"
+keyboard_text2="Delete"
+keyboard_data2="delete"
+
+reply_markup="$(
+ jq --null-input --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg url1 "${keyboard_url1}" \
+ --arg text2 "${keyboard_text2}" \
+ --arg data2 "${keyboard_data2}" \
+ '{"inline_keyboard": [[{"text": $text1, "url": $url1}], [{"text": $text2, "callback_data": $data2}]]}'
+)"
+
+if [[ ${#ib_tags} -gt 0 ]]
+then
+ keyboard_text1="Tags (${#ib_tags})"
+ keyboard_data1="tags ${ib_board} ${ib_post_id}"
+
+ reply_markup="$(
+ jq --compact-output \
+ --arg text1 "${keyboard_text1}" \
+ --arg data1 "${keyboard_data1}" \
+ '.inline_keyboard[0] += [{"text": $text1, "callback_data": $data1}]' \
+ <<< "${reply_markup}"
+ )"
+fi
+
+rmdir "${cache}/${ib_hash}.lock"