diff --git a/internal/domain/access.go b/internal/domain/access.go index dac85e31..4e002ecf 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -121,6 +121,11 @@ type AccessConfigForDingTalkBot struct { Secret string `json:"secret"` } +type AccessConfigForDiscordBot struct { + BotToken string `json:"botToken"` + DefaultChannelId string `json:"defaultChannelId,omitempty"` +} + type AccessConfigForDNSLA struct { ApiId string `json:"apiId"` ApiSecret string `json:"apiSecret"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 14a107b6..8f5c959a 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -33,6 +33,7 @@ const ( AccessProviderTypeDeSEC = AccessProviderType("desec") AccessProviderTypeDigitalOcean = AccessProviderType("digitalocean") AccessProviderTypeDingTalkBot = AccessProviderType("dingtalkbot") + AccessProviderTypeDiscordBot = AccessProviderType("discordbot") AccessProviderTypeDNSLA = AccessProviderType("dnsla") AccessProviderTypeDogeCloud = AccessProviderType("dogecloud") AccessProviderTypeDuckDNS = AccessProviderType("duckdns") @@ -269,6 +270,7 @@ type NotificationProviderType string */ const ( NotificationProviderTypeDingTalkBot = NotificationProviderType(AccessProviderTypeDingTalkBot) + NotificationProviderTypeDiscordBot = NotificationProviderType(AccessProviderTypeDiscordBot) NotificationProviderTypeEmail = NotificationProviderType(AccessProviderTypeEmail) NotificationProviderTypeLarkBot = NotificationProviderType(AccessProviderTypeLarkBot) NotificationProviderTypeMattermost = NotificationProviderType(AccessProviderTypeMattermost) diff --git a/internal/notify/providers.go b/internal/notify/providers.go index 3a8c575d..23f5bb43 100644 --- a/internal/notify/providers.go +++ b/internal/notify/providers.go @@ -7,6 +7,7 @@ import ( "github.com/usual2970/certimate/internal/domain" "github.com/usual2970/certimate/internal/pkg/core/notifier" pDingTalkBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/dingtalkbot" + pDiscordBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/discordbot" pEmail "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/email" pLarkBot "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/larkbot" pMattermost "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/mattermost" @@ -42,6 +43,19 @@ func createNotifierProvider(options *notifierProviderOptions) (notifier.Notifier }) } + case domain.NotificationProviderTypeDiscordBot: + { + access := domain.AccessConfigForDiscordBot{} + if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + return pDiscordBot.NewNotifier(&pDiscordBot.NotifierConfig{ + BotToken: access.BotToken, + ChannelId: maputil.GetOrDefaultString(options.ProviderServiceConfig, "channelId", access.DefaultChannelId), + }) + } + case domain.NotificationProviderTypeEmail: { access := domain.AccessConfigForEmail{} diff --git a/internal/pkg/core/notifier/providers/discordbot/discordbot.go b/internal/pkg/core/notifier/providers/discordbot/discordbot.go new file mode 100644 index 00000000..dbffaba7 --- /dev/null +++ b/internal/pkg/core/notifier/providers/discordbot/discordbot.go @@ -0,0 +1,67 @@ +package discordbot + +import ( + "context" + "fmt" + "log/slog" + + "github.com/go-resty/resty/v2" + + "github.com/usual2970/certimate/internal/pkg/core/notifier" +) + +type NotifierConfig struct { + // Discord Bot API Token。 + BotToken string `json:"botToken"` + // Discord Channel ID。 + ChannelId string `json:"channelId"` +} + +type NotifierProvider struct { + config *NotifierConfig + logger *slog.Logger + httpClient *resty.Client +} + +var _ notifier.Notifier = (*NotifierProvider)(nil) + +func NewNotifier(config *NotifierConfig) (*NotifierProvider, error) { + if config == nil { + panic("config is nil") + } + + client := resty.New() + + return &NotifierProvider{ + config: config, + logger: slog.Default(), + httpClient: client, + }, nil +} + +func (n *NotifierProvider) WithLogger(logger *slog.Logger) notifier.Notifier { + if logger == nil { + n.logger = slog.Default() + } else { + n.logger = logger + } + return n +} + +func (n *NotifierProvider) Notify(ctx context.Context, subject string, message string) (res *notifier.NotifyResult, err error) { + // REF: https://discord.com/developers/docs/resources/message#create-message + req := n.httpClient.R(). + SetHeader("Content-Type", "application/json"). + SetHeader("Authorization", "Bot "+n.config.BotToken). + SetBody(map[string]any{ + "content": subject + "\n" + message, + }) + resp, err := req.Post(fmt.Sprintf("https://discord.com/api/v9/channels/%s/messages", n.config.ChannelId)) + if err != nil { + return nil, fmt.Errorf("discord api error: failed to send request: %w", err) + } else if resp.IsError() { + return nil, fmt.Errorf("discord api error: unexpected status code: %d, resp: %s", resp.StatusCode(), resp.String()) + } + + return ¬ifier.NotifyResult{}, nil +} diff --git a/internal/pkg/core/notifier/providers/discordbot/discordbot_test.go b/internal/pkg/core/notifier/providers/discordbot/discordbot_test.go new file mode 100644 index 00000000..42edf95e --- /dev/null +++ b/internal/pkg/core/notifier/providers/discordbot/discordbot_test.go @@ -0,0 +1,64 @@ +package discordbot_test + +import ( + "context" + "flag" + "fmt" + "strings" + "testing" + + provider "github.com/usual2970/certimate/internal/pkg/core/notifier/providers/discordbot" +) + +const ( + mockSubject = "test_subject" + mockMessage = "test_message" +) + +var ( + fApiToken string + fChannelId string +) + +func init() { + argsPrefix := "CERTIMATE_NOTIFIER_DISCORDBOT_" + + flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "") + flag.StringVar(&fChannelId, argsPrefix+"CHANNELID", 0, "") +} + +/* +Shell command to run this test: + + go test -v ./discordbot_test.go -args \ + --CERTIMATE_NOTIFIER_DISCORDBOT_APITOKEN="your-bot-token" \ + --CERTIMATE_NOTIFIER_DISCORDBOT_CHANNELID="your-channel-id" +*/ +func TestNotify(t *testing.T) { + flag.Parse() + + t.Run("Notify", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("APITOKEN: %v", fApiToken), + fmt.Sprintf("CHANNELID: %v", fChannelId), + }, "\n")) + + notifier, err := provider.NewNotifier(&provider.NotifierConfig{ + BotToken: fApiToken, + ChannelId: fChannelId, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + res, err := notifier.Notify(context.Background(), mockSubject, mockMessage) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) +} diff --git a/internal/pkg/core/notifier/providers/telegrambot/telegrambot_test.go b/internal/pkg/core/notifier/providers/telegrambot/telegrambot_test.go index 3a207384..8dc18b95 100644 --- a/internal/pkg/core/notifier/providers/telegrambot/telegrambot_test.go +++ b/internal/pkg/core/notifier/providers/telegrambot/telegrambot_test.go @@ -17,14 +17,14 @@ const ( var ( fApiToken string - fChartId int64 + fChatId int64 ) func init() { argsPrefix := "CERTIMATE_NOTIFIER_TELEGRAMBOT_" flag.StringVar(&fApiToken, argsPrefix+"APITOKEN", "", "") - flag.Int64Var(&fChartId, argsPrefix+"CHATID", 0, "") + flag.Int64Var(&fChatId, argsPrefix+"CHATID", 0, "") } /* @@ -41,12 +41,12 @@ func TestNotify(t *testing.T) { t.Log(strings.Join([]string{ "args:", fmt.Sprintf("APITOKEN: %v", fApiToken), - fmt.Sprintf("CHATID: %v", fChartId), + fmt.Sprintf("CHATID: %v", fChatId), }, "\n")) notifier, err := provider.NewNotifier(&provider.NotifierConfig{ BotToken: fApiToken, - ChatId: fChartId, + ChatId: fChatId, }) if err != nil { t.Errorf("err: %+v", err) diff --git a/ui/public/imgs/providers/discord.svg b/ui/public/imgs/providers/discord.svg new file mode 100644 index 00000000..04f8f5e9 --- /dev/null +++ b/ui/public/imgs/providers/discord.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index baea8ed8..2a28aaac 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -31,6 +31,7 @@ import AccessFormCMCCCloudConfig from "./AccessFormCMCCCloudConfig"; import AccessFormDeSECConfig from "./AccessFormDeSECConfig"; import AccessFormDigitalOceanConfig from "./AccessFormDigitalOceanConfig"; import AccessFormDingTalkBotConfig from "./AccessFormDingTalkBotConfig"; +import AccessFormDiscordBotConfig from "./AccessFormDiscordBotConfig"; import AccessFormDNSLAConfig from "./AccessFormDNSLAConfig"; import AccessFormDogeCloudConfig from "./AccessFormDogeCloudConfig"; import AccessFormDuckDNSConfig from "./AccessFormDuckDNSConfig"; @@ -222,6 +223,8 @@ const AccessForm = forwardRef(({ className, return ; case ACCESS_PROVIDERS.DINGTALKBOT: return ; + case ACCESS_PROVIDERS.DISCORDBOT: + return ; case ACCESS_PROVIDERS.DNSLA: return ; case ACCESS_PROVIDERS.DOGECLOUD: diff --git a/ui/src/components/access/AccessFormDiscordBotConfig.tsx b/ui/src/components/access/AccessFormDiscordBotConfig.tsx new file mode 100644 index 00000000..5f844ccc --- /dev/null +++ b/ui/src/components/access/AccessFormDiscordBotConfig.tsx @@ -0,0 +1,70 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { type AccessConfigForDiscordBot } from "@/domain/access"; + +type AccessFormDiscordBotConfigFieldValues = Nullish; + +export type AccessFormDiscordBotConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: AccessFormDiscordBotConfigFieldValues; + onValuesChange?: (values: AccessFormDiscordBotConfigFieldValues) => void; +}; + +const initFormModel = (): AccessFormDiscordBotConfigFieldValues => { + return { + botToken: "", + }; +}; + +const AccessFormDiscordBotConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: AccessFormDiscordBotConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + botToken: z + .string({ message: t("access.form.discordbot_token.placeholder") }) + .min(1, t("access.form.discordbot_token.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + defaultChannelId: z.string().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default AccessFormDiscordBotConfig; diff --git a/ui/src/components/access/AccessFormTelegramBotConfig.tsx b/ui/src/components/access/AccessFormTelegramBotConfig.tsx index 3181c71d..a347610f 100644 --- a/ui/src/components/access/AccessFormTelegramBotConfig.tsx +++ b/ui/src/components/access/AccessFormTelegramBotConfig.tsx @@ -26,8 +26,8 @@ const AccessFormTelegramBotConfig = ({ form: formInst, formName, disabled, initi const formSchema = z.object({ botToken: z - .string({ message: t("access.form.telegram_bot_token.placeholder") }) - .min(1, t("access.form.telegram_bot_token.placeholder")) + .string({ message: t("access.form.telegrambot_token.placeholder") }) + .min(1, t("access.form.telegrambot_token.placeholder")) .max(256, t("common.errmsg.string_max", { max: 256 })), defaultChatId: z .preprocess( @@ -38,7 +38,7 @@ const AccessFormTelegramBotConfig = ({ form: formInst, formName, disabled, initi .refine((v) => { if (v == null || v + "" === "") return true; return !Number.isNaN(+v!) && +v! !== 0; - }, t("access.form.telegram_bot_default_chat_id.placeholder")) + }, t("access.form.telegrambot_default_chat_id.placeholder")) ) .nullish(), }); @@ -59,20 +59,20 @@ const AccessFormTelegramBotConfig = ({ form: formInst, formName, disabled, initi > } + tooltip={} > - + } + tooltip={} > - + ); diff --git a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx index d303c2ba..44a4a604 100644 --- a/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/NotifyNodeConfigForm.tsx @@ -17,6 +17,7 @@ import { useAntdForm, useAntdFormName, useZustandShallowSelector } from "@/hooks import { useAccessesStore } from "@/stores/access"; import { useNotifyChannelsStore } from "@/stores/notify"; +import NotifyNodeConfigFormDiscordBotConfig from "./NotifyNodeConfigFormDiscordBotConfig"; import NotifyNodeConfigFormEmailConfig from "./NotifyNodeConfigFormEmailConfig"; import NotifyNodeConfigFormMattermostConfig from "./NotifyNodeConfigFormMattermostConfig"; import NotifyNodeConfigFormTelegramBotConfig from "./NotifyNodeConfigFormTelegramBotConfig"; @@ -110,6 +111,8 @@ const NotifyNodeConfigForm = forwardRef; case NOTIFICATION_PROVIDERS.EMAIL: return ; case NOTIFICATION_PROVIDERS.MATTERMOST: diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormDiscordBotConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormDiscordBotConfig.tsx new file mode 100644 index 00000000..200f2684 --- /dev/null +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormDiscordBotConfig.tsx @@ -0,0 +1,61 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type NotifyNodeConfigFormDiscordBotConfigFieldValues = Nullish<{ + channelId?: string; +}>; + +export type NotifyNodeConfigFormDiscordBotConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: NotifyNodeConfigFormDiscordBotConfigFieldValues; + onValuesChange?: (values: NotifyNodeConfigFormDiscordBotConfigFieldValues) => void; +}; + +const initFormModel = (): NotifyNodeConfigFormDiscordBotConfigFieldValues => { + return {}; +}; + +const NotifyNodeConfigFormDiscordBotConfig = ({ + form: formInst, + formName, + disabled, + initialValues, + onValuesChange, +}: NotifyNodeConfigFormDiscordBotConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + channelId: z.string().nullish(), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + +
+ ); +}; + +export default NotifyNodeConfigFormDiscordBotConfig; diff --git a/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramBotConfig.tsx b/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramBotConfig.tsx index a40142ee..29eaa807 100644 --- a/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramBotConfig.tsx +++ b/ui/src/components/workflow/node/NotifyNodeConfigFormTelegramBotConfig.tsx @@ -38,7 +38,7 @@ const NotifyNodeConfigFormTelegramBotConfig = ({ .refine((v) => { if (v == null || v + "" === "") return true; return !Number.isNaN(+v!) && +v! !== 0; - }, t("workflow_node.notify.form.telegram_bot_chat_id.placeholder")) + }, t("workflow_node.notify.form.telegrambot_chat_id.placeholder")) ) .nullish(), }); @@ -59,11 +59,11 @@ const NotifyNodeConfigFormTelegramBotConfig = ({ > } + tooltip={} > - + ); diff --git a/ui/src/domain/access.ts b/ui/src/domain/access.ts index 9e2d4f70..9e953963 100644 --- a/ui/src/domain/access.ts +++ b/ui/src/domain/access.ts @@ -26,6 +26,7 @@ export interface AccessModel extends BaseModel { | AccessConfigForDeSEC | AccessConfigForDigitalOcean | AccessConfigForDingTalkBot + | AccessConfigForDiscordBot | AccessConfigForDNSLA | AccessConfigForDogeCloud | AccessConfigForDuckDNS @@ -181,6 +182,11 @@ export type AccessConfigForDingTalkBot = { secret?: string; }; +export type AccessConfigForDiscordBot = { + botToken: string; + defaultChannelId?: string; +}; + export type AccessConfigForDNSLA = { apiId: string; apiSecret: string; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 6576aa94..806c8283 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -25,6 +25,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ DESEC: "desec", DIGITALOCEAN: "digitalocean", DINGTALKBOT: "dingtalkbot", + DISCORDBOT: "discordbot", DNSLA: "dnsla", DOGECLOUD: "dogecloud", DUCKDNS: "duckdns", @@ -172,8 +173,9 @@ export const accessProvidersMap: Map [ e[0] as string, { @@ -588,6 +590,7 @@ export const deploymentProvidersMap: Map [ type, { diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 31772bf5..fe437f87 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -161,6 +161,12 @@ "access.form.dingtalkbot_secret.label": "DingTalk bot secret", "access.form.dingtalkbot_secret.placeholder": "Please enter DingTalk bot secret", "access.form.dingtalkbot_secret.tooltip": "For more information, see https://open.dingtalk.com/document/orgapp/customize-robot-security-settings", + "access.form.discordbot_token.label": "Discord bot token", + "access.form.discordbot_token.placeholder": "Please enter Discord bot token", + "access.form.discordbot_token.tooltip": "How to get it? Please refer to https://docs.discordbotstudio.org/setting-up-dbs/finding-your-bot-token", + "access.form.discordbot_default_channel_id.label": "Default Discord channel ID (Optional)", + "access.form.discordbot_default_channel_id.placeholder": "Please enter default Discord channel ID", + "access.form.discordbot_default_channel_id.tooltip": "For more information, see https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID", "access.form.dnsla_api_id.label": "DNS.LA API ID", "access.form.dnsla_api_id.placeholder": "Please enter DNS.LA API ID", "access.form.dnsla_api_id.tooltip": "For more information, see https://www.dns.la/docs/ApiDoc", @@ -284,7 +290,7 @@ "access.form.mattermost_password.placeholder": "Please enter Mattermost password", "access.form.mattermost_default_channel_id.label": "Default Mattermost channel ID (Optional)", "access.form.mattermost_default_channel_id.placeholder": "Please enter default Mattermost channel ID", - "access.form.mattermost_default_channel_id.tooltip": "How to get the channel ID? Select the target channel from the left sidebar, click on the channel name at the top, and choose ”Channel Details.” You can directly see the channel ID on the pop-up page.", + "access.form.mattermost_default_channel_id.tooltip": "How to get it? Select the target channel from the left sidebar, click on the channel name at the top, and choose ”Channel Details.” You can directly see the channel ID on the pop-up page.", "access.form.namecheap_username.label": "Namecheap username", "access.form.namecheap_username.placeholder": "Please enter Namecheap username", "access.form.namecheap_username.tooltip": "For more information, see https://www.namecheap.com/support/api/intro/", @@ -380,12 +386,12 @@ "access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.tooltip": "For more information, see https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", - "access.form.telegram_bot_token.label": "Telegram bot token", - "access.form.telegram_bot_token.placeholder": "Please enter Telegram bot token", - "access.form.telegram_bot_token.tooltip": "How to get the bot token? Please refer to https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", - "access.form.telegram_bot_default_chat_id.label": "Default Telegram chat ID (Optional)", - "access.form.telegram_bot_default_chat_id.placeholder": "Please enter default Telegram chat ID", - "access.form.telegram_bot_default_chat_id.tooltip": "How to get the chat ID? Please refer to https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "access.form.telegrambot_token.label": "Telegram bot token", + "access.form.telegrambot_token.placeholder": "Please enter Telegram bot token", + "access.form.telegrambot_token.tooltip": "How to get it? Please refer to https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "access.form.telegrambot_default_chat_id.label": "Default Telegram chat ID (Optional)", + "access.form.telegrambot_default_chat_id.placeholder": "Please enter default Telegram chat ID", + "access.form.telegrambot_default_chat_id.tooltip": "How to get it? Please refer to https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", "access.form.tencentcloud_secret_id.label": "Tencent Cloud SecretId", "access.form.tencentcloud_secret_id.placeholder": "Please enter Tencent Cloud SecretId", "access.form.tencentcloud_secret_id.tooltip": "For more information, see https://cloud.tencent.com/document/product/598/40488?lang=en", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 80cb65cf..b34c92a7 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -60,6 +60,7 @@ "provider.desec": "deSEC", "provider.digitalocean": "DigitalOcean", "provider.dingtalkbot": "DingTalk Bot", + "provider.discordbot": "Discord Bot", "provider.dnsla": "DNS.LA", "provider.dogecloud": "Doge Cloud", "provider.dogecloud.cdn": "Doge Cloud - CDN (Content Delivery Network)", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index e0602370..172d707b 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -815,6 +815,9 @@ "workflow_node.notify.form.provider_access.placeholder": "Please select an authorization of notification provider", "workflow_node.notify.form.provider_access.button": "Create", "workflow_node.notify.form.params_config.label": "Parameter settings", + "workflow_node.notify.form.discordbot_channel_id.label": "Discord channel ID (Optional)", + "workflow_node.notify.form.discordbot_channel_id.placeholder": "Please enter Discord channel ID to override the default value", + "workflow_node.notify.form.discordbot_channel_id.tooltip": "Leave it blank to use the default channel ID provided by the authorization.", "workflow_node.notify.form.email_sender_address.label": "Sender email address (Optional)", "workflow_node.notify.form.email_sender_address.placeholder": "Please enter sender email address to override the default value", "workflow_node.notify.form.email_sender_address.tooltip": "Leave it blank to use the default sender email address provided by the authorization.", @@ -822,11 +825,11 @@ "workflow_node.notify.form.email_receiver_address.placeholder": "Please enter receiver email address to override the default value", "workflow_node.notify.form.email_receiver_address.tooltip": "Leave it blank to use the default receiver email address provided by the selected authorization.", "workflow_node.notify.form.mattermost_channel_id.label": "Mattermost channel ID (Optional)", - "workflow_node.notify.form.mattermost_channel_id.placeholder": "Please enter Mattermost channel ID to override the default value", + "workflow_node.notify.form.mattermost_channel_id.placeholder": "Please enter Mattermost channel ID to override the default value", "workflow_node.notify.form.mattermost_channel_id.tooltip": "Leave it blank to use the default channel ID provided by the authorization.", - "workflow_node.notify.form.telegram_bot_chat_id.label": "Telegram chat ID (Optional)", - "workflow_node.notify.form.telegram_bot_chat_id.placeholder": "Please enter Telegram chat ID to override the default value", - "workflow_node.notify.form.telegram_bot_chat_id.tooltip": "Leave it blank to use the default chat ID provided by the selected authorization.", + "workflow_node.notify.form.telegrambot_chat_id.label": "Telegram chat ID (Optional)", + "workflow_node.notify.form.telegrambot_chat_id.placeholder": "Please enter Telegram chat ID to override the default value", + "workflow_node.notify.form.telegrambot_chat_id.tooltip": "Leave it blank to use the default chat ID provided by the selected authorization.", "workflow_node.notify.form.webhook_data.label": "Webhook data (Optional)", "workflow_node.notify.form.webhook_data.placeholder": "Please enter Webhook data to override the default value", "workflow_node.notify.form.webhook_data.tooltip": "Leave it blank to use the default Webhook data provided by the authorization.", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 1f538ac5..21ad79c5 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -155,6 +155,12 @@ "access.form.dingtalkbot_secret.label": "钉钉群机器人加签密钥", "access.form.dingtalkbot_secret.placeholder": "请输入钉钉群机器人加签密钥", "access.form.dingtalkbot_secret.tooltip": "这是什么?请参阅 https://open.dingtalk.com/document/orgapp/customize-robot-security-settings", + "access.form.discordbot_token.label": "Discord 机器人 API Token", + "access.form.discordbot_token.placeholder": "请输入 Discord 机器人 API Token", + "access.form.discordbot_token.tooltip": "如何获取此参数?请参阅 https://docs.discordbotstudio.org/setting-up-dbs/finding-your-bot-token", + "access.form.discordbot_default_channel_id.label": "默认的 Discord 频道 ID(可选)", + "access.form.discordbot_default_channel_id.placeholder": "请输入默认的 Discord 频道 ID", + "access.form.discordbot_default_channel_id.tooltip": "这是什么?请参阅 https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID", "access.form.dnsla_api_id.label": "DNS.LA API ID", "access.form.dnsla_api_id.placeholder": "请输入 DNS.LA API ID", "access.form.dnsla_api_id.tooltip": "这是什么?请参阅 https://www.dns.la/docs/ApiDoc", @@ -278,7 +284,7 @@ "access.form.mattermost_password.placeholder": "请输入 Mattermost 密码", "access.form.mattermost_default_channel_id.label": "默认的 Mattermost 频道 ID(可选)", "access.form.mattermost_default_channel_id.placeholder": "请输入默认的 Mattermost 频道 ID", - "access.form.mattermost_default_channel_id.tooltip": "如何获取频道 ID?从左侧边栏中选择目标频道,点击顶部的频道名称,选择“频道详情”,即可在弹出页面中直接看到频道 ID。", + "access.form.mattermost_default_channel_id.tooltip": "如何获取此参数?从左侧边栏中选择目标频道,点击顶部的频道名称,选择“频道详情”,即可在弹出页面中直接看到频道 ID。", "access.form.namecheap_username.label": "Namecheap 用户名", "access.form.namecheap_username.placeholder": "请输入 Namecheap 用户名", "access.form.namecheap_username.tooltip": "这是什么?请参阅 https://www.namecheap.com/support/api/intro/", @@ -374,12 +380,12 @@ "access.form.sslcom_eab_hmac_key.label": "ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC key", "access.form.sslcom_eab_hmac_key.tooltip": "这是什么?请参阅 https://www.ssl.com/how-to/generate-acme-credentials-for-reseller-customers/", - "access.form.telegram_bot_token.label": "Telegram 群机器人 API Token", - "access.form.telegram_bot_token.placeholder": "请输入 Telegram 群机器人 API Token", - "access.form.telegram_bot_token.tooltip": "如何获取机器人 API Token?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", - "access.form.telegram_bot_default_chat_id.label": "默认的 Telegram 会话 ID(可选)", - "access.form.telegram_bot_default_chat_id.placeholder": "请输入默认的 Telegram 会话 ID", - "access.form.telegram_bot_default_chat_id.tooltip": "如何获取会话 ID?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "access.form.telegrambot_token.label": "Telegram 机器人 API Token", + "access.form.telegrambot_token.placeholder": "请输入 Telegram 机器人 API Token", + "access.form.telegrambot_token.tooltip": "如何获取此参数?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", + "access.form.telegrambot_default_chat_id.label": "默认的 Telegram 会话 ID(可选)", + "access.form.telegrambot_default_chat_id.placeholder": "请输入默认的 Telegram 会话 ID", + "access.form.telegrambot_default_chat_id.tooltip": "如何获取此参数?请参阅 https://gist.github.com/nafiesl/4ad622f344cd1dc3bb1ecbe468ff9f8a", "access.form.tencentcloud_secret_id.label": "腾讯云 SecretId", "access.form.tencentcloud_secret_id.placeholder": "请输入腾讯云 SecretId", "access.form.tencentcloud_secret_id.tooltip": "这是什么?请参阅 https://cloud.tencent.com/document/product/598/40488", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 8de1ccf5..dd12aa27 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -60,6 +60,7 @@ "provider.desec": "deSEC", "provider.digitalocean": "DigitalOcean", "provider.dingtalkbot": "钉钉群机器人", + "provider.discordbot": "Discord 机器人", "provider.dnsla": "DNS.LA", "provider.dogecloud": "多吉云", "provider.dogecloud.cdn": "多吉云 - 内容分发网络 CDN", @@ -119,7 +120,7 @@ "provider.safeline": "雷池", "provider.ssh": "SSH 部署", "provider.sslcom": "SSL.com", - "provider.telegrambot": "Telegram 群机器人", + "provider.telegrambot": "Telegram 机器人", "provider.tencentcloud": "腾讯云", "provider.tencentcloud.cdn": "腾讯云 - 内容分发网络 CDN", "provider.tencentcloud.clb": "腾讯云 - 负载均衡 CLB", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 2dbcb3ca..b6d27dc7 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -814,6 +814,9 @@ "workflow_node.notify.form.provider_access.placeholder": "请选择通知渠道授权", "workflow_node.notify.form.provider_access.button": "新建", "workflow_node.notify.form.params_config.label": "参数设置", + "workflow_node.notify.form.discordbot_channel_id.label": "Discord 频道 ID(可选)", + "workflow_node.notify.form.discordbot_channel_id.placeholder": "请输入 Discord 频道 ID 以覆盖默认值", + "workflow_node.notify.form.discordbot_channel_id.tooltip": "不填写时,将使用所选通知渠道授权的默认频道 ID。", "workflow_node.notify.form.email_sender_address.label": "发送邮箱地址(可选)", "workflow_node.notify.form.email_sender_address.placeholder": "请输入发送邮箱地址以覆盖默认值", "workflow_node.notify.form.email_sender_address.tooltip": "不填写时,将使用所选通知渠道授权的默认发送邮箱地址。", @@ -823,9 +826,9 @@ "workflow_node.notify.form.mattermost_channel_id.label": "Mattermost 频道 ID(可选)", "workflow_node.notify.form.mattermost_channel_id.placeholder": "请输入 Mattermost 频道 ID 以覆盖默认值", "workflow_node.notify.form.mattermost_channel_id.tooltip": "不填写时,将使用所选通知渠道授权的默认频道 ID。", - "workflow_node.notify.form.telegram_bot_chat_id.label": "Telegram 会话 ID(可选)", - "workflow_node.notify.form.telegram_bot_chat_id.placeholder": "请输入 Telegram 会话 ID 以覆盖默认值", - "workflow_node.notify.form.telegram_bot_chat_id.tooltip": "不填写时,将使用所选通知渠道授权的默认会话 ID。", + "workflow_node.notify.form.telegrambot_chat_id.label": "Telegram 会话 ID(可选)", + "workflow_node.notify.form.telegrambot_chat_id.placeholder": "请输入 Telegram 会话 ID 以覆盖默认值", + "workflow_node.notify.form.telegrambot_chat_id.tooltip": "不填写时,将使用所选通知渠道授权的默认会话 ID。", "workflow_node.notify.form.webhook_data.label": "Webhook 回调数据(可选)", "workflow_node.notify.form.webhook_data.placeholder": "请输入 Webhook 回调数据以覆盖默认值", "workflow_node.notify.form.webhook_data.tooltip": "不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。",