From 9c5c604944428d3f682f35c9d05a87c1d4c3152f Mon Sep 17 00:00:00 2001 From: boojack Date: Wed, 29 Apr 2026 22:38:08 +0800 Subject: [PATCH] feat: add link metadata endpoints --- internal/httpgetter/html_meta.go | 8 +- internal/httpgetter/html_meta_test.go | 54 ++- proto/api/v1/memo_service.proto | 40 ++ .../v1/apiv1connect/memo_service.connect.go | 98 +++- proto/gen/api/v1/memo_service.pb.go | 433 +++++++++++++----- proto/gen/api/v1/memo_service.pb.gw.go | 212 +++++++-- proto/gen/api/v1/memo_service_grpc.pb.go | 116 ++++- proto/gen/openapi.yaml | 83 ++++ server/router/api/v1/acl_config.go | 8 +- server/router/api/v1/acl_config_test.go | 2 + server/router/api/v1/connect_services.go | 16 + server/router/api/v1/link_metadata_test.go | 103 +++++ server/router/api/v1/memo_service.go | 51 +++ web/src/types/proto/api/v1/memo_service_pb.ts | 119 ++++- 14 files changed, 1154 insertions(+), 189 deletions(-) create mode 100644 server/router/api/v1/link_metadata_test.go diff --git a/internal/httpgetter/html_meta.go b/internal/httpgetter/html_meta.go index 3ac719487..b98a01f4d 100644 --- a/internal/httpgetter/html_meta.go +++ b/internal/httpgetter/html_meta.go @@ -6,6 +6,7 @@ import ( "net" "net/http" "net/url" + "time" "github.com/pkg/errors" "golang.org/x/net/html" @@ -14,7 +15,10 @@ import ( var ErrInternalIP = errors.New("internal IP addresses are not allowed") +const maxHTMLMetaBytes = 512 * 1024 + var httpClient = &http.Client{ + Timeout: 5 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error { if err := validateURL(req.URL.String()); err != nil { return errors.Wrap(err, "redirect to internal IP") @@ -51,9 +55,7 @@ func GetHTMLMeta(urlStr string) (*HTMLMeta, error) { return nil, errors.New("not a HTML page") } - // TODO: limit the size of the response body - - htmlMeta := extractHTMLMeta(response.Body) + htmlMeta := extractHTMLMeta(io.LimitReader(response.Body, maxHTMLMetaBytes)) enrichSiteMeta(response.Request.URL, htmlMeta) return htmlMeta, nil } diff --git a/internal/httpgetter/html_meta_test.go b/internal/httpgetter/html_meta_test.go index d1668db1d..f01b0d1e9 100644 --- a/internal/httpgetter/html_meta_test.go +++ b/internal/httpgetter/html_meta_test.go @@ -2,21 +2,55 @@ package httpgetter import ( "errors" + "io" + "net/http" + "strings" "testing" "github.com/stretchr/testify/require" ) +type roundTripFunc func(*http.Request) (*http.Response, error) + +func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req) +} + func TestGetHTMLMeta(t *testing.T) { - tests := []struct { - urlStr string - htmlMeta HTMLMeta - }{} - for _, test := range tests { - metadata, err := GetHTMLMeta(test.urlStr) - require.NoError(t, err) - require.Equal(t, test.htmlMeta, *metadata) + originalHTTPClient := httpClient + t.Cleanup(func() { + httpClient = originalHTTPClient + }) + + httpClient = &http.Client{ + Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) { + require.Equal(t, "http://93.184.216.34/article", req.URL.String()) + return &http.Response{ + StatusCode: http.StatusOK, + Header: http.Header{"Content-Type": []string{"text/html; charset=utf-8"}}, + Body: io.NopCloser(strings.NewReader(` + + + Fallback title + + + + + +ignored +`)), + Request: req, + }, nil + }), } + + metadata, err := GetHTMLMeta("http://93.184.216.34/article") + require.NoError(t, err) + require.Equal(t, HTMLMeta{ + Title: "Open Graph title", + Description: "Open Graph description", + Image: "https://example.com/cover.png", + }, *metadata) } func TestGetHTMLMetaForInternal(t *testing.T) { @@ -30,3 +64,7 @@ func TestGetHTMLMetaForInternal(t *testing.T) { t.Errorf("Expected error for resolved internal IP, got %v", err) } } + +func TestHTTPClientHasTimeout(t *testing.T) { + require.NotZero(t, httpClient.Timeout) +} diff --git a/proto/api/v1/memo_service.proto b/proto/api/v1/memo_service.proto index 56c3eeab0..4cc161364 100644 --- a/proto/api/v1/memo_service.proto +++ b/proto/api/v1/memo_service.proto @@ -126,6 +126,17 @@ service MemoService { rpc GetMemoByShare(GetMemoByShareRequest) returns (Memo) { option (google.api.http) = {get: "/api/v1/shares/{share_id}"}; } + // GetLinkMetadata gets metadata for a link. + rpc GetLinkMetadata(GetLinkMetadataRequest) returns (LinkMetadata) { + option (google.api.http) = {get: "/api/v1/memos/-/linkMetadata"}; + } + // BatchGetLinkMetadata gets metadata for links. + rpc BatchGetLinkMetadata(BatchGetLinkMetadataRequest) returns (BatchGetLinkMetadataResponse) { + option (google.api.http) = { + post: "/api/v1/memos/-/linkMetadata:batchGet" + body: "*" + }; + } } enum Visibility { @@ -595,3 +606,32 @@ message GetMemoByShareRequest { // Required. The share token extracted from the share URL (/s/{share_id}). string share_id = 1 [(google.api.field_behavior) = REQUIRED]; } + +message GetLinkMetadataRequest { + // Required. The link URL. + string url = 1 [(google.api.field_behavior) = REQUIRED]; +} + +message BatchGetLinkMetadataRequest { + // Required. The link URLs. + repeated string urls = 1 [(google.api.field_behavior) = REQUIRED]; +} + +message BatchGetLinkMetadataResponse { + // The link metadata list, in the same order as the input URLs. + repeated LinkMetadata link_metadata = 1; +} + +message LinkMetadata { + // The original link URL. + string url = 1; + + // The link title. + string title = 2; + + // The link description. + string description = 3; + + // The link image URL. + string image = 4; +} diff --git a/proto/gen/api/v1/apiv1connect/memo_service.connect.go b/proto/gen/api/v1/apiv1connect/memo_service.connect.go index 1e32fabe9..b37ea3f73 100644 --- a/proto/gen/api/v1/apiv1connect/memo_service.connect.go +++ b/proto/gen/api/v1/apiv1connect/memo_service.connect.go @@ -83,6 +83,12 @@ const ( // MemoServiceGetMemoByShareProcedure is the fully-qualified name of the MemoService's // GetMemoByShare RPC. MemoServiceGetMemoByShareProcedure = "/memos.api.v1.MemoService/GetMemoByShare" + // MemoServiceGetLinkMetadataProcedure is the fully-qualified name of the MemoService's + // GetLinkMetadata RPC. + MemoServiceGetLinkMetadataProcedure = "/memos.api.v1.MemoService/GetLinkMetadata" + // MemoServiceBatchGetLinkMetadataProcedure is the fully-qualified name of the MemoService's + // BatchGetLinkMetadata RPC. + MemoServiceBatchGetLinkMetadataProcedure = "/memos.api.v1.MemoService/BatchGetLinkMetadata" ) // MemoServiceClient is a client for the memos.api.v1.MemoService service. @@ -124,6 +130,10 @@ type MemoServiceClient interface { // GetMemoByShare resolves a share token to its memo. No authentication required. // Returns NOT_FOUND if the token is invalid or expired. GetMemoByShare(context.Context, *connect.Request[v1.GetMemoByShareRequest]) (*connect.Response[v1.Memo], error) + // GetLinkMetadata gets metadata for a link. + GetLinkMetadata(context.Context, *connect.Request[v1.GetLinkMetadataRequest]) (*connect.Response[v1.LinkMetadata], error) + // BatchGetLinkMetadata gets metadata for links. + BatchGetLinkMetadata(context.Context, *connect.Request[v1.BatchGetLinkMetadataRequest]) (*connect.Response[v1.BatchGetLinkMetadataResponse], error) } // NewMemoServiceClient constructs a client for the memos.api.v1.MemoService service. By default, it @@ -245,29 +255,43 @@ func NewMemoServiceClient(httpClient connect.HTTPClient, baseURL string, opts .. connect.WithSchema(memoServiceMethods.ByName("GetMemoByShare")), connect.WithClientOptions(opts...), ), + getLinkMetadata: connect.NewClient[v1.GetLinkMetadataRequest, v1.LinkMetadata]( + httpClient, + baseURL+MemoServiceGetLinkMetadataProcedure, + connect.WithSchema(memoServiceMethods.ByName("GetLinkMetadata")), + connect.WithClientOptions(opts...), + ), + batchGetLinkMetadata: connect.NewClient[v1.BatchGetLinkMetadataRequest, v1.BatchGetLinkMetadataResponse]( + httpClient, + baseURL+MemoServiceBatchGetLinkMetadataProcedure, + connect.WithSchema(memoServiceMethods.ByName("BatchGetLinkMetadata")), + connect.WithClientOptions(opts...), + ), } } // memoServiceClient implements MemoServiceClient. type memoServiceClient struct { - createMemo *connect.Client[v1.CreateMemoRequest, v1.Memo] - listMemos *connect.Client[v1.ListMemosRequest, v1.ListMemosResponse] - getMemo *connect.Client[v1.GetMemoRequest, v1.Memo] - updateMemo *connect.Client[v1.UpdateMemoRequest, v1.Memo] - deleteMemo *connect.Client[v1.DeleteMemoRequest, emptypb.Empty] - setMemoAttachments *connect.Client[v1.SetMemoAttachmentsRequest, emptypb.Empty] - listMemoAttachments *connect.Client[v1.ListMemoAttachmentsRequest, v1.ListMemoAttachmentsResponse] - setMemoRelations *connect.Client[v1.SetMemoRelationsRequest, emptypb.Empty] - listMemoRelations *connect.Client[v1.ListMemoRelationsRequest, v1.ListMemoRelationsResponse] - createMemoComment *connect.Client[v1.CreateMemoCommentRequest, v1.Memo] - listMemoComments *connect.Client[v1.ListMemoCommentsRequest, v1.ListMemoCommentsResponse] - listMemoReactions *connect.Client[v1.ListMemoReactionsRequest, v1.ListMemoReactionsResponse] - upsertMemoReaction *connect.Client[v1.UpsertMemoReactionRequest, v1.Reaction] - deleteMemoReaction *connect.Client[v1.DeleteMemoReactionRequest, emptypb.Empty] - createMemoShare *connect.Client[v1.CreateMemoShareRequest, v1.MemoShare] - listMemoShares *connect.Client[v1.ListMemoSharesRequest, v1.ListMemoSharesResponse] - deleteMemoShare *connect.Client[v1.DeleteMemoShareRequest, emptypb.Empty] - getMemoByShare *connect.Client[v1.GetMemoByShareRequest, v1.Memo] + createMemo *connect.Client[v1.CreateMemoRequest, v1.Memo] + listMemos *connect.Client[v1.ListMemosRequest, v1.ListMemosResponse] + getMemo *connect.Client[v1.GetMemoRequest, v1.Memo] + updateMemo *connect.Client[v1.UpdateMemoRequest, v1.Memo] + deleteMemo *connect.Client[v1.DeleteMemoRequest, emptypb.Empty] + setMemoAttachments *connect.Client[v1.SetMemoAttachmentsRequest, emptypb.Empty] + listMemoAttachments *connect.Client[v1.ListMemoAttachmentsRequest, v1.ListMemoAttachmentsResponse] + setMemoRelations *connect.Client[v1.SetMemoRelationsRequest, emptypb.Empty] + listMemoRelations *connect.Client[v1.ListMemoRelationsRequest, v1.ListMemoRelationsResponse] + createMemoComment *connect.Client[v1.CreateMemoCommentRequest, v1.Memo] + listMemoComments *connect.Client[v1.ListMemoCommentsRequest, v1.ListMemoCommentsResponse] + listMemoReactions *connect.Client[v1.ListMemoReactionsRequest, v1.ListMemoReactionsResponse] + upsertMemoReaction *connect.Client[v1.UpsertMemoReactionRequest, v1.Reaction] + deleteMemoReaction *connect.Client[v1.DeleteMemoReactionRequest, emptypb.Empty] + createMemoShare *connect.Client[v1.CreateMemoShareRequest, v1.MemoShare] + listMemoShares *connect.Client[v1.ListMemoSharesRequest, v1.ListMemoSharesResponse] + deleteMemoShare *connect.Client[v1.DeleteMemoShareRequest, emptypb.Empty] + getMemoByShare *connect.Client[v1.GetMemoByShareRequest, v1.Memo] + getLinkMetadata *connect.Client[v1.GetLinkMetadataRequest, v1.LinkMetadata] + batchGetLinkMetadata *connect.Client[v1.BatchGetLinkMetadataRequest, v1.BatchGetLinkMetadataResponse] } // CreateMemo calls memos.api.v1.MemoService.CreateMemo. @@ -360,6 +384,16 @@ func (c *memoServiceClient) GetMemoByShare(ctx context.Context, req *connect.Req return c.getMemoByShare.CallUnary(ctx, req) } +// GetLinkMetadata calls memos.api.v1.MemoService.GetLinkMetadata. +func (c *memoServiceClient) GetLinkMetadata(ctx context.Context, req *connect.Request[v1.GetLinkMetadataRequest]) (*connect.Response[v1.LinkMetadata], error) { + return c.getLinkMetadata.CallUnary(ctx, req) +} + +// BatchGetLinkMetadata calls memos.api.v1.MemoService.BatchGetLinkMetadata. +func (c *memoServiceClient) BatchGetLinkMetadata(ctx context.Context, req *connect.Request[v1.BatchGetLinkMetadataRequest]) (*connect.Response[v1.BatchGetLinkMetadataResponse], error) { + return c.batchGetLinkMetadata.CallUnary(ctx, req) +} + // MemoServiceHandler is an implementation of the memos.api.v1.MemoService service. type MemoServiceHandler interface { // CreateMemo creates a memo. @@ -399,6 +433,10 @@ type MemoServiceHandler interface { // GetMemoByShare resolves a share token to its memo. No authentication required. // Returns NOT_FOUND if the token is invalid or expired. GetMemoByShare(context.Context, *connect.Request[v1.GetMemoByShareRequest]) (*connect.Response[v1.Memo], error) + // GetLinkMetadata gets metadata for a link. + GetLinkMetadata(context.Context, *connect.Request[v1.GetLinkMetadataRequest]) (*connect.Response[v1.LinkMetadata], error) + // BatchGetLinkMetadata gets metadata for links. + BatchGetLinkMetadata(context.Context, *connect.Request[v1.BatchGetLinkMetadataRequest]) (*connect.Response[v1.BatchGetLinkMetadataResponse], error) } // NewMemoServiceHandler builds an HTTP handler from the service implementation. It returns the path @@ -516,6 +554,18 @@ func NewMemoServiceHandler(svc MemoServiceHandler, opts ...connect.HandlerOption connect.WithSchema(memoServiceMethods.ByName("GetMemoByShare")), connect.WithHandlerOptions(opts...), ) + memoServiceGetLinkMetadataHandler := connect.NewUnaryHandler( + MemoServiceGetLinkMetadataProcedure, + svc.GetLinkMetadata, + connect.WithSchema(memoServiceMethods.ByName("GetLinkMetadata")), + connect.WithHandlerOptions(opts...), + ) + memoServiceBatchGetLinkMetadataHandler := connect.NewUnaryHandler( + MemoServiceBatchGetLinkMetadataProcedure, + svc.BatchGetLinkMetadata, + connect.WithSchema(memoServiceMethods.ByName("BatchGetLinkMetadata")), + connect.WithHandlerOptions(opts...), + ) return "/memos.api.v1.MemoService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case MemoServiceCreateMemoProcedure: @@ -554,6 +604,10 @@ func NewMemoServiceHandler(svc MemoServiceHandler, opts ...connect.HandlerOption memoServiceDeleteMemoShareHandler.ServeHTTP(w, r) case MemoServiceGetMemoByShareProcedure: memoServiceGetMemoByShareHandler.ServeHTTP(w, r) + case MemoServiceGetLinkMetadataProcedure: + memoServiceGetLinkMetadataHandler.ServeHTTP(w, r) + case MemoServiceBatchGetLinkMetadataProcedure: + memoServiceBatchGetLinkMetadataHandler.ServeHTTP(w, r) default: http.NotFound(w, r) } @@ -634,3 +688,11 @@ func (UnimplementedMemoServiceHandler) DeleteMemoShare(context.Context, *connect func (UnimplementedMemoServiceHandler) GetMemoByShare(context.Context, *connect.Request[v1.GetMemoByShareRequest]) (*connect.Response[v1.Memo], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.MemoService.GetMemoByShare is not implemented")) } + +func (UnimplementedMemoServiceHandler) GetLinkMetadata(context.Context, *connect.Request[v1.GetLinkMetadataRequest]) (*connect.Response[v1.LinkMetadata], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.MemoService.GetLinkMetadata is not implemented")) +} + +func (UnimplementedMemoServiceHandler) BatchGetLinkMetadata(context.Context, *connect.Request[v1.BatchGetLinkMetadataRequest]) (*connect.Response[v1.BatchGetLinkMetadataResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("memos.api.v1.MemoService.BatchGetLinkMetadata is not implemented")) +} diff --git a/proto/gen/api/v1/memo_service.pb.go b/proto/gen/api/v1/memo_service.pb.go index cdda2c346..3fac50edd 100644 --- a/proto/gen/api/v1/memo_service.pb.go +++ b/proto/gen/api/v1/memo_service.pb.go @@ -1966,6 +1966,213 @@ func (x *GetMemoByShareRequest) GetShareId() string { return "" } +type GetLinkMetadataRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Required. The link URL. + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetLinkMetadataRequest) Reset() { + *x = GetLinkMetadataRequest{} + mi := &file_api_v1_memo_service_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetLinkMetadataRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetLinkMetadataRequest) ProtoMessage() {} + +func (x *GetLinkMetadataRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_memo_service_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetLinkMetadataRequest.ProtoReflect.Descriptor instead. +func (*GetLinkMetadataRequest) Descriptor() ([]byte, []int) { + return file_api_v1_memo_service_proto_rawDescGZIP(), []int{29} +} + +func (x *GetLinkMetadataRequest) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +type BatchGetLinkMetadataRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Required. The link URLs. + Urls []string `protobuf:"bytes,1,rep,name=urls,proto3" json:"urls,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BatchGetLinkMetadataRequest) Reset() { + *x = BatchGetLinkMetadataRequest{} + mi := &file_api_v1_memo_service_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetLinkMetadataRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetLinkMetadataRequest) ProtoMessage() {} + +func (x *BatchGetLinkMetadataRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_memo_service_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchGetLinkMetadataRequest.ProtoReflect.Descriptor instead. +func (*BatchGetLinkMetadataRequest) Descriptor() ([]byte, []int) { + return file_api_v1_memo_service_proto_rawDescGZIP(), []int{30} +} + +func (x *BatchGetLinkMetadataRequest) GetUrls() []string { + if x != nil { + return x.Urls + } + return nil +} + +type BatchGetLinkMetadataResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The link metadata list, in the same order as the input URLs. + LinkMetadata []*LinkMetadata `protobuf:"bytes,1,rep,name=link_metadata,json=linkMetadata,proto3" json:"link_metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BatchGetLinkMetadataResponse) Reset() { + *x = BatchGetLinkMetadataResponse{} + mi := &file_api_v1_memo_service_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetLinkMetadataResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetLinkMetadataResponse) ProtoMessage() {} + +func (x *BatchGetLinkMetadataResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_memo_service_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BatchGetLinkMetadataResponse.ProtoReflect.Descriptor instead. +func (*BatchGetLinkMetadataResponse) Descriptor() ([]byte, []int) { + return file_api_v1_memo_service_proto_rawDescGZIP(), []int{31} +} + +func (x *BatchGetLinkMetadataResponse) GetLinkMetadata() []*LinkMetadata { + if x != nil { + return x.LinkMetadata + } + return nil +} + +type LinkMetadata struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The original link URL. + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + // The link title. + Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` + // The link description. + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + // The link image URL. + Image string `protobuf:"bytes,4,opt,name=image,proto3" json:"image,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LinkMetadata) Reset() { + *x = LinkMetadata{} + mi := &file_api_v1_memo_service_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LinkMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LinkMetadata) ProtoMessage() {} + +func (x *LinkMetadata) ProtoReflect() protoreflect.Message { + mi := &file_api_v1_memo_service_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LinkMetadata.ProtoReflect.Descriptor instead. +func (*LinkMetadata) Descriptor() ([]byte, []int) { + return file_api_v1_memo_service_proto_rawDescGZIP(), []int{32} +} + +func (x *LinkMetadata) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *LinkMetadata) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *LinkMetadata) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *LinkMetadata) GetImage() string { + if x != nil { + return x.Image + } + return "" +} + // Computed properties of a memo. type Memo_Property struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1981,7 +2188,7 @@ type Memo_Property struct { func (x *Memo_Property) Reset() { *x = Memo_Property{} - mi := &file_api_v1_memo_service_proto_msgTypes[29] + mi := &file_api_v1_memo_service_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1993,7 +2200,7 @@ func (x *Memo_Property) String() string { func (*Memo_Property) ProtoMessage() {} func (x *Memo_Property) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_memo_service_proto_msgTypes[29] + mi := &file_api_v1_memo_service_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2058,7 +2265,7 @@ type MemoRelation_Memo struct { func (x *MemoRelation_Memo) Reset() { *x = MemoRelation_Memo{} - mi := &file_api_v1_memo_service_proto_msgTypes[30] + mi := &file_api_v1_memo_service_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2070,7 +2277,7 @@ func (x *MemoRelation_Memo) String() string { func (*MemoRelation_Memo) ProtoMessage() {} func (x *MemoRelation_Memo) ProtoReflect() protoreflect.Message { - mi := &file_api_v1_memo_service_proto_msgTypes[30] + mi := &file_api_v1_memo_service_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2275,14 +2482,25 @@ const file_api_v1_memo_service_proto_rawDesc = "" + "\x04name\x18\x01 \x01(\tB\x1e\xe0A\x02\xfaA\x18\n" + "\x16memos.api.v1/MemoShareR\x04name\"7\n" + "\x15GetMemoByShareRequest\x12\x1e\n" + - "\bshare_id\x18\x01 \x01(\tB\x03\xe0A\x02R\ashareId*P\n" + + "\bshare_id\x18\x01 \x01(\tB\x03\xe0A\x02R\ashareId\"/\n" + + "\x16GetLinkMetadataRequest\x12\x15\n" + + "\x03url\x18\x01 \x01(\tB\x03\xe0A\x02R\x03url\"6\n" + + "\x1bBatchGetLinkMetadataRequest\x12\x17\n" + + "\x04urls\x18\x01 \x03(\tB\x03\xe0A\x02R\x04urls\"_\n" + + "\x1cBatchGetLinkMetadataResponse\x12?\n" + + "\rlink_metadata\x18\x01 \x03(\v2\x1a.memos.api.v1.LinkMetadataR\flinkMetadata\"n\n" + + "\fLinkMetadata\x12\x10\n" + + "\x03url\x18\x01 \x01(\tR\x03url\x12\x14\n" + + "\x05title\x18\x02 \x01(\tR\x05title\x12 \n" + + "\vdescription\x18\x03 \x01(\tR\vdescription\x12\x14\n" + + "\x05image\x18\x04 \x01(\tR\x05image*P\n" + "\n" + "Visibility\x12\x1a\n" + "\x16VISIBILITY_UNSPECIFIED\x10\x00\x12\v\n" + "\aPRIVATE\x10\x01\x12\r\n" + "\tPROTECTED\x10\x02\x12\n" + "\n" + - "\x06PUBLIC\x10\x032\xee\x12\n" + + "\x06PUBLIC\x10\x032\x8b\x15\n" + "\vMemoService\x12e\n" + "\n" + "CreateMemo\x12\x1f.memos.api.v1.CreateMemoRequest\x1a\x12.memos.api.v1.Memo\"\"\xdaA\x04memo\x82\xd3\xe4\x93\x02\x15:\x04memo\"\r/api/v1/memos\x12f\n" + @@ -2305,7 +2523,9 @@ const file_api_v1_memo_service_proto_rawDesc = "" + "memo_share\"\x1f/api/v1/{parent=memos/*}/shares\x12\x8d\x01\n" + "\x0eListMemoShares\x12#.memos.api.v1.ListMemoSharesRequest\x1a$.memos.api.v1.ListMemoSharesResponse\"0\xdaA\x06parent\x82\xd3\xe4\x93\x02!\x12\x1f/api/v1/{parent=memos/*}/shares\x12\x7f\n" + "\x0fDeleteMemoShare\x12$.memos.api.v1.DeleteMemoShareRequest\x1a\x16.google.protobuf.Empty\".\xdaA\x04name\x82\xd3\xe4\x93\x02!*\x1f/api/v1/{name=memos/*/shares/*}\x12l\n" + - "\x0eGetMemoByShare\x12#.memos.api.v1.GetMemoByShareRequest\x1a\x12.memos.api.v1.Memo\"!\x82\xd3\xe4\x93\x02\x1b\x12\x19/api/v1/shares/{share_id}B\xa8\x01\n" + + "\x0eGetMemoByShare\x12#.memos.api.v1.GetMemoByShareRequest\x1a\x12.memos.api.v1.Memo\"!\x82\xd3\xe4\x93\x02\x1b\x12\x19/api/v1/shares/{share_id}\x12y\n" + + "\x0fGetLinkMetadata\x12$.memos.api.v1.GetLinkMetadataRequest\x1a\x1a.memos.api.v1.LinkMetadata\"$\x82\xd3\xe4\x93\x02\x1e\x12\x1c/api/v1/memos/-/linkMetadata\x12\x9f\x01\n" + + "\x14BatchGetLinkMetadata\x12).memos.api.v1.BatchGetLinkMetadataRequest\x1a*.memos.api.v1.BatchGetLinkMetadataResponse\"0\x82\xd3\xe4\x93\x02*:\x01*\"%/api/v1/memos/-/linkMetadata:batchGetB\xa8\x01\n" + "\x10com.memos.api.v1B\x10MemoServiceProtoP\x01Z0github.com/usememos/memos/proto/gen/api/v1;apiv1\xa2\x02\x03MAX\xaa\x02\fMemos.Api.V1\xca\x02\fMemos\\Api\\V1\xe2\x02\x18Memos\\Api\\V1\\GPBMetadata\xea\x02\x0eMemos::Api::V1b\x06proto3" var ( @@ -2321,67 +2541,71 @@ func file_api_v1_memo_service_proto_rawDescGZIP() []byte { } var file_api_v1_memo_service_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_api_v1_memo_service_proto_msgTypes = make([]protoimpl.MessageInfo, 31) +var file_api_v1_memo_service_proto_msgTypes = make([]protoimpl.MessageInfo, 35) var file_api_v1_memo_service_proto_goTypes = []any{ - (Visibility)(0), // 0: memos.api.v1.Visibility - (MemoRelation_Type)(0), // 1: memos.api.v1.MemoRelation.Type - (*Reaction)(nil), // 2: memos.api.v1.Reaction - (*Memo)(nil), // 3: memos.api.v1.Memo - (*Location)(nil), // 4: memos.api.v1.Location - (*CreateMemoRequest)(nil), // 5: memos.api.v1.CreateMemoRequest - (*ListMemosRequest)(nil), // 6: memos.api.v1.ListMemosRequest - (*ListMemosResponse)(nil), // 7: memos.api.v1.ListMemosResponse - (*GetMemoRequest)(nil), // 8: memos.api.v1.GetMemoRequest - (*UpdateMemoRequest)(nil), // 9: memos.api.v1.UpdateMemoRequest - (*DeleteMemoRequest)(nil), // 10: memos.api.v1.DeleteMemoRequest - (*SetMemoAttachmentsRequest)(nil), // 11: memos.api.v1.SetMemoAttachmentsRequest - (*ListMemoAttachmentsRequest)(nil), // 12: memos.api.v1.ListMemoAttachmentsRequest - (*ListMemoAttachmentsResponse)(nil), // 13: memos.api.v1.ListMemoAttachmentsResponse - (*MemoRelation)(nil), // 14: memos.api.v1.MemoRelation - (*SetMemoRelationsRequest)(nil), // 15: memos.api.v1.SetMemoRelationsRequest - (*ListMemoRelationsRequest)(nil), // 16: memos.api.v1.ListMemoRelationsRequest - (*ListMemoRelationsResponse)(nil), // 17: memos.api.v1.ListMemoRelationsResponse - (*CreateMemoCommentRequest)(nil), // 18: memos.api.v1.CreateMemoCommentRequest - (*ListMemoCommentsRequest)(nil), // 19: memos.api.v1.ListMemoCommentsRequest - (*ListMemoCommentsResponse)(nil), // 20: memos.api.v1.ListMemoCommentsResponse - (*ListMemoReactionsRequest)(nil), // 21: memos.api.v1.ListMemoReactionsRequest - (*ListMemoReactionsResponse)(nil), // 22: memos.api.v1.ListMemoReactionsResponse - (*UpsertMemoReactionRequest)(nil), // 23: memos.api.v1.UpsertMemoReactionRequest - (*DeleteMemoReactionRequest)(nil), // 24: memos.api.v1.DeleteMemoReactionRequest - (*MemoShare)(nil), // 25: memos.api.v1.MemoShare - (*CreateMemoShareRequest)(nil), // 26: memos.api.v1.CreateMemoShareRequest - (*ListMemoSharesRequest)(nil), // 27: memos.api.v1.ListMemoSharesRequest - (*ListMemoSharesResponse)(nil), // 28: memos.api.v1.ListMemoSharesResponse - (*DeleteMemoShareRequest)(nil), // 29: memos.api.v1.DeleteMemoShareRequest - (*GetMemoByShareRequest)(nil), // 30: memos.api.v1.GetMemoByShareRequest - (*Memo_Property)(nil), // 31: memos.api.v1.Memo.Property - (*MemoRelation_Memo)(nil), // 32: memos.api.v1.MemoRelation.Memo - (*timestamppb.Timestamp)(nil), // 33: google.protobuf.Timestamp - (State)(0), // 34: memos.api.v1.State - (*Attachment)(nil), // 35: memos.api.v1.Attachment - (*fieldmaskpb.FieldMask)(nil), // 36: google.protobuf.FieldMask - (*emptypb.Empty)(nil), // 37: google.protobuf.Empty + (Visibility)(0), // 0: memos.api.v1.Visibility + (MemoRelation_Type)(0), // 1: memos.api.v1.MemoRelation.Type + (*Reaction)(nil), // 2: memos.api.v1.Reaction + (*Memo)(nil), // 3: memos.api.v1.Memo + (*Location)(nil), // 4: memos.api.v1.Location + (*CreateMemoRequest)(nil), // 5: memos.api.v1.CreateMemoRequest + (*ListMemosRequest)(nil), // 6: memos.api.v1.ListMemosRequest + (*ListMemosResponse)(nil), // 7: memos.api.v1.ListMemosResponse + (*GetMemoRequest)(nil), // 8: memos.api.v1.GetMemoRequest + (*UpdateMemoRequest)(nil), // 9: memos.api.v1.UpdateMemoRequest + (*DeleteMemoRequest)(nil), // 10: memos.api.v1.DeleteMemoRequest + (*SetMemoAttachmentsRequest)(nil), // 11: memos.api.v1.SetMemoAttachmentsRequest + (*ListMemoAttachmentsRequest)(nil), // 12: memos.api.v1.ListMemoAttachmentsRequest + (*ListMemoAttachmentsResponse)(nil), // 13: memos.api.v1.ListMemoAttachmentsResponse + (*MemoRelation)(nil), // 14: memos.api.v1.MemoRelation + (*SetMemoRelationsRequest)(nil), // 15: memos.api.v1.SetMemoRelationsRequest + (*ListMemoRelationsRequest)(nil), // 16: memos.api.v1.ListMemoRelationsRequest + (*ListMemoRelationsResponse)(nil), // 17: memos.api.v1.ListMemoRelationsResponse + (*CreateMemoCommentRequest)(nil), // 18: memos.api.v1.CreateMemoCommentRequest + (*ListMemoCommentsRequest)(nil), // 19: memos.api.v1.ListMemoCommentsRequest + (*ListMemoCommentsResponse)(nil), // 20: memos.api.v1.ListMemoCommentsResponse + (*ListMemoReactionsRequest)(nil), // 21: memos.api.v1.ListMemoReactionsRequest + (*ListMemoReactionsResponse)(nil), // 22: memos.api.v1.ListMemoReactionsResponse + (*UpsertMemoReactionRequest)(nil), // 23: memos.api.v1.UpsertMemoReactionRequest + (*DeleteMemoReactionRequest)(nil), // 24: memos.api.v1.DeleteMemoReactionRequest + (*MemoShare)(nil), // 25: memos.api.v1.MemoShare + (*CreateMemoShareRequest)(nil), // 26: memos.api.v1.CreateMemoShareRequest + (*ListMemoSharesRequest)(nil), // 27: memos.api.v1.ListMemoSharesRequest + (*ListMemoSharesResponse)(nil), // 28: memos.api.v1.ListMemoSharesResponse + (*DeleteMemoShareRequest)(nil), // 29: memos.api.v1.DeleteMemoShareRequest + (*GetMemoByShareRequest)(nil), // 30: memos.api.v1.GetMemoByShareRequest + (*GetLinkMetadataRequest)(nil), // 31: memos.api.v1.GetLinkMetadataRequest + (*BatchGetLinkMetadataRequest)(nil), // 32: memos.api.v1.BatchGetLinkMetadataRequest + (*BatchGetLinkMetadataResponse)(nil), // 33: memos.api.v1.BatchGetLinkMetadataResponse + (*LinkMetadata)(nil), // 34: memos.api.v1.LinkMetadata + (*Memo_Property)(nil), // 35: memos.api.v1.Memo.Property + (*MemoRelation_Memo)(nil), // 36: memos.api.v1.MemoRelation.Memo + (*timestamppb.Timestamp)(nil), // 37: google.protobuf.Timestamp + (State)(0), // 38: memos.api.v1.State + (*Attachment)(nil), // 39: memos.api.v1.Attachment + (*fieldmaskpb.FieldMask)(nil), // 40: google.protobuf.FieldMask + (*emptypb.Empty)(nil), // 41: google.protobuf.Empty } var file_api_v1_memo_service_proto_depIdxs = []int32{ - 33, // 0: memos.api.v1.Reaction.create_time:type_name -> google.protobuf.Timestamp - 34, // 1: memos.api.v1.Memo.state:type_name -> memos.api.v1.State - 33, // 2: memos.api.v1.Memo.create_time:type_name -> google.protobuf.Timestamp - 33, // 3: memos.api.v1.Memo.update_time:type_name -> google.protobuf.Timestamp + 37, // 0: memos.api.v1.Reaction.create_time:type_name -> google.protobuf.Timestamp + 38, // 1: memos.api.v1.Memo.state:type_name -> memos.api.v1.State + 37, // 2: memos.api.v1.Memo.create_time:type_name -> google.protobuf.Timestamp + 37, // 3: memos.api.v1.Memo.update_time:type_name -> google.protobuf.Timestamp 0, // 4: memos.api.v1.Memo.visibility:type_name -> memos.api.v1.Visibility - 35, // 5: memos.api.v1.Memo.attachments:type_name -> memos.api.v1.Attachment + 39, // 5: memos.api.v1.Memo.attachments:type_name -> memos.api.v1.Attachment 14, // 6: memos.api.v1.Memo.relations:type_name -> memos.api.v1.MemoRelation 2, // 7: memos.api.v1.Memo.reactions:type_name -> memos.api.v1.Reaction - 31, // 8: memos.api.v1.Memo.property:type_name -> memos.api.v1.Memo.Property + 35, // 8: memos.api.v1.Memo.property:type_name -> memos.api.v1.Memo.Property 4, // 9: memos.api.v1.Memo.location:type_name -> memos.api.v1.Location 3, // 10: memos.api.v1.CreateMemoRequest.memo:type_name -> memos.api.v1.Memo - 34, // 11: memos.api.v1.ListMemosRequest.state:type_name -> memos.api.v1.State + 38, // 11: memos.api.v1.ListMemosRequest.state:type_name -> memos.api.v1.State 3, // 12: memos.api.v1.ListMemosResponse.memos:type_name -> memos.api.v1.Memo 3, // 13: memos.api.v1.UpdateMemoRequest.memo:type_name -> memos.api.v1.Memo - 36, // 14: memos.api.v1.UpdateMemoRequest.update_mask:type_name -> google.protobuf.FieldMask - 35, // 15: memos.api.v1.SetMemoAttachmentsRequest.attachments:type_name -> memos.api.v1.Attachment - 35, // 16: memos.api.v1.ListMemoAttachmentsResponse.attachments:type_name -> memos.api.v1.Attachment - 32, // 17: memos.api.v1.MemoRelation.memo:type_name -> memos.api.v1.MemoRelation.Memo - 32, // 18: memos.api.v1.MemoRelation.related_memo:type_name -> memos.api.v1.MemoRelation.Memo + 40, // 14: memos.api.v1.UpdateMemoRequest.update_mask:type_name -> google.protobuf.FieldMask + 39, // 15: memos.api.v1.SetMemoAttachmentsRequest.attachments:type_name -> memos.api.v1.Attachment + 39, // 16: memos.api.v1.ListMemoAttachmentsResponse.attachments:type_name -> memos.api.v1.Attachment + 36, // 17: memos.api.v1.MemoRelation.memo:type_name -> memos.api.v1.MemoRelation.Memo + 36, // 18: memos.api.v1.MemoRelation.related_memo:type_name -> memos.api.v1.MemoRelation.Memo 1, // 19: memos.api.v1.MemoRelation.type:type_name -> memos.api.v1.MemoRelation.Type 14, // 20: memos.api.v1.SetMemoRelationsRequest.relations:type_name -> memos.api.v1.MemoRelation 14, // 21: memos.api.v1.ListMemoRelationsResponse.relations:type_name -> memos.api.v1.MemoRelation @@ -2389,51 +2613,56 @@ var file_api_v1_memo_service_proto_depIdxs = []int32{ 3, // 23: memos.api.v1.ListMemoCommentsResponse.memos:type_name -> memos.api.v1.Memo 2, // 24: memos.api.v1.ListMemoReactionsResponse.reactions:type_name -> memos.api.v1.Reaction 2, // 25: memos.api.v1.UpsertMemoReactionRequest.reaction:type_name -> memos.api.v1.Reaction - 33, // 26: memos.api.v1.MemoShare.create_time:type_name -> google.protobuf.Timestamp - 33, // 27: memos.api.v1.MemoShare.expire_time:type_name -> google.protobuf.Timestamp + 37, // 26: memos.api.v1.MemoShare.create_time:type_name -> google.protobuf.Timestamp + 37, // 27: memos.api.v1.MemoShare.expire_time:type_name -> google.protobuf.Timestamp 25, // 28: memos.api.v1.CreateMemoShareRequest.memo_share:type_name -> memos.api.v1.MemoShare 25, // 29: memos.api.v1.ListMemoSharesResponse.memo_shares:type_name -> memos.api.v1.MemoShare - 5, // 30: memos.api.v1.MemoService.CreateMemo:input_type -> memos.api.v1.CreateMemoRequest - 6, // 31: memos.api.v1.MemoService.ListMemos:input_type -> memos.api.v1.ListMemosRequest - 8, // 32: memos.api.v1.MemoService.GetMemo:input_type -> memos.api.v1.GetMemoRequest - 9, // 33: memos.api.v1.MemoService.UpdateMemo:input_type -> memos.api.v1.UpdateMemoRequest - 10, // 34: memos.api.v1.MemoService.DeleteMemo:input_type -> memos.api.v1.DeleteMemoRequest - 11, // 35: memos.api.v1.MemoService.SetMemoAttachments:input_type -> memos.api.v1.SetMemoAttachmentsRequest - 12, // 36: memos.api.v1.MemoService.ListMemoAttachments:input_type -> memos.api.v1.ListMemoAttachmentsRequest - 15, // 37: memos.api.v1.MemoService.SetMemoRelations:input_type -> memos.api.v1.SetMemoRelationsRequest - 16, // 38: memos.api.v1.MemoService.ListMemoRelations:input_type -> memos.api.v1.ListMemoRelationsRequest - 18, // 39: memos.api.v1.MemoService.CreateMemoComment:input_type -> memos.api.v1.CreateMemoCommentRequest - 19, // 40: memos.api.v1.MemoService.ListMemoComments:input_type -> memos.api.v1.ListMemoCommentsRequest - 21, // 41: memos.api.v1.MemoService.ListMemoReactions:input_type -> memos.api.v1.ListMemoReactionsRequest - 23, // 42: memos.api.v1.MemoService.UpsertMemoReaction:input_type -> memos.api.v1.UpsertMemoReactionRequest - 24, // 43: memos.api.v1.MemoService.DeleteMemoReaction:input_type -> memos.api.v1.DeleteMemoReactionRequest - 26, // 44: memos.api.v1.MemoService.CreateMemoShare:input_type -> memos.api.v1.CreateMemoShareRequest - 27, // 45: memos.api.v1.MemoService.ListMemoShares:input_type -> memos.api.v1.ListMemoSharesRequest - 29, // 46: memos.api.v1.MemoService.DeleteMemoShare:input_type -> memos.api.v1.DeleteMemoShareRequest - 30, // 47: memos.api.v1.MemoService.GetMemoByShare:input_type -> memos.api.v1.GetMemoByShareRequest - 3, // 48: memos.api.v1.MemoService.CreateMemo:output_type -> memos.api.v1.Memo - 7, // 49: memos.api.v1.MemoService.ListMemos:output_type -> memos.api.v1.ListMemosResponse - 3, // 50: memos.api.v1.MemoService.GetMemo:output_type -> memos.api.v1.Memo - 3, // 51: memos.api.v1.MemoService.UpdateMemo:output_type -> memos.api.v1.Memo - 37, // 52: memos.api.v1.MemoService.DeleteMemo:output_type -> google.protobuf.Empty - 37, // 53: memos.api.v1.MemoService.SetMemoAttachments:output_type -> google.protobuf.Empty - 13, // 54: memos.api.v1.MemoService.ListMemoAttachments:output_type -> memos.api.v1.ListMemoAttachmentsResponse - 37, // 55: memos.api.v1.MemoService.SetMemoRelations:output_type -> google.protobuf.Empty - 17, // 56: memos.api.v1.MemoService.ListMemoRelations:output_type -> memos.api.v1.ListMemoRelationsResponse - 3, // 57: memos.api.v1.MemoService.CreateMemoComment:output_type -> memos.api.v1.Memo - 20, // 58: memos.api.v1.MemoService.ListMemoComments:output_type -> memos.api.v1.ListMemoCommentsResponse - 22, // 59: memos.api.v1.MemoService.ListMemoReactions:output_type -> memos.api.v1.ListMemoReactionsResponse - 2, // 60: memos.api.v1.MemoService.UpsertMemoReaction:output_type -> memos.api.v1.Reaction - 37, // 61: memos.api.v1.MemoService.DeleteMemoReaction:output_type -> google.protobuf.Empty - 25, // 62: memos.api.v1.MemoService.CreateMemoShare:output_type -> memos.api.v1.MemoShare - 28, // 63: memos.api.v1.MemoService.ListMemoShares:output_type -> memos.api.v1.ListMemoSharesResponse - 37, // 64: memos.api.v1.MemoService.DeleteMemoShare:output_type -> google.protobuf.Empty - 3, // 65: memos.api.v1.MemoService.GetMemoByShare:output_type -> memos.api.v1.Memo - 48, // [48:66] is the sub-list for method output_type - 30, // [30:48] is the sub-list for method input_type - 30, // [30:30] is the sub-list for extension type_name - 30, // [30:30] is the sub-list for extension extendee - 0, // [0:30] is the sub-list for field type_name + 34, // 30: memos.api.v1.BatchGetLinkMetadataResponse.link_metadata:type_name -> memos.api.v1.LinkMetadata + 5, // 31: memos.api.v1.MemoService.CreateMemo:input_type -> memos.api.v1.CreateMemoRequest + 6, // 32: memos.api.v1.MemoService.ListMemos:input_type -> memos.api.v1.ListMemosRequest + 8, // 33: memos.api.v1.MemoService.GetMemo:input_type -> memos.api.v1.GetMemoRequest + 9, // 34: memos.api.v1.MemoService.UpdateMemo:input_type -> memos.api.v1.UpdateMemoRequest + 10, // 35: memos.api.v1.MemoService.DeleteMemo:input_type -> memos.api.v1.DeleteMemoRequest + 11, // 36: memos.api.v1.MemoService.SetMemoAttachments:input_type -> memos.api.v1.SetMemoAttachmentsRequest + 12, // 37: memos.api.v1.MemoService.ListMemoAttachments:input_type -> memos.api.v1.ListMemoAttachmentsRequest + 15, // 38: memos.api.v1.MemoService.SetMemoRelations:input_type -> memos.api.v1.SetMemoRelationsRequest + 16, // 39: memos.api.v1.MemoService.ListMemoRelations:input_type -> memos.api.v1.ListMemoRelationsRequest + 18, // 40: memos.api.v1.MemoService.CreateMemoComment:input_type -> memos.api.v1.CreateMemoCommentRequest + 19, // 41: memos.api.v1.MemoService.ListMemoComments:input_type -> memos.api.v1.ListMemoCommentsRequest + 21, // 42: memos.api.v1.MemoService.ListMemoReactions:input_type -> memos.api.v1.ListMemoReactionsRequest + 23, // 43: memos.api.v1.MemoService.UpsertMemoReaction:input_type -> memos.api.v1.UpsertMemoReactionRequest + 24, // 44: memos.api.v1.MemoService.DeleteMemoReaction:input_type -> memos.api.v1.DeleteMemoReactionRequest + 26, // 45: memos.api.v1.MemoService.CreateMemoShare:input_type -> memos.api.v1.CreateMemoShareRequest + 27, // 46: memos.api.v1.MemoService.ListMemoShares:input_type -> memos.api.v1.ListMemoSharesRequest + 29, // 47: memos.api.v1.MemoService.DeleteMemoShare:input_type -> memos.api.v1.DeleteMemoShareRequest + 30, // 48: memos.api.v1.MemoService.GetMemoByShare:input_type -> memos.api.v1.GetMemoByShareRequest + 31, // 49: memos.api.v1.MemoService.GetLinkMetadata:input_type -> memos.api.v1.GetLinkMetadataRequest + 32, // 50: memos.api.v1.MemoService.BatchGetLinkMetadata:input_type -> memos.api.v1.BatchGetLinkMetadataRequest + 3, // 51: memos.api.v1.MemoService.CreateMemo:output_type -> memos.api.v1.Memo + 7, // 52: memos.api.v1.MemoService.ListMemos:output_type -> memos.api.v1.ListMemosResponse + 3, // 53: memos.api.v1.MemoService.GetMemo:output_type -> memos.api.v1.Memo + 3, // 54: memos.api.v1.MemoService.UpdateMemo:output_type -> memos.api.v1.Memo + 41, // 55: memos.api.v1.MemoService.DeleteMemo:output_type -> google.protobuf.Empty + 41, // 56: memos.api.v1.MemoService.SetMemoAttachments:output_type -> google.protobuf.Empty + 13, // 57: memos.api.v1.MemoService.ListMemoAttachments:output_type -> memos.api.v1.ListMemoAttachmentsResponse + 41, // 58: memos.api.v1.MemoService.SetMemoRelations:output_type -> google.protobuf.Empty + 17, // 59: memos.api.v1.MemoService.ListMemoRelations:output_type -> memos.api.v1.ListMemoRelationsResponse + 3, // 60: memos.api.v1.MemoService.CreateMemoComment:output_type -> memos.api.v1.Memo + 20, // 61: memos.api.v1.MemoService.ListMemoComments:output_type -> memos.api.v1.ListMemoCommentsResponse + 22, // 62: memos.api.v1.MemoService.ListMemoReactions:output_type -> memos.api.v1.ListMemoReactionsResponse + 2, // 63: memos.api.v1.MemoService.UpsertMemoReaction:output_type -> memos.api.v1.Reaction + 41, // 64: memos.api.v1.MemoService.DeleteMemoReaction:output_type -> google.protobuf.Empty + 25, // 65: memos.api.v1.MemoService.CreateMemoShare:output_type -> memos.api.v1.MemoShare + 28, // 66: memos.api.v1.MemoService.ListMemoShares:output_type -> memos.api.v1.ListMemoSharesResponse + 41, // 67: memos.api.v1.MemoService.DeleteMemoShare:output_type -> google.protobuf.Empty + 3, // 68: memos.api.v1.MemoService.GetMemoByShare:output_type -> memos.api.v1.Memo + 34, // 69: memos.api.v1.MemoService.GetLinkMetadata:output_type -> memos.api.v1.LinkMetadata + 33, // 70: memos.api.v1.MemoService.BatchGetLinkMetadata:output_type -> memos.api.v1.BatchGetLinkMetadataResponse + 51, // [51:71] is the sub-list for method output_type + 31, // [31:51] is the sub-list for method input_type + 31, // [31:31] is the sub-list for extension type_name + 31, // [31:31] is the sub-list for extension extendee + 0, // [0:31] is the sub-list for field type_name } func init() { file_api_v1_memo_service_proto_init() } @@ -2451,7 +2680,7 @@ func file_api_v1_memo_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v1_memo_service_proto_rawDesc), len(file_api_v1_memo_service_proto_rawDesc)), NumEnums: 2, - NumMessages: 31, + NumMessages: 35, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/gen/api/v1/memo_service.pb.gw.go b/proto/gen/api/v1/memo_service.pb.gw.go index ed0fd6966..b0beb9f71 100644 --- a/proto/gen/api/v1/memo_service.pb.gw.go +++ b/proto/gen/api/v1/memo_service.pb.gw.go @@ -891,6 +891,68 @@ func local_request_MemoService_GetMemoByShare_0(ctx context.Context, marshaler r return msg, metadata, err } +var filter_MemoService_GetLinkMetadata_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_MemoService_GetLinkMetadata_0(ctx context.Context, marshaler runtime.Marshaler, client MemoServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetLinkMetadataRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_MemoService_GetLinkMetadata_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.GetLinkMetadata(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_MemoService_GetLinkMetadata_0(ctx context.Context, marshaler runtime.Marshaler, server MemoServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetLinkMetadataRequest + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_MemoService_GetLinkMetadata_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.GetLinkMetadata(ctx, &protoReq) + return msg, metadata, err +} + +func request_MemoService_BatchGetLinkMetadata_0(ctx context.Context, marshaler runtime.Marshaler, client MemoServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq BatchGetLinkMetadataRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.BatchGetLinkMetadata(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_MemoService_BatchGetLinkMetadata_0(ctx context.Context, marshaler runtime.Marshaler, server MemoServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq BatchGetLinkMetadataRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.BatchGetLinkMetadata(ctx, &protoReq) + return msg, metadata, err +} + // RegisterMemoServiceHandlerServer registers the http handlers for service MemoService to "mux". // UnaryRPC :call MemoServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -1257,6 +1319,46 @@ func RegisterMemoServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux } forward_MemoService_GetMemoByShare_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodGet, pattern_MemoService_GetLinkMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.MemoService/GetLinkMetadata", runtime.WithHTTPPathPattern("/api/v1/memos/-/linkMetadata")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_MemoService_GetLinkMetadata_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_MemoService_GetLinkMetadata_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_MemoService_BatchGetLinkMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.MemoService/BatchGetLinkMetadata", runtime.WithHTTPPathPattern("/api/v1/memos/-/linkMetadata:batchGet")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_MemoService_BatchGetLinkMetadata_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_MemoService_BatchGetLinkMetadata_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } @@ -1603,47 +1705,85 @@ func RegisterMemoServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux } forward_MemoService_GetMemoByShare_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodGet, pattern_MemoService_GetLinkMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.MemoService/GetLinkMetadata", runtime.WithHTTPPathPattern("/api/v1/memos/-/linkMetadata")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_MemoService_GetLinkMetadata_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_MemoService_GetLinkMetadata_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_MemoService_BatchGetLinkMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.MemoService/BatchGetLinkMetadata", runtime.WithHTTPPathPattern("/api/v1/memos/-/linkMetadata:batchGet")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_MemoService_BatchGetLinkMetadata_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_MemoService_BatchGetLinkMetadata_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } var ( - pattern_MemoService_CreateMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "memos"}, "")) - pattern_MemoService_ListMemos_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "memos"}, "")) - pattern_MemoService_GetMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "memos", "name"}, "")) - pattern_MemoService_UpdateMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "memos", "memo.name"}, "")) - pattern_MemoService_DeleteMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "memos", "name"}, "")) - pattern_MemoService_SetMemoAttachments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "attachments"}, "")) - pattern_MemoService_ListMemoAttachments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "attachments"}, "")) - pattern_MemoService_SetMemoRelations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "relations"}, "")) - pattern_MemoService_ListMemoRelations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "relations"}, "")) - pattern_MemoService_CreateMemoComment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "comments"}, "")) - pattern_MemoService_ListMemoComments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "comments"}, "")) - pattern_MemoService_ListMemoReactions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "reactions"}, "")) - pattern_MemoService_UpsertMemoReaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "reactions"}, "")) - pattern_MemoService_DeleteMemoReaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "memos", "reactions", "name"}, "")) - pattern_MemoService_CreateMemoShare_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "parent", "shares"}, "")) - pattern_MemoService_ListMemoShares_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "parent", "shares"}, "")) - pattern_MemoService_DeleteMemoShare_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "memos", "shares", "name"}, "")) - pattern_MemoService_GetMemoByShare_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "shares", "share_id"}, "")) + pattern_MemoService_CreateMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "memos"}, "")) + pattern_MemoService_ListMemos_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "memos"}, "")) + pattern_MemoService_GetMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "memos", "name"}, "")) + pattern_MemoService_UpdateMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "memos", "memo.name"}, "")) + pattern_MemoService_DeleteMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "memos", "name"}, "")) + pattern_MemoService_SetMemoAttachments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "attachments"}, "")) + pattern_MemoService_ListMemoAttachments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "attachments"}, "")) + pattern_MemoService_SetMemoRelations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "relations"}, "")) + pattern_MemoService_ListMemoRelations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "relations"}, "")) + pattern_MemoService_CreateMemoComment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "comments"}, "")) + pattern_MemoService_ListMemoComments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "comments"}, "")) + pattern_MemoService_ListMemoReactions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "reactions"}, "")) + pattern_MemoService_UpsertMemoReaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "reactions"}, "")) + pattern_MemoService_DeleteMemoReaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "memos", "reactions", "name"}, "")) + pattern_MemoService_CreateMemoShare_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "parent", "shares"}, "")) + pattern_MemoService_ListMemoShares_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "parent", "shares"}, "")) + pattern_MemoService_DeleteMemoShare_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "memos", "shares", "name"}, "")) + pattern_MemoService_GetMemoByShare_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v1", "shares", "share_id"}, "")) + pattern_MemoService_GetLinkMetadata_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"api", "v1", "memos", "-", "linkMetadata"}, "")) + pattern_MemoService_BatchGetLinkMetadata_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"api", "v1", "memos", "-", "linkMetadata"}, "batchGet")) ) var ( - forward_MemoService_CreateMemo_0 = runtime.ForwardResponseMessage - forward_MemoService_ListMemos_0 = runtime.ForwardResponseMessage - forward_MemoService_GetMemo_0 = runtime.ForwardResponseMessage - forward_MemoService_UpdateMemo_0 = runtime.ForwardResponseMessage - forward_MemoService_DeleteMemo_0 = runtime.ForwardResponseMessage - forward_MemoService_SetMemoAttachments_0 = runtime.ForwardResponseMessage - forward_MemoService_ListMemoAttachments_0 = runtime.ForwardResponseMessage - forward_MemoService_SetMemoRelations_0 = runtime.ForwardResponseMessage - forward_MemoService_ListMemoRelations_0 = runtime.ForwardResponseMessage - forward_MemoService_CreateMemoComment_0 = runtime.ForwardResponseMessage - forward_MemoService_ListMemoComments_0 = runtime.ForwardResponseMessage - forward_MemoService_ListMemoReactions_0 = runtime.ForwardResponseMessage - forward_MemoService_UpsertMemoReaction_0 = runtime.ForwardResponseMessage - forward_MemoService_DeleteMemoReaction_0 = runtime.ForwardResponseMessage - forward_MemoService_CreateMemoShare_0 = runtime.ForwardResponseMessage - forward_MemoService_ListMemoShares_0 = runtime.ForwardResponseMessage - forward_MemoService_DeleteMemoShare_0 = runtime.ForwardResponseMessage - forward_MemoService_GetMemoByShare_0 = runtime.ForwardResponseMessage + forward_MemoService_CreateMemo_0 = runtime.ForwardResponseMessage + forward_MemoService_ListMemos_0 = runtime.ForwardResponseMessage + forward_MemoService_GetMemo_0 = runtime.ForwardResponseMessage + forward_MemoService_UpdateMemo_0 = runtime.ForwardResponseMessage + forward_MemoService_DeleteMemo_0 = runtime.ForwardResponseMessage + forward_MemoService_SetMemoAttachments_0 = runtime.ForwardResponseMessage + forward_MemoService_ListMemoAttachments_0 = runtime.ForwardResponseMessage + forward_MemoService_SetMemoRelations_0 = runtime.ForwardResponseMessage + forward_MemoService_ListMemoRelations_0 = runtime.ForwardResponseMessage + forward_MemoService_CreateMemoComment_0 = runtime.ForwardResponseMessage + forward_MemoService_ListMemoComments_0 = runtime.ForwardResponseMessage + forward_MemoService_ListMemoReactions_0 = runtime.ForwardResponseMessage + forward_MemoService_UpsertMemoReaction_0 = runtime.ForwardResponseMessage + forward_MemoService_DeleteMemoReaction_0 = runtime.ForwardResponseMessage + forward_MemoService_CreateMemoShare_0 = runtime.ForwardResponseMessage + forward_MemoService_ListMemoShares_0 = runtime.ForwardResponseMessage + forward_MemoService_DeleteMemoShare_0 = runtime.ForwardResponseMessage + forward_MemoService_GetMemoByShare_0 = runtime.ForwardResponseMessage + forward_MemoService_GetLinkMetadata_0 = runtime.ForwardResponseMessage + forward_MemoService_BatchGetLinkMetadata_0 = runtime.ForwardResponseMessage ) diff --git a/proto/gen/api/v1/memo_service_grpc.pb.go b/proto/gen/api/v1/memo_service_grpc.pb.go index 79a07d28b..25104707e 100644 --- a/proto/gen/api/v1/memo_service_grpc.pb.go +++ b/proto/gen/api/v1/memo_service_grpc.pb.go @@ -20,24 +20,26 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - MemoService_CreateMemo_FullMethodName = "/memos.api.v1.MemoService/CreateMemo" - MemoService_ListMemos_FullMethodName = "/memos.api.v1.MemoService/ListMemos" - MemoService_GetMemo_FullMethodName = "/memos.api.v1.MemoService/GetMemo" - MemoService_UpdateMemo_FullMethodName = "/memos.api.v1.MemoService/UpdateMemo" - MemoService_DeleteMemo_FullMethodName = "/memos.api.v1.MemoService/DeleteMemo" - MemoService_SetMemoAttachments_FullMethodName = "/memos.api.v1.MemoService/SetMemoAttachments" - MemoService_ListMemoAttachments_FullMethodName = "/memos.api.v1.MemoService/ListMemoAttachments" - MemoService_SetMemoRelations_FullMethodName = "/memos.api.v1.MemoService/SetMemoRelations" - MemoService_ListMemoRelations_FullMethodName = "/memos.api.v1.MemoService/ListMemoRelations" - MemoService_CreateMemoComment_FullMethodName = "/memos.api.v1.MemoService/CreateMemoComment" - MemoService_ListMemoComments_FullMethodName = "/memos.api.v1.MemoService/ListMemoComments" - MemoService_ListMemoReactions_FullMethodName = "/memos.api.v1.MemoService/ListMemoReactions" - MemoService_UpsertMemoReaction_FullMethodName = "/memos.api.v1.MemoService/UpsertMemoReaction" - MemoService_DeleteMemoReaction_FullMethodName = "/memos.api.v1.MemoService/DeleteMemoReaction" - MemoService_CreateMemoShare_FullMethodName = "/memos.api.v1.MemoService/CreateMemoShare" - MemoService_ListMemoShares_FullMethodName = "/memos.api.v1.MemoService/ListMemoShares" - MemoService_DeleteMemoShare_FullMethodName = "/memos.api.v1.MemoService/DeleteMemoShare" - MemoService_GetMemoByShare_FullMethodName = "/memos.api.v1.MemoService/GetMemoByShare" + MemoService_CreateMemo_FullMethodName = "/memos.api.v1.MemoService/CreateMemo" + MemoService_ListMemos_FullMethodName = "/memos.api.v1.MemoService/ListMemos" + MemoService_GetMemo_FullMethodName = "/memos.api.v1.MemoService/GetMemo" + MemoService_UpdateMemo_FullMethodName = "/memos.api.v1.MemoService/UpdateMemo" + MemoService_DeleteMemo_FullMethodName = "/memos.api.v1.MemoService/DeleteMemo" + MemoService_SetMemoAttachments_FullMethodName = "/memos.api.v1.MemoService/SetMemoAttachments" + MemoService_ListMemoAttachments_FullMethodName = "/memos.api.v1.MemoService/ListMemoAttachments" + MemoService_SetMemoRelations_FullMethodName = "/memos.api.v1.MemoService/SetMemoRelations" + MemoService_ListMemoRelations_FullMethodName = "/memos.api.v1.MemoService/ListMemoRelations" + MemoService_CreateMemoComment_FullMethodName = "/memos.api.v1.MemoService/CreateMemoComment" + MemoService_ListMemoComments_FullMethodName = "/memos.api.v1.MemoService/ListMemoComments" + MemoService_ListMemoReactions_FullMethodName = "/memos.api.v1.MemoService/ListMemoReactions" + MemoService_UpsertMemoReaction_FullMethodName = "/memos.api.v1.MemoService/UpsertMemoReaction" + MemoService_DeleteMemoReaction_FullMethodName = "/memos.api.v1.MemoService/DeleteMemoReaction" + MemoService_CreateMemoShare_FullMethodName = "/memos.api.v1.MemoService/CreateMemoShare" + MemoService_ListMemoShares_FullMethodName = "/memos.api.v1.MemoService/ListMemoShares" + MemoService_DeleteMemoShare_FullMethodName = "/memos.api.v1.MemoService/DeleteMemoShare" + MemoService_GetMemoByShare_FullMethodName = "/memos.api.v1.MemoService/GetMemoByShare" + MemoService_GetLinkMetadata_FullMethodName = "/memos.api.v1.MemoService/GetLinkMetadata" + MemoService_BatchGetLinkMetadata_FullMethodName = "/memos.api.v1.MemoService/BatchGetLinkMetadata" ) // MemoServiceClient is the client API for MemoService service. @@ -81,6 +83,10 @@ type MemoServiceClient interface { // GetMemoByShare resolves a share token to its memo. No authentication required. // Returns NOT_FOUND if the token is invalid or expired. GetMemoByShare(ctx context.Context, in *GetMemoByShareRequest, opts ...grpc.CallOption) (*Memo, error) + // GetLinkMetadata gets metadata for a link. + GetLinkMetadata(ctx context.Context, in *GetLinkMetadataRequest, opts ...grpc.CallOption) (*LinkMetadata, error) + // BatchGetLinkMetadata gets metadata for links. + BatchGetLinkMetadata(ctx context.Context, in *BatchGetLinkMetadataRequest, opts ...grpc.CallOption) (*BatchGetLinkMetadataResponse, error) } type memoServiceClient struct { @@ -271,6 +277,26 @@ func (c *memoServiceClient) GetMemoByShare(ctx context.Context, in *GetMemoBySha return out, nil } +func (c *memoServiceClient) GetLinkMetadata(ctx context.Context, in *GetLinkMetadataRequest, opts ...grpc.CallOption) (*LinkMetadata, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LinkMetadata) + err := c.cc.Invoke(ctx, MemoService_GetLinkMetadata_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *memoServiceClient) BatchGetLinkMetadata(ctx context.Context, in *BatchGetLinkMetadataRequest, opts ...grpc.CallOption) (*BatchGetLinkMetadataResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BatchGetLinkMetadataResponse) + err := c.cc.Invoke(ctx, MemoService_BatchGetLinkMetadata_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // MemoServiceServer is the server API for MemoService service. // All implementations must embed UnimplementedMemoServiceServer // for forward compatibility. @@ -312,6 +338,10 @@ type MemoServiceServer interface { // GetMemoByShare resolves a share token to its memo. No authentication required. // Returns NOT_FOUND if the token is invalid or expired. GetMemoByShare(context.Context, *GetMemoByShareRequest) (*Memo, error) + // GetLinkMetadata gets metadata for a link. + GetLinkMetadata(context.Context, *GetLinkMetadataRequest) (*LinkMetadata, error) + // BatchGetLinkMetadata gets metadata for links. + BatchGetLinkMetadata(context.Context, *BatchGetLinkMetadataRequest) (*BatchGetLinkMetadataResponse, error) mustEmbedUnimplementedMemoServiceServer() } @@ -376,6 +406,12 @@ func (UnimplementedMemoServiceServer) DeleteMemoShare(context.Context, *DeleteMe func (UnimplementedMemoServiceServer) GetMemoByShare(context.Context, *GetMemoByShareRequest) (*Memo, error) { return nil, status.Error(codes.Unimplemented, "method GetMemoByShare not implemented") } +func (UnimplementedMemoServiceServer) GetLinkMetadata(context.Context, *GetLinkMetadataRequest) (*LinkMetadata, error) { + return nil, status.Error(codes.Unimplemented, "method GetLinkMetadata not implemented") +} +func (UnimplementedMemoServiceServer) BatchGetLinkMetadata(context.Context, *BatchGetLinkMetadataRequest) (*BatchGetLinkMetadataResponse, error) { + return nil, status.Error(codes.Unimplemented, "method BatchGetLinkMetadata not implemented") +} func (UnimplementedMemoServiceServer) mustEmbedUnimplementedMemoServiceServer() {} func (UnimplementedMemoServiceServer) testEmbeddedByValue() {} @@ -721,6 +757,42 @@ func _MemoService_GetMemoByShare_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _MemoService_GetLinkMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetLinkMetadataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MemoServiceServer).GetLinkMetadata(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: MemoService_GetLinkMetadata_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MemoServiceServer).GetLinkMetadata(ctx, req.(*GetLinkMetadataRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _MemoService_BatchGetLinkMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BatchGetLinkMetadataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MemoServiceServer).BatchGetLinkMetadata(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: MemoService_BatchGetLinkMetadata_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MemoServiceServer).BatchGetLinkMetadata(ctx, req.(*BatchGetLinkMetadataRequest)) + } + return interceptor(ctx, in, info, handler) +} + // MemoService_ServiceDesc is the grpc.ServiceDesc for MemoService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -800,6 +872,14 @@ var MemoService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetMemoByShare", Handler: _MemoService_GetMemoByShare_Handler, }, + { + MethodName: "GetLinkMetadata", + Handler: _MemoService_GetLinkMetadata_Handler, + }, + { + MethodName: "BatchGetLinkMetadata", + Handler: _MemoService_BatchGetLinkMetadata_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "api/v1/memo_service.proto", diff --git a/proto/gen/openapi.yaml b/proto/gen/openapi.yaml index e70901d25..3d1c2131b 100644 --- a/proto/gen/openapi.yaml +++ b/proto/gen/openapi.yaml @@ -643,6 +643,56 @@ paths: application/json: schema: $ref: '#/components/schemas/Status' + /api/v1/memos/-/linkMetadata: + get: + tags: + - MemoService + description: GetLinkMetadata gets metadata for a link. + operationId: MemoService_GetLinkMetadata + parameters: + - name: url + in: query + description: Required. The link URL. + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/LinkMetadata' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /api/v1/memos/-/linkMetadata:batchGet: + post: + tags: + - MemoService + description: BatchGetLinkMetadata gets metadata for links. + operationId: MemoService_BatchGetLinkMetadata + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BatchGetLinkMetadataRequest' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/BatchGetLinkMetadataResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' /api/v1/memos/{memo}: get: tags: @@ -2217,6 +2267,24 @@ components: type: array items: type: string + BatchGetLinkMetadataRequest: + required: + - urls + type: object + properties: + urls: + type: array + items: + type: string + description: Required. The link URLs. + BatchGetLinkMetadataResponse: + type: object + properties: + linkMetadata: + type: array + items: + $ref: '#/components/schemas/LinkMetadata' + description: The link metadata list, in the same order as the input URLs. BatchGetUsersRequest: type: object properties: @@ -2699,6 +2767,21 @@ components: so a single entry like "project/.*" matches all tags under that prefix. Exact tag names are also valid (they are trivially valid regex patterns). description: Tag metadata configuration. + LinkMetadata: + type: object + properties: + url: + type: string + description: The original link URL. + title: + type: string + description: The link title. + description: + type: string + description: The link description. + image: + type: string + description: The link image URL. LinkedIdentity: type: object properties: diff --git a/server/router/api/v1/acl_config.go b/server/router/api/v1/acl_config.go index d1a2b5540..eabf83ba2 100644 --- a/server/router/api/v1/acl_config.go +++ b/server/router/api/v1/acl_config.go @@ -29,9 +29,11 @@ var PublicMethods = map[string]struct{}{ "/memos.api.v1.IdentityProviderService/ListIdentityProviders": {}, // Memo Service - public memos (visibility filtering done in service layer) - "/memos.api.v1.MemoService/GetMemo": {}, - "/memos.api.v1.MemoService/ListMemos": {}, - "/memos.api.v1.MemoService/ListMemoComments": {}, + "/memos.api.v1.MemoService/GetMemo": {}, + "/memos.api.v1.MemoService/ListMemos": {}, + "/memos.api.v1.MemoService/ListMemoComments": {}, + "/memos.api.v1.MemoService/GetLinkMetadata": {}, + "/memos.api.v1.MemoService/BatchGetLinkMetadata": {}, // Memo sharing - share-token endpoints require no authentication "/memos.api.v1.MemoService/GetMemoByShare": {}, diff --git a/server/router/api/v1/acl_config_test.go b/server/router/api/v1/acl_config_test.go index 53f898aec..483ec8f12 100644 --- a/server/router/api/v1/acl_config_test.go +++ b/server/router/api/v1/acl_config_test.go @@ -27,6 +27,8 @@ func TestPublicMethodsArePublic(t *testing.T) { // Memo Service "/memos.api.v1.MemoService/GetMemo", "/memos.api.v1.MemoService/ListMemos", + "/memos.api.v1.MemoService/GetLinkMetadata", + "/memos.api.v1.MemoService/BatchGetLinkMetadata", } for _, method := range publicMethods { diff --git a/server/router/api/v1/connect_services.go b/server/router/api/v1/connect_services.go index e8ccae66d..35793ee0e 100644 --- a/server/router/api/v1/connect_services.go +++ b/server/router/api/v1/connect_services.go @@ -415,6 +415,22 @@ func (s *ConnectServiceHandler) GetMemoByShare(ctx context.Context, req *connect return connect.NewResponse(resp), nil } +func (s *ConnectServiceHandler) GetLinkMetadata(ctx context.Context, req *connect.Request[v1pb.GetLinkMetadataRequest]) (*connect.Response[v1pb.LinkMetadata], error) { + resp, err := s.APIV1Service.GetLinkMetadata(ctx, req.Msg) + if err != nil { + return nil, convertGRPCError(err) + } + return connect.NewResponse(resp), nil +} + +func (s *ConnectServiceHandler) BatchGetLinkMetadata(ctx context.Context, req *connect.Request[v1pb.BatchGetLinkMetadataRequest]) (*connect.Response[v1pb.BatchGetLinkMetadataResponse], error) { + resp, err := s.APIV1Service.BatchGetLinkMetadata(ctx, req.Msg) + if err != nil { + return nil, convertGRPCError(err) + } + return connect.NewResponse(resp), nil +} + // AttachmentService func (s *ConnectServiceHandler) CreateAttachment(ctx context.Context, req *connect.Request[v1pb.CreateAttachmentRequest]) (*connect.Response[v1pb.Attachment], error) { diff --git a/server/router/api/v1/link_metadata_test.go b/server/router/api/v1/link_metadata_test.go new file mode 100644 index 000000000..c9c2ce848 --- /dev/null +++ b/server/router/api/v1/link_metadata_test.go @@ -0,0 +1,103 @@ +package v1 + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/usememos/memos/internal/httpgetter" + v1pb "github.com/usememos/memos/proto/gen/api/v1" +) + +func TestGetLinkMetadata(t *testing.T) { + originalFetchHTMLMeta := fetchHTMLMeta + t.Cleanup(func() { + fetchHTMLMeta = originalFetchHTMLMeta + }) + + fetchHTMLMeta = func(url string) (*httpgetter.HTMLMeta, error) { + require.Equal(t, "https://example.com/article", url) + return &httpgetter.HTMLMeta{ + Title: "Example title", + Description: "Example description", + Image: "https://example.com/cover.png", + }, nil + } + + metadata, err := (&APIV1Service{}).GetLinkMetadata(context.Background(), &v1pb.GetLinkMetadataRequest{ + Url: "https://example.com/article", + }) + require.NoError(t, err) + require.Equal(t, "https://example.com/article", metadata.Url) + require.Equal(t, "Example title", metadata.Title) + require.Equal(t, "Example description", metadata.Description) + require.Equal(t, "https://example.com/cover.png", metadata.Image) +} + +func TestGetLinkMetadataEmptyURL(t *testing.T) { + _, err := (&APIV1Service{}).GetLinkMetadata(context.Background(), &v1pb.GetLinkMetadataRequest{}) + require.Error(t, err) + require.Equal(t, codes.InvalidArgument, status.Code(err)) +} + +func TestGetLinkMetadataInternalURL(t *testing.T) { + _, err := (&APIV1Service{}).GetLinkMetadata(context.Background(), &v1pb.GetLinkMetadataRequest{ + Url: "http://192.168.0.1", + }) + require.Error(t, err) + require.Equal(t, codes.InvalidArgument, status.Code(err)) +} + +func TestBatchGetLinkMetadata(t *testing.T) { + originalFetchHTMLMeta := fetchHTMLMeta + t.Cleanup(func() { + fetchHTMLMeta = originalFetchHTMLMeta + }) + + var fetchedURLs []string + fetchHTMLMeta = func(url string) (*httpgetter.HTMLMeta, error) { + fetchedURLs = append(fetchedURLs, url) + return &httpgetter.HTMLMeta{ + Title: fmt.Sprintf("Title for %s", url), + Description: fmt.Sprintf("Description for %s", url), + Image: fmt.Sprintf("%s/cover.png", url), + }, nil + } + + response, err := (&APIV1Service{}).BatchGetLinkMetadata(context.Background(), &v1pb.BatchGetLinkMetadataRequest{ + Urls: []string{ + "https://example.com/one", + "https://example.com/two", + }, + }) + require.NoError(t, err) + require.Equal(t, []string{"https://example.com/one", "https://example.com/two"}, fetchedURLs) + require.Len(t, response.LinkMetadata, 2) + require.Equal(t, "https://example.com/one", response.LinkMetadata[0].Url) + require.Equal(t, "Title for https://example.com/one", response.LinkMetadata[0].Title) + require.Equal(t, "https://example.com/two", response.LinkMetadata[1].Url) + require.Equal(t, "Title for https://example.com/two", response.LinkMetadata[1].Title) +} + +func TestBatchGetLinkMetadataEmptyURLs(t *testing.T) { + _, err := (&APIV1Service{}).BatchGetLinkMetadata(context.Background(), &v1pb.BatchGetLinkMetadataRequest{}) + require.Error(t, err) + require.Equal(t, codes.InvalidArgument, status.Code(err)) +} + +func TestBatchGetLinkMetadataTooManyURLs(t *testing.T) { + urls := make([]string, maxBatchGetLinkMetadata+1) + for i := range urls { + urls[i] = fmt.Sprintf("https://example.com/%d", i) + } + + _, err := (&APIV1Service{}).BatchGetLinkMetadata(context.Background(), &v1pb.BatchGetLinkMetadataRequest{ + Urls: urls, + }) + require.Error(t, err) + require.Equal(t, codes.InvalidArgument, status.Code(err)) +} diff --git a/server/router/api/v1/memo_service.go b/server/router/api/v1/memo_service.go index 27382970d..420a59ffc 100644 --- a/server/router/api/v1/memo_service.go +++ b/server/router/api/v1/memo_service.go @@ -13,6 +13,7 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" + "github.com/usememos/memos/internal/httpgetter" "github.com/usememos/memos/internal/webhook" v1pb "github.com/usememos/memos/proto/gen/api/v1" storepb "github.com/usememos/memos/proto/gen/store" @@ -24,6 +25,10 @@ import ( // CreateMemo when it is called internally (e.g., from CreateMemoComment). type suppressSSEKey struct{} +const maxBatchGetLinkMetadata = 10 + +var fetchHTMLMeta = httpgetter.GetHTMLMeta + func withSuppressSSE(ctx context.Context) context.Context { return context.WithValue(ctx, suppressSSEKey{}, true) } @@ -382,6 +387,52 @@ func (s *APIV1Service) GetMemo(ctx context.Context, request *v1pb.GetMemoRequest return memoMessage, nil } +// GetLinkMetadata gets metadata for a link. +func (*APIV1Service) GetLinkMetadata(_ context.Context, request *v1pb.GetLinkMetadataRequest) (*v1pb.LinkMetadata, error) { + return getLinkMetadata(request.GetUrl()) +} + +// BatchGetLinkMetadata gets metadata for links. +func (*APIV1Service) BatchGetLinkMetadata(_ context.Context, request *v1pb.BatchGetLinkMetadataRequest) (*v1pb.BatchGetLinkMetadataResponse, error) { + if len(request.Urls) == 0 { + return nil, status.Errorf(codes.InvalidArgument, "urls are required") + } + if len(request.Urls) > maxBatchGetLinkMetadata { + return nil, status.Errorf(codes.InvalidArgument, "too many urls (max %d)", maxBatchGetLinkMetadata) + } + + linkMetadata := make([]*v1pb.LinkMetadata, 0, len(request.Urls)) + for _, url := range request.Urls { + metadata, err := getLinkMetadata(url) + if err != nil { + return nil, err + } + linkMetadata = append(linkMetadata, metadata) + } + + return &v1pb.BatchGetLinkMetadataResponse{ + LinkMetadata: linkMetadata, + }, nil +} + +func getLinkMetadata(inputURL string) (*v1pb.LinkMetadata, error) { + url := strings.TrimSpace(inputURL) + if url == "" { + return nil, status.Errorf(codes.InvalidArgument, "url is required") + } + htmlMeta, err := fetchHTMLMeta(url) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "failed to fetch link metadata: %v", err) + } + + return &v1pb.LinkMetadata{ + Url: inputURL, + Title: htmlMeta.Title, + Description: htmlMeta.Description, + Image: htmlMeta.Image, + }, nil +} + func (s *APIV1Service) UpdateMemo(ctx context.Context, request *v1pb.UpdateMemoRequest) (*v1pb.Memo, error) { memoUID, err := ExtractMemoUIDFromName(request.Memo.Name) if err != nil { diff --git a/web/src/types/proto/api/v1/memo_service_pb.ts b/web/src/types/proto/api/v1/memo_service_pb.ts index 592d466c0..7a5b294c1 100644 --- a/web/src/types/proto/api/v1/memo_service_pb.ts +++ b/web/src/types/proto/api/v1/memo_service_pb.ts @@ -20,7 +20,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file api/v1/memo_service.proto. */ export const file_api_v1_memo_service: GenFile = /*@__PURE__*/ - fileDesc("ChlhcGkvdjEvbWVtb19zZXJ2aWNlLnByb3RvEgxtZW1vcy5hcGkudjEipwIKCFJlYWN0aW9uEhQKBG5hbWUYASABKAlCBuBBA+BBCBIqCgdjcmVhdG9yGAIgASgJQhngQQP6QRMKEW1lbW9zLmFwaS52MS9Vc2VyEi0KCmNvbnRlbnRfaWQYAyABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL01lbW8SGgoNcmVhY3Rpb25fdHlwZRgEIAEoCUID4EECEjQKC2NyZWF0ZV90aW1lGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEID4EEDOljqQVUKFW1lbW9zLmFwaS52MS9SZWFjdGlvbhIhbWVtb3Mve21lbW99L3JlYWN0aW9ucy97cmVhY3Rpb259GgRuYW1lKglyZWFjdGlvbnMyCHJlYWN0aW9uIuoGCgRNZW1vEhEKBG5hbWUYASABKAlCA+BBCBInCgVzdGF0ZRgCIAEoDjITLm1lbW9zLmFwaS52MS5TdGF0ZUID4EECEioKB2NyZWF0b3IYAyABKAlCGeBBA/pBEwoRbWVtb3MuYXBpLnYxL1VzZXISNAoLY3JlYXRlX3RpbWUYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQESNAoLdXBkYXRlX3RpbWUYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQESFAoHY29udGVudBgHIAEoCUID4EECEjEKCnZpc2liaWxpdHkYCSABKA4yGC5tZW1vcy5hcGkudjEuVmlzaWJpbGl0eUID4EECEhEKBHRhZ3MYCiADKAlCA+BBAxITCgZwaW5uZWQYCyABKAhCA+BBARIyCgthdHRhY2htZW50cxgMIAMoCzIYLm1lbW9zLmFwaS52MS5BdHRhY2htZW50QgPgQQESMgoJcmVsYXRpb25zGA0gAygLMhoubWVtb3MuYXBpLnYxLk1lbW9SZWxhdGlvbkID4EEBEi4KCXJlYWN0aW9ucxgOIAMoCzIWLm1lbW9zLmFwaS52MS5SZWFjdGlvbkID4EEDEjIKCHByb3BlcnR5GA8gASgLMhsubWVtb3MuYXBpLnYxLk1lbW8uUHJvcGVydHlCA+BBAxIuCgZwYXJlbnQYECABKAlCGeBBA/pBEwoRbWVtb3MuYXBpLnYxL01lbW9IAIgBARIUCgdzbmlwcGV0GBEgASgJQgPgQQMSMgoIbG9jYXRpb24YEiABKAsyFi5tZW1vcy5hcGkudjEuTG9jYXRpb25CA+BBAUgBiAEBGnIKCFByb3BlcnR5EhAKCGhhc19saW5rGAEgASgIEhUKDWhhc190YXNrX2xpc3QYAiABKAgSEAoIaGFzX2NvZGUYAyABKAgSHAoUaGFzX2luY29tcGxldGVfdGFza3MYBCABKAgSDQoFdGl0bGUYBSABKAk6N+pBNAoRbWVtb3MuYXBpLnYxL01lbW8SDG1lbW9zL3ttZW1vfRoEbmFtZSoFbWVtb3MyBG1lbW9CCQoHX3BhcmVudEILCglfbG9jYXRpb25KBAgGEAdSDGRpc3BsYXlfdGltZSJTCghMb2NhdGlvbhIYCgtwbGFjZWhvbGRlchgBIAEoCUID4EEBEhUKCGxhdGl0dWRlGAIgASgBQgPgQQESFgoJbG9uZ2l0dWRlGAMgASgBQgPgQQEiUAoRQ3JlYXRlTWVtb1JlcXVlc3QSJQoEbWVtbxgBIAEoCzISLm1lbW9zLmFwaS52MS5NZW1vQgPgQQISFAoHbWVtb19pZBgCIAEoCUID4EEBIrMBChBMaXN0TWVtb3NSZXF1ZXN0EhYKCXBhZ2Vfc2l6ZRgBIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAiABKAlCA+BBARInCgVzdGF0ZRgDIAEoDjITLm1lbW9zLmFwaS52MS5TdGF0ZUID4EEBEhUKCG9yZGVyX2J5GAQgASgJQgPgQQESEwoGZmlsdGVyGAUgASgJQgPgQQESGQoMc2hvd19kZWxldGVkGAYgASgIQgPgQQEiTwoRTGlzdE1lbW9zUmVzcG9uc2USIQoFbWVtb3MYASADKAsyEi5tZW1vcy5hcGkudjEuTWVtbxIXCg9uZXh0X3BhZ2VfdG9rZW4YAiABKAkiOQoOR2V0TWVtb1JlcXVlc3QSJwoEbmFtZRgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvTWVtbyJwChFVcGRhdGVNZW1vUmVxdWVzdBIlCgRtZW1vGAEgASgLMhIubWVtb3MuYXBpLnYxLk1lbW9CA+BBAhI0Cgt1cGRhdGVfbWFzaxgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE1hc2tCA+BBAiJQChFEZWxldGVNZW1vUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9NZW1vEhIKBWZvcmNlGAIgASgIQgPgQQEieAoZU2V0TWVtb0F0dGFjaG1lbnRzUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9NZW1vEjIKC2F0dGFjaG1lbnRzGAIgAygLMhgubWVtb3MuYXBpLnYxLkF0dGFjaG1lbnRCA+BBAiJ2ChpMaXN0TWVtb0F0dGFjaG1lbnRzUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9NZW1vEhYKCXBhZ2Vfc2l6ZRgCIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAyABKAlCA+BBASJlChtMaXN0TWVtb0F0dGFjaG1lbnRzUmVzcG9uc2USLQoLYXR0YWNobWVudHMYASADKAsyGC5tZW1vcy5hcGkudjEuQXR0YWNobWVudBIXCg9uZXh0X3BhZ2VfdG9rZW4YAiABKAkiswIKDE1lbW9SZWxhdGlvbhIyCgRtZW1vGAEgASgLMh8ubWVtb3MuYXBpLnYxLk1lbW9SZWxhdGlvbi5NZW1vQgPgQQISOgoMcmVsYXRlZF9tZW1vGAIgASgLMh8ubWVtb3MuYXBpLnYxLk1lbW9SZWxhdGlvbi5NZW1vQgPgQQISMgoEdHlwZRgDIAEoDjIfLm1lbW9zLmFwaS52MS5NZW1vUmVsYXRpb24uVHlwZUID4EECGkUKBE1lbW8SJwoEbmFtZRgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvTWVtbxIUCgdzbmlwcGV0GAIgASgJQgPgQQMiOAoEVHlwZRIUChBUWVBFX1VOU1BFQ0lGSUVEEAASDQoJUkVGRVJFTkNFEAESCwoHQ09NTUVOVBACInYKF1NldE1lbW9SZWxhdGlvbnNSZXF1ZXN0EicKBG5hbWUYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL01lbW8SMgoJcmVsYXRpb25zGAIgAygLMhoubWVtb3MuYXBpLnYxLk1lbW9SZWxhdGlvbkID4EECInQKGExpc3RNZW1vUmVsYXRpb25zUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9NZW1vEhYKCXBhZ2Vfc2l6ZRgCIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAyABKAlCA+BBASJjChlMaXN0TWVtb1JlbGF0aW9uc1Jlc3BvbnNlEi0KCXJlbGF0aW9ucxgBIAMoCzIaLm1lbW9zLmFwaS52MS5NZW1vUmVsYXRpb24SFwoPbmV4dF9wYWdlX3Rva2VuGAIgASgJIoYBChhDcmVhdGVNZW1vQ29tbWVudFJlcXVlc3QSJwoEbmFtZRgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvTWVtbxIoCgdjb21tZW50GAIgASgLMhIubWVtb3MuYXBpLnYxLk1lbW9CA+BBAhIXCgpjb21tZW50X2lkGAMgASgJQgPgQQEiigEKF0xpc3RNZW1vQ29tbWVudHNSZXF1ZXN0EicKBG5hbWUYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL01lbW8SFgoJcGFnZV9zaXplGAIgASgFQgPgQQESFwoKcGFnZV90b2tlbhgDIAEoCUID4EEBEhUKCG9yZGVyX2J5GAQgASgJQgPgQQEiagoYTGlzdE1lbW9Db21tZW50c1Jlc3BvbnNlEiEKBW1lbW9zGAEgAygLMhIubWVtb3MuYXBpLnYxLk1lbW8SFwoPbmV4dF9wYWdlX3Rva2VuGAIgASgJEhIKCnRvdGFsX3NpemUYAyABKAUidAoYTGlzdE1lbW9SZWFjdGlvbnNSZXF1ZXN0EicKBG5hbWUYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL01lbW8SFgoJcGFnZV9zaXplGAIgASgFQgPgQQESFwoKcGFnZV90b2tlbhgDIAEoCUID4EEBInMKGUxpc3RNZW1vUmVhY3Rpb25zUmVzcG9uc2USKQoJcmVhY3Rpb25zGAEgAygLMhYubWVtb3MuYXBpLnYxLlJlYWN0aW9uEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRISCgp0b3RhbF9zaXplGAMgASgFInMKGVVwc2VydE1lbW9SZWFjdGlvblJlcXVlc3QSJwoEbmFtZRgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvTWVtbxItCghyZWFjdGlvbhgCIAEoCzIWLm1lbW9zLmFwaS52MS5SZWFjdGlvbkID4EECIkgKGURlbGV0ZU1lbW9SZWFjdGlvblJlcXVlc3QSKwoEbmFtZRgBIAEoCUId4EEC+kEXChVtZW1vcy5hcGkudjEvUmVhY3Rpb24i6AEKCU1lbW9TaGFyZRIRCgRuYW1lGAEgASgJQgPgQQgSNAoLY3JlYXRlX3RpbWUYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQMSOQoLZXhwaXJlX3RpbWUYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQFIAIgBATpH6kFEChZtZW1vcy5hcGkudjEvTWVtb1NoYXJlEhttZW1vcy97bWVtb30vc2hhcmVzL3tzaGFyZX0qBnNoYXJlczIFc2hhcmVCDgoMX2V4cGlyZV90aW1lInUKFkNyZWF0ZU1lbW9TaGFyZVJlcXVlc3QSKQoGcGFyZW50GAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9NZW1vEjAKCm1lbW9fc2hhcmUYAiABKAsyFy5tZW1vcy5hcGkudjEuTWVtb1NoYXJlQgPgQQIiQgoVTGlzdE1lbW9TaGFyZXNSZXF1ZXN0EikKBnBhcmVudBgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvTWVtbyJGChZMaXN0TWVtb1NoYXJlc1Jlc3BvbnNlEiwKC21lbW9fc2hhcmVzGAEgAygLMhcubWVtb3MuYXBpLnYxLk1lbW9TaGFyZSJGChZEZWxldGVNZW1vU2hhcmVSZXF1ZXN0EiwKBG5hbWUYASABKAlCHuBBAvpBGAoWbWVtb3MuYXBpLnYxL01lbW9TaGFyZSIuChVHZXRNZW1vQnlTaGFyZVJlcXVlc3QSFQoIc2hhcmVfaWQYASABKAlCA+BBAipQCgpWaXNpYmlsaXR5EhoKFlZJU0lCSUxJVFlfVU5TUEVDSUZJRUQQABILCgdQUklWQVRFEAESDQoJUFJPVEVDVEVEEAISCgoGUFVCTElDEAMy7hIKC01lbW9TZXJ2aWNlEmUKCkNyZWF0ZU1lbW8SHy5tZW1vcy5hcGkudjEuQ3JlYXRlTWVtb1JlcXVlc3QaEi5tZW1vcy5hcGkudjEuTWVtbyIi2kEEbWVtb4LT5JMCFToEbWVtbyINL2FwaS92MS9tZW1vcxJmCglMaXN0TWVtb3MSHi5tZW1vcy5hcGkudjEuTGlzdE1lbW9zUmVxdWVzdBofLm1lbW9zLmFwaS52MS5MaXN0TWVtb3NSZXNwb25zZSIY2kEAgtPkkwIPEg0vYXBpL3YxL21lbW9zEmIKB0dldE1lbW8SHC5tZW1vcy5hcGkudjEuR2V0TWVtb1JlcXVlc3QaEi5tZW1vcy5hcGkudjEuTWVtbyIl2kEEbmFtZYLT5JMCGBIWL2FwaS92MS97bmFtZT1tZW1vcy8qfRJ/CgpVcGRhdGVNZW1vEh8ubWVtb3MuYXBpLnYxLlVwZGF0ZU1lbW9SZXF1ZXN0GhIubWVtb3MuYXBpLnYxLk1lbW8iPNpBEG1lbW8sdXBkYXRlX21hc2uC0+STAiM6BG1lbW8yGy9hcGkvdjEve21lbW8ubmFtZT1tZW1vcy8qfRJsCgpEZWxldGVNZW1vEh8ubWVtb3MuYXBpLnYxLkRlbGV0ZU1lbW9SZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5IiXaQQRuYW1lgtPkkwIYKhYvYXBpL3YxL3tuYW1lPW1lbW9zLyp9EosBChJTZXRNZW1vQXR0YWNobWVudHMSJy5tZW1vcy5hcGkudjEuU2V0TWVtb0F0dGFjaG1lbnRzUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSI02kEEbmFtZYLT5JMCJzoBKjIiL2FwaS92MS97bmFtZT1tZW1vcy8qfS9hdHRhY2htZW50cxKdAQoTTGlzdE1lbW9BdHRhY2htZW50cxIoLm1lbW9zLmFwaS52MS5MaXN0TWVtb0F0dGFjaG1lbnRzUmVxdWVzdBopLm1lbW9zLmFwaS52MS5MaXN0TWVtb0F0dGFjaG1lbnRzUmVzcG9uc2UiMdpBBG5hbWWC0+STAiQSIi9hcGkvdjEve25hbWU9bWVtb3MvKn0vYXR0YWNobWVudHMShQEKEFNldE1lbW9SZWxhdGlvbnMSJS5tZW1vcy5hcGkudjEuU2V0TWVtb1JlbGF0aW9uc1JlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiMtpBBG5hbWWC0+STAiU6ASoyIC9hcGkvdjEve25hbWU9bWVtb3MvKn0vcmVsYXRpb25zEpUBChFMaXN0TWVtb1JlbGF0aW9ucxImLm1lbW9zLmFwaS52MS5MaXN0TWVtb1JlbGF0aW9uc1JlcXVlc3QaJy5tZW1vcy5hcGkudjEuTGlzdE1lbW9SZWxhdGlvbnNSZXNwb25zZSIv2kEEbmFtZYLT5JMCIhIgL2FwaS92MS97bmFtZT1tZW1vcy8qfS9yZWxhdGlvbnMSkAEKEUNyZWF0ZU1lbW9Db21tZW50EiYubWVtb3MuYXBpLnYxLkNyZWF0ZU1lbW9Db21tZW50UmVxdWVzdBoSLm1lbW9zLmFwaS52MS5NZW1vIj/aQQxuYW1lLGNvbW1lbnSC0+STAio6B2NvbW1lbnQiHy9hcGkvdjEve25hbWU9bWVtb3MvKn0vY29tbWVudHMSkQEKEExpc3RNZW1vQ29tbWVudHMSJS5tZW1vcy5hcGkudjEuTGlzdE1lbW9Db21tZW50c1JlcXVlc3QaJi5tZW1vcy5hcGkudjEuTGlzdE1lbW9Db21tZW50c1Jlc3BvbnNlIi7aQQRuYW1lgtPkkwIhEh8vYXBpL3YxL3tuYW1lPW1lbW9zLyp9L2NvbW1lbnRzEpUBChFMaXN0TWVtb1JlYWN0aW9ucxImLm1lbW9zLmFwaS52MS5MaXN0TWVtb1JlYWN0aW9uc1JlcXVlc3QaJy5tZW1vcy5hcGkudjEuTGlzdE1lbW9SZWFjdGlvbnNSZXNwb25zZSIv2kEEbmFtZYLT5JMCIhIgL2FwaS92MS97bmFtZT1tZW1vcy8qfS9yZWFjdGlvbnMSiQEKElVwc2VydE1lbW9SZWFjdGlvbhInLm1lbW9zLmFwaS52MS5VcHNlcnRNZW1vUmVhY3Rpb25SZXF1ZXN0GhYubWVtb3MuYXBpLnYxLlJlYWN0aW9uIjLaQQRuYW1lgtPkkwIlOgEqIiAvYXBpL3YxL3tuYW1lPW1lbW9zLyp9L3JlYWN0aW9ucxKIAQoSRGVsZXRlTWVtb1JlYWN0aW9uEicubWVtb3MuYXBpLnYxLkRlbGV0ZU1lbW9SZWFjdGlvblJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiMdpBBG5hbWWC0+STAiQqIi9hcGkvdjEve25hbWU9bWVtb3MvKi9yZWFjdGlvbnMvKn0SmQEKD0NyZWF0ZU1lbW9TaGFyZRIkLm1lbW9zLmFwaS52MS5DcmVhdGVNZW1vU2hhcmVSZXF1ZXN0GhcubWVtb3MuYXBpLnYxLk1lbW9TaGFyZSJH2kERcGFyZW50LG1lbW9fc2hhcmWC0+STAi06Cm1lbW9fc2hhcmUiHy9hcGkvdjEve3BhcmVudD1tZW1vcy8qfS9zaGFyZXMSjQEKDkxpc3RNZW1vU2hhcmVzEiMubWVtb3MuYXBpLnYxLkxpc3RNZW1vU2hhcmVzUmVxdWVzdBokLm1lbW9zLmFwaS52MS5MaXN0TWVtb1NoYXJlc1Jlc3BvbnNlIjDaQQZwYXJlbnSC0+STAiESHy9hcGkvdjEve3BhcmVudD1tZW1vcy8qfS9zaGFyZXMSfwoPRGVsZXRlTWVtb1NoYXJlEiQubWVtb3MuYXBpLnYxLkRlbGV0ZU1lbW9TaGFyZVJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiLtpBBG5hbWWC0+STAiEqHy9hcGkvdjEve25hbWU9bWVtb3MvKi9zaGFyZXMvKn0SbAoOR2V0TWVtb0J5U2hhcmUSIy5tZW1vcy5hcGkudjEuR2V0TWVtb0J5U2hhcmVSZXF1ZXN0GhIubWVtb3MuYXBpLnYxLk1lbW8iIYLT5JMCGxIZL2FwaS92MS9zaGFyZXMve3NoYXJlX2lkfUKoAQoQY29tLm1lbW9zLmFwaS52MUIQTWVtb1NlcnZpY2VQcm90b1ABWjBnaXRodWIuY29tL3VzZW1lbW9zL21lbW9zL3Byb3RvL2dlbi9hcGkvdjE7YXBpdjGiAgNNQViqAgxNZW1vcy5BcGkuVjHKAgxNZW1vc1xBcGlcVjHiAhhNZW1vc1xBcGlcVjFcR1BCTWV0YWRhdGHqAg5NZW1vczo6QXBpOjpWMWIGcHJvdG8z", [file_api_v1_attachment_service, file_api_v1_common, file_google_api_annotations, file_google_api_client, file_google_api_field_behavior, file_google_api_resource, file_google_protobuf_empty, file_google_protobuf_field_mask, file_google_protobuf_timestamp]); + fileDesc("ChlhcGkvdjEvbWVtb19zZXJ2aWNlLnByb3RvEgxtZW1vcy5hcGkudjEipwIKCFJlYWN0aW9uEhQKBG5hbWUYASABKAlCBuBBA+BBCBIqCgdjcmVhdG9yGAIgASgJQhngQQP6QRMKEW1lbW9zLmFwaS52MS9Vc2VyEi0KCmNvbnRlbnRfaWQYAyABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL01lbW8SGgoNcmVhY3Rpb25fdHlwZRgEIAEoCUID4EECEjQKC2NyZWF0ZV90aW1lGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEID4EEDOljqQVUKFW1lbW9zLmFwaS52MS9SZWFjdGlvbhIhbWVtb3Mve21lbW99L3JlYWN0aW9ucy97cmVhY3Rpb259GgRuYW1lKglyZWFjdGlvbnMyCHJlYWN0aW9uIuoGCgRNZW1vEhEKBG5hbWUYASABKAlCA+BBCBInCgVzdGF0ZRgCIAEoDjITLm1lbW9zLmFwaS52MS5TdGF0ZUID4EECEioKB2NyZWF0b3IYAyABKAlCGeBBA/pBEwoRbWVtb3MuYXBpLnYxL1VzZXISNAoLY3JlYXRlX3RpbWUYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQESNAoLdXBkYXRlX3RpbWUYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQESFAoHY29udGVudBgHIAEoCUID4EECEjEKCnZpc2liaWxpdHkYCSABKA4yGC5tZW1vcy5hcGkudjEuVmlzaWJpbGl0eUID4EECEhEKBHRhZ3MYCiADKAlCA+BBAxITCgZwaW5uZWQYCyABKAhCA+BBARIyCgthdHRhY2htZW50cxgMIAMoCzIYLm1lbW9zLmFwaS52MS5BdHRhY2htZW50QgPgQQESMgoJcmVsYXRpb25zGA0gAygLMhoubWVtb3MuYXBpLnYxLk1lbW9SZWxhdGlvbkID4EEBEi4KCXJlYWN0aW9ucxgOIAMoCzIWLm1lbW9zLmFwaS52MS5SZWFjdGlvbkID4EEDEjIKCHByb3BlcnR5GA8gASgLMhsubWVtb3MuYXBpLnYxLk1lbW8uUHJvcGVydHlCA+BBAxIuCgZwYXJlbnQYECABKAlCGeBBA/pBEwoRbWVtb3MuYXBpLnYxL01lbW9IAIgBARIUCgdzbmlwcGV0GBEgASgJQgPgQQMSMgoIbG9jYXRpb24YEiABKAsyFi5tZW1vcy5hcGkudjEuTG9jYXRpb25CA+BBAUgBiAEBGnIKCFByb3BlcnR5EhAKCGhhc19saW5rGAEgASgIEhUKDWhhc190YXNrX2xpc3QYAiABKAgSEAoIaGFzX2NvZGUYAyABKAgSHAoUaGFzX2luY29tcGxldGVfdGFza3MYBCABKAgSDQoFdGl0bGUYBSABKAk6N+pBNAoRbWVtb3MuYXBpLnYxL01lbW8SDG1lbW9zL3ttZW1vfRoEbmFtZSoFbWVtb3MyBG1lbW9CCQoHX3BhcmVudEILCglfbG9jYXRpb25KBAgGEAdSDGRpc3BsYXlfdGltZSJTCghMb2NhdGlvbhIYCgtwbGFjZWhvbGRlchgBIAEoCUID4EEBEhUKCGxhdGl0dWRlGAIgASgBQgPgQQESFgoJbG9uZ2l0dWRlGAMgASgBQgPgQQEiUAoRQ3JlYXRlTWVtb1JlcXVlc3QSJQoEbWVtbxgBIAEoCzISLm1lbW9zLmFwaS52MS5NZW1vQgPgQQISFAoHbWVtb19pZBgCIAEoCUID4EEBIrMBChBMaXN0TWVtb3NSZXF1ZXN0EhYKCXBhZ2Vfc2l6ZRgBIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAiABKAlCA+BBARInCgVzdGF0ZRgDIAEoDjITLm1lbW9zLmFwaS52MS5TdGF0ZUID4EEBEhUKCG9yZGVyX2J5GAQgASgJQgPgQQESEwoGZmlsdGVyGAUgASgJQgPgQQESGQoMc2hvd19kZWxldGVkGAYgASgIQgPgQQEiTwoRTGlzdE1lbW9zUmVzcG9uc2USIQoFbWVtb3MYASADKAsyEi5tZW1vcy5hcGkudjEuTWVtbxIXCg9uZXh0X3BhZ2VfdG9rZW4YAiABKAkiOQoOR2V0TWVtb1JlcXVlc3QSJwoEbmFtZRgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvTWVtbyJwChFVcGRhdGVNZW1vUmVxdWVzdBIlCgRtZW1vGAEgASgLMhIubWVtb3MuYXBpLnYxLk1lbW9CA+BBAhI0Cgt1cGRhdGVfbWFzaxgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5GaWVsZE1hc2tCA+BBAiJQChFEZWxldGVNZW1vUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9NZW1vEhIKBWZvcmNlGAIgASgIQgPgQQEieAoZU2V0TWVtb0F0dGFjaG1lbnRzUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9NZW1vEjIKC2F0dGFjaG1lbnRzGAIgAygLMhgubWVtb3MuYXBpLnYxLkF0dGFjaG1lbnRCA+BBAiJ2ChpMaXN0TWVtb0F0dGFjaG1lbnRzUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9NZW1vEhYKCXBhZ2Vfc2l6ZRgCIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAyABKAlCA+BBASJlChtMaXN0TWVtb0F0dGFjaG1lbnRzUmVzcG9uc2USLQoLYXR0YWNobWVudHMYASADKAsyGC5tZW1vcy5hcGkudjEuQXR0YWNobWVudBIXCg9uZXh0X3BhZ2VfdG9rZW4YAiABKAkiswIKDE1lbW9SZWxhdGlvbhIyCgRtZW1vGAEgASgLMh8ubWVtb3MuYXBpLnYxLk1lbW9SZWxhdGlvbi5NZW1vQgPgQQISOgoMcmVsYXRlZF9tZW1vGAIgASgLMh8ubWVtb3MuYXBpLnYxLk1lbW9SZWxhdGlvbi5NZW1vQgPgQQISMgoEdHlwZRgDIAEoDjIfLm1lbW9zLmFwaS52MS5NZW1vUmVsYXRpb24uVHlwZUID4EECGkUKBE1lbW8SJwoEbmFtZRgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvTWVtbxIUCgdzbmlwcGV0GAIgASgJQgPgQQMiOAoEVHlwZRIUChBUWVBFX1VOU1BFQ0lGSUVEEAASDQoJUkVGRVJFTkNFEAESCwoHQ09NTUVOVBACInYKF1NldE1lbW9SZWxhdGlvbnNSZXF1ZXN0EicKBG5hbWUYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL01lbW8SMgoJcmVsYXRpb25zGAIgAygLMhoubWVtb3MuYXBpLnYxLk1lbW9SZWxhdGlvbkID4EECInQKGExpc3RNZW1vUmVsYXRpb25zUmVxdWVzdBInCgRuYW1lGAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9NZW1vEhYKCXBhZ2Vfc2l6ZRgCIAEoBUID4EEBEhcKCnBhZ2VfdG9rZW4YAyABKAlCA+BBASJjChlMaXN0TWVtb1JlbGF0aW9uc1Jlc3BvbnNlEi0KCXJlbGF0aW9ucxgBIAMoCzIaLm1lbW9zLmFwaS52MS5NZW1vUmVsYXRpb24SFwoPbmV4dF9wYWdlX3Rva2VuGAIgASgJIoYBChhDcmVhdGVNZW1vQ29tbWVudFJlcXVlc3QSJwoEbmFtZRgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvTWVtbxIoCgdjb21tZW50GAIgASgLMhIubWVtb3MuYXBpLnYxLk1lbW9CA+BBAhIXCgpjb21tZW50X2lkGAMgASgJQgPgQQEiigEKF0xpc3RNZW1vQ29tbWVudHNSZXF1ZXN0EicKBG5hbWUYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL01lbW8SFgoJcGFnZV9zaXplGAIgASgFQgPgQQESFwoKcGFnZV90b2tlbhgDIAEoCUID4EEBEhUKCG9yZGVyX2J5GAQgASgJQgPgQQEiagoYTGlzdE1lbW9Db21tZW50c1Jlc3BvbnNlEiEKBW1lbW9zGAEgAygLMhIubWVtb3MuYXBpLnYxLk1lbW8SFwoPbmV4dF9wYWdlX3Rva2VuGAIgASgJEhIKCnRvdGFsX3NpemUYAyABKAUidAoYTGlzdE1lbW9SZWFjdGlvbnNSZXF1ZXN0EicKBG5hbWUYASABKAlCGeBBAvpBEwoRbWVtb3MuYXBpLnYxL01lbW8SFgoJcGFnZV9zaXplGAIgASgFQgPgQQESFwoKcGFnZV90b2tlbhgDIAEoCUID4EEBInMKGUxpc3RNZW1vUmVhY3Rpb25zUmVzcG9uc2USKQoJcmVhY3Rpb25zGAEgAygLMhYubWVtb3MuYXBpLnYxLlJlYWN0aW9uEhcKD25leHRfcGFnZV90b2tlbhgCIAEoCRISCgp0b3RhbF9zaXplGAMgASgFInMKGVVwc2VydE1lbW9SZWFjdGlvblJlcXVlc3QSJwoEbmFtZRgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvTWVtbxItCghyZWFjdGlvbhgCIAEoCzIWLm1lbW9zLmFwaS52MS5SZWFjdGlvbkID4EECIkgKGURlbGV0ZU1lbW9SZWFjdGlvblJlcXVlc3QSKwoEbmFtZRgBIAEoCUId4EEC+kEXChVtZW1vcy5hcGkudjEvUmVhY3Rpb24i6AEKCU1lbW9TaGFyZRIRCgRuYW1lGAEgASgJQgPgQQgSNAoLY3JlYXRlX3RpbWUYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQMSOQoLZXhwaXJlX3RpbWUYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wQgPgQQFIAIgBATpH6kFEChZtZW1vcy5hcGkudjEvTWVtb1NoYXJlEhttZW1vcy97bWVtb30vc2hhcmVzL3tzaGFyZX0qBnNoYXJlczIFc2hhcmVCDgoMX2V4cGlyZV90aW1lInUKFkNyZWF0ZU1lbW9TaGFyZVJlcXVlc3QSKQoGcGFyZW50GAEgASgJQhngQQL6QRMKEW1lbW9zLmFwaS52MS9NZW1vEjAKCm1lbW9fc2hhcmUYAiABKAsyFy5tZW1vcy5hcGkudjEuTWVtb1NoYXJlQgPgQQIiQgoVTGlzdE1lbW9TaGFyZXNSZXF1ZXN0EikKBnBhcmVudBgBIAEoCUIZ4EEC+kETChFtZW1vcy5hcGkudjEvTWVtbyJGChZMaXN0TWVtb1NoYXJlc1Jlc3BvbnNlEiwKC21lbW9fc2hhcmVzGAEgAygLMhcubWVtb3MuYXBpLnYxLk1lbW9TaGFyZSJGChZEZWxldGVNZW1vU2hhcmVSZXF1ZXN0EiwKBG5hbWUYASABKAlCHuBBAvpBGAoWbWVtb3MuYXBpLnYxL01lbW9TaGFyZSIuChVHZXRNZW1vQnlTaGFyZVJlcXVlc3QSFQoIc2hhcmVfaWQYASABKAlCA+BBAiIqChZHZXRMaW5rTWV0YWRhdGFSZXF1ZXN0EhAKA3VybBgBIAEoCUID4EECIjAKG0JhdGNoR2V0TGlua01ldGFkYXRhUmVxdWVzdBIRCgR1cmxzGAEgAygJQgPgQQIiUQocQmF0Y2hHZXRMaW5rTWV0YWRhdGFSZXNwb25zZRIxCg1saW5rX21ldGFkYXRhGAEgAygLMhoubWVtb3MuYXBpLnYxLkxpbmtNZXRhZGF0YSJOCgxMaW5rTWV0YWRhdGESCwoDdXJsGAEgASgJEg0KBXRpdGxlGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEg0KBWltYWdlGAQgASgJKlAKClZpc2liaWxpdHkSGgoWVklTSUJJTElUWV9VTlNQRUNJRklFRBAAEgsKB1BSSVZBVEUQARINCglQUk9URUNURUQQAhIKCgZQVUJMSUMQAzKLFQoLTWVtb1NlcnZpY2USZQoKQ3JlYXRlTWVtbxIfLm1lbW9zLmFwaS52MS5DcmVhdGVNZW1vUmVxdWVzdBoSLm1lbW9zLmFwaS52MS5NZW1vIiLaQQRtZW1vgtPkkwIVOgRtZW1vIg0vYXBpL3YxL21lbW9zEmYKCUxpc3RNZW1vcxIeLm1lbW9zLmFwaS52MS5MaXN0TWVtb3NSZXF1ZXN0Gh8ubWVtb3MuYXBpLnYxLkxpc3RNZW1vc1Jlc3BvbnNlIhjaQQCC0+STAg8SDS9hcGkvdjEvbWVtb3MSYgoHR2V0TWVtbxIcLm1lbW9zLmFwaS52MS5HZXRNZW1vUmVxdWVzdBoSLm1lbW9zLmFwaS52MS5NZW1vIiXaQQRuYW1lgtPkkwIYEhYvYXBpL3YxL3tuYW1lPW1lbW9zLyp9En8KClVwZGF0ZU1lbW8SHy5tZW1vcy5hcGkudjEuVXBkYXRlTWVtb1JlcXVlc3QaEi5tZW1vcy5hcGkudjEuTWVtbyI82kEQbWVtbyx1cGRhdGVfbWFza4LT5JMCIzoEbWVtbzIbL2FwaS92MS97bWVtby5uYW1lPW1lbW9zLyp9EmwKCkRlbGV0ZU1lbW8SHy5tZW1vcy5hcGkudjEuRGVsZXRlTWVtb1JlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiJdpBBG5hbWWC0+STAhgqFi9hcGkvdjEve25hbWU9bWVtb3MvKn0SiwEKElNldE1lbW9BdHRhY2htZW50cxInLm1lbW9zLmFwaS52MS5TZXRNZW1vQXR0YWNobWVudHNSZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5IjTaQQRuYW1lgtPkkwInOgEqMiIvYXBpL3YxL3tuYW1lPW1lbW9zLyp9L2F0dGFjaG1lbnRzEp0BChNMaXN0TWVtb0F0dGFjaG1lbnRzEigubWVtb3MuYXBpLnYxLkxpc3RNZW1vQXR0YWNobWVudHNSZXF1ZXN0GikubWVtb3MuYXBpLnYxLkxpc3RNZW1vQXR0YWNobWVudHNSZXNwb25zZSIx2kEEbmFtZYLT5JMCJBIiL2FwaS92MS97bmFtZT1tZW1vcy8qfS9hdHRhY2htZW50cxKFAQoQU2V0TWVtb1JlbGF0aW9ucxIlLm1lbW9zLmFwaS52MS5TZXRNZW1vUmVsYXRpb25zUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSIy2kEEbmFtZYLT5JMCJToBKjIgL2FwaS92MS97bmFtZT1tZW1vcy8qfS9yZWxhdGlvbnMSlQEKEUxpc3RNZW1vUmVsYXRpb25zEiYubWVtb3MuYXBpLnYxLkxpc3RNZW1vUmVsYXRpb25zUmVxdWVzdBonLm1lbW9zLmFwaS52MS5MaXN0TWVtb1JlbGF0aW9uc1Jlc3BvbnNlIi/aQQRuYW1lgtPkkwIiEiAvYXBpL3YxL3tuYW1lPW1lbW9zLyp9L3JlbGF0aW9ucxKQAQoRQ3JlYXRlTWVtb0NvbW1lbnQSJi5tZW1vcy5hcGkudjEuQ3JlYXRlTWVtb0NvbW1lbnRSZXF1ZXN0GhIubWVtb3MuYXBpLnYxLk1lbW8iP9pBDG5hbWUsY29tbWVudILT5JMCKjoHY29tbWVudCIfL2FwaS92MS97bmFtZT1tZW1vcy8qfS9jb21tZW50cxKRAQoQTGlzdE1lbW9Db21tZW50cxIlLm1lbW9zLmFwaS52MS5MaXN0TWVtb0NvbW1lbnRzUmVxdWVzdBomLm1lbW9zLmFwaS52MS5MaXN0TWVtb0NvbW1lbnRzUmVzcG9uc2UiLtpBBG5hbWWC0+STAiESHy9hcGkvdjEve25hbWU9bWVtb3MvKn0vY29tbWVudHMSlQEKEUxpc3RNZW1vUmVhY3Rpb25zEiYubWVtb3MuYXBpLnYxLkxpc3RNZW1vUmVhY3Rpb25zUmVxdWVzdBonLm1lbW9zLmFwaS52MS5MaXN0TWVtb1JlYWN0aW9uc1Jlc3BvbnNlIi/aQQRuYW1lgtPkkwIiEiAvYXBpL3YxL3tuYW1lPW1lbW9zLyp9L3JlYWN0aW9ucxKJAQoSVXBzZXJ0TWVtb1JlYWN0aW9uEicubWVtb3MuYXBpLnYxLlVwc2VydE1lbW9SZWFjdGlvblJlcXVlc3QaFi5tZW1vcy5hcGkudjEuUmVhY3Rpb24iMtpBBG5hbWWC0+STAiU6ASoiIC9hcGkvdjEve25hbWU9bWVtb3MvKn0vcmVhY3Rpb25zEogBChJEZWxldGVNZW1vUmVhY3Rpb24SJy5tZW1vcy5hcGkudjEuRGVsZXRlTWVtb1JlYWN0aW9uUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSIx2kEEbmFtZYLT5JMCJCoiL2FwaS92MS97bmFtZT1tZW1vcy8qL3JlYWN0aW9ucy8qfRKZAQoPQ3JlYXRlTWVtb1NoYXJlEiQubWVtb3MuYXBpLnYxLkNyZWF0ZU1lbW9TaGFyZVJlcXVlc3QaFy5tZW1vcy5hcGkudjEuTWVtb1NoYXJlIkfaQRFwYXJlbnQsbWVtb19zaGFyZYLT5JMCLToKbWVtb19zaGFyZSIfL2FwaS92MS97cGFyZW50PW1lbW9zLyp9L3NoYXJlcxKNAQoOTGlzdE1lbW9TaGFyZXMSIy5tZW1vcy5hcGkudjEuTGlzdE1lbW9TaGFyZXNSZXF1ZXN0GiQubWVtb3MuYXBpLnYxLkxpc3RNZW1vU2hhcmVzUmVzcG9uc2UiMNpBBnBhcmVudILT5JMCIRIfL2FwaS92MS97cGFyZW50PW1lbW9zLyp9L3NoYXJlcxJ/Cg9EZWxldGVNZW1vU2hhcmUSJC5tZW1vcy5hcGkudjEuRGVsZXRlTWVtb1NoYXJlUmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSIu2kEEbmFtZYLT5JMCISofL2FwaS92MS97bmFtZT1tZW1vcy8qL3NoYXJlcy8qfRJsCg5HZXRNZW1vQnlTaGFyZRIjLm1lbW9zLmFwaS52MS5HZXRNZW1vQnlTaGFyZVJlcXVlc3QaEi5tZW1vcy5hcGkudjEuTWVtbyIhgtPkkwIbEhkvYXBpL3YxL3NoYXJlcy97c2hhcmVfaWR9EnkKD0dldExpbmtNZXRhZGF0YRIkLm1lbW9zLmFwaS52MS5HZXRMaW5rTWV0YWRhdGFSZXF1ZXN0GhoubWVtb3MuYXBpLnYxLkxpbmtNZXRhZGF0YSIkgtPkkwIeEhwvYXBpL3YxL21lbW9zLy0vbGlua01ldGFkYXRhEp8BChRCYXRjaEdldExpbmtNZXRhZGF0YRIpLm1lbW9zLmFwaS52MS5CYXRjaEdldExpbmtNZXRhZGF0YVJlcXVlc3QaKi5tZW1vcy5hcGkudjEuQmF0Y2hHZXRMaW5rTWV0YWRhdGFSZXNwb25zZSIwgtPkkwIqOgEqIiUvYXBpL3YxL21lbW9zLy0vbGlua01ldGFkYXRhOmJhdGNoR2V0QqgBChBjb20ubWVtb3MuYXBpLnYxQhBNZW1vU2VydmljZVByb3RvUAFaMGdpdGh1Yi5jb20vdXNlbWVtb3MvbWVtb3MvcHJvdG8vZ2VuL2FwaS92MTthcGl2MaICA01BWKoCDE1lbW9zLkFwaS5WMcoCDE1lbW9zXEFwaVxWMeICGE1lbW9zXEFwaVxWMVxHUEJNZXRhZGF0YeoCDk1lbW9zOjpBcGk6OlYxYgZwcm90bzM", [file_api_v1_attachment_service, file_api_v1_common, file_google_api_annotations, file_google_api_client, file_google_api_field_behavior, file_google_api_resource, file_google_protobuf_empty, file_google_protobuf_field_mask, file_google_protobuf_timestamp]); /** * @generated from message memos.api.v1.Reaction @@ -1095,6 +1095,103 @@ export type GetMemoByShareRequest = Message<"memos.api.v1.GetMemoByShareRequest" export const GetMemoByShareRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_api_v1_memo_service, 28); +/** + * @generated from message memos.api.v1.GetLinkMetadataRequest + */ +export type GetLinkMetadataRequest = Message<"memos.api.v1.GetLinkMetadataRequest"> & { + /** + * Required. The link URL. + * + * @generated from field: string url = 1; + */ + url: string; +}; + +/** + * Describes the message memos.api.v1.GetLinkMetadataRequest. + * Use `create(GetLinkMetadataRequestSchema)` to create a new message. + */ +export const GetLinkMetadataRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_memo_service, 29); + +/** + * @generated from message memos.api.v1.BatchGetLinkMetadataRequest + */ +export type BatchGetLinkMetadataRequest = Message<"memos.api.v1.BatchGetLinkMetadataRequest"> & { + /** + * Required. The link URLs. + * + * @generated from field: repeated string urls = 1; + */ + urls: string[]; +}; + +/** + * Describes the message memos.api.v1.BatchGetLinkMetadataRequest. + * Use `create(BatchGetLinkMetadataRequestSchema)` to create a new message. + */ +export const BatchGetLinkMetadataRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_memo_service, 30); + +/** + * @generated from message memos.api.v1.BatchGetLinkMetadataResponse + */ +export type BatchGetLinkMetadataResponse = Message<"memos.api.v1.BatchGetLinkMetadataResponse"> & { + /** + * The link metadata list, in the same order as the input URLs. + * + * @generated from field: repeated memos.api.v1.LinkMetadata link_metadata = 1; + */ + linkMetadata: LinkMetadata[]; +}; + +/** + * Describes the message memos.api.v1.BatchGetLinkMetadataResponse. + * Use `create(BatchGetLinkMetadataResponseSchema)` to create a new message. + */ +export const BatchGetLinkMetadataResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_memo_service, 31); + +/** + * @generated from message memos.api.v1.LinkMetadata + */ +export type LinkMetadata = Message<"memos.api.v1.LinkMetadata"> & { + /** + * The original link URL. + * + * @generated from field: string url = 1; + */ + url: string; + + /** + * The link title. + * + * @generated from field: string title = 2; + */ + title: string; + + /** + * The link description. + * + * @generated from field: string description = 3; + */ + description: string; + + /** + * The link image URL. + * + * @generated from field: string image = 4; + */ + image: string; +}; + +/** + * Describes the message memos.api.v1.LinkMetadata. + * Use `create(LinkMetadataSchema)` to create a new message. + */ +export const LinkMetadataSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_api_v1_memo_service, 32); + /** * @generated from enum memos.api.v1.Visibility */ @@ -1311,6 +1408,26 @@ export const MemoService: GenService<{ input: typeof GetMemoByShareRequestSchema; output: typeof MemoSchema; }, + /** + * GetLinkMetadata gets metadata for a link. + * + * @generated from rpc memos.api.v1.MemoService.GetLinkMetadata + */ + getLinkMetadata: { + methodKind: "unary"; + input: typeof GetLinkMetadataRequestSchema; + output: typeof LinkMetadataSchema; + }, + /** + * BatchGetLinkMetadata gets metadata for links. + * + * @generated from rpc memos.api.v1.MemoService.BatchGetLinkMetadata + */ + batchGetLinkMetadata: { + methodKind: "unary"; + input: typeof BatchGetLinkMetadataRequestSchema; + output: typeof BatchGetLinkMetadataResponseSchema; + }, }> = /*@__PURE__*/ serviceDesc(file_api_v1_memo_service, 0);