Skip to content

Commit

Permalink
create & update notes
Browse files Browse the repository at this point in the history
  • Loading branch information
ThaddeusJiang committed Oct 25, 2024
1 parent 256dde8 commit c9bacc8
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 66 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ data

# scripts
_local*
_stag*
_dev*
_stag*
_prod*
nohup.out
2 changes: 0 additions & 2 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,3 @@ config :save_it, :typesense_api_key, System.get_env("TYPESENSE_API_KEY", "xyz")
# optional
config :save_it, :google_oauth_client_id, System.get_env("GOOGLE_OAUTH_CLIENT_ID")
config :save_it, :google_oauth_client_secret, System.get_env("GOOGLE_OAUTH_CLIENT_SECRET")

config :save_it, :web_url, System.get_env("WEB_URL", "http://localhost:4000")
1 change: 1 addition & 0 deletions lib/migration/typesense/note.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule Migration.Typesense.Note do
# note: 抉择:这个 app 核心是给予图片的视觉笔记,暂时不考虑单独 text 的笔记
# %{"name" => "photo_id", "type" => "string"},
# note: 既然不能实现 RDB reference,那么就直接存储 file_id
%{"name" => "message_id", "type" => "string"},
%{"name" => "file_id", "type" => "string"},
%{"name" => "belongs_to_id", "type" => "string"},
%{"name" => "inserted_at", "type" => "int64"},
Expand Down
6 changes: 1 addition & 5 deletions lib/migration/typesense/photo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,8 @@ defmodule Migration.Typesense.Photo do
}
},
%{"name" => "caption", "type" => "string", "optional" => true, "facet" => false},
# "telegram://<bot_id>/<file_id>"
# TODO: 不能再简单的 reset 了,reset 会导致数据丢失,应该合理 migrate 数据
%{"name" => "url", "type" => "string"},
# chat.id -> string
%{"name" => "file_id", "type" => "string"},
%{"name" => "belongs_to_id", "type" => "string"},
# unix timestamp
%{"name" => "inserted_at", "type" => "int64"}
],
"default_sorting_field" => "inserted_at"
Expand Down
68 changes: 29 additions & 39 deletions lib/save_it/bot.ex
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,11 @@ defmodule SaveIt.Bot do
# end

def handle(
{:command, :note, %{chat: chat, text: text, reply_to_message: reply_to_message}},
{:command, :note,
%{message_id: message_id, chat: chat, text: text, reply_to_message: reply_to_message}},
_context
)
when is_binary(text) do
Logger.debug("photo: #{inspect(reply_to_message.photo)}")

file_id = reply_to_message.photo |> List.last() |> Map.get(:file_id)

case file_id do
Expand All @@ -160,18 +159,17 @@ defmodule SaveIt.Bot do
send_message(chat.id, "What note do you want to add?")

note_content ->
# photo_url = photo_url(chat.id, file_id)
# PhotoService.update_photo!(photo_url, %{"note" => text})

note =
NoteService.create_note!(%{
content: note_content,
message_id: message_id,
file_id: file_id,
belongs_to_id: chat.id
})

case note do
nil -> send_message(chat.id, "Failed to add note.")
# TODO:nice_to_have: 添加一个 emoji 即可
_ -> send_message(chat.id, "Note added successfully.")
end
end
Expand All @@ -187,20 +185,19 @@ defmodule SaveIt.Bot do
# end

# caption: nil -> find same photos
def handle({:message, %{chat: chat, caption: nil, photo: photos}}, ctx) do
def handle({:message, %{chat: chat, caption: nil, photo: photos}}, _ctx) do
photo = List.last(photos)

file = ExGram.get_file!(photo.file_id)
photo_file_content = Telegram.download_file_content!(file.file_path)

bot_id = ctx.bot_info.id
chat_id = chat.id

typesense_photo =
PhotoService.create_photo!(%{
image: Base.encode64(photo_file_content),
caption: "",
url: photo_url(bot_id, file.file_id),
file_id: file.file_id,
belongs_to_id: chat_id
})

Expand All @@ -218,13 +215,12 @@ defmodule SaveIt.Bot do
end

# caption: contains /similar or /search -> search similar photos; otherwise, find same photos
def handle({:message, %{chat: chat, caption: caption, photo: photos}}, ctx) do
def handle({:message, %{chat: chat, caption: caption, photo: photos}}, _ctx) do
photo = List.last(photos)

file = ExGram.get_file!(photo.file_id)
photo_file_content = Telegram.download_file_content!(file.file_path)

bot_id = ctx.bot_info.id
chat_id = chat.id

caption =
Expand All @@ -238,7 +234,7 @@ defmodule SaveIt.Bot do
PhotoService.create_photo!(%{
image: Base.encode64(photo_file_content),
caption: caption,
url: photo_url(bot_id, file.file_id),
file_id: file.file_id,
belongs_to_id: chat_id
})

Expand Down Expand Up @@ -367,9 +363,24 @@ defmodule SaveIt.Bot do
{:ok, nil}
end

def handle({:edited_message, _msg}, _context) do
Logger.warning("this is an edited message, ignore it")
{:ok, nil}
def handle({:edited_message, msg}, _context) do
%{message_id: message_id, chat: chat, text: text} = msg

edited_note_text =
case Regex.run(~r/\/note\s+(.*)/, text) do
[_, edited_note_text] -> edited_note_text
_ -> nil
end

case String.contains?(text, "/note") do
true ->
note = NoteService.get_note!(message_id, chat.id)

NoteService.update_note!(note["id"], %{"content" => edited_note_text})

false ->
Logger.debug("edited message: #{inspect(_msg)}")
end
end

def handle({:update, _update}, _context) do
Expand All @@ -382,19 +393,6 @@ defmodule SaveIt.Bot do
{:ok, nil}
end

defp pick_file_id_from_photo_url(photo_url) do
captures =
Regex.named_captures(~r"/files/(?<bot_id>\d+)/(?<file_id>.+)", photo_url)

if captures == nil do
Logger.error("Invalid photo URL: #{photo_url}")
nil
else
%{"file_id" => file_id} = captures
file_id
end
end

defp answer_photos(chat_id, nil) do
send_message(chat_id, "No photos found.")
end
Expand All @@ -408,7 +406,7 @@ defmodule SaveIt.Bot do
Enum.map(similar_photos, fn photo ->
%ExGram.Model.InputMediaPhoto{
type: "photo",
media: pick_file_id_from_photo_url(photo["url"]),
media: photo["file_id"],
caption: "Found photos",
show_caption_above_media: true
}
Expand Down Expand Up @@ -474,7 +472,7 @@ defmodule SaveIt.Bot do
case file_extension(file_name) do
ext when ext in [".png", ".jpg", ".jpeg"] ->
{:ok, msg} = ExGram.send_photo(chat_id, content)
bot_id = msg.from.id

file_id = get_file_id(msg)

image_base64 =
Expand All @@ -486,7 +484,7 @@ defmodule SaveIt.Bot do
PhotoService.create_photo!(%{
image: image_base64,
caption: file_name,
url: photo_url(bot_id, file_id),
file_id: file_id,
belongs_to_id: chat_id
})

Expand Down Expand Up @@ -535,12 +533,4 @@ defmodule SaveIt.Bot do
""")
end
end

defp photo_url(bot_id, file_id) do
proxy_url = Application.fetch_env!(:save_it, :web_url) <> "/telegram/files"

encoded_bot_id = URI.encode(bot_id |> to_string())
encoded_file_id = URI.encode(file_id)
"#{proxy_url}/#{encoded_bot_id}/#{encoded_file_id}"
end
end
60 changes: 46 additions & 14 deletions lib/save_it/note_service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,61 @@ defmodule SaveIt.NoteService do

alias SmallSdk.Typesense

def create_note!(%{
content: content,
file_id: file_id,
belongs_to_id: belongs_to_id
}) do
def create_note!(
%{
message_id: message_id,
belongs_to_id: belongs_to_id
} = note_params
) do
now_unix = DateTime.utc_now() |> DateTime.to_unix()

note_create_input =
%{
content: content,
file_id: file_id
}
note_params
|> Map.put(:message_id, Integer.to_string(message_id))
|> Map.put(:belongs_to_id, Integer.to_string(belongs_to_id))
|> Map.put(:inserted_at, now_unix)
|> Map.put(:updated_at, now_unix)

doc =
Typesense.create_document!(
Typesense.create_document!(
"notes",
note_create_input
)
end

def update_note!(id, %{} = note_params) do
now_unix = DateTime.utc_now() |> DateTime.to_unix()

note_update_input =
note_params
|> Map.put(:updated_at, now_unix)

Typesense.update_document!(
"notes",
id,
note_update_input
)
end

def get_note!(message_id, chat_id) do
docs =
Typesense.search_documents!(
"notes",
note_create_input
q: "*",
query_by: "content",
filter_by: "message_id:=#{message_id} && belongs_to_id:=#{chat_id}"
)

Logger.debug("doc: #{inspect(doc)}")
doc
case docs do
nil ->
nil

[] ->
nil

[doc | rest] ->
Logger.warning("Found multiple notes, skipping the rest: #{inspect(rest)}")

doc
end
end
end
2 changes: 1 addition & 1 deletion lib/save_it/photo_service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ defmodule SaveIt.PhotoService do
end

def update_photo(photo) do
Typesense.update_document("photos", photo.id, photo)
Typesense.update_document!("photos", photo.id, photo)
end

def get_photo(photo_id) do
Expand Down
29 changes: 25 additions & 4 deletions lib/small_sdk/typesense.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule SmallSdk.Typesense do
raise "Unauthorized"

%Req.Response{status: 404} ->
raise "Not Found"
nil

%Req.Response{status: 409} ->
raise "Conflict"
Expand All @@ -40,18 +40,39 @@ defmodule SmallSdk.Typesense do
handle_response(res)
end

def search_documents!(collection_name, opts) do
q = Keyword.get(opts, :q, "*")
query_by = Keyword.get(opts, :query_by, "")
filter_by = Keyword.get(opts, :filter_by, "")

req = build_request("/collections/#{collection_name}/documents/search")

{:ok, res} =
Req.get(req,
params: %{
q: q,
query_by: query_by,
filter_by: filter_by
}
)

data = handle_response(res)

data["hits"] |> Enum.map(&Map.get(&1, "document"))
end

def get_document(collection_name, document_id) do
req = build_request("/collections/#{collection_name}/documents/#{document_id}")
{:ok, res} = Req.get(req)

res.body
handle_response(res)
end

def update_document(collection_name, document_id, update_input) do
def update_document!(collection_name, document_id, update_input) do
req = build_request("/collections/#{collection_name}/documents/#{document_id}")
{:ok, res} = Req.patch(req, json: update_input)

res.body
handle_response(res)
end

def create_search_key() do
Expand Down

0 comments on commit c9bacc8

Please sign in to comment.