diff --git a/lib/save_it/bot.ex b/lib/save_it/bot.ex index 2c6feb1..e9f20d7 100644 --- a/lib/save_it/bot.ex +++ b/lib/save_it/bot.ex @@ -25,14 +25,17 @@ defmodule SaveIt.Bot do setup_commands: true command("start") + command("search", description: "Search photos") command("similar", description: "Find similar photos") - command("about", description: "About the bot") + command("delete", description: "Delete message") command("login", description: "Login") command("code", description: "Get code for login") command("folder", description: "Update Google Drive folder ID") + command("about", description: "Know more about this bot") + middleware(ExGram.Middleware.IgnoreUsername) def bot(), do: @bot @@ -43,11 +46,11 @@ defmodule SaveIt.Bot do def handle({:command, :about, _msg}, context) do answer(context, """ - This bot is created by @ThaddeusJiang, feel free to contact me if you have any questions. + SaveIt can download images and videos, just give me a link. - GitHub: https://github.com/ThaddeusJiang - Blog: https://thaddeusjiang.com - X: https://x.com/thaddeusjiang + Created by @ThaddeusJiang, powered by Cobalt, Typesense, and Elixir.Access + + Give a star ⭐ if you like it, https://github.com/ThaddeusJiang/save_it """) end @@ -137,7 +140,25 @@ defmodule SaveIt.Bot do end def handle({:command, :similar, %{chat: chat, photo: nil}}, _context) do - send_message(chat.id, "Upload a photo to find similar photos.") + send_message(chat.id, "Upload a photo with /similar for finding similar photos.") + end + + def handle({:command, :delete, %{chat: chat, reply_to_message: nil}}, _ctx) do + send_message(chat.id, "reply a message with /delete command.") + end + + def handle( + {:command, :delete, + %{chat: chat, message_id: message_id, from: from, reply_to_message: reply_to_message}}, + _ctx + ) do + {:ok, %{id: bot_id, username: bot_username}} = ExGram.get_me() + + if Enum.member?([bot_id, from.id], reply_to_message.from.id) do + handle_delete_command(chat.id, message_id, reply_to_message) + else + send_message(chat.id, "Only delete messages from @#{bot_username} and yourself.") + end end # caption: nil -> find same photos @@ -467,4 +488,23 @@ defmodule SaveIt.Bot do """) end end + + defp handle_delete_command(chat_id, message_id, reply_to_message) do + case reply_to_message do + %{photo: nil} -> + delete_message(chat_id, reply_to_message.message_id) + + %{photo: photo} -> + photo + |> Enum.map(& &1.file_id) + |> PhotoService.delete_photos() + + delete_message(chat_id, reply_to_message.message_id) + + _ -> + send_message(chat_id, "reply a message with /delete command.") + end + + delete_message(chat_id, message_id) + end end diff --git a/lib/save_it/migration/typesense.ex b/lib/save_it/migration/typesense.ex index 9ad8a9d..0097c95 100644 --- a/lib/save_it/migration/typesense.ex +++ b/lib/save_it/migration/typesense.ex @@ -24,11 +24,11 @@ defmodule SaveIt.Migration.Typesense do Typesense.handle_response!(res) end - def delete_collection!(collection_name) do + def delete_collection(collection_name) do req = build_request("/collections/#{collection_name}") - res = Req.delete!(req) + res = Req.delete(req) - Typesense.handle_response!(res) + Typesense.handle_response(res) end defp get_env() do diff --git a/lib/save_it/migration/typesense/photo.ex b/lib/save_it/migration/typesense/photo.ex index a401728..1f94701 100644 --- a/lib/save_it/migration/typesense/photo.ex +++ b/lib/save_it/migration/typesense/photo.ex @@ -37,7 +37,8 @@ defmodule SaveIt.Migration.Typesense.Photo do Typesense.update_collection!(@collection_name, %{ "fields" => [ - %{"name" => "file_id", "type" => "string", "optional" => true} + %{"name" => "file_id", "type" => "string", "optional" => true}, + %{"name" => "url", "drop" => true} ] }) end @@ -66,12 +67,12 @@ defmodule SaveIt.Migration.Typesense.Photo do Logger.info("migrated #{count} photos") end - def drop_photos!() do - Typesense.delete_collection!(@collection_name) + def drop_photos() do + Typesense.delete_collection(@collection_name) end def reset!() do - drop_photos!() + drop_photos() create_photos_20241024!() migrate_photos_20241029!() end diff --git a/lib/save_it/photo_service.ex b/lib/save_it/photo_service.ex index 8331aca..fda9767 100644 --- a/lib/save_it/photo_service.ex +++ b/lib/save_it/photo_service.ex @@ -36,6 +36,14 @@ defmodule SaveIt.PhotoService do end end + def delete_photo(file_id) do + Typesense.delete_document_by_query("photos", "file_id:[#{file_id}]") + end + + def delete_photos(file_ids) do + Typesense.delete_document_by_query("photos", "file_id:[#{Enum.join(file_ids, ",")}]") + end + def get_photo(photo_id) do Typesense.get_document("photos", photo_id) end diff --git a/lib/small_sdk/typesense.ex b/lib/small_sdk/typesense.ex index 05ab2bc..a18e3dd 100644 --- a/lib/small_sdk/typesense.ex +++ b/lib/small_sdk/typesense.ex @@ -63,13 +63,23 @@ defmodule SmallSdk.Typesense do handle_response(res) end - def delete_document!(collection_name, document_id) do + def delete_document(collection_name, document_id) do req = build_request("/collections/#{collection_name}/documents/#{document_id}") res = Req.delete(req) handle_response(res) end + # docs: https://typesense.org/docs/27.1/api/documents.html#delete-documents + def delete_document_by_query(collection_name, filter_by, opts \\ []) do + batch_size = Keyword.get(opts, :batch_size, 100) + + req = build_request("/collections/#{collection_name}/documents") + + Req.delete(req, params: %{"filter_by" => filter_by, "batch_size" => batch_size}) + |> handle_response() + end + def create_search_key() do {url, _} = get_env() req = build_request("/keys") diff --git a/lib/small_sdk/web_downloader.ex b/lib/small_sdk/web_downloader.ex index 096bd27..2179875 100644 --- a/lib/small_sdk/web_downloader.ex +++ b/lib/small_sdk/web_downloader.ex @@ -1,10 +1,7 @@ defmodule SmallSdk.WebDownloader do require Logger - # FIXME:TODAY return {:ok, file_name, file_content} | {:error, reason} def download_files(urls) do - Logger.info("download_files started, urls: #{inspect(urls)}") - res = urls |> Enum.map(&download_file/1) @@ -13,7 +10,10 @@ defmodule SmallSdk.WebDownloader do {:error, reason}, _ -> {:halt, {:error, reason}} end) - {:ok, res} + case res do + {:error, reason} -> {:error, reason} + files -> {:ok, files} + end end # TODO: have to handle Stream data diff --git a/priv/typesense/2024-10-29_photos_url_to_file_id.ex b/priv/typesense/2024-10-29_photos_url_to_file_id.ex index 1358964..90e938c 100644 --- a/priv/typesense/2024-10-29_photos_url_to_file_id.ex +++ b/priv/typesense/2024-10-29_photos_url_to_file_id.ex @@ -1 +1,2 @@ +# mix run priv/typesense/2024-10-29_photos_url_to_file_id.ex SaveIt.Migration.Typesense.Photo.migrate_photos_20241029!()