mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: add Telegram bot config UI (#1747)
* Add retry wait for telegram.GetUpdates * Add support to set telegram robot token from UI * Change validator of UserSettingTelegramUserID * Add support to set telegram user id from UI * Fix typescript check * Add validator for SystemSettingTelegramRobotTokenName * Optimize error notice while config telegram params * Change for review * Fix telegram user id could not be empty * Fix telegram robot could not be empty * Fix for eslint (again) * Update web/src/components/Settings/SystemSection.tsx --------- Co-authored-by: Athurg Feng <athurg@gooth.org> Co-authored-by: boojack <stevenlgtm@gmail.com>
This commit is contained in:
@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
@ -186,6 +187,15 @@ func (upsert SystemSettingUpsert) Validate() error {
|
|||||||
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SystemSettingTelegramRobotTokenName:
|
||||||
|
if upsert.Value == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fragments := strings.Split(upsert.Value, ":")
|
||||||
|
if len(fragments) != 2 {
|
||||||
|
return fmt.Errorf(systemSettingUnmarshalError, settingName)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid system setting name")
|
return fmt.Errorf("invalid system setting name")
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
@ -101,13 +102,17 @@ func (upsert UserSettingUpsert) Validate() error {
|
|||||||
return fmt.Errorf("invalid user setting memo visibility value")
|
return fmt.Errorf("invalid user setting memo visibility value")
|
||||||
}
|
}
|
||||||
} else if upsert.Key == UserSettingTelegramUserIDKey {
|
} else if upsert.Key == UserSettingTelegramUserIDKey {
|
||||||
telegramUserID := 0
|
var s string
|
||||||
err := json.Unmarshal([]byte(upsert.Value), &telegramUserID)
|
err := json.Unmarshal([]byte(upsert.Value), &s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to unmarshal user setting telegram userid value")
|
return fmt.Errorf("invalid user setting telegram user id value")
|
||||||
}
|
}
|
||||||
if telegramUserID <= 0 {
|
|
||||||
return fmt.Errorf("invalid user setting telegram userid value")
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, err := strconv.Atoi(s); err != nil {
|
||||||
|
return fmt.Errorf("invalid user setting telegram user id value")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("invalid user setting key")
|
return fmt.Errorf("invalid user setting key")
|
||||||
|
@ -24,6 +24,7 @@ func NewRobotWithHandler(h Handler) *Robot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const noTokenWait = 30 * time.Second
|
const noTokenWait = 30 * time.Second
|
||||||
|
const errRetryWait = 10 * time.Second
|
||||||
|
|
||||||
// Start start an infinity call of getUpdates from Telegram, call r.MessageHandle while get new message updates.
|
// Start start an infinity call of getUpdates from Telegram, call r.MessageHandle while get new message updates.
|
||||||
func (r *Robot) Start(ctx context.Context) {
|
func (r *Robot) Start(ctx context.Context) {
|
||||||
@ -37,6 +38,7 @@ func (r *Robot) Start(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("fail to telegram.GetUpdates", zap.Error(err))
|
log.Warn("fail to telegram.GetUpdates", zap.Error(err))
|
||||||
|
time.Sleep(errRetryWait)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -33,7 +34,12 @@ func (t *telegramHandler) MessageHandle(ctx context.Context, message telegram.Me
|
|||||||
return fmt.Errorf("Fail to find memo user: %s", err)
|
return fmt.Errorf("Fail to find memo user: %s", err)
|
||||||
}
|
}
|
||||||
for _, userSetting := range userSettingList {
|
for _, userSetting := range userSettingList {
|
||||||
if userSetting.Value == strconv.Itoa(message.From.ID) {
|
var value string
|
||||||
|
if err := json.Unmarshal([]byte(userSetting.Value), &value); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if value == strconv.Itoa(message.From.ID) {
|
||||||
creatorID = userSetting.UserID
|
creatorID = userSetting.UserID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import { Switch, Option, Select } from "@mui/joy";
|
import { Input, Button, Divider, Switch, Option, Select } from "@mui/joy";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "react-hot-toast";
|
||||||
|
import { getMyselfUser } from "@/helpers/api";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useGlobalStore, useUserStore } from "@/store/module";
|
import { useGlobalStore, useUserStore } from "@/store/module";
|
||||||
@ -13,6 +16,7 @@ const PreferencesSection = () => {
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const { appearance, locale } = globalStore.state;
|
const { appearance, locale } = globalStore.state;
|
||||||
const { setting, localSetting } = userStore.state.user as User;
|
const { setting, localSetting } = userStore.state.user as User;
|
||||||
|
const [telegramUserId, setTelegramUserId] = useState<string>("");
|
||||||
const visibilitySelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
|
const visibilitySelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
|
||||||
return {
|
return {
|
||||||
value: item.value,
|
value: item.value,
|
||||||
@ -20,6 +24,21 @@ const PreferencesSection = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getMyselfUser().then(
|
||||||
|
({
|
||||||
|
data: {
|
||||||
|
data: { userSettingList: userSettingList },
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
const telegramUserIdSetting = userSettingList.find((setting: any) => setting.key === "telegram-user-id");
|
||||||
|
if (telegramUserIdSetting) {
|
||||||
|
setTelegramUserId(JSON.parse(telegramUserIdSetting.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const dailyReviewTimeOffsetOptions: number[] = [...Array(24).keys()];
|
const dailyReviewTimeOffsetOptions: number[] = [...Array(24).keys()];
|
||||||
|
|
||||||
const handleLocaleSelectChange = async (locale: Locale) => {
|
const handleLocaleSelectChange = async (locale: Locale) => {
|
||||||
@ -49,6 +68,21 @@ const PreferencesSection = () => {
|
|||||||
userStore.upsertLocalSetting({ ...localSetting, enableAutoCollapse: event.target.checked });
|
userStore.upsertLocalSetting({ ...localSetting, enableAutoCollapse: event.target.checked });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSaveTelegramUserId = async () => {
|
||||||
|
try {
|
||||||
|
await userStore.upsertUserSetting("telegram-user-id", telegramUserId);
|
||||||
|
toast.success(t("common.dialog.success"));
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error(error.response.data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTelegramUserIdChanged = async (value: string) => {
|
||||||
|
setTelegramUserId(value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="section-container preferences-section-container">
|
<div className="section-container preferences-section-container">
|
||||||
<p className="title-text">{t("common.basic")}</p>
|
<p className="title-text">{t("common.basic")}</p>
|
||||||
@ -118,6 +152,25 @@ const PreferencesSection = () => {
|
|||||||
<span className="normal-text">{t("setting.preference-section.auto-collapse")}</span>
|
<span className="normal-text">{t("setting.preference-section.auto-collapse")}</span>
|
||||||
<Switch className="ml-2" checked={localSetting.enableAutoCollapse} onChange={handleAutoCollapseChanged} />
|
<Switch className="ml-2" checked={localSetting.enableAutoCollapse} onChange={handleAutoCollapseChanged} />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<Divider className="!mt-3 !my-4" />
|
||||||
|
|
||||||
|
<div className="form-label">
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<span className="text-sm mr-1">{t("setting.preference-section.telegram-user-id")}</span>
|
||||||
|
</div>
|
||||||
|
<Button onClick={handleSaveTelegramUserId}>{t("common.save")}</Button>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
className="w-full"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "monospace",
|
||||||
|
fontSize: "14px",
|
||||||
|
}}
|
||||||
|
value={telegramUserId}
|
||||||
|
onChange={(event) => handleTelegramUserIdChanged(event.target.value)}
|
||||||
|
placeholder={t("setting.preference-section.telegram-user-id-placeholder")}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -32,6 +32,7 @@ const SystemSection = () => {
|
|||||||
disablePublicMemos: systemStatus.disablePublicMemos,
|
disablePublicMemos: systemStatus.disablePublicMemos,
|
||||||
maxUploadSizeMiB: systemStatus.maxUploadSizeMiB,
|
maxUploadSizeMiB: systemStatus.maxUploadSizeMiB,
|
||||||
});
|
});
|
||||||
|
const [telegramRobotToken, setTelegramRobotToken] = useState<string>("");
|
||||||
const [openAIConfig, setOpenAIConfig] = useState<OpenAIConfig>({
|
const [openAIConfig, setOpenAIConfig] = useState<OpenAIConfig>({
|
||||||
key: "",
|
key: "",
|
||||||
host: "",
|
host: "",
|
||||||
@ -47,6 +48,11 @@ const SystemSection = () => {
|
|||||||
if (openAIConfigSetting) {
|
if (openAIConfigSetting) {
|
||||||
setOpenAIConfig(JSON.parse(openAIConfigSetting.value));
|
setOpenAIConfig(JSON.parse(openAIConfigSetting.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const telegramRobotSetting = systemSettings.find((setting) => setting.name === "telegram-robot-token");
|
||||||
|
if (telegramRobotSetting) {
|
||||||
|
setTelegramRobotToken(telegramRobotSetting.value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -126,6 +132,24 @@ const SystemSection = () => {
|
|||||||
toast.success("OpenAI Config updated");
|
toast.success("OpenAI Config updated");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTelegramRobotTokenChanged = (value: string) => {
|
||||||
|
setTelegramRobotToken(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveTelegramRobotToken = async () => {
|
||||||
|
try {
|
||||||
|
await api.upsertSystemSetting({
|
||||||
|
name: "telegram-robot-token",
|
||||||
|
value: telegramRobotToken,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error(error.response.data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast.success("OpenAI Config updated");
|
||||||
|
};
|
||||||
|
|
||||||
const handleAdditionalStyleChanged = (value: string) => {
|
const handleAdditionalStyleChanged = (value: string) => {
|
||||||
setState({
|
setState({
|
||||||
...state,
|
...state,
|
||||||
@ -246,6 +270,27 @@ const SystemSection = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Divider className="!mt-3 !my-4" />
|
<Divider className="!mt-3 !my-4" />
|
||||||
|
<div className="form-label">
|
||||||
|
<div className="flex flex-row items-center">
|
||||||
|
<span className="text-sm mr-1">{t("setting.system-section.telegram-robot-token")}</span>
|
||||||
|
<HelpButton
|
||||||
|
hint={t("setting.system-section.telegram-robot-token-description")}
|
||||||
|
url="https://core.telegram.org/bots#how-do-i-create-a-bot"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button onClick={handleSaveTelegramRobotToken}>{t("common.save")}</Button>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
className="w-full"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "monospace",
|
||||||
|
fontSize: "14px",
|
||||||
|
}}
|
||||||
|
placeholder={t("setting.system-section.telegram-robot-token-placeholder")}
|
||||||
|
value={telegramRobotToken}
|
||||||
|
onChange={(event) => handleTelegramRobotTokenChanged(event.target.value)}
|
||||||
|
/>
|
||||||
|
<Divider className="!mt-3 !my-4" />
|
||||||
<div className="form-label">
|
<div className="form-label">
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<span className="text-sm mr-1">{t("setting.system-section.openai-api-key")}</span>
|
<span className="text-sm mr-1">{t("setting.system-section.openai-api-key")}</span>
|
||||||
|
@ -192,6 +192,8 @@
|
|||||||
"editor-font-style": "Editor font style",
|
"editor-font-style": "Editor font style",
|
||||||
"mobile-editor-style": "Mobile editor style",
|
"mobile-editor-style": "Mobile editor style",
|
||||||
"default-memo-sort-option": "Memo display time",
|
"default-memo-sort-option": "Memo display time",
|
||||||
|
"telegram-user-id": "Telegram UserID",
|
||||||
|
"telegram-user-id-placeholder": "Send any words to Your Telegram Robot to get",
|
||||||
"created_ts": "Created Time",
|
"created_ts": "Created Time",
|
||||||
"updated_ts": "Updated Time",
|
"updated_ts": "Updated Time",
|
||||||
"daily-review-time-offset": "Daily Review Time Offset",
|
"daily-review-time-offset": "Daily Review Time Offset",
|
||||||
@ -256,6 +258,9 @@
|
|||||||
"additional-script": "Additional script",
|
"additional-script": "Additional script",
|
||||||
"additional-style-placeholder": "Additional CSS code",
|
"additional-style-placeholder": "Additional CSS code",
|
||||||
"additional-script-placeholder": "Additional JavaScript code",
|
"additional-script-placeholder": "Additional JavaScript code",
|
||||||
|
"telegram-robot-token": "Telegram Robot Token",
|
||||||
|
"telegram-robot-token-description": "Get from @BotFather of Telegram",
|
||||||
|
"telegram-robot-token-placeholder": "Your Telegram Robot token",
|
||||||
"openai-api-key": "OpenAI: API Key",
|
"openai-api-key": "OpenAI: API Key",
|
||||||
"openai-api-key-description": "Get API key",
|
"openai-api-key-description": "Get API key",
|
||||||
"openai-api-key-placeholder": "Your OpenAI API Key",
|
"openai-api-key-placeholder": "Your OpenAI API Key",
|
||||||
|
@ -328,6 +328,8 @@
|
|||||||
"enable-double-click": "开启双击编辑",
|
"enable-double-click": "开启双击编辑",
|
||||||
"enable-folding-memo": "开启折叠备忘录",
|
"enable-folding-memo": "开启折叠备忘录",
|
||||||
"mobile-editor-style": "移动端编辑器样式",
|
"mobile-editor-style": "移动端编辑器样式",
|
||||||
|
"telegram-user-id": "Telegram UserID",
|
||||||
|
"telegram-user-id-placeholder": "向Telegram机器人发送任意消息以获取",
|
||||||
"theme": "主题",
|
"theme": "主题",
|
||||||
"updated_ts": "更新时间"
|
"updated_ts": "更新时间"
|
||||||
},
|
},
|
||||||
@ -404,6 +406,9 @@
|
|||||||
"database-file-size": "数据库文件大小",
|
"database-file-size": "数据库文件大小",
|
||||||
"disable-public-memos": "禁用公共备忘录",
|
"disable-public-memos": "禁用公共备忘录",
|
||||||
"ignore-version-upgrade": "忽略版本升级",
|
"ignore-version-upgrade": "忽略版本升级",
|
||||||
|
"telegram-robot-token": "Telegram 机器人 Token",
|
||||||
|
"telegram-robot-token-description": "从 Telegram 的 @BotFather 处获取",
|
||||||
|
"telegram-robot-token-placeholder": "Telegram 的机器人 Token",
|
||||||
"openai-api-host": "OpenAI:API Host",
|
"openai-api-host": "OpenAI:API Host",
|
||||||
"openai-api-host-placeholder": "默认:https://api.openai.com/",
|
"openai-api-host-placeholder": "默认:https://api.openai.com/",
|
||||||
"openai-api-key": "OpenAI:API 密钥",
|
"openai-api-key": "OpenAI:API 密钥",
|
||||||
|
Reference in New Issue
Block a user