2023-09-19 22:35:20 +08:00
package integration
2023-05-26 09:43:51 +08:00
import (
2023-07-14 11:14:10 +08:00
"bytes"
2023-05-26 09:43:51 +08:00
"context"
"fmt"
2023-12-29 07:50:15 +08:00
"path/filepath"
2023-05-26 09:43:51 +08:00
"strconv"
2023-07-14 00:18:44 +08:00
"unicode/utf16"
2023-05-26 09:43:51 +08:00
2024-01-21 10:33:31 +08:00
"github.com/lithammer/shortuuid/v4"
2023-06-26 23:06:53 +08:00
"github.com/pkg/errors"
2023-09-17 22:55:13 +08:00
2023-06-26 23:06:53 +08:00
apiv1 "github.com/usememos/memos/api/v1"
2023-05-26 09:43:51 +08:00
"github.com/usememos/memos/plugin/telegram"
2023-12-08 22:41:47 +08:00
storepb "github.com/usememos/memos/proto/gen/store"
2023-05-26 09:43:51 +08:00
"github.com/usememos/memos/store"
)
2023-09-19 22:35:20 +08:00
type TelegramHandler struct {
2023-05-26 09:43:51 +08:00
store * store . Store
}
2023-09-19 22:35:20 +08:00
func NewTelegramHandler ( store * store . Store ) * TelegramHandler {
return & TelegramHandler { store : store }
2023-05-26 09:43:51 +08:00
}
2023-09-19 22:35:20 +08:00
func ( t * TelegramHandler ) BotToken ( ctx context . Context ) string {
2024-01-29 22:43:40 +08:00
return t . store . GetWorkspaceSettingWithDefaultValue ( ctx , apiv1 . SystemSettingTelegramBotTokenName . String ( ) , "" )
2023-05-26 09:43:51 +08:00
}
2023-06-14 22:10:01 +08:00
const (
2023-08-07 11:26:57 +08:00
workingMessage = "Working on sending your memo..."
2023-06-14 22:10:01 +08:00
successMessage = "Success"
)
2023-09-19 22:35:20 +08:00
func ( t * TelegramHandler ) MessageHandle ( ctx context . Context , bot * telegram . Bot , message telegram . Message , attachments [ ] telegram . Attachment ) error {
2023-06-14 22:10:01 +08:00
reply , err := bot . SendReplyMessage ( ctx , message . Chat . ID , message . MessageID , workingMessage )
if err != nil {
2023-09-17 22:55:13 +08:00
return errors . Wrap ( err , "Failed to SendReplyMessage" )
2023-06-14 22:10:01 +08:00
}
2023-08-04 21:55:07 +08:00
var creatorID int32
2023-12-16 12:18:53 +08:00
userSettingList , err := t . store . ListUserSettings ( ctx , & store . FindUserSetting {
2023-12-08 22:41:47 +08:00
Key : storepb . UserSettingKey_USER_SETTING_TELEGRAM_USER_ID ,
2023-05-26 09:43:51 +08:00
} )
if err != nil {
2023-06-26 23:06:53 +08:00
return errors . Wrap ( err , "Failed to find userSettingList" )
2023-05-26 09:43:51 +08:00
}
2023-06-29 22:55:03 +08:00
for _ , userSetting := range userSettingList {
2023-12-08 22:41:47 +08:00
if userSetting . GetTelegramUserId ( ) == strconv . FormatInt ( message . From . ID , 10 ) {
creatorID = userSetting . UserId
2023-05-26 09:43:51 +08:00
}
}
if creatorID == 0 {
2023-10-18 06:05:19 +08:00
_ , err := bot . EditMessage ( ctx , message . Chat . ID , reply . MessageID , fmt . Sprintf ( "Please set your telegram userid %d in UserSetting of memos" , message . From . ID ) , nil )
2023-06-14 22:10:01 +08:00
return err
2023-05-26 09:43:51 +08:00
}
2023-07-06 21:56:42 +08:00
create := & store . Memo {
2024-01-21 10:33:31 +08:00
ResourceName : shortuuid . New ( ) ,
CreatorID : creatorID ,
Visibility : store . Private ,
2023-05-26 09:43:51 +08:00
}
if message . Text != nil {
2023-07-14 00:18:44 +08:00
create . Content = convertToMarkdown ( * message . Text , message . Entities )
2023-05-26 09:43:51 +08:00
}
2023-07-14 00:18:44 +08:00
if message . Caption != nil {
create . Content = convertToMarkdown ( * message . Caption , message . CaptionEntities )
}
if message . ForwardFromChat != nil {
create . Content += fmt . Sprintf ( "\n\n[Message link](%s)" , message . GetMessageLink ( ) )
2023-05-26 09:43:51 +08:00
}
2023-07-06 21:56:42 +08:00
memoMessage , err := t . store . CreateMemo ( ctx , create )
2023-05-26 09:43:51 +08:00
if err != nil {
2023-08-07 11:26:57 +08:00
_ , err := bot . EditMessage ( ctx , message . Chat . ID , reply . MessageID , fmt . Sprintf ( "Failed to CreateMemo: %s" , err ) , nil )
2023-06-14 22:10:01 +08:00
return err
2023-05-26 09:43:51 +08:00
}
// create resources
2023-07-14 00:18:44 +08:00
for _ , attachment := range attachments {
2023-07-14 11:14:10 +08:00
// Fill the common field of create
create := store . Resource {
2024-01-21 10:49:30 +08:00
ResourceName : shortuuid . New ( ) ,
CreatorID : creatorID ,
Filename : filepath . Base ( attachment . FileName ) ,
Type : attachment . GetMimeType ( ) ,
Size : attachment . FileSize ,
MemoID : & memoMessage . ID ,
2023-07-14 11:14:10 +08:00
}
err := apiv1 . SaveResourceBlob ( ctx , t . store , & create , bytes . NewReader ( attachment . Data ) )
if err != nil {
2023-08-07 11:26:57 +08:00
_ , err := bot . EditMessage ( ctx , message . Chat . ID , reply . MessageID , fmt . Sprintf ( "Failed to SaveResourceBlob: %s" , err ) , nil )
2023-07-14 11:14:10 +08:00
return err
}
2023-09-27 00:40:16 +08:00
_ , err = t . store . CreateResource ( ctx , & create )
2023-05-26 09:43:51 +08:00
if err != nil {
2023-08-07 11:26:57 +08:00
_ , err := bot . EditMessage ( ctx , message . Chat . ID , reply . MessageID , fmt . Sprintf ( "Failed to CreateResource: %s" , err ) , nil )
2023-06-14 22:10:01 +08:00
return err
2023-05-26 09:43:51 +08:00
}
2023-06-14 22:10:01 +08:00
}
keyboard := generateKeyboardForMemoID ( memoMessage . ID )
_ , err = bot . EditMessage ( ctx , message . Chat . ID , reply . MessageID , fmt . Sprintf ( "Saved as %s Memo %d" , memoMessage . Visibility , memoMessage . ID ) , keyboard )
return err
}
2023-09-19 22:35:20 +08:00
func ( t * TelegramHandler ) CallbackQueryHandle ( ctx context . Context , bot * telegram . Bot , callbackQuery telegram . CallbackQuery ) error {
2023-08-04 21:55:07 +08:00
var memoID int32
2023-06-14 22:10:01 +08:00
var visibility store . Visibility
n , err := fmt . Sscanf ( callbackQuery . Data , "%s %d" , & visibility , & memoID )
if err != nil || n != 2 {
2023-08-07 11:26:57 +08:00
return bot . AnswerCallbackQuery ( ctx , callbackQuery . ID , fmt . Sprintf ( "Failed to parse callbackQuery.Data %s" , callbackQuery . Data ) )
2023-06-14 22:10:01 +08:00
}
2023-07-06 21:56:42 +08:00
update := store . UpdateMemo {
2023-06-14 22:10:01 +08:00
ID : memoID ,
Visibility : & visibility ,
}
err = t . store . UpdateMemo ( ctx , & update )
if err != nil {
2023-08-07 11:26:57 +08:00
return bot . AnswerCallbackQuery ( ctx , callbackQuery . ID , fmt . Sprintf ( "Failed to call UpdateMemo %s" , err ) )
2023-06-14 22:10:01 +08:00
}
keyboard := generateKeyboardForMemoID ( memoID )
_ , err = bot . EditMessage ( ctx , callbackQuery . Message . Chat . ID , callbackQuery . Message . MessageID , fmt . Sprintf ( "Saved as %s Memo %d" , visibility , memoID ) , keyboard )
if err != nil {
2023-08-07 11:26:57 +08:00
return bot . AnswerCallbackQuery ( ctx , callbackQuery . ID , fmt . Sprintf ( "Failed to EditMessage %s" , err ) )
2023-06-14 22:10:01 +08:00
}
2023-08-07 11:26:57 +08:00
return bot . AnswerCallbackQuery ( ctx , callbackQuery . ID , fmt . Sprintf ( "Success changing Memo %d to %s" , memoID , visibility ) )
2023-06-14 22:10:01 +08:00
}
2023-08-04 21:55:07 +08:00
func generateKeyboardForMemoID ( id int32 ) [ ] [ ] telegram . InlineKeyboardButton {
2023-06-14 22:10:01 +08:00
allVisibility := [ ] store . Visibility {
store . Public ,
store . Protected ,
store . Private ,
}
buttons := make ( [ ] telegram . InlineKeyboardButton , 0 , len ( allVisibility ) )
for _ , v := range allVisibility {
button := telegram . InlineKeyboardButton {
Text : v . String ( ) ,
CallbackData : fmt . Sprintf ( "%s %d" , v , id ) ,
2023-05-26 09:43:51 +08:00
}
2023-06-14 22:10:01 +08:00
buttons = append ( buttons , button )
2023-05-26 09:43:51 +08:00
}
2023-06-14 22:10:01 +08:00
return [ ] [ ] telegram . InlineKeyboardButton { buttons }
2023-05-26 09:43:51 +08:00
}
2023-07-14 00:18:44 +08:00
func convertToMarkdown ( text string , messageEntities [ ] telegram . MessageEntity ) string {
insertions := make ( map [ int ] string )
for _ , e := range messageEntities {
var before , after string
// this is supported by the current markdown
switch e . Type {
case telegram . Bold :
before = "**"
after = "**"
case telegram . Italic :
before = "*"
after = "*"
case telegram . Strikethrough :
before = "~~"
after = "~~"
case telegram . Code :
before = "`"
after = "`"
case telegram . Pre :
before = "```" + e . Language
after = "```"
case telegram . TextLink :
before = "["
after = fmt . Sprintf ( ` ](%s) ` , e . URL )
}
if before != "" {
insertions [ e . Offset ] += before
insertions [ e . Offset + e . Length ] = after + insertions [ e . Offset + e . Length ]
}
}
input := [ ] rune ( text )
var output [ ] rune
utf16pos := 0
for i := 0 ; i < len ( input ) ; i ++ {
output = append ( output , [ ] rune ( insertions [ utf16pos ] ) ... )
output = append ( output , input [ i ] )
utf16pos += len ( utf16 . Encode ( [ ] rune { input [ i ] } ) )
}
output = append ( output , [ ] rune ( insertions [ utf16pos ] ) ... )
return string ( output )
}