├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── README_en.md ├── docker-push.sh ├── pom.xml ├── run.sh └── src └── main ├── java └── code │ ├── Main.java │ ├── commands │ ├── HelpCommand.java │ └── StartCommand.java │ ├── config │ ├── Config.java │ ├── ConfigField.java │ ├── ConfigSettings.java │ ├── ExecutorsConfig.java │ ├── I18nConfig.java │ ├── I18nEnum.java │ ├── I18nLocaleEnum.java │ ├── ProxyTypeEnum.java │ ├── RequestProxyConfig.java │ └── TableEnum.java │ ├── eneity │ ├── GptTokenTableEntity.java │ ├── RecordTableEntity.java │ └── cons │ │ ├── GptTokenStatusEnum.java │ │ └── YesOrNoEnum.java │ ├── handler │ ├── Command.java │ ├── CommandsHandler.java │ ├── Handler.java │ ├── I18nHandle.java │ ├── StepsCenter.java │ ├── commands │ │ └── AdminCommands.java │ ├── message │ │ ├── InlineKeyboardButtonBuilder.java │ │ ├── InlineKeyboardButtonListBuilder.java │ │ └── MessageHandle.java │ ├── steps │ │ ├── StepErrorApi.java │ │ ├── StepExecuteResult.java │ │ ├── StepHandleApi.java │ │ ├── StepResult.java │ │ ├── StepsBuilder.java │ │ ├── StepsChatSession.java │ │ ├── StepsChatSessionBuilder.java │ │ ├── StepsHandler.java │ │ └── StepsRegisterCenter.java │ └── store │ │ ├── ChatButtonsStore.java │ │ ├── GptTokenStore.java │ │ └── Store.java │ ├── repository │ ├── GptTokenTableRepository.java │ ├── I18nTableRepository.java │ ├── RecordTableRepository.java │ └── mapper │ │ ├── SqlBuilder.java │ │ ├── TableEntity.java │ │ ├── TableField.java │ │ ├── TableName.java │ │ └── TableRepository.java │ └── util │ ├── BytesUtil.java │ ├── CompressUtil.java │ ├── DownloadUtil.java │ ├── ExceptionUtil.java │ ├── FFmpegUtil.java │ ├── GPTUtil.java │ ├── GithubUtil.java │ ├── ProgramUtil.java │ ├── SqliteUtil.java │ ├── ThreadUtil.java │ ├── ffmpeg │ └── FfmpegDownloadUrl.java │ └── gpt │ ├── GPTCallback.java │ ├── GPTModel.java │ ├── GPTRole.java │ ├── GPTTranscriptionsModel.java │ ├── parameter │ ├── GPTChatParameter.java │ ├── GPTCreateImageParameter.java │ ├── GPTMessage.java │ └── GPTTranscriptionsParameter.java │ └── response │ ├── GPTCallbackChatContent.java │ ├── GPTChatContentChoices.java │ ├── GPTChatContentChoicesDelta.java │ ├── GPTChatResponse.java │ ├── GPTCreateImage.java │ ├── GPTCreateImageResponse.java │ └── GPTTranscriptionsResponse.java └── resources ├── i18n ├── i18n_en.properties └── i18n_zh_CN.properties └── logback.xml /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | /logs 36 | /config 37 | 38 | .mvn 39 | mvnw 40 | mvnw.cmd 41 | 42 | *.imi 43 | /ChatGPTForTelegram-universal.jar 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jdk-alpine 2 | ENV TZ=Asia/Shanghai 3 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 4 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories 5 | # 增加字体,解决验证码没有字体报空指针问题 6 | RUN set -xe \ 7 | && apk --no-cache add ttf-dejavu fontconfig 8 | # 系统编码 9 | ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 10 | 11 | ENV GPT_TOKEN='' 12 | ENV BOT_ADMIN_ID='' 13 | ENV BOT_NAME='' 14 | ENV BOT_TOKEN='' 15 | ENV PROXY=false 16 | ENV PROXY_HOST=127.0.0.1 17 | ENV PROXY_PORT=7890 18 | 19 | WORKDIR /app 20 | COPY ChatGPTForTelegram-universal.jar ChatGPTForTelegram-universal.jar 21 | COPY run.sh run.sh 22 | 23 | ENTRYPOINT ["sh", "run.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 kylelin1998 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 简体中文 | [English](./README_en.md) 2 | 3 | ![License](https://img.shields.io/badge/license-MIT-green) 4 | [![release](https://img.shields.io/github/v/release/kylelin1998/ChatGPTForTelegram)](https://github.com/kylelin1998/ChatGPTForTelegram/releases/latest) 5 | 6 | ## 介绍 7 | - Youtube: https://youtu.be/a_WLFtRWGzY 8 | - 哔哩哔哩: https://www.bilibili.com/video/BV1qX4y1z7Dt/ 9 | 10 | ChatGPT机器人, 这是一个开源项目, 你可以基于它搭建属于自己的机器人 11 | 12 | 使用机器人可以让你轻松进行对话, 后续机器人的更新升级一个命令即可搞定, 无需再上服务器进行升级机器人 13 | 14 | 1. 支持各种场景文本对话 15 | 2. 语音对话 16 | 3. 录制重放对话 17 | 4. 支持生成图片 18 | 19 | 你可以事先录制一段对话, 后面用到的时候直接快速重放出来, 就不用每次建立新对话就要发送一堆引导文本了 20 | 21 | ## 最近更新日志 22 | 开源ChatGPT TG机器人 v1.0.50 更新说明视频 23 | - ⭐ Youtube: https://youtu.be/9hczaDzOvGA 24 | - ⭐ 哔哩哔哩: https://www.bilibili.com/video/BV1wV41137Yf/ 25 | 26 | 1. 支持多ChatGPT Api Key, 随机切换使用, 死号自动通知管理员 27 | 2. 支持精简回复, 开启后部分命令会去除回复的问题显示和退出提示, 仅显示回复内容 28 | 3. 超级加强引导, 录制重放对话支持替换变量, 直接发送文本即可, 无需再增加文本来引导 29 | 30 | ## 部署 31 | 机器人的部署步骤是基于 Docker 的,其机器人升级功能也基于 Docker,因此请使用 Docker 进行部署,以防出现错误 32 | 33 | ### 部署方式1 (推荐) 34 | - ⭐ Youtube: https://youtu.be/mNg6TFyozZk 35 | - ⭐ 哔哩哔哩: https://www.bilibili.com/video/BV1qF411f7pg/ 36 | 37 | #### 一键部署 38 | ``` 39 | docker run --name gpttb -d -v $(pwd)/config:/app/config -e GPT_TOKEN=你的GPTApiKey -e BOT_ADMIN_ID=管理者的ChatId -e BOT_NAME=机器人的username -e BOT_TOKEN=机器人token --restart=always kylelin1998/chatgpt-tg-bot 40 | ``` 41 | #### 一键部署(开启代理) 42 | ``` 43 | docker run --name gpttb -d -v $(pwd)/config:/app/config -e GPT_TOKEN=你的GPTApiKey -e BOT_ADMIN_ID=管理者的ChatId -e BOT_NAME=机器人的username -e BOT_TOKEN=机器人token -e PROXY=true -e PROXY_HOST=127.0.0.1 -e PROXY_PORT=7890 --restart=always kylelin1998/chatgpt-tg-bot 44 | ``` 45 | 46 | ### 部署方式2 (不推荐) 47 | - Youtube:https://youtu.be/CiDxb1ESijQ 48 | - 哔哩哔哩: https://www.bilibili.com/video/BV1Ts4y1S7bn/ 49 | 50 | ### 准备 51 | ![ff3379f00b462db7b016f361c9b8fb7cd9097dc8.png](https://openimg.kylelin1998.com/img/ff3379f00b462db7b016f361c9b8fb7cd9097dc8.png) 52 | 53 | 首先,在您的服务器上创建一个文件夹 54 | 55 | 然后,在其中创建名为 config 的另一个文件夹,config文件夹下必须包含名为 config.json 的JSON文件 56 | 57 | 接着,将 ChatGPTForTelegram-universal.jar, run.sh 和 Dockerfile 传输到该文件夹中 58 | 59 | ### config.json 60 | ``` 61 | { 62 | "open": false, 63 | "on_proxy": false, 64 | "proxy_host": "127.0.0.1", 65 | "proxy_port": 7890, 66 | "bot_admin_id": "xxxx", 67 | "bot_name": "xxx_bot", 68 | "bot_token": "xxxx", 69 | "debug": false, 70 | "gpt_token": "xxxxx", 71 | "gpt_model": "gpt-3.5-turbo", 72 | "permission_chat_id_array": [ 73 | "xxxx" 74 | ], 75 | "block_chat_id_array": [] 76 | } 77 | ``` 78 | ``` 79 | open -> 80 | 开放状态:任何人都可以使用这个机器人。 81 | 关闭状态:只有permission chat id列表才能使用这个bot。 82 | on proxy 代表是否开启代理 83 | bot admin id 就是你要指定那个号是管理员, 这个id是chat id 84 | bot name, 和 bot token 就是机器人创建好就有的,你肯定知道 85 | gpt token -> 就是 gpt的token 86 | permission chat id array -> 这个就是代表你只能允许列表下的这些chat id使用机器人, 可以填写个人的,或者是群的chat id 87 | block_chat_id_array -> 不允许使用机器人的chat id列表 88 | ``` 89 | 90 | ### 第一步: 91 | 编译镜像 92 | ``` 93 | docker build -t gptft . 94 | ``` 95 | 96 | ### 第二步: 97 | 运行容器镜像 98 | ``` 99 | docker run --name gptft -d -v $(pwd):/app --restart=always gptft 100 | ``` 101 | 102 | ## 使用 103 | 机器人命令: 104 | ``` 105 | chat - 发起对话 106 | c - 发起对话 107 | ask - 单次提问 108 | a - 单次提问 109 | cml - 发起消息限制对话 110 | nccm - 发起无上下文对话 111 | image - 生成图片 112 | record - 录制对话 113 | p - 重放对话 114 | record_list - 录制对话管理 115 | exit - 退出 116 | language - 切换语言 117 | admin - 管理员命令 118 | restart - 重启机器人 119 | upgrade - 升级机器人 120 | help - 帮助 121 | ``` 122 | ![560c3fe7450239da5ad0d9638cfd4fd66551d576.png](https://openimg.kylelin1998.com/img/560c3fe7450239da5ad0d9638cfd4fd66551d576.png) 123 | ![9d57081a0e248f157e427618c3430a44f3b1785d.png](https://openimg.kylelin1998.com/img/9d57081a0e248f157e427618c3430a44f3b1785d.png) 124 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | ### [简体中文](./README.md) | English 2 | 3 | ![License](https://img.shields.io/badge/license-MIT-green) 4 | [![release](https://img.shields.io/github/v/release/kylelin1998/ChatGPTForTelegram)](https://github.com/kylelin1998/ChatGPTForTelegram/releases/latest) 5 | 6 | ## Introduction 7 | Youtube: https://youtu.be/a_WLFtRWGzY 8 | 9 | 哔哩哔哩: https://www.bilibili.com/video/BV1qX4y1z7Dt/ 10 | 11 | The ChatGPT Telegram Bot based on OpenAI's ChatGPT. It can let you easily to chat or ask. 12 | 13 | The bot has some commands that consider different scenes for chatting. 14 | 15 | This project is an open-source project that you can trust. Once deployed, the bot's version is easy with a single command to upgrade. 16 | 17 | 1. Support the voice chat with the bot. 18 | 2. Added record & playback mode feature. 19 | 3. Support text chat with the bot. 20 | 4. Support image generation 21 | 22 | The record & playback mode allows you to record chats with the bot, then send 'end_record' to stop and save the recording. Afterward, you can playback the recorded chat. It can help your work and is a nice feature. 23 | 24 | ## Recent Update Log 25 | Open-source ChatGPT TG Robot v1.0.50 Update Notes Video 26 | - ⭐ Youtube: https://youtu.be/9hczaDzOvGA 27 | - ⭐ Bilibili: https://www.bilibili.com/video/BV1wV41137Yf/ 28 | 29 | 1. Added support for multiple ChatGPT API keys, which switch randomly. Invalid keys are automatically notified to the administrator. 30 | 2. Added support for streamlined replies. When enabled, certain commands will remove the display of the question and exit prompts, only showing the reply content. 31 | 3. Enhanced onboarding process. The recording and playback of conversations now support variable substitution. Simply send the text and no longer need to add additional text for guidance. 32 | 33 | ## Deploy 34 | The bot's deploy steps based on the Docker, its upgrade feature also based on the Docker, so please use the Docker to deploy it in case appear error. 35 | 36 | ### Deployment method 1 (recommended) 37 | ⭐ Youtube: https://youtu.be/mNg6TFyozZk 38 | 39 | ⭐ 哔哩哔哩: https://www.bilibili.com/video/BV1qF411f7pg/ 40 | 41 | #### One-click deployment 42 | ``` 43 | docker run --name gpttb -d -v $(pwd)/config:/app/config -e GPT_TOKEN=YourGPTApiKey -e BOT_ADMIN_ID=AdminChatId -e BOT_NAME=BotUsername -e BOT_TOKEN=BotToken --restart=always kylelin1998/chatgpt-tg-bot 44 | ``` 45 | #### One-click deployment (with proxy enabled) 46 | ``` 47 | docker run --name gpttb -d -v $(pwd)/config:/app/config -e GPT_TOKEN=YourGPTApiKey -e BOT_ADMIN_ID=AdminChatId -e BOT_NAME=BotUsername -e BOT_TOKEN=BotToken -e PROXY=true -e PROXY_HOST=127.0.0.1 -e PROXY_PORT=7890 --restart=always kylelin1998/chatgpt-tg-bot 48 | ``` 49 | 50 | ### Deployment method 2 (not recommended) 51 | Youtube:https://youtu.be/CiDxb1ESijQ 52 | 53 | 哔哩哔哩: https://www.bilibili.com/video/BV1Ts4y1S7bn/ 54 | 55 | ### Prepare 56 | ![ff3379f00b462db7b016f361c9b8fb7cd9097dc8.png](https://openimg.kylelin1998.com/img/ff3379f00b462db7b016f361c9b8fb7cd9097dc8.png) 57 | 58 | To start, create a folder named whatever you prefer on your server. 59 | Then create another folder named config and the config folder must contains a json file named config.json in, then transfer ChatGPTForTelegram-universal.jar, run.sh and Dockerfile to the folder. 60 | ### config.json 61 | ``` 62 | { 63 | "open": false, 64 | "on_proxy": false, 65 | "proxy_host": "127.0.0.1", 66 | "proxy_port": 7890, 67 | "bot_admin_id": "xxxx", 68 | "bot_name": "xxx_bot", 69 | "bot_token": "xxxx", 70 | "debug": false, 71 | "gpt_token": "xxxxx", 72 | "gpt_model": "gpt-3.5-turbo", 73 | "permission_chat_id_array": [ 74 | "xxxx" 75 | ], 76 | "block_chat_id_array": [] 77 | } 78 | ``` 79 | ``` 80 | open -> 81 | Open status: Anyone can use this bot. 82 | Close status: Only permission chat id list can use this bot. 83 | on proxy -> Whether to open proxy 84 | bot admin id -> Bot's admin, the id is chat id of Telegram. 85 | bot name, and token you -> @BotFather has given bot name, bot token 86 | permission chat id array -> Allow using the bot. 87 | block_chat_id_array -> Not allow using the bot. 88 | ``` 89 | 90 | ### First step: 91 | Build a docker image for use. 92 | ``` 93 | docker build -t gptft . 94 | ``` 95 | 96 | ### Second step: 97 | Run the docker image of just then build. 98 | ``` 99 | docker run --name gptft -d -v $(pwd):/app --restart=always gptft 100 | ``` 101 | 102 | ## Usage 103 | How to use the bot's commands: 104 | ``` 105 | chat - The current mode is continuous chat mode 106 | c - The current mode is continuous chat mode 107 | ask - Ask a problem 108 | a - Ask a problem 109 | cml - The current mode is chat message limit mode 110 | nccm - The current mode is none of message context mode 111 | image - Create a image for you 112 | record - Record chat messages. 113 | p - Playback chat messages. 114 | record_list - Manage the record list. 115 | exit - Exit chat 116 | language - Change language 117 | admin - Admin 118 | restart - Restart the bot 119 | upgrade - Upgrade the bot 120 | help - Help 121 | ``` 122 | 123 | ![560c3fe7450239da5ad0d9638cfd4fd66551d576.png](https://openimg.kylelin1998.com/img/560c3fe7450239da5ad0d9638cfd4fd66551d576.png) 124 | ![9d57081a0e248f157e427618c3430a44f3b1785d.png](https://openimg.kylelin1998.com/img/9d57081a0e248f157e427618c3430a44f3b1785d.png) 125 | -------------------------------------------------------------------------------- /docker-push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sudo apt install -y qemu-user-static binfmt-support 4 | docker buildx create --use 5 | docker buildx ls 6 | 7 | docker buildx build --platform linux/amd64,linux/386,linux/arm64 -t kylelin1998/chatgpt-tg-bot . --push -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.kylelin1998 8 | ChatGPTForTelegram 9 | universal 10 | 11 | 12 | 8 13 | 8 14 | UTF-8 15 | 16 | 17 | 18 | 19 | nexus-tencentyun 20 | Nexus tencentyun 21 | http://mirrors.cloud.tencent.com/nexus/repository/maven-public/ 22 | default 23 | 24 | 25 | true 26 | 27 | 28 | 29 | false 30 | 31 | 32 | 33 | central 34 | aliyun maven 35 | http://maven.aliyun.com/nexus/content/groups/public/ 36 | default 37 | 38 | 39 | true 40 | 41 | 42 | 43 | false 44 | 45 | 46 | 47 | 48 | 49 | 50 | com.alibaba.fastjson2 51 | fastjson2 52 | 2.0.23 53 | 54 | 55 | 56 | org.projectlombok 57 | lombok 58 | 1.18.22 59 | provided 60 | 61 | 62 | 63 | commons-io 64 | commons-io 65 | 2.11.0 66 | 67 | 68 | org.apache.commons 69 | commons-lang3 70 | 3.12.0 71 | 72 | 73 | com.konghq 74 | unirest-java 75 | 3.14.1 76 | 77 | 78 | org.jsoup 79 | jsoup 80 | 1.11.2 81 | 82 | 83 | 84 | 85 | org.slf4j 86 | slf4j-api 87 | 1.7.28 88 | jar 89 | 90 | 91 | ch.qos.logback 92 | logback-core 93 | 1.2.3 94 | jar 95 | 96 | 97 | ch.qos.logback 98 | logback-classic 99 | 1.2.3 100 | jar 101 | 102 | 103 | 104 | org.telegram 105 | telegrambots 106 | 6.5.0 107 | 108 | 109 | org.telegram 110 | telegrambotsextensions 111 | 6.5.0 112 | 113 | 114 | 115 | 116 | org.xerial 117 | sqlite-jdbc 118 | 3.40.0.0 119 | 120 | 121 | 122 | org.apache.httpcomponents.client5 123 | httpclient5 124 | 5.1.3 125 | 126 | 127 | org.apache.httpcomponents.client5 128 | httpclient5-fluent 129 | 5.1.3 130 | 131 | 132 | 133 | org.codehaus.plexus 134 | plexus-archiver 135 | 4.6.3 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | org.apache.maven.plugins 144 | maven-shade-plugin 145 | 2.3 146 | 147 | false 148 | 149 | 150 | 151 | package 152 | 153 | shade 154 | 155 | 156 | 157 | 158 | code.Main 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | temp=temp.jar 4 | app=/app/ChatGPTForTelegram-universal.jar 5 | temp_jar=/app/temp.jar 6 | 7 | if [ -e $temp ] 8 | then 9 | echo "updating..." 10 | rm -rf $app 11 | mv $temp_jar $app 12 | echo "updated..." 13 | fi 14 | 15 | java -Djava.security.egd=file:/dev/./urandom -DgptToken=$GPT_TOKEN -DbotAdminId=$BOT_ADMIN_ID -DbotName=$BOT_NAME -DbotToken=$BOT_TOKEN -DbotProxy=$PROXY -DbotProxyHost=$PROXY_HOST -DbotProxyPort=$PROXY_PORT -jar $app -------------------------------------------------------------------------------- /src/main/java/code/Main.java: -------------------------------------------------------------------------------- 1 | package code; 2 | 3 | import code.config.Config; 4 | import code.config.ConfigSettings; 5 | import code.config.I18nEnum; 6 | import code.config.RequestProxyConfig; 7 | import code.handler.CommandsHandler; 8 | import code.handler.Handler; 9 | import code.handler.I18nHandle; 10 | import code.handler.message.MessageHandle; 11 | import code.handler.store.Store; 12 | import code.repository.GptTokenTableRepository; 13 | import code.repository.I18nTableRepository; 14 | import code.repository.RecordTableRepository; 15 | import code.util.ExceptionUtil; 16 | import com.alibaba.fastjson2.JSON; 17 | import kong.unirest.Unirest; 18 | import lombok.extern.slf4j.Slf4j; 19 | import org.telegram.telegrambots.meta.TelegramBotsApi; 20 | import org.telegram.telegrambots.meta.exceptions.TelegramApiException; 21 | import org.telegram.telegrambots.updatesreceivers.DefaultBotSession; 22 | 23 | @Slf4j 24 | public class Main { 25 | public static volatile CommandsHandler Bot = null; 26 | public static volatile ConfigSettings GlobalConfig = Config.initConfig(); 27 | public static volatile code.repository.I18nTableRepository I18nTableRepository = new I18nTableRepository(); 28 | public static volatile code.repository.RecordTableRepository RecordTableRepository = new RecordTableRepository(); 29 | public static volatile code.repository.GptTokenTableRepository GptTokenTableRepository = new GptTokenTableRepository(); 30 | 31 | public static void main(String[] args) throws InterruptedException { 32 | log.info(String.format("Main args: %s", JSON.toJSONString(args))); 33 | log.info(String.format("System properties: %s", System.getProperties())); 34 | log.info(String.format("Config: %s", JSON.toJSONString(GlobalConfig))); 35 | 36 | Unirest 37 | .config() 38 | .enableCookieManagement(false) 39 | ; 40 | 41 | new Thread(() -> { 42 | while (true) { 43 | try { 44 | GlobalConfig = Config.readConfig(); 45 | Thread.sleep(5000); 46 | } catch (InterruptedException e) {} 47 | } 48 | }).start(); 49 | Handler.init(); 50 | 51 | log.info("Program is running"); 52 | 53 | try { 54 | TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class); 55 | 56 | if (GlobalConfig.getOnProxy()) { 57 | Bot = new CommandsHandler(RequestProxyConfig.create().buildDefaultBotOptions()); 58 | } else { 59 | Bot = new CommandsHandler(); 60 | } 61 | 62 | botsApi.registerBot(Bot); 63 | Store.init(); 64 | MessageHandle.sendMessage(GlobalConfig.getBotAdminId(), I18nHandle.getText(GlobalConfig.getBotAdminId(), I18nEnum.BotStartSucceed) + I18nHandle.getText(GlobalConfig.getBotAdminId(), I18nEnum.CurrentVersion) + ": " + Config.MetaData.CurrentVersion, false); 65 | 66 | } catch (TelegramApiException e) { 67 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/code/commands/HelpCommand.java: -------------------------------------------------------------------------------- 1 | package code.commands; 2 | 3 | import code.config.I18nEnum; 4 | import code.handler.I18nHandle; 5 | import code.handler.message.MessageHandle; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.telegram.telegrambots.extensions.bots.commandbot.commands.BotCommand; 9 | import org.telegram.telegrambots.meta.api.objects.Chat; 10 | import org.telegram.telegrambots.meta.api.objects.Message; 11 | import org.telegram.telegrambots.meta.api.objects.User; 12 | import org.telegram.telegrambots.meta.bots.AbsSender; 13 | 14 | import static code.Main.GlobalConfig; 15 | 16 | @Slf4j 17 | public class HelpCommand extends BotCommand { 18 | 19 | public HelpCommand() { 20 | super("help", ""); 21 | } 22 | 23 | @Override 24 | public void processMessage(AbsSender absSender, Message message, String[] arguments) { 25 | execute(absSender, message, arguments); 26 | } 27 | 28 | public void execute(AbsSender absSender, Message message, String[] arguments) { 29 | String chatId = message.getChat().getId().toString(); 30 | String fromId = String.valueOf(message.getFrom().getId()); 31 | 32 | if (StringUtils.isBlank(GlobalConfig.getHelpText())) { 33 | MessageHandle.sendMessage(chatId, I18nHandle.getText(fromId, I18nEnum.HelpText), false); 34 | } else { 35 | MessageHandle.sendMessage(chatId, GlobalConfig.getHelpText(), false); 36 | } 37 | } 38 | 39 | @Override 40 | public void execute(AbsSender absSender, User user, Chat chat, String[] strings) { 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/code/commands/StartCommand.java: -------------------------------------------------------------------------------- 1 | package code.commands; 2 | 3 | import code.config.I18nEnum; 4 | import code.handler.I18nHandle; 5 | import code.handler.message.MessageHandle; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.telegram.telegrambots.extensions.bots.commandbot.commands.BotCommand; 9 | import org.telegram.telegrambots.meta.api.objects.Chat; 10 | import org.telegram.telegrambots.meta.api.objects.Message; 11 | import org.telegram.telegrambots.meta.api.objects.User; 12 | import org.telegram.telegrambots.meta.bots.AbsSender; 13 | 14 | import static code.Main.GlobalConfig; 15 | 16 | @Slf4j 17 | public class StartCommand extends BotCommand { 18 | 19 | public StartCommand() { 20 | super("start", ""); 21 | } 22 | 23 | @Override 24 | public void processMessage(AbsSender absSender, Message message, String[] arguments) { 25 | execute(absSender, message, arguments); 26 | } 27 | 28 | public void execute(AbsSender absSender, Message message, String[] arguments) { 29 | String chatId = message.getChat().getId().toString(); 30 | String fromId = String.valueOf(message.getFrom().getId()); 31 | 32 | if (StringUtils.isBlank(GlobalConfig.getStartText())) { 33 | MessageHandle.sendMessage(chatId, I18nHandle.getText(fromId, I18nEnum.HelpText), false); 34 | } else { 35 | MessageHandle.sendMessage(chatId, GlobalConfig.getStartText(), false); 36 | } 37 | } 38 | 39 | @Override 40 | public void execute(AbsSender absSender, User user, Chat chat, String[] strings) { 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/code/config/Config.java: -------------------------------------------------------------------------------- 1 | package code.config; 2 | 3 | import code.util.ExceptionUtil; 4 | import code.util.gpt.GPTModel; 5 | import com.alibaba.fastjson2.JSON; 6 | import com.alibaba.fastjson2.JSONException; 7 | import com.alibaba.fastjson2.JSONReader; 8 | import com.alibaba.fastjson2.JSONWriter; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.apache.commons.io.FileUtils; 11 | import org.apache.commons.lang3.StringUtils; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.lang.reflect.Field; 16 | import java.nio.charset.StandardCharsets; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Properties; 20 | import java.util.concurrent.locks.ReentrantReadWriteLock; 21 | import java.util.stream.Collectors; 22 | 23 | @Slf4j 24 | public class Config { 25 | 26 | private final static String UserDir = System.getProperty("user.dir"); 27 | 28 | public final static String CurrentDir = (UserDir.equals("/") ? "" : UserDir) + File.separator + "config"; 29 | 30 | public final static String SettingsPath = CurrentDir + File.separator + "config.json"; 31 | 32 | public final static String TempDir = CurrentDir + File.separator + "temp"; 33 | 34 | public final static String DBPath = CurrentDir + File.separator + "db.db"; 35 | 36 | public final static String FFMPEGDir = CurrentDir + File.separator + "ffmpeg"; 37 | public final static String FFMPEGPath = FFMPEGDir + File.separator + (System.getProperty("os.name").toLowerCase().contains("windows") ? "bin"+File.separator+"ffmpeg.exe" : "ffmpeg"); 38 | 39 | private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); 40 | 41 | public static class MetaData { 42 | public final static String CurrentVersion = "1.0.62"; 43 | public final static String GitOwner = "kylelin1998"; 44 | public final static String GitRepo = "ChatGPTForTelegram"; 45 | public final static String ProcessName = "ChatGPTForTelegram-universal.jar"; 46 | public final static String JarName = "ChatGPTForTelegram-universal.jar"; 47 | } 48 | 49 | static { 50 | mkdirs(CurrentDir); 51 | 52 | List list = new ArrayList<>(); 53 | list.add(UserDir); 54 | list.add(CurrentDir); 55 | list.add(SettingsPath); 56 | list.add(TempDir); 57 | log.info(list.stream().collect(Collectors.joining("\n"))); 58 | } 59 | 60 | private static void mkdirs(String... path) { 61 | for (String s : path) { 62 | File file = new File(s); 63 | if (!file.exists()) { 64 | file.mkdirs(); 65 | } 66 | } 67 | } 68 | 69 | public static ConfigSettings initConfig() { 70 | File file = new File(SettingsPath); 71 | if (!file.exists()) { 72 | Properties properties = System.getProperties(); 73 | 74 | ConfigSettings configSettings = new ConfigSettings(); 75 | configSettings.setGptToken(properties.getProperty("gptToken", "")); 76 | configSettings.setBotAdminId(properties.getProperty("botAdminId", "")); 77 | configSettings.setBotName(properties.getProperty("botName", "")); 78 | configSettings.setBotToken(properties.getProperty("botToken", "")); 79 | configSettings.setOnProxy(Boolean.valueOf(properties.getProperty("botProxy", "false"))); 80 | configSettings.setProxyHost(properties.getProperty("botProxyHost", "127.0.0.1")); 81 | configSettings.setProxyPort(Integer.valueOf(properties.getProperty("botProxyPort", "7890"))); 82 | 83 | saveConfig(handle(configSettings)); 84 | } 85 | return readConfig(); 86 | } 87 | 88 | private static ConfigSettings handle(ConfigSettings configSettings) { 89 | Boolean debug = configSettings.getDebug(); 90 | if (null == debug) { 91 | configSettings.setDebug(false); 92 | } 93 | Boolean open = configSettings.getOpen(); 94 | if (null == open) { 95 | configSettings.setOpen(false); 96 | } 97 | Boolean conciseReplies = configSettings.getConciseReplies(); 98 | if (null == conciseReplies) { 99 | configSettings.setConciseReplies(false); 100 | } 101 | Boolean voice = configSettings.getVoice(); 102 | if (null == voice) { 103 | configSettings.setVoice(false); 104 | } 105 | String[] blockChatIdArray = configSettings.getBlockChatIdArray(); 106 | if (null == blockChatIdArray) { 107 | configSettings.setBlockChatIdArray(new String[]{}); 108 | } 109 | String[] permissionChatIdArray = configSettings.getPermissionChatIdArray(); 110 | if (null == permissionChatIdArray) { 111 | configSettings.setPermissionChatIdArray(new String[]{}); 112 | } 113 | String gptModel = configSettings.getGptModel(); 114 | if (StringUtils.isBlank(gptModel)) { 115 | configSettings.setGptModel(GPTModel.Gpt3_5Turbo.getModel()); 116 | } 117 | String openaiAPIPrefix = configSettings.getOpenaiAPIPrefix(); 118 | if (StringUtils.isBlank(openaiAPIPrefix)) { 119 | configSettings.setOpenaiAPIPrefix("https://api.openai.com"); 120 | } 121 | return configSettings; 122 | } 123 | 124 | public static ConfigSettings readConfig() { 125 | ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock(); 126 | readLock.lock(); 127 | try { 128 | File file = new File(SettingsPath); 129 | boolean exists = file.exists(); 130 | if (exists) { 131 | String text = FileUtils.readFileToString(file, StandardCharsets.UTF_8); 132 | ConfigSettings configSettings = JSON.parseObject(text, ConfigSettings.class, JSONReader.Feature.SupportSmartMatch); 133 | return handle(configSettings); 134 | } else { 135 | log.warn("Settings file not found, " + SettingsPath); 136 | } 137 | } catch (IOException e) { 138 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e), SettingsPath); 139 | } finally { 140 | readLock.unlock(); 141 | } 142 | return null; 143 | } 144 | 145 | public static ConfigSettings verifyConfig(String configJson) { 146 | if (StringUtils.isBlank(configJson)) { 147 | return null; 148 | } 149 | ConfigSettings configSettings = null; 150 | try { 151 | configSettings = JSON.parseObject(configJson, ConfigSettings.class, JSONReader.Feature.SupportSmartMatch); 152 | } catch (JSONException e) { 153 | } 154 | if (null == configSettings) { 155 | return null; 156 | } 157 | for (Field field : configSettings.getClass().getDeclaredFields()) { 158 | ConfigField configField = field.getAnnotation(ConfigField.class); 159 | if (null == configField) { 160 | continue; 161 | } 162 | if (configField.isNotNull()) { 163 | try { 164 | field.setAccessible(true); 165 | Object o = field.get(configSettings); 166 | if (null == o) { 167 | return null; 168 | } 169 | } catch (IllegalAccessException e) { 170 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 171 | return null; 172 | } 173 | } 174 | } 175 | 176 | return configSettings; 177 | } 178 | 179 | public static boolean saveConfig(ConfigSettings configSettings) { 180 | ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock(); 181 | writeLock.lock(); 182 | try { 183 | File file = new File(SettingsPath); 184 | FileUtils.write(file, JSON.toJSONString(configSettings, JSONWriter.Feature.PrettyFormat), StandardCharsets.UTF_8); 185 | return true; 186 | } catch (IOException e) { 187 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 188 | } finally { 189 | writeLock.unlock(); 190 | } 191 | return false; 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/code/config/ConfigField.java: -------------------------------------------------------------------------------- 1 | package code.config; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(value = ElementType.FIELD) 9 | @Retention(value = RetentionPolicy.RUNTIME) 10 | public @interface ConfigField { 11 | 12 | boolean isNotNull(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/code/config/ConfigSettings.java: -------------------------------------------------------------------------------- 1 | package code.config; 2 | 3 | import com.alibaba.fastjson2.annotation.JSONField; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class ConfigSettings { 8 | 9 | @ConfigField(isNotNull = true) 10 | private Boolean debug; 11 | 12 | @JSONField(name = "on_proxy") 13 | @ConfigField(isNotNull = true) 14 | private Boolean onProxy; 15 | @JSONField(name = "proxy_host") 16 | private String proxyHost; 17 | @JSONField(name = "proxy_port") 18 | private Integer proxyPort; 19 | 20 | @JSONField(name = "bot_admin_id") 21 | @ConfigField(isNotNull = true) 22 | private String botAdminId; 23 | 24 | @ConfigField(isNotNull = true) 25 | private Boolean voice; 26 | 27 | @ConfigField(isNotNull = true) 28 | private Boolean open; 29 | @JSONField(name = "permission_chat_id_array") 30 | private String[] permissionChatIdArray; 31 | @JSONField(name = "block_chat_id_array") 32 | private String[] blockChatIdArray; 33 | 34 | @JSONField(name = "bot_name") 35 | @ConfigField(isNotNull = true) 36 | private String botName; 37 | @JSONField(name = "bot_token") 38 | @ConfigField(isNotNull = true) 39 | private String botToken; 40 | 41 | @JSONField(name = "gpt_token") 42 | @ConfigField(isNotNull = true) 43 | private String gptToken; 44 | @JSONField(name = "gpt_model") 45 | @ConfigField(isNotNull = true) 46 | private String gptModel; 47 | 48 | @JSONField(name = "chat_buttons") 49 | @ConfigField(isNotNull = false) 50 | private String chatButtons; 51 | 52 | @JSONField(name = "concise_replies") 53 | @ConfigField(isNotNull = false) 54 | private Boolean conciseReplies; 55 | 56 | @JSONField(name = "openai_api_prefix") 57 | @ConfigField(isNotNull = true) 58 | private String openaiAPIPrefix; 59 | 60 | @JSONField(name = "start_text") 61 | @ConfigField(isNotNull = false) 62 | private String startText; 63 | 64 | @JSONField(name = "help_text") 65 | @ConfigField(isNotNull = false) 66 | private String helpText; 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/code/config/ExecutorsConfig.java: -------------------------------------------------------------------------------- 1 | package code.config; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder; 5 | 6 | import java.util.concurrent.*; 7 | 8 | @Slf4j 9 | public class ExecutorsConfig { 10 | 11 | private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setDaemon(false).setNameFormat("handle-pool-%d").build(); 12 | 13 | private static ExecutorService fixedThreadPool = new ThreadPoolExecutor( 14 | 5, 15 | 20, 16 | 60, 17 | TimeUnit.SECONDS, 18 | new ArrayBlockingQueue<>(200), 19 | threadFactory, 20 | (Runnable r, ThreadPoolExecutor executor) -> { 21 | log.error(r.toString() + " is Rejected"); 22 | } 23 | ); 24 | 25 | public static void submit(Runnable task) { 26 | fixedThreadPool.submit(task); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/code/config/I18nConfig.java: -------------------------------------------------------------------------------- 1 | package code.config; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.util.HashMap; 6 | import java.util.LinkedHashMap; 7 | import java.util.Map; 8 | import java.util.ResourceBundle; 9 | 10 | public class I18nConfig { 11 | 12 | private static Map> cacheMap = new LinkedHashMap<>(); 13 | 14 | static { 15 | for (I18nLocaleEnum value : I18nLocaleEnum.values()) { 16 | ResourceBundle bundle = ResourceBundle.getBundle("i18n/i18n", value.getLocale()); 17 | HashMap hashMap = new HashMap<>(); 18 | for (String s : bundle.keySet()) { 19 | hashMap.put(s, bundle.getString(s)); 20 | } 21 | 22 | cacheMap.put(value.getAlias(), hashMap); 23 | } 24 | } 25 | 26 | public static String getText(String i18nAlias, String key) { 27 | Map map = cacheMap.get(StringUtils.isNotBlank(i18nAlias) ? i18nAlias : I18nLocaleEnum.ZH_CN.getAlias()); 28 | return map.get(key); 29 | } 30 | 31 | public static String getText(String i18nAlias, I18nEnum i18nEnum) { 32 | Map map = cacheMap.get(StringUtils.isNotBlank(i18nAlias) ? i18nAlias : I18nLocaleEnum.ZH_CN.getAlias()); 33 | return map.get(i18nEnum.getKey()); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/code/config/I18nEnum.java: -------------------------------------------------------------------------------- 1 | package code.config; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum I18nEnum { 7 | 8 | BotStartSucceed("bot_start_succeed"), 9 | HelpText("help_text"), 10 | 11 | InvalidCommand("invalid_command"), 12 | On("on"), 13 | Off("off"), 14 | Test("test"), 15 | Update("update"), 16 | NotFound("not_found"), 17 | UnknownError("unknown_error"), 18 | NothingAtAll("nothing_at_all"), 19 | CancelSucceeded("cancel_succeeded"), 20 | Confirm("confirm"), 21 | Cancel("cancel"), 22 | Delete("delete"), 23 | Finish("finish"), 24 | ExitSucceeded("exit_succeeded"), 25 | Getting("getting"), 26 | Downloading("downloading"), 27 | UpdateConfig("update_config"), 28 | Restart("restart"), 29 | Upgrade("upgrade"), 30 | UpdateSucceeded("update_succeeded"), 31 | UpdateFailed("update_failed"), 32 | Open("open"), 33 | Close("close"), 34 | Processing("processing"), 35 | 36 | LanguageList("language_list"), 37 | ChangeLanguageFinish("change_language_finish"), 38 | 39 | ThisChatIsANewChat("this_chat_is_a_new_chat"), 40 | AreYouSureToUpdateTheConfig("are_you_sure_to_update_the_config"), 41 | PleaseSendMeConfigContent("please_send_me_config_content"), 42 | UpdateConfigFail("update_config_fail"), 43 | PleaseSendMeAProblemThatYouWantToAsk("please_send_me_a_problem_that_you_want_to_ask"), 44 | RequestingOpenAiApi("requesting_open_ai_api"), 45 | TheCurrentModeIsContinuousChatMode("the_current_mode_is_continuous_chat_mode"), 46 | AnErrorOccurredOfRequestingOpenAiApiFailed("an_error_occurred_of_requesting_open_ai_api_failed"), 47 | ContinueThisChat("continue_this_chat"), 48 | AskChatEnded("ask_chat_ended"), 49 | TheCurrentModeIsChatMessageLimitMode("the_current_mode_is_chat_message_limit_mode"), 50 | CmlChatEnded("cml_chat_ended"), 51 | CmlContinueThisChat("cml_continue_this_chat"), 52 | TheCurrentModeIsNoneOfMessageContextMode("the_current_mode_is_none_of_message_context_mode"), 53 | 54 | YouAreNotAnAdmin("you_are_not_an_admin"), 55 | AreYouSureToRestartRightNow("are_you_sure_to_restart_right_now"), 56 | Restarting("restarting"), 57 | GettingUpdateData("getting_update_data"), 58 | AreYouSureToUpgradeThisBotRightNow("are_you_sure_to_upgrade_this_bot_right_now"), 59 | TargetVersion("target_version"), 60 | CurrentVersion("current_version"), 61 | UpdateLogs("update_logs"), 62 | Updating("updating"), 63 | Downloaded("downloaded"), 64 | PleaseSendMeAnImageDescription("please_send_me_an_image_description"), 65 | ImageDescriptionTextCharacterCountMoreThan("image_description_text_character_count_more_than"), 66 | 67 | ChatHasTooManyConversations("chat_has_too_many_conversations"), 68 | SetOpenStatus("set_open_status"), 69 | ChooseOpenStatus("choose_open_status"), 70 | Model("model"), 71 | ChangeModel("change_model"), 72 | PleaseSendMeTheModelYouWantToChange("please_send_me_the_model_you_want_to_change"), 73 | 74 | SetVoiceStatus("set_voice_status"), 75 | Unzipping("unzipping"), 76 | PleaseSendMeThePlaybackAlias("please_send_me_the_playback_alias"), 77 | XXXNotFound("xxx_not_found"), 78 | RecordModeOpened("record_mode_opened"), 79 | PleaseSendMeRecordAlias("please_send_me_record_alias"), 80 | InvalidAlias("invalid_alias"), 81 | PleaseSendMeRecordExplanations("Please_send_me_record_explanations"), 82 | InvalidExplanations("invalid_explanations"), 83 | Saving("saving"), 84 | SaveSucceeded("save_succeeded"), 85 | SaveFailed("save_failed"), 86 | RecordAlias("record_alias"), 87 | RecordExplanations("record_explanations"), 88 | YouCanUseRecordToStartRecoding("you_can_use_record_to_start_recoding"), 89 | DeleteSucceeded("delete_succeeded"), 90 | DeleteFailed("delete_failed"), 91 | ChooseVoiceStatus("choose_voice_status"), 92 | DownloadingXXX("downloading_xxx"), 93 | 94 | PleaseSendMeChatButtons("please_send_me_chat_buttons"), 95 | FormatError("format_error"), 96 | SetChatButtons("set_chat_buttons"), 97 | 98 | Tip429("tip_429"), 99 | SetGptToken("set_gpt_token"), 100 | PleaseSendMeGptToken("please_send_me_gpt_token"), 101 | Alive("alive"), 102 | Die("die"), 103 | PleaseChooseConciseReplies("please_choose_concise_replies"), 104 | SetConciseReplies("set_concise_replies"), 105 | SetOpenAIAPIPrefix("set_openai_api_prefix"), 106 | SetStartText("set_start_text"), 107 | SetHelpText("set_help_text"), 108 | PleaseSendMeStartText("please_send_me_start_text"), 109 | PleaseSendMeHelpText("please_send_me_help_text"), 110 | 111 | ; 112 | 113 | private String key; 114 | 115 | I18nEnum(String key) { 116 | this.key = key; 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/code/config/I18nLocaleEnum.java: -------------------------------------------------------------------------------- 1 | package code.config; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.Locale; 6 | 7 | @Getter 8 | public enum I18nLocaleEnum { 9 | ZH_CN(Locale.SIMPLIFIED_CHINESE, "zh-cn", "简体中文"), 10 | EN(Locale.US, "en", "English"), 11 | 12 | ; 13 | 14 | private Locale locale; 15 | private String alias; 16 | private String displayText; 17 | 18 | I18nLocaleEnum(Locale locale, String alias, String displayText) { 19 | this.locale = locale; 20 | this.alias = alias; 21 | this.displayText = displayText; 22 | } 23 | 24 | public static I18nLocaleEnum getI18nLocaleEnumByAlias(String alias) { 25 | for (I18nLocaleEnum value : I18nLocaleEnum.values()) { 26 | if (value.getAlias().equals(alias)) { 27 | return value; 28 | } 29 | } 30 | return null; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/code/config/ProxyTypeEnum.java: -------------------------------------------------------------------------------- 1 | package code.config; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum ProxyTypeEnum { 7 | 8 | NotOpen(0, "Not Open"), 9 | HttpProxy(1, "Http Proxy"), 10 | 11 | ; 12 | 13 | private int type; 14 | private String alias; 15 | 16 | ProxyTypeEnum(int type, String alias) { 17 | this.type = type; 18 | this.alias = alias; 19 | } 20 | 21 | public static ProxyTypeEnum getDefault() { 22 | return NotOpen; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/code/config/RequestProxyConfig.java: -------------------------------------------------------------------------------- 1 | package code.config; 2 | 3 | import kong.unirest.HttpRequest; 4 | import org.apache.hc.client5.http.fluent.Request; 5 | import org.apache.hc.core5.http.HttpHost; 6 | import org.telegram.telegrambots.bots.DefaultBotOptions; 7 | 8 | import static code.Main.GlobalConfig; 9 | 10 | public class RequestProxyConfig { 11 | 12 | private ProxyTypeEnum type; 13 | private String hostName; 14 | private Integer port; 15 | 16 | public static RequestProxyConfig create() { 17 | RequestProxyConfig config = new RequestProxyConfig(); 18 | if (null != GlobalConfig && GlobalConfig.getOnProxy()) { 19 | config.type = ProxyTypeEnum.HttpProxy; 20 | config.hostName = GlobalConfig.getProxyHost(); 21 | config.port = GlobalConfig.getProxyPort(); 22 | } else { 23 | config.type = ProxyTypeEnum.getDefault(); 24 | } 25 | return config; 26 | } 27 | 28 | public void viaProxy(HttpRequest request) { 29 | switch (this.type) { 30 | case HttpProxy: 31 | request.proxy(this.hostName, this.port); 32 | break; 33 | } 34 | } 35 | 36 | public void viaProxy(Request request) { 37 | switch (this.type) { 38 | case HttpProxy: 39 | request.viaProxy(new HttpHost(this.hostName, this.port)); 40 | break; 41 | } 42 | } 43 | 44 | public DefaultBotOptions buildDefaultBotOptions() { 45 | switch (this.type) { 46 | case HttpProxy: 47 | DefaultBotOptions botOptions = new DefaultBotOptions(); 48 | 49 | botOptions.setProxyHost(this.hostName); 50 | botOptions.setProxyPort(this.port); 51 | botOptions.setProxyType(DefaultBotOptions.ProxyType.HTTP); 52 | return botOptions; 53 | } 54 | return null; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/code/config/TableEnum.java: -------------------------------------------------------------------------------- 1 | package code.config; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum TableEnum { 7 | 8 | UserGeoTable("user_geo_table"), 9 | I18nTable("i18n_table"), 10 | 11 | ; 12 | 13 | private String name; 14 | 15 | TableEnum(String name) { 16 | this.name = name; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/code/eneity/GptTokenTableEntity.java: -------------------------------------------------------------------------------- 1 | package code.eneity; 2 | 3 | import code.repository.mapper.TableEntity; 4 | import code.repository.mapper.TableField; 5 | import code.repository.mapper.TableName; 6 | import lombok.Data; 7 | 8 | @TableName(name = "gpt_token_table") 9 | @Data 10 | public class GptTokenTableEntity implements TableEntity { 11 | @TableField(name = "token", sql = "token varchar(100) primary key") 12 | private String token; 13 | 14 | @TableField(name = "status", sql = "status int comment '1.正常 2.死亡'") 15 | private Integer status; 16 | 17 | @TableField(name = "send", sql = "send int comment '1.未发送 2.已发送'") 18 | private Integer send; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/code/eneity/RecordTableEntity.java: -------------------------------------------------------------------------------- 1 | package code.eneity; 2 | 3 | import code.repository.mapper.TableEntity; 4 | import code.repository.mapper.TableField; 5 | import code.repository.mapper.TableName; 6 | import lombok.Data; 7 | 8 | @TableName(name = "record_table") 9 | @Data 10 | public class RecordTableEntity implements TableEntity { 11 | 12 | @TableField(name = "id", sql = "id varchar(55) primary key") 13 | private String id; 14 | 15 | @TableField(name = "record_alias", sql = "record_alias varchar(50)") 16 | private String recordAlias; 17 | 18 | @TableField(name = "record_explains", sql = "record_explains varchar(255)") 19 | private String recordExplains; 20 | 21 | @TableField(name = "create_time", sql = "create_time timestamp") 22 | private Long createTime; 23 | @TableField(name = "update_time", sql = "update_time timestamp") 24 | private Long updateTime; 25 | 26 | @TableField(name = "chat_id", sql = "chat_id varchar(88)") 27 | private String chatId; 28 | 29 | @TableField(name = "chat_template_json", sql = "chat_template_json text") 30 | private String chatTemplateJson; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/code/eneity/cons/GptTokenStatusEnum.java: -------------------------------------------------------------------------------- 1 | package code.eneity.cons; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.Optional; 6 | 7 | @Getter 8 | public enum GptTokenStatusEnum { 9 | 10 | Alive(1, "正常"), 11 | Die(2, "死亡"), 12 | 13 | ; 14 | 15 | private int num; 16 | private String name; 17 | GptTokenStatusEnum(int num, String name) { 18 | this.num = num; 19 | this.name = name; 20 | } 21 | 22 | public static Optional get(int num) { 23 | for (GptTokenStatusEnum value : values()) { 24 | if (value.getNum() == num) { 25 | return Optional.of(value); 26 | } 27 | } 28 | return Optional.empty(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/code/eneity/cons/YesOrNoEnum.java: -------------------------------------------------------------------------------- 1 | package code.eneity.cons; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.Optional; 6 | 7 | @Getter 8 | public enum YesOrNoEnum { 9 | 10 | Yes(2, true), 11 | No(1, false), 12 | 13 | ; 14 | 15 | private int num; 16 | private boolean bool; 17 | 18 | YesOrNoEnum(int num, boolean bool) { 19 | this.num = num; 20 | this.bool = bool; 21 | } 22 | 23 | public static int toInt(boolean bool) { 24 | return bool ? Yes.getNum() : No.getNum(); 25 | } 26 | 27 | public static Optional toBoolean(int num) { 28 | for (YesOrNoEnum value : values()) { 29 | if (num == value.getNum()) { 30 | return Optional.of(value.isBool()); 31 | } 32 | } 33 | return Optional.empty(); 34 | } 35 | 36 | public static Optional get(int num) { 37 | for (YesOrNoEnum value : values()) { 38 | if (value.getNum() == num) { 39 | return Optional.of(value); 40 | } 41 | } 42 | return Optional.empty(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/code/handler/Command.java: -------------------------------------------------------------------------------- 1 | package code.handler; 2 | 3 | import lombok.Getter; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | @Getter 10 | public enum Command { 11 | 12 | Chat("chat", false), 13 | ChatShorter("c", false), 14 | Ask("ask", false), 15 | AskShorter("a", false), 16 | ChatMsgLimit("cml", false), 17 | NoneOfContextChatMessage("nccm", false), 18 | Exit("exit", false), 19 | Admin("admin", false), 20 | Image("image", false), 21 | 22 | Record("record", false), 23 | Playback("p", false), 24 | PlaybackRegex("^[,,]", true), 25 | RecordList("record_list", false), 26 | GetRecord("get_record", false), 27 | DeleteRecord("delete_record", false), 28 | 29 | SetChatButtons("set_chat_buttons", false), 30 | SetVoiceStatus("set_voice_status", false), 31 | ChangeModel("change_model", false), 32 | SetOpenStatus("set_open_status", false), 33 | SetGptToken("set_gpt_token", false), 34 | SetConciseReplies("set_concise_replies", false), 35 | SetOpenAIAPIPrefix("set_openai_api_prefix", false), 36 | SetStartText("set_start_text", false), 37 | SetHelpText("set_help_text", false), 38 | UpdateConfig("uc", false), 39 | Language("language", false), 40 | Restart("restart", false), 41 | Upgrade("upgrade", false), 42 | 43 | ; 44 | 45 | private String cmd; 46 | private boolean regex; 47 | 48 | Command(String cmd, boolean regex) { 49 | this.cmd = cmd; 50 | this.regex = regex; 51 | } 52 | 53 | public static Command toCmd(String cmd) { 54 | for (Command value : Command.values()) { 55 | if (value.getCmd().equals(cmd)) { 56 | return value; 57 | } 58 | } 59 | return null; 60 | } 61 | public static Command regexMatch(String cmd) { 62 | if (StringUtils.isBlank(cmd)) { 63 | return null; 64 | } 65 | 66 | for (Command value : values()) { 67 | if (value.isRegex()) { 68 | Pattern pattern = Pattern.compile(value.getCmd()); 69 | Matcher matcher = pattern.matcher(cmd); 70 | if (matcher.find()) { 71 | return value; 72 | } 73 | } 74 | } 75 | return null; 76 | } 77 | 78 | public static boolean exist(String cmd) { 79 | return null != toCmd(cmd); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/code/handler/CommandsHandler.java: -------------------------------------------------------------------------------- 1 | package code.handler; 2 | 3 | import code.commands.*; 4 | import code.handler.message.MessageHandle; 5 | import code.handler.steps.StepsChatSession; 6 | import code.handler.steps.StepsChatSessionBuilder; 7 | import com.alibaba.fastjson2.JSON; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.telegram.telegrambots.bots.DefaultBotOptions; 11 | import org.telegram.telegrambots.extensions.bots.commandbot.TelegramLongPollingCommandBot; 12 | import org.telegram.telegrambots.meta.api.objects.CallbackQuery; 13 | import org.telegram.telegrambots.meta.api.objects.Message; 14 | import org.telegram.telegrambots.meta.api.objects.Update; 15 | 16 | import static code.Main.GlobalConfig; 17 | 18 | @Slf4j 19 | public class CommandsHandler extends TelegramLongPollingCommandBot { 20 | 21 | public CommandsHandler() { 22 | super(); 23 | start(); 24 | } 25 | 26 | public CommandsHandler(DefaultBotOptions botOptions) { 27 | super(botOptions); 28 | start(); 29 | } 30 | 31 | public void start() { 32 | register(new HelpCommand()); 33 | register(new StartCommand()); 34 | } 35 | 36 | @Override 37 | public String getBotUsername() { 38 | return GlobalConfig.getBotName(); 39 | } 40 | 41 | @Override 42 | public String getBotToken() { 43 | return GlobalConfig.getBotToken(); 44 | } 45 | 46 | @Override 47 | public void processNonCommandUpdate(Update update) { 48 | if (GlobalConfig.getDebug()) { 49 | log.info(JSON.toJSONString(update)); 50 | } 51 | 52 | CallbackQuery callbackQuery = update.getCallbackQuery(); 53 | if (null != callbackQuery) { 54 | String data = callbackQuery.getData(); 55 | StepsCenter.CallbackData callbackData = StepsCenter.parseCallbackData(data); 56 | if (null == callbackData) { 57 | MessageHandle.sendMessage(String.valueOf(callbackQuery.getMessage().getChatId()), "Error...", false); 58 | return; 59 | } 60 | 61 | StepsChatSession session = StepsChatSessionBuilder 62 | .create(callbackQuery) 63 | .setText(callbackData.getText()) 64 | .build(); 65 | 66 | if (!session.getFromId().equals(String.valueOf(callbackData.getFromId()))) { 67 | return; 68 | } 69 | 70 | if (StringUtils.isNotBlank(data)) { 71 | StepsCenter.cmdHandle(callbackData, session); 72 | return; 73 | } 74 | } 75 | 76 | Message message = update.getMessage(); 77 | if (null == message) { 78 | return; 79 | } 80 | if (StringUtils.isNotEmpty(message.getText()) || null != message.getVoice()) { 81 | boolean handle = StepsCenter.cmdHandle(StepsChatSessionBuilder.create(message).build()); 82 | if (!handle) { 83 | StepsCenter.messageHandle(StepsChatSessionBuilder.create(message).build()); 84 | } 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/code/handler/I18nHandle.java: -------------------------------------------------------------------------------- 1 | package code.handler; 2 | 3 | import code.config.I18nConfig; 4 | import code.config.I18nEnum; 5 | import code.config.I18nLocaleEnum; 6 | import org.apache.commons.lang3.StringUtils; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import static code.Main.I18nTableRepository; 12 | 13 | public class I18nHandle { 14 | 15 | private static Map cacheMap = new HashMap<>(); 16 | 17 | public static String getText(String chatId, String key) { 18 | return getText(chatId, key, null); 19 | } 20 | 21 | public static String getText(String chatId, String key, Object... args) { 22 | String alias = cacheMap.get(chatId); 23 | if (StringUtils.isBlank(alias)) { 24 | alias = I18nTableRepository.selectI18nAlias(chatId); 25 | cacheMap.put(chatId, alias); 26 | } 27 | 28 | String text = I18nConfig.getText(alias, key); 29 | if (null != args && args.length > 0) { 30 | return String.format(text, args); 31 | } 32 | return text; 33 | } 34 | 35 | public static String getText(String chatId, I18nEnum i18nEnum) { 36 | return getText(chatId, i18nEnum, null); 37 | } 38 | public static String getText(String chatId, I18nEnum i18nEnum, Object... args) { 39 | String alias = cacheMap.get(chatId); 40 | if (StringUtils.isBlank(alias)) { 41 | alias = I18nTableRepository.selectI18nAlias(chatId); 42 | cacheMap.put(chatId, alias); 43 | } 44 | 45 | String text = I18nConfig.getText(alias, i18nEnum); 46 | if (null != args && args.length > 0) { 47 | return String.format(text, args); 48 | } 49 | return text; 50 | } 51 | 52 | public static void save(String chatId, I18nLocaleEnum i18nLocaleEnum) { 53 | I18nTableRepository.save(chatId, i18nLocaleEnum.getAlias()); 54 | cacheMap.put(chatId, i18nLocaleEnum.getAlias()); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/code/handler/StepsCenter.java: -------------------------------------------------------------------------------- 1 | package code.handler; 2 | 3 | import code.config.ExecutorsConfig; 4 | import code.handler.message.MessageHandle; 5 | import code.handler.steps.StepsChatSession; 6 | import code.handler.steps.StepsHandler; 7 | import code.handler.steps.StepsRegisterCenter; 8 | import code.util.ExceptionUtil; 9 | import lombok.Data; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.apache.commons.lang3.StringUtils; 12 | 13 | import java.util.Collection; 14 | import java.util.stream.Collectors; 15 | import java.util.stream.Stream; 16 | 17 | import static code.Main.GlobalConfig; 18 | 19 | @Slf4j 20 | public class StepsCenter { 21 | 22 | @Data 23 | public static class CallbackData { 24 | private boolean init; 25 | private String fromId; 26 | private Command command; 27 | private String text; 28 | } 29 | 30 | public static String buildCallbackData(boolean init, StepsChatSession session, Command command, String text) { 31 | StringBuilder builder = new StringBuilder(); 32 | builder.append("f[" + session.getFromId() + "]"); 33 | builder.append(command.getCmd()); 34 | builder.append(" "); 35 | builder.append(init); 36 | builder.append(" "); 37 | builder.append(text); 38 | return builder.toString(); 39 | } 40 | public static CallbackData parseCallbackData(String callbackData) { 41 | try { 42 | CallbackData data = new CallbackData(); 43 | data.setFromId(StringUtils.substringBetween(callbackData, "f[", "]")); 44 | 45 | String s = StringUtils.replace(callbackData, "f[" + data.getFromId() + "]", ""); 46 | String[] arguments = s.split(" "); 47 | 48 | data.setCommand(Command.toCmd(arguments[0])); 49 | data.setInit(Boolean.valueOf(arguments[1])); 50 | data.setText(arguments.length > 2 ? arguments[2] : null); 51 | 52 | return data; 53 | } catch (Exception e) { 54 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 55 | } 56 | return null; 57 | } 58 | 59 | public static boolean cmdHandle(StepsChatSession session) { 60 | if (StringUtils.isNotBlank(session.getText())) { 61 | if (session.getText().startsWith("/")) { 62 | String s = StringUtils.remove(session.getText(), "/"); 63 | String[] split = s.split(" "); 64 | if (split.length > 0) { 65 | String cmd = split[0]; 66 | cmd = StringUtils.replace(cmd, "@" + GlobalConfig.getBotName(), ""); 67 | if (Command.exist(cmd)) { 68 | split[0] = cmd; 69 | session.setText(Stream.of(split).skip(1).collect(Collectors.joining(" "))); 70 | cmdHandle( 71 | Command.toCmd(cmd), 72 | false, 73 | session, 74 | null 75 | ); 76 | return true; 77 | } 78 | } 79 | } else { 80 | Command command = Command.regexMatch(session.getText()); 81 | if (null != command) { 82 | session.setText(session.getText().replaceFirst(command.getCmd(), "")); 83 | cmdHandle( 84 | command, 85 | false, 86 | session, 87 | null 88 | ); 89 | return true; 90 | } 91 | } 92 | } 93 | return false; 94 | } 95 | 96 | public static void cmdHandle(CallbackData callbackData, StepsChatSession stepsChatSession) { 97 | cmdHandle(callbackData.getCommand(), true, stepsChatSession, callbackData); 98 | } 99 | 100 | public static void cmdHandle(Command command, StepsChatSession stepsChatSession) { 101 | cmdHandle(command, false, stepsChatSession, null); 102 | } 103 | 104 | private static void cmdHandle(Command command, boolean isCall, StepsChatSession stepsChatSession, CallbackData callbackData) { 105 | Boolean open = GlobalConfig.getOpen(); 106 | if (!open) { 107 | boolean permission = false; 108 | 109 | String botAdminId = GlobalConfig.getBotAdminId(); 110 | if (botAdminId.equals(stepsChatSession.getChatId()) || botAdminId.equals(stepsChatSession.getFromId())) { 111 | permission = true; 112 | } 113 | for (String s : GlobalConfig.getPermissionChatIdArray()) { 114 | if (s.equals(stepsChatSession.getChatId()) || s.equals(stepsChatSession.getFromId())) { 115 | permission = true; 116 | break; 117 | } 118 | } 119 | 120 | if (!permission) { 121 | MessageHandle.sendMessage(stepsChatSession.getChatId(), stepsChatSession.getReplyToMessageId(), "你没有使用权限, 不过你可以自己搭建一个\nhttps://github.com/kylelin1998/ChatGPTForTelegram", false); 122 | return; 123 | } 124 | } 125 | for (String s : GlobalConfig.getBlockChatIdArray()) { 126 | if (s.equals(stepsChatSession.getChatId()) || s.equals(stepsChatSession.getFromId())) { 127 | MessageHandle.sendMessage(stepsChatSession.getChatId(), stepsChatSession.getReplyToMessageId(), "系统限制, 你没有使用权限, 不过你可以自己搭建一个\nhttps://github.com/kylelin1998/ChatGPTForTelegram", false); 128 | return; 129 | } 130 | } 131 | 132 | if (null != callbackData){ 133 | StepsHandler handler = StepsRegisterCenter.getRegister(command.getCmd()); 134 | if (!callbackData.isInit() && !handler.hasInit(stepsChatSession)) { 135 | return; 136 | } 137 | } 138 | 139 | ExecutorsConfig.submit(() -> { 140 | StepsHandler handler = StepsRegisterCenter.getRegister(command.getCmd()); 141 | if (null != handler.getInitStep() && (!handler.hasInit(stepsChatSession) || !isCall)) { 142 | handler.init(stepsChatSession); 143 | } else { 144 | handler.step(stepsChatSession); 145 | } 146 | }); 147 | } 148 | 149 | public static void messageHandle(StepsChatSession stepsChatSession) { 150 | StepsHandler handler = StepsRegisterCenter.getPriority(stepsChatSession); 151 | if (null == handler) { 152 | return; 153 | } 154 | ExecutorsConfig.submit(() -> { 155 | handler.step(stepsChatSession); 156 | }); 157 | } 158 | 159 | public static void exit(StepsChatSession stepsChatSession) { 160 | Collection list = StepsRegisterCenter.getRegisterList(); 161 | for (StepsHandler handler : list) { 162 | handler.exit(stepsChatSession); 163 | } 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/code/handler/message/InlineKeyboardButtonBuilder.java: -------------------------------------------------------------------------------- 1 | package code.handler.message; 2 | 3 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class InlineKeyboardButtonBuilder { 9 | 10 | private List inlineKeyboardButtonList; 11 | 12 | private String callbackData; 13 | 14 | private InlineKeyboardButtonBuilder() {} 15 | 16 | public static InlineKeyboardButtonBuilder create() { 17 | InlineKeyboardButtonBuilder builder = new InlineKeyboardButtonBuilder(); 18 | builder.inlineKeyboardButtonList = new ArrayList<>(); 19 | return builder; 20 | } 21 | 22 | public InlineKeyboardButtonBuilder setCallbackData(String callbackData) { 23 | this.callbackData = callbackData; 24 | return this; 25 | } 26 | 27 | public InlineKeyboardButtonBuilder add(String text, String callbackData) { 28 | InlineKeyboardButton button = new InlineKeyboardButton(); 29 | button.setText(text); 30 | button.setCallbackData(callbackData); 31 | inlineKeyboardButtonList.add(button); 32 | return this; 33 | } 34 | 35 | public InlineKeyboardButtonBuilder add(String text) { 36 | InlineKeyboardButton button = new InlineKeyboardButton(); 37 | button.setText(text); 38 | button.setCallbackData(this.callbackData); 39 | inlineKeyboardButtonList.add(button); 40 | return this; 41 | } 42 | 43 | public InlineKeyboardButtonBuilder add(InlineKeyboardButton button) { 44 | inlineKeyboardButtonList.add(button); 45 | return this; 46 | } 47 | 48 | public List build() { 49 | return inlineKeyboardButtonList; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/code/handler/message/InlineKeyboardButtonListBuilder.java: -------------------------------------------------------------------------------- 1 | package code.handler.message; 2 | 3 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class InlineKeyboardButtonListBuilder { 9 | private List> keyboard; 10 | private InlineKeyboardButtonListBuilder() {} 11 | 12 | public static InlineKeyboardButtonListBuilder create() { 13 | InlineKeyboardButtonListBuilder builder = new InlineKeyboardButtonListBuilder(); 14 | builder.keyboard = new ArrayList<>(); 15 | return builder; 16 | } 17 | 18 | public InlineKeyboardButtonListBuilder add(List inlineKeyboardButtonList) { 19 | this.keyboard.add(inlineKeyboardButtonList); 20 | return this; 21 | } 22 | 23 | public List> build() { 24 | return keyboard; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/code/handler/message/MessageHandle.java: -------------------------------------------------------------------------------- 1 | package code.handler.message; 2 | 3 | import code.util.ExceptionUtil; 4 | import com.alibaba.fastjson2.JSON; 5 | import lombok.Data; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.telegram.telegrambots.meta.api.methods.ParseMode; 9 | import org.telegram.telegrambots.meta.api.methods.send.SendMessage; 10 | import org.telegram.telegrambots.meta.api.methods.send.SendPhoto; 11 | import org.telegram.telegrambots.meta.api.methods.updatingmessages.DeleteMessage; 12 | import org.telegram.telegrambots.meta.api.methods.updatingmessages.EditMessageText; 13 | import org.telegram.telegrambots.meta.api.objects.InputFile; 14 | import org.telegram.telegrambots.meta.api.objects.Message; 15 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup; 16 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.ReplyKeyboardMarkup; 17 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton; 18 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.KeyboardRow; 19 | import org.telegram.telegrambots.meta.exceptions.TelegramApiException; 20 | 21 | import java.io.File; 22 | import java.io.InputStream; 23 | import java.util.ArrayList; 24 | import java.util.Arrays; 25 | import java.util.List; 26 | 27 | import static code.Main.Bot; 28 | 29 | @Slf4j 30 | public class MessageHandle { 31 | 32 | public enum MessageError { 33 | BotWasBlockedByTheUser, 34 | 35 | ; 36 | } 37 | 38 | @Data 39 | public static class MessageResponse { 40 | private boolean ok; 41 | private Message message; 42 | private MessageError messageError; 43 | } 44 | 45 | public static Message sendImage(String chatId, Integer replyToMessageId, String text, InputStream image) { 46 | SendPhoto sendPhoto = new SendPhoto(); 47 | sendPhoto.setChatId(chatId); 48 | sendPhoto.setReplyToMessageId(replyToMessageId); 49 | sendPhoto.setCaption(text); 50 | sendPhoto.setPhoto(new InputFile(image, "image.png")); 51 | 52 | try { 53 | return Bot.execute(sendPhoto); 54 | } catch (TelegramApiException e) { 55 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 56 | } 57 | return null; 58 | } 59 | 60 | public static Message sendImage(String chatId, Integer replyToMessageId, String text, File image) { 61 | SendPhoto sendPhoto = new SendPhoto(); 62 | sendPhoto.setChatId(chatId); 63 | sendPhoto.setReplyToMessageId(replyToMessageId); 64 | sendPhoto.setCaption(text); 65 | sendPhoto.setPhoto(new InputFile(image)); 66 | 67 | try { 68 | return Bot.execute(sendPhoto); 69 | } catch (TelegramApiException e) { 70 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 71 | } 72 | return null; 73 | } 74 | 75 | public static Message sendInlineKeyboard(String chatId, String text, InlineKeyboardButton... inlineKeyboardButtonList) { 76 | return sendInlineKeyboard(chatId, text, Arrays.asList(inlineKeyboardButtonList)); 77 | } 78 | 79 | public static Message sendInlineKeyboardList(String chatId, String text, List> keyboard) { 80 | return sendInlineKeyboardList(chatId, null, text, keyboard); 81 | } 82 | 83 | public static Message sendInlineKeyboardList(String chatId, Integer replyToMessageId, String text, List> keyboard) { 84 | SendMessage message = new SendMessage(); 85 | message.setChatId(chatId); 86 | message.setText(text); 87 | message.setReplyToMessageId(replyToMessageId); 88 | message.setDisableWebPagePreview(true); 89 | 90 | InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup(); 91 | 92 | inlineKeyboardMarkup.setKeyboard(keyboard); 93 | message.setReplyMarkup(inlineKeyboardMarkup); 94 | 95 | try { 96 | return Bot.execute(message); 97 | } catch (TelegramApiException e) { 98 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 99 | } 100 | return null; 101 | } 102 | 103 | public static Message sendInlineKeyboard(String chatId, String text, List inlineKeyboardButtonList) { 104 | SendMessage message = new SendMessage(); 105 | message.setChatId(chatId); 106 | message.setText(text); 107 | 108 | InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup(); 109 | List> keyboard = new ArrayList<>(); 110 | 111 | for (InlineKeyboardButton button : inlineKeyboardButtonList) { 112 | List list = new ArrayList<>(); 113 | list.add(button); 114 | keyboard.add(list); 115 | } 116 | 117 | inlineKeyboardMarkup.setKeyboard(keyboard); 118 | message.setReplyMarkup(inlineKeyboardMarkup); 119 | 120 | try { 121 | return Bot.execute(message); 122 | } catch (TelegramApiException e) { 123 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 124 | } 125 | return null; 126 | } 127 | 128 | public static Message sendCustomKeyboard(String chatId, String text, KeyboardRow row) { 129 | List list = new ArrayList<>(); 130 | list.add(row); 131 | 132 | return sendCustomKeyboard(chatId, text, list); 133 | } 134 | 135 | public static Message sendCustomKeyboard(String chatId, String text, List keyboard) { 136 | SendMessage message = new SendMessage(); 137 | message.setChatId(chatId); 138 | message.setText(text); 139 | 140 | ReplyKeyboardMarkup keyboardMarkup = new ReplyKeyboardMarkup(); 141 | 142 | keyboardMarkup.setKeyboard(keyboard); 143 | message.setReplyMarkup(keyboardMarkup); 144 | 145 | try { 146 | return Bot.execute(message); 147 | } catch (TelegramApiException e) { 148 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 149 | } 150 | return null; 151 | } 152 | 153 | public static MessageResponse sendMsg(String chatId, String text, boolean webPagePreview) { 154 | MessageResponse response = new MessageResponse(); 155 | response.setOk(false); 156 | 157 | SendMessage sendMessage = new SendMessage(); 158 | sendMessage.setChatId(chatId); 159 | sendMessage.setText(text); 160 | sendMessage.setParseMode(ParseMode.HTML); 161 | if (!webPagePreview) { 162 | sendMessage.disableWebPagePreview(); 163 | } 164 | try { 165 | Message execute = Bot.execute(sendMessage); 166 | response.setOk(true); 167 | response.setMessage(execute); 168 | return response; 169 | } catch (Exception e) { 170 | String message = e.getMessage(); 171 | if (message.contains("bot was blocked by the user")) { 172 | response.setMessageError(MessageError.BotWasBlockedByTheUser); 173 | } else { 174 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e, JSON.toJSONString(sendMessage))); 175 | } 176 | } 177 | 178 | return response; 179 | } 180 | 181 | public static Message sendMessage(String chatId, String text, boolean webPagePreview) { 182 | return sendMessage(chatId, null, text, webPagePreview, true, null); 183 | } 184 | public static Message sendMessage(String chatId, String text, boolean webPagePreview, boolean notification) { 185 | return sendMessage(chatId, null, text, webPagePreview, notification, null); 186 | } 187 | public static Message sendMessage(String chatId, Integer replyToMessageId, String text, boolean webPagePreview) { 188 | return sendMessage(chatId, replyToMessageId, text, webPagePreview, true, null); 189 | } 190 | public static Message sendMessage(String chatId, Integer replyToMessageId, String text, boolean webPagePreview, boolean notification, List> buttons) { 191 | SendMessage sendMessage = new SendMessage(); 192 | sendMessage.setChatId(chatId); 193 | sendMessage.setReplyToMessageId(replyToMessageId); 194 | sendMessage.setText(text); 195 | sendMessage.setParseMode(ParseMode.HTML); 196 | if (!notification) { 197 | sendMessage.disableNotification(); 198 | } 199 | if (!webPagePreview) { 200 | sendMessage.disableWebPagePreview(); 201 | } 202 | if (null != buttons && !buttons.isEmpty()) { 203 | InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup(); 204 | inlineKeyboardMarkup.setKeyboard(buttons); 205 | sendMessage.setReplyMarkup(inlineKeyboardMarkup); 206 | } 207 | return sendMessage(sendMessage); 208 | } 209 | 210 | public static Message sendMessage(String chatId, String text, boolean webPagePreview, List> buttons) { 211 | return sendMessage(chatId, null, text, webPagePreview, true, null); 212 | } 213 | 214 | public static Message sendMessage(SendMessage sendMessage) { 215 | try { 216 | String text = sendMessage.getText(); 217 | if (StringUtils.isNotBlank(text)) { 218 | text = StringUtils.replace(text, "<", "<"); 219 | text = StringUtils.replace(text, ">", ">"); 220 | sendMessage.setText(text); 221 | } 222 | 223 | Message execute = Bot.execute(sendMessage); 224 | return execute; 225 | } catch (Exception e) { 226 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e, JSON.toJSONString(sendMessage))); 227 | } 228 | return null; 229 | } 230 | 231 | public static boolean editMessage(Message message, String text) { 232 | try { 233 | EditMessageText editMessageText = new EditMessageText(); 234 | editMessageText.setChatId(message.getChatId()); 235 | editMessageText.setMessageId(message.getMessageId()); 236 | editMessageText.setText(text); 237 | 238 | Bot.execute(editMessageText); 239 | return true; 240 | } catch (Exception e) { 241 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e, JSON.toJSONString(message))); 242 | } 243 | return false; 244 | } 245 | 246 | public static boolean editMessage(Message message, String text, List> buttons) { 247 | try { 248 | EditMessageText editMessageText = new EditMessageText(); 249 | editMessageText.setChatId(message.getChatId()); 250 | editMessageText.setMessageId(message.getMessageId()); 251 | editMessageText.setText(text); 252 | 253 | if (null != buttons && !buttons.isEmpty()) { 254 | InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup(); 255 | inlineKeyboardMarkup.setKeyboard(buttons); 256 | editMessageText.setReplyMarkup(inlineKeyboardMarkup); 257 | } 258 | 259 | Bot.execute(editMessageText); 260 | return true; 261 | } catch (Exception e) { 262 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e, JSON.toJSONString(message))); 263 | } 264 | return false; 265 | } 266 | 267 | public static boolean deleteMessage(Message message) { 268 | if (null == message) { 269 | return false; 270 | } 271 | 272 | DeleteMessage deleteMessage = new DeleteMessage(); 273 | deleteMessage.setChatId(message.getChatId()); 274 | deleteMessage.setMessageId(message.getMessageId()); 275 | 276 | try { 277 | Boolean execute = Bot.execute(deleteMessage); 278 | return null == execute ? false : execute; 279 | } catch (Exception e) { 280 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e, JSON.toJSONString(deleteMessage))); 281 | } 282 | return false; 283 | } 284 | 285 | public static boolean deleteMessage(DeleteMessage deleteMessage) { 286 | try { 287 | Boolean execute = Bot.execute(deleteMessage); 288 | return null == execute ? false : execute; 289 | } catch (Exception e) { 290 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e, JSON.toJSONString(deleteMessage))); 291 | } 292 | return false; 293 | } 294 | 295 | } 296 | -------------------------------------------------------------------------------- /src/main/java/code/handler/steps/StepErrorApi.java: -------------------------------------------------------------------------------- 1 | package code.handler.steps; 2 | 3 | public interface StepErrorApi { 4 | 5 | void callback(Exception e, StepsChatSession stepsChatSession); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/code/handler/steps/StepExecuteResult.java: -------------------------------------------------------------------------------- 1 | package code.handler.steps; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class StepExecuteResult { 9 | 10 | private boolean init; 11 | private StepResult stepResult; 12 | private boolean isWork; 13 | 14 | public static StepExecuteResult not() { 15 | return new StepExecuteResult(false, null, false); 16 | } 17 | 18 | public static StepExecuteResult work() { 19 | return new StepExecuteResult(true, null, true); 20 | } 21 | 22 | public static StepExecuteResult ok(StepResult stepResult) { 23 | return new StepExecuteResult(true, stepResult, true); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/code/handler/steps/StepHandleApi.java: -------------------------------------------------------------------------------- 1 | package code.handler.steps; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public interface StepHandleApi { 7 | 8 | StepResult execute(StepsChatSession stepsChatSession, int index, List list, Map context); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/code/handler/steps/StepResult.java: -------------------------------------------------------------------------------- 1 | package code.handler.steps; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class StepResult { 9 | 10 | private boolean ok; 11 | 12 | private boolean next; 13 | private String text; 14 | 15 | private boolean end; 16 | 17 | public static StepResult ok() { 18 | return new StepResult(true, false, null, false); 19 | } 20 | 21 | public static StepResult reject() { 22 | return new StepResult(false, false, null, false); 23 | } 24 | 25 | public static StepResult next() { 26 | return new StepResult(true, true, null, false); 27 | } 28 | 29 | public static StepResult next(String text) { 30 | return new StepResult(true, true, text, false); 31 | } 32 | 33 | public static StepResult end() { 34 | return new StepResult(true, false, null, true); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/code/handler/steps/StepsBuilder.java: -------------------------------------------------------------------------------- 1 | package code.handler.steps; 2 | 3 | import code.handler.Command; 4 | 5 | public class StepsBuilder { 6 | 7 | private Command[] commands; 8 | private boolean debug = true; 9 | private StepErrorApi errorApi; 10 | private StepHandleApi initStep; 11 | private StepHandleApi[] steps; 12 | 13 | private StepsBuilder() {} 14 | 15 | public static StepsBuilder create() { 16 | return new StepsBuilder(); 17 | } 18 | 19 | public StepsBuilder bindCommand(Command... commands) { 20 | this.commands = commands; 21 | return this; 22 | } 23 | public StepsBuilder debug(boolean debug) { 24 | this.debug = debug; 25 | return this; 26 | } 27 | public StepsBuilder error(StepErrorApi errorApi) { 28 | this.errorApi = errorApi; 29 | return this; 30 | } 31 | public StepsBuilder init(StepHandleApi initStep) { 32 | this.initStep = initStep; 33 | return this; 34 | } 35 | public StepsBuilder steps(StepHandleApi... steps) { 36 | this.steps = steps; 37 | return this; 38 | } 39 | 40 | public StepsHandler build() { 41 | return StepsHandler.build(debug, errorApi, initStep, steps).bindCommand(commands); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/code/handler/steps/StepsChatSession.java: -------------------------------------------------------------------------------- 1 | package code.handler.steps; 2 | 3 | import lombok.Data; 4 | import org.telegram.telegrambots.meta.api.objects.CallbackQuery; 5 | import org.telegram.telegrambots.meta.api.objects.Message; 6 | import org.telegram.telegrambots.meta.api.objects.Voice; 7 | 8 | @Data 9 | public class StepsChatSession { 10 | 11 | private String sessionId; 12 | private String chatId; 13 | private String fromId; 14 | 15 | private Integer replyToMessageId; 16 | 17 | private Message message; 18 | private CallbackQuery callbackQuery; 19 | private String text; 20 | private Voice voice; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/code/handler/steps/StepsChatSessionBuilder.java: -------------------------------------------------------------------------------- 1 | package code.handler.steps; 2 | 3 | import org.telegram.telegrambots.meta.api.objects.CallbackQuery; 4 | import org.telegram.telegrambots.meta.api.objects.Message; 5 | 6 | public class StepsChatSessionBuilder { 7 | private StepsChatSession session; 8 | 9 | private StepsChatSessionBuilder(StepsChatSession session) { 10 | this.session = session; 11 | } 12 | 13 | public static StepsChatSessionBuilder clone(StepsChatSession session) { 14 | return create(session.getMessage()); 15 | } 16 | public static StepsChatSessionBuilder create(CallbackQuery callbackQuery) { 17 | String chatId = String.valueOf(callbackQuery.getMessage().getChat().getId()); 18 | String fromId = String.valueOf(callbackQuery.getFrom().getId()); 19 | 20 | StepsChatSession session = new StepsChatSession(); 21 | session.setChatId(chatId); 22 | session.setFromId(fromId); 23 | session.setSessionId(chatId + "_" + fromId); 24 | session.setCallbackQuery(callbackQuery); 25 | return new StepsChatSessionBuilder(session); 26 | } 27 | public static StepsChatSessionBuilder create(Message message) { 28 | String chatId = message.getChat().getId().toString(); 29 | String fromId = String.valueOf(message.getFrom().getId()); 30 | String text = message.getText(); 31 | 32 | StepsChatSession session = new StepsChatSession(); 33 | session.setChatId(chatId); 34 | session.setFromId(fromId); 35 | session.setSessionId(chatId + "_" + fromId); 36 | session.setText(text); 37 | session.setReplyToMessageId(message.getMessageId()); 38 | session.setMessage(message); 39 | session.setVoice(message.getVoice()); 40 | return new StepsChatSessionBuilder(session); 41 | } 42 | public StepsChatSessionBuilder setText(String text) { 43 | session.setText(text); 44 | return this; 45 | } 46 | 47 | public StepsChatSessionBuilder setText(String[] arguments) { 48 | session.setText(String.join(" ", arguments)); 49 | return this; 50 | } 51 | 52 | public StepsChatSession build() { 53 | return session; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/code/handler/steps/StepsHandler.java: -------------------------------------------------------------------------------- 1 | package code.handler.steps; 2 | 3 | import code.handler.Command; 4 | import com.alibaba.fastjson2.JSON; 5 | import lombok.Getter; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.commons.lang3.StringUtils; 8 | 9 | import java.util.*; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | 13 | @Getter 14 | @Slf4j 15 | public class StepsHandler { 16 | 17 | private static volatile AtomicInteger IdAtomic = new AtomicInteger(1); 18 | 19 | private Command[] commands; 20 | 21 | private boolean debug; 22 | private StepErrorApi errorApi; 23 | private StepHandleApi initStep; 24 | private StepHandleApi[] stepHandleApis; 25 | 26 | private Map> context = new ConcurrentHashMap<>(); 27 | 28 | private Map> message = new ConcurrentHashMap<>(); 29 | 30 | private Map stepWorkStatus = new ConcurrentHashMap<>(); 31 | private Map stepId = new ConcurrentHashMap<>(); 32 | 33 | private StepsHandler() { 34 | } 35 | 36 | public StepsHandler bindCommand(Command[] commands) { 37 | this.commands = commands; 38 | for (Command command : commands) { 39 | StepsRegisterCenter.register(command.getCmd(), this); 40 | } 41 | return this; 42 | } 43 | 44 | public static StepsHandler build(boolean debug, StepErrorApi errorApi, StepHandleApi step) { 45 | return build(debug, errorApi, null, step); 46 | } 47 | 48 | public static StepsHandler build(boolean debug, StepErrorApi errorApi, StepHandleApi initStep, StepHandleApi... steps) { 49 | StepsHandler handler = new StepsHandler(); 50 | 51 | handler.debug = debug; 52 | handler.errorApi = errorApi; 53 | handler.initStep = initStep; 54 | handler.stepHandleApis = steps; 55 | 56 | return handler; 57 | } 58 | 59 | public boolean hasInit(StepsChatSession stepsChatSession) { 60 | return stepId.containsKey(stepsChatSession.getSessionId()); 61 | } 62 | public boolean isInit() { 63 | return null != initStep; 64 | } 65 | 66 | public void init(StepsChatSession stepsChatSession) { 67 | StepResult execute = null; 68 | try { 69 | StepsRegisterCenter.priority(stepsChatSession, this); 70 | String sessionId = stepsChatSession.getSessionId(); 71 | 72 | Boolean stepsWorkStatusBool = stepWorkStatus.get(sessionId); 73 | if (null != stepsWorkStatusBool && stepsWorkStatusBool) { 74 | return; 75 | } 76 | 77 | stepWorkStatus.put(sessionId, true); 78 | 79 | List list = Collections.synchronizedList(new ArrayList<>()); 80 | ConcurrentHashMap contextMap = new ConcurrentHashMap<>(); 81 | if (null != initStep) { 82 | execute = initStep.execute(stepsChatSession, 0, list, contextMap); 83 | } 84 | if ((null != execute && execute.isOk()) || null == initStep) { 85 | context.remove(sessionId); 86 | message.remove(sessionId); 87 | stepId.remove(sessionId); 88 | list.add(stepsChatSession.getText()); 89 | message.put(sessionId, list); 90 | context.put(sessionId, contextMap); 91 | stepId.put(sessionId, IdAtomic.incrementAndGet()); 92 | if (debug) { 93 | log.info("Steps init, id: {}, chat id: {}, text: {}, list: {}", stepId.get(sessionId), stepsChatSession.getChatId(), stepsChatSession.getText(), JSON.toJSONString(list)); 94 | } 95 | } 96 | } catch (Exception e) { 97 | errorApi.callback(e, stepsChatSession); 98 | } finally { 99 | stepWorkStatus.put(stepsChatSession.getSessionId(), false); 100 | 101 | if (null != execute) { 102 | if (execute.isNext()) { 103 | step(stepsChatSession); 104 | } 105 | if (execute.isEnd()) { 106 | exit(stepsChatSession); 107 | } 108 | } 109 | } 110 | } 111 | 112 | public void next(StepsChatSession stepsChatSession) { 113 | step(stepsChatSession); 114 | } 115 | 116 | public StepExecuteResult step(StepsChatSession stepsChatSession) { 117 | StepResult execute = null; 118 | try { 119 | String sessionId = stepsChatSession.getSessionId(); 120 | if (!hasInit(stepsChatSession) && !isInit()) { 121 | init(stepsChatSession); 122 | } 123 | 124 | Boolean stepsWorkStatusBool = stepWorkStatus.get(sessionId); 125 | if (null != stepsWorkStatusBool && stepsWorkStatusBool) { 126 | return StepExecuteResult.work(); 127 | } 128 | stepWorkStatus.put(sessionId, true); 129 | 130 | if (!message.containsKey(sessionId)) { 131 | return StepExecuteResult.not(); 132 | } 133 | 134 | List list = message.get(sessionId); 135 | Map contextMap = context.get(sessionId); 136 | execute = this.stepHandleApis[list.size() - 1].execute(stepsChatSession, list.size(), list, contextMap); 137 | if (execute.isOk()) { 138 | list.add(stepsChatSession.getText()); 139 | } 140 | if (debug) { 141 | log.info("Step, id: {}, chat id: {}, text: {}, list: {}, context: {}", stepId.get(sessionId), stepsChatSession.getChatId(), stepsChatSession.getText(), JSON.toJSONString(list), JSON.toJSONString(contextMap)); 142 | } 143 | if ((list.size() - 1) >= this.stepHandleApis.length) { 144 | if (debug) { 145 | log.info("Step finish, id: {}, chat id: {}, text: {}, list: {}, context: {}", stepId.get(sessionId), stepsChatSession.getChatId(), stepsChatSession.getText(), JSON.toJSONString(list), JSON.toJSONString(contextMap)); 146 | } 147 | exit(stepsChatSession); 148 | } 149 | } catch (Exception e) { 150 | errorApi.callback(e, stepsChatSession); 151 | } finally { 152 | stepWorkStatus.put(stepsChatSession.getSessionId(), false); 153 | 154 | if (null != execute) { 155 | if (execute.isEnd()) { 156 | exit(stepsChatSession); 157 | } else if (execute.isNext()) { 158 | next( 159 | StepsChatSessionBuilder.clone(stepsChatSession).setText(execute.getText()).build() 160 | ); 161 | } 162 | } 163 | } 164 | return StepExecuteResult.ok(execute); 165 | } 166 | 167 | public void exit(StepsChatSession stepsChatSession) { 168 | String sessionId = stepsChatSession.getSessionId(); 169 | 170 | message.remove(sessionId); 171 | context.remove(sessionId); 172 | stepWorkStatus.remove(sessionId); 173 | stepId.remove(sessionId); 174 | StepsRegisterCenter.finish(stepsChatSession); 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/code/handler/steps/StepsRegisterCenter.java: -------------------------------------------------------------------------------- 1 | package code.handler.steps; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.*; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | @Slf4j 9 | public class StepsRegisterCenter { 10 | 11 | private static Map stepsHandlerMap = new HashMap<>(); 12 | 13 | private static volatile Map priorityMap = new ConcurrentHashMap<>(); 14 | 15 | public static void register(String cmd, StepsHandler handler) { 16 | stepsHandlerMap.put(cmd, handler); 17 | } 18 | 19 | public static StepsHandler getRegister(String cmd) { 20 | return stepsHandlerMap.get(cmd); 21 | } 22 | public static Collection getRegisterList() { 23 | return stepsHandlerMap.values(); 24 | } 25 | 26 | public synchronized static void priority(StepsChatSession stepsChatSession, StepsHandler stepsHandler) { 27 | for (StepsHandler value : stepsHandlerMap.values()) { 28 | value.exit(stepsChatSession); 29 | } 30 | 31 | priorityMap.put(stepsChatSession.getSessionId(), stepsHandler); 32 | } 33 | public synchronized static void finish(StepsChatSession stepsChatSession) { 34 | priorityMap.remove(stepsChatSession.getSessionId()); 35 | } 36 | public static StepsHandler getPriority(StepsChatSession stepsChatSession) { 37 | return priorityMap.get(stepsChatSession.getSessionId()); 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/code/handler/store/ChatButtonsStore.java: -------------------------------------------------------------------------------- 1 | package code.handler.store; 2 | 3 | import lombok.Data; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton; 6 | 7 | import java.util.*; 8 | 9 | public class ChatButtonsStore { 10 | 11 | private volatile static ChatButtonsToInlineKeyboardButtons buttons; 12 | 13 | public static void set(String chatButtons) { 14 | Optional keyboardButtons = chatButtonsToInlineKeyboardButtons(chatButtons); 15 | buttons = keyboardButtons.orElse(null); 16 | } 17 | 18 | public static Optional verify(String chatButtons) { 19 | return chatButtonsToInlineKeyboardButtons(chatButtons); 20 | } 21 | 22 | public static Optional get() { 23 | return Optional.ofNullable(buttons); 24 | } 25 | 26 | @Data 27 | public static class ChatButtonsToInlineKeyboardButtons { 28 | private boolean isAll; 29 | 30 | private Map> map; 31 | 32 | public Optional> getButtons(String chatId) { 33 | if (isAll) { 34 | return Optional.ofNullable(map.get("all")); 35 | } 36 | for (Map.Entry> entry : map.entrySet()) { 37 | if (entry.getKey().equals(chatId)) { 38 | return Optional.ofNullable(entry.getValue()); 39 | } 40 | } 41 | return Optional.empty(); 42 | } 43 | } 44 | private static Optional chatButtonsToInlineKeyboardButtons(String chatButtons) { 45 | if (StringUtils.isBlank(chatButtons)) { 46 | return Optional.empty(); 47 | } 48 | 49 | String[] split = chatButtons.split("---"); 50 | if (split.length == 0) { 51 | return Optional.empty(); 52 | } 53 | ChatButtonsToInlineKeyboardButtons chatButtonsToInlineKeyboardButtons = new ChatButtonsToInlineKeyboardButtons(); 54 | Map> map = new LinkedHashMap<>(); 55 | for (String s : split) { 56 | s = StringUtils.removeStart(s, "\n"); 57 | List buttons = new ArrayList<>(); 58 | String[] dataSplit = s.split("\n"); 59 | if (dataSplit.length < 2) { 60 | return Optional.empty(); 61 | } 62 | for (int i = 1; i < dataSplit.length; i++) { 63 | String data = dataSplit[i]; 64 | if (StringUtils.isBlank(data)) { 65 | return Optional.empty(); 66 | } 67 | String[] buttonSplit = data.split(" "); 68 | if (buttonSplit.length != 2) { 69 | return Optional.empty(); 70 | } 71 | String url = buttonSplit[1]; 72 | if (!StringUtils.startsWith(url, "http")) { 73 | return Optional.empty(); 74 | } 75 | 76 | InlineKeyboardButton button = new InlineKeyboardButton(); 77 | button.setText(buttonSplit[0]); 78 | button.setUrl(url); 79 | buttons.add(button); 80 | } 81 | 82 | String chatId = dataSplit[0]; 83 | chatButtonsToInlineKeyboardButtons.setAll(chatId.equals("all")); 84 | map.put(chatId, buttons); 85 | if (chatButtonsToInlineKeyboardButtons.isAll()) { 86 | break; 87 | } 88 | } 89 | if (chatButtonsToInlineKeyboardButtons.isAll()) { 90 | List deleteKeys = new ArrayList<>(); 91 | for (Map.Entry> entry : map.entrySet()) { 92 | if (!entry.getKey().equals("all")) { 93 | deleteKeys.add(entry.getKey()); 94 | } 95 | } 96 | for (String key : deleteKeys) { 97 | map.remove(key); 98 | } 99 | } 100 | chatButtonsToInlineKeyboardButtons.setMap(map); 101 | 102 | return Optional.of(chatButtonsToInlineKeyboardButtons); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/code/handler/store/GptTokenStore.java: -------------------------------------------------------------------------------- 1 | package code.handler.store; 2 | 3 | import code.config.I18nEnum; 4 | import code.eneity.GptTokenTableEntity; 5 | import code.eneity.cons.GptTokenStatusEnum; 6 | import code.handler.I18nHandle; 7 | import code.handler.message.MessageHandle; 8 | import code.util.gpt.response.GPTChatResponse; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.telegram.telegrambots.meta.api.objects.Message; 11 | 12 | import java.util.List; 13 | 14 | import static code.Main.GlobalConfig; 15 | import static code.Main.GptTokenTableRepository; 16 | 17 | public class GptTokenStore { 18 | public static String getRandomToken() { 19 | GptTokenTableEntity entity = GptTokenTableRepository.selectOneByRand(GptTokenStatusEnum.Alive); 20 | return null == entity ? GlobalConfig.getGptToken() : entity.getToken(); 21 | } 22 | public static String getListText(String chatId) { 23 | StringBuilder builder = new StringBuilder(); 24 | List gptTokenTableEntities = GptTokenTableRepository.selectListByStatus(GptTokenStatusEnum.Alive); 25 | builder.append(I18nHandle.getText(chatId, I18nEnum.Alive) + ": " + gptTokenTableEntities.size()); 26 | builder.append("\n"); 27 | for (GptTokenTableEntity entity : gptTokenTableEntities) { 28 | builder.append(entity.getToken()); 29 | builder.append("\n"); 30 | } 31 | builder.append("\n"); 32 | List gptTokenTableEntities2 = GptTokenTableRepository.selectListByStatus(GptTokenStatusEnum.Die); 33 | builder.append(I18nHandle.getText(chatId, I18nEnum.Die) + ": " + gptTokenTableEntities2.size()); 34 | builder.append("\n"); 35 | for (GptTokenTableEntity entity : gptTokenTableEntities2) { 36 | builder.append(entity.getToken()); 37 | builder.append("\n"); 38 | } 39 | 40 | return builder.toString(); 41 | } 42 | public static void forceSave(List tokens) { 43 | GptTokenTableRepository.forceSave(tokens); 44 | } 45 | public static void deleteAll() { 46 | GptTokenTableRepository.deleteAll(); 47 | } 48 | public static void handle(String token, GPTChatResponse response) { 49 | if (StringUtils.isBlank(token)) { 50 | return; 51 | } 52 | if (null == response || response.isOk()) { 53 | return; 54 | } 55 | if (response.getStatusCode() == 429) { 56 | if (response.getResponse().contains("You exceeded your current quota, please check your plan and billing details")) { 57 | send(token); 58 | } 59 | } else if (response.getStatusCode() == 401) { 60 | send(token); 61 | } 62 | } 63 | private static void send(String token) { 64 | String text = token + " " + I18nHandle.getText(GlobalConfig.getBotAdminId(), I18nEnum.Tip429); 65 | Message message = MessageHandle.sendMessage(GlobalConfig.getBotAdminId(), text, false); 66 | if (null != message) { 67 | GptTokenTableRepository.dieAndSend(token); 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/code/handler/store/Store.java: -------------------------------------------------------------------------------- 1 | package code.handler.store; 2 | 3 | import static code.Main.GlobalConfig; 4 | 5 | public class Store { 6 | 7 | public static void init() { 8 | ChatButtonsStore.set(GlobalConfig.getChatButtons()); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/code/repository/GptTokenTableRepository.java: -------------------------------------------------------------------------------- 1 | package code.repository; 2 | 3 | import code.config.Config; 4 | import code.eneity.GptTokenTableEntity; 5 | import code.eneity.cons.GptTokenStatusEnum; 6 | import code.eneity.cons.YesOrNoEnum; 7 | import code.repository.mapper.TableRepository; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.commons.lang3.BooleanUtils; 10 | 11 | import java.util.List; 12 | 13 | @Slf4j 14 | public class GptTokenTableRepository extends TableRepository { 15 | 16 | public GptTokenTableRepository() { 17 | super(Config.DBPath); 18 | } 19 | 20 | public void deleteAll() { 21 | super.deleteAll(GptTokenTableEntity.class); 22 | } 23 | public synchronized void forceSave(List tokens) { 24 | super.deleteAll(GptTokenTableEntity.class); 25 | for (String token : tokens) { 26 | GptTokenTableEntity entity = new GptTokenTableEntity(); 27 | entity.setToken(token); 28 | entity.setStatus(GptTokenStatusEnum.Alive.getNum()); 29 | entity.setSend(YesOrNoEnum.No.getNum()); 30 | super.insert(entity); 31 | } 32 | } 33 | 34 | public List selectListByStatus(GptTokenStatusEnum gptTokenStatusEnum) { 35 | GptTokenTableEntity where = new GptTokenTableEntity(); 36 | where.setStatus(gptTokenStatusEnum.getNum()); 37 | return super.selectList(where); 38 | } 39 | 40 | public GptTokenTableEntity selectOneByRand(GptTokenStatusEnum gptTokenStatusEnum) { 41 | GptTokenTableEntity where = new GptTokenTableEntity(); 42 | where.setStatus(gptTokenStatusEnum.getNum()); 43 | return super.selectOneByRand(where); 44 | } 45 | 46 | public boolean die(String gptToken) { 47 | GptTokenTableEntity where = new GptTokenTableEntity(); 48 | where.setToken(gptToken); 49 | 50 | GptTokenTableEntity entity = new GptTokenTableEntity(); 51 | entity.setStatus(GptTokenStatusEnum.Die.getNum()); 52 | 53 | return BooleanUtils.toBooleanDefaultIfNull(super.update(entity, where), false); 54 | } 55 | public boolean dieAndSend(String gptToken) { 56 | GptTokenTableEntity where = new GptTokenTableEntity(); 57 | where.setToken(gptToken); 58 | 59 | GptTokenTableEntity entity = new GptTokenTableEntity(); 60 | entity.setStatus(GptTokenStatusEnum.Die.getNum()); 61 | entity.setSend(YesOrNoEnum.Yes.getNum()); 62 | 63 | return BooleanUtils.toBooleanDefaultIfNull(super.update(entity, where), false); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/code/repository/I18nTableRepository.java: -------------------------------------------------------------------------------- 1 | package code.repository; 2 | 3 | import code.config.Config; 4 | import code.config.TableEnum; 5 | import code.repository.mapper.TableRepository; 6 | import code.util.ExceptionUtil; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.sql.ResultSet; 10 | 11 | @Slf4j 12 | public class I18nTableRepository extends TableRepository { 13 | 14 | public I18nTableRepository() { 15 | super(Config.DBPath, TableEnum.I18nTable.getName()); 16 | } 17 | 18 | @Override 19 | public String getCreateTableSql() { 20 | return String.format("create table if not exists %s (chat_id varchar(88), i18n_alias varchar(20))", super.getTableName()); 21 | } 22 | 23 | public String selectI18nAlias(String chatId) { 24 | try { 25 | String i18nAlias = (String) execute((statement) -> { 26 | String sql = String.format("select i18n_alias from %s where chat_id = '%s'", super.getTableName(), chatId); 27 | ResultSet query = statement.executeQuery(sql); 28 | return query.getString("i18n_alias"); 29 | }); 30 | return i18nAlias; 31 | } catch (Exception e) { 32 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 33 | } 34 | return null; 35 | } 36 | 37 | public synchronized boolean save(String chatId, String i18nAlias) { 38 | String sql = String.format("insert into %s values('%s', '%s')", super.getTableName(), chatId, i18nAlias); 39 | try { 40 | execute((statement) -> { 41 | statement.executeUpdate(String.format("delete from %s where chat_id = '%s'", super.getTableName(), chatId)); 42 | statement.executeUpdate(sql); 43 | return null; 44 | }); 45 | return true; 46 | } catch (Exception e) { 47 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 48 | return false; 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/code/repository/RecordTableRepository.java: -------------------------------------------------------------------------------- 1 | package code.repository; 2 | 3 | import code.config.Config; 4 | import code.eneity.RecordTableEntity; 5 | import code.repository.mapper.TableRepository; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.util.List; 9 | 10 | @Slf4j 11 | public class RecordTableRepository extends TableRepository { 12 | 13 | public RecordTableRepository() { 14 | super(Config.DBPath); 15 | } 16 | 17 | public List selectListByChatId(String chatId) { 18 | RecordTableEntity where = new RecordTableEntity(); 19 | where.setChatId(chatId); 20 | return super.selectList(where); 21 | } 22 | 23 | public RecordTableEntity selectOne(String id, String chatId) { 24 | RecordTableEntity where = new RecordTableEntity(); 25 | where.setId(id); 26 | where.setChatId(chatId); 27 | return super.selectOne(where); 28 | } 29 | 30 | public RecordTableEntity selectOneByAlias(String alias, String chatId) { 31 | RecordTableEntity where = new RecordTableEntity(); 32 | where.setRecordAlias(alias); 33 | where.setChatId(chatId); 34 | return super.selectOne(where); 35 | } 36 | 37 | public Boolean delete(String id, String chatId) { 38 | RecordTableEntity where = new RecordTableEntity(); 39 | where.setId(id); 40 | where.setChatId(chatId); 41 | return super.delete(where); 42 | } 43 | 44 | public Integer selectCount(String chatId, String alias) { 45 | RecordTableEntity where = new RecordTableEntity(); 46 | where.setChatId(chatId); 47 | where.setRecordAlias(alias); 48 | return super.selectCount(where); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/code/repository/mapper/SqlBuilder.java: -------------------------------------------------------------------------------- 1 | package code.repository.mapper; 2 | 3 | import code.util.ExceptionUtil; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import java.lang.reflect.Field; 7 | import java.util.StringJoiner; 8 | 9 | @Slf4j 10 | public class SqlBuilder { 11 | 12 | public static String getTableName(TableEntity tableEntity) { 13 | return getTableName(tableEntity.getClass()); 14 | } 15 | public static String getTableName(Class tableClass) { 16 | return tableClass.getAnnotation(TableName.class).name(); 17 | } 18 | 19 | public static String buildCreateTableSql(Class tableClass) { 20 | String tableName = getTableName(tableClass); 21 | StringJoiner joiner = new StringJoiner(", ", "(", ")"); 22 | 23 | for (Field field : tableClass.getDeclaredFields()) { 24 | TableField tableField = field.getAnnotation(TableField.class); 25 | if (null == tableField) { 26 | continue; 27 | } 28 | joiner.add(tableField.sql()); 29 | } 30 | 31 | String sql = "create table if not exists " + tableName + " " + joiner; 32 | return sql; 33 | } 34 | 35 | private static String buildFieldValueSql(TableEntity tableEntity, String delimiter, String prefix, String suffix) { 36 | Class entityClass = tableEntity.getClass(); 37 | StringJoiner joiner = new StringJoiner(delimiter, prefix, suffix); 38 | for (Field field : entityClass.getDeclaredFields()) { 39 | try { 40 | TableField tableField = field.getAnnotation(TableField.class); 41 | if (null == tableField) { 42 | continue; 43 | } 44 | field.setAccessible(true); 45 | Object o = field.get(tableEntity); 46 | if (null == o) { 47 | continue; 48 | } 49 | 50 | if (o instanceof String) { 51 | joiner.add(tableField.name() + "=" + "'" + o + "'"); 52 | } else { 53 | joiner.add(tableField.name() + "=" + o); 54 | } 55 | } catch (IllegalAccessException e) { 56 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 57 | } 58 | } 59 | return joiner.toString(); 60 | } 61 | 62 | public static String buildFieldSql(Class tableClass, String prefix, String suffix) { 63 | StringJoiner joiner = new StringJoiner(", ", prefix, suffix); 64 | 65 | for (Field field : tableClass.getDeclaredFields()) { 66 | TableField tableField = field.getAnnotation(TableField.class); 67 | if (null == tableField) { 68 | continue; 69 | } 70 | joiner.add(tableField.name()); 71 | } 72 | return joiner.toString(); 73 | } 74 | public static String buildFieldSql(TableEntity tableEntity, String prefix, String suffix) { 75 | StringJoiner joiner = new StringJoiner(", ", prefix, suffix); 76 | 77 | Class entityClass = tableEntity.getClass(); 78 | for (Field field : entityClass.getDeclaredFields()) { 79 | TableField tableField = field.getAnnotation(TableField.class); 80 | if (null == tableField) { 81 | continue; 82 | } 83 | joiner.add(tableField.name()); 84 | } 85 | return joiner.toString(); 86 | } 87 | 88 | public static String buildWhereSql(TableEntity tableEntity) { 89 | return buildFieldValueSql(tableEntity, " and ", " where ", ""); 90 | } 91 | 92 | public static String buildSelectSql(Class tableClass) { 93 | String tableName = getTableName(tableClass); 94 | 95 | String sql = "select" + buildFieldSql(tableClass, " ", " ") + "from " + tableName; 96 | return sql; 97 | } 98 | public static String buildSelectSql(TableEntity tableEntity) { 99 | String tableName = getTableName(tableEntity); 100 | 101 | String sql = "select" + buildFieldSql(tableEntity, " ", " ") + "from " + tableName + buildWhereSql(tableEntity); 102 | return sql; 103 | } 104 | public static String buildSelectOneRandSql(TableEntity tableEntity) { 105 | String tableName = getTableName(tableEntity); 106 | 107 | String sql = "select" + buildFieldSql(tableEntity, " ", " ") + "from " + tableName + buildWhereSql(tableEntity); 108 | sql = sql + " order by random() limit 1"; 109 | return sql; 110 | } 111 | 112 | public static String buildDeleteAllSql(TableEntity entity) { 113 | String tableName = getTableName(entity); 114 | 115 | String sql = "delete from " + tableName; 116 | return sql; 117 | } 118 | public static String buildDeleteSql(TableEntity where) { 119 | String tableName = getTableName(where); 120 | 121 | String sql = "delete from " + tableName + buildWhereSql(where); 122 | return sql; 123 | } 124 | 125 | public static String buildSelectCountSql(Class tableClass) { 126 | String tableName = getTableName(tableClass); 127 | 128 | String sql = "select count(*) as total from " + tableName; 129 | return sql; 130 | } 131 | 132 | public static String buildSelectCountSql(TableEntity where) { 133 | String tableName = getTableName(where); 134 | 135 | String sql = "select count(*) as total from " + tableName + buildWhereSql(where); 136 | return sql; 137 | } 138 | 139 | public static String buildInsertSql(TableEntity tableEntity, boolean isForceInsertNullValue) { 140 | String tableName = getTableName(tableEntity); 141 | 142 | Class entityClass = tableEntity.getClass(); 143 | StringJoiner prefixJoiner = new StringJoiner(", ", "(", ")"); 144 | StringJoiner suffixJoiner = new StringJoiner(", ", "values (", ")"); 145 | for (Field field : entityClass.getDeclaredFields()) { 146 | try { 147 | TableField tableField = field.getAnnotation(TableField.class); 148 | if (null == tableField) { 149 | continue; 150 | } 151 | field.setAccessible(true); 152 | Object o = field.get(tableEntity); 153 | if (null == o && !isForceInsertNullValue) { 154 | continue; 155 | } 156 | prefixJoiner.add(tableField.name()); 157 | if (null == o) { 158 | suffixJoiner.add(null); 159 | } 160 | else if (o instanceof String) { 161 | suffixJoiner.add("'" + o + "'"); 162 | } else { 163 | suffixJoiner.add(String.valueOf(o)); 164 | } 165 | } catch (IllegalAccessException e) { 166 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 167 | } 168 | } 169 | 170 | String sql = "insert into " + tableName + " " + prefixJoiner + " " + suffixJoiner; 171 | return sql; 172 | } 173 | 174 | public static String buildUpdateSql(TableEntity tableEntity, TableEntity where) { 175 | String tableName = getTableName(tableEntity); 176 | 177 | String sql = "update " + tableName + buildFieldValueSql(tableEntity, ", ", " set ", "") + buildWhereSql(where); 178 | return sql; 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/main/java/code/repository/mapper/TableEntity.java: -------------------------------------------------------------------------------- 1 | package code.repository.mapper; 2 | 3 | public interface TableEntity { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/code/repository/mapper/TableField.java: -------------------------------------------------------------------------------- 1 | package code.repository.mapper; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(value = ElementType.FIELD) 9 | @Retention(value = RetentionPolicy.RUNTIME) 10 | public @interface TableField { 11 | 12 | String sql(); 13 | 14 | String name(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/code/repository/mapper/TableName.java: -------------------------------------------------------------------------------- 1 | package code.repository.mapper; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface TableName { 11 | 12 | String name(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/code/repository/mapper/TableRepository.java: -------------------------------------------------------------------------------- 1 | package code.repository.mapper; 2 | 3 | import code.util.ExceptionUtil; 4 | import code.util.SqliteUtil; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.apache.commons.lang3.BooleanUtils; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.sqlite.jdbc4.JDBC4ResultSet; 9 | 10 | import java.lang.reflect.Field; 11 | import java.lang.reflect.ParameterizedType; 12 | import java.lang.reflect.Type; 13 | import java.sql.ResultSet; 14 | import java.sql.SQLException; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | @Slf4j 19 | public abstract class TableRepository { 20 | 21 | private String tableName; 22 | private String dbPath; 23 | 24 | public TableRepository(String dbPath, String tableName) { 25 | this.tableName = tableName; 26 | this.dbPath = dbPath; 27 | try { 28 | SqliteUtil.execute(dbPath, (statement) -> { 29 | String sql = getCreateTableSql(); 30 | if (StringUtils.isNotBlank(sql)) { 31 | statement.execute(sql); 32 | } 33 | return null; 34 | }); 35 | } catch (Exception e) { 36 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 37 | } 38 | } 39 | 40 | public TableRepository(String dbPath) { 41 | this.tableName = getT().getAnnotation(TableName.class).name(); 42 | this.dbPath = dbPath; 43 | try { 44 | SqliteUtil.execute(dbPath, (statement) -> { 45 | String sql = SqlBuilder.buildCreateTableSql(getT()); 46 | if (StringUtils.isNotBlank(sql)) { 47 | statement.execute(sql); 48 | } 49 | return null; 50 | }); 51 | } catch (Exception e) { 52 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 53 | } 54 | } 55 | 56 | private Class getT() { 57 | ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass(); 58 | Type actualTypeArgument = parameterizedType.getActualTypeArguments()[0]; 59 | String typeName = actualTypeArgument.getTypeName(); 60 | try { 61 | return (Class) Class.forName(typeName); 62 | } catch (ClassNotFoundException e) { 63 | throw new RuntimeException(e); 64 | } 65 | } 66 | 67 | public String getTableName() { 68 | return this.tableName; 69 | } 70 | public String sql(String sql) { 71 | return sql(sql, null); 72 | } 73 | public String sql(String sql, String[] args) { 74 | String table = StringUtils.replace(sql, "$table", this.getTableName()); 75 | return String.format(table, args); 76 | } 77 | 78 | public Object execute(SqliteUtil.SqliteInterface sqliteInterface) throws Exception { 79 | return SqliteUtil.execute(this.dbPath, sqliteInterface); 80 | } 81 | public Object executeWithTryCatch(SqliteUtil.SqliteInterface sqliteInterface) { 82 | try { 83 | return SqliteUtil.execute(this.dbPath, sqliteInterface); 84 | } catch (Exception e) { 85 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 86 | } 87 | return null; 88 | } 89 | 90 | public boolean delete(T where) { 91 | try { 92 | return (boolean) execute((statement) -> { 93 | int update = statement.executeUpdate(SqlBuilder.buildDeleteSql(where)); 94 | return update > 0; 95 | }); 96 | } catch (Exception e) { 97 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 98 | } 99 | return false; 100 | } 101 | public boolean deleteAll(Class tClass) { 102 | try { 103 | return (boolean) execute((statement) -> { 104 | int update = statement.executeUpdate(SqlBuilder.buildDeleteAllSql(tClass.newInstance())); 105 | return update > 0; 106 | }); 107 | } catch (Exception e) { 108 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 109 | } 110 | return false; 111 | } 112 | 113 | public boolean update(T entity, T where) { 114 | try { 115 | return (boolean) execute((statement) -> { 116 | int update = statement.executeUpdate(SqlBuilder.buildUpdateSql(entity, where)); 117 | return update > 0; 118 | }); 119 | } catch (Exception e) { 120 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 121 | } 122 | return false; 123 | } 124 | 125 | public boolean insert(T entity) { 126 | try { 127 | return (boolean) execute((statement) -> { 128 | int update = statement.executeUpdate(SqlBuilder.buildInsertSql(entity, false)); 129 | return update > 0; 130 | }); 131 | } catch (Exception e) { 132 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 133 | } 134 | return false; 135 | } 136 | 137 | public T getOne(ResultSet resultSet) throws SQLException, IllegalAccessException, InstantiationException { 138 | JDBC4ResultSet jdbc4ResultSet = (JDBC4ResultSet) resultSet; 139 | if (jdbc4ResultSet.emptyResultSet) { 140 | return null; 141 | } 142 | 143 | Class t = getT(); 144 | T instance = t.newInstance(); 145 | for (Field field : t.getDeclaredFields()) { 146 | TableField tableField = field.getAnnotation(TableField.class); 147 | if (null == tableField) { 148 | continue; 149 | } 150 | field.setAccessible(true); 151 | field.set(instance, resultSet.getObject(tableField.name())); 152 | } 153 | return instance; 154 | } 155 | 156 | public List getList(ResultSet resultSet) throws SQLException, IllegalAccessException, InstantiationException { 157 | List list = new ArrayList<>(); 158 | 159 | JDBC4ResultSet jdbc4ResultSet = (JDBC4ResultSet) resultSet; 160 | if (jdbc4ResultSet.emptyResultSet) { 161 | return list; 162 | } 163 | 164 | Class t = getT(); 165 | while (resultSet.next()) { 166 | T instance = t.newInstance(); 167 | for (Field field : t.getDeclaredFields()) { 168 | TableField tableField = field.getAnnotation(TableField.class); 169 | if (null == tableField) { 170 | continue; 171 | } 172 | field.setAccessible(true); 173 | field.set(instance, resultSet.getObject(tableField.name())); 174 | } 175 | list.add(instance); 176 | } 177 | return list; 178 | } 179 | 180 | public T selectOne(T where) { 181 | try { 182 | return (T) execute((statement) -> { 183 | ResultSet resultSet = statement.executeQuery(SqlBuilder.buildSelectSql(where)); 184 | 185 | return getOne(resultSet); 186 | }); 187 | } catch (Exception e) { 188 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 189 | } 190 | return null; 191 | } 192 | public T selectOneByRand(T where) { 193 | try { 194 | return (T) execute((statement) -> { 195 | ResultSet resultSet = statement.executeQuery(SqlBuilder.buildSelectOneRandSql(where)); 196 | 197 | return getOne(resultSet); 198 | }); 199 | } catch (Exception e) { 200 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 201 | } 202 | return null; 203 | } 204 | 205 | public List selectList() { 206 | try { 207 | return (List) execute((statement) -> { 208 | ResultSet resultSet = statement.executeQuery(SqlBuilder.buildSelectSql(getT())); 209 | 210 | return getList(resultSet); 211 | }); 212 | } catch (Exception e) { 213 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 214 | } 215 | return new ArrayList<>(); 216 | } 217 | 218 | public List selectList(T where) { 219 | try { 220 | return (List) execute((statement) -> { 221 | ResultSet resultSet = statement.executeQuery(SqlBuilder.buildSelectSql(where)); 222 | 223 | return getList(resultSet); 224 | }); 225 | } catch (Exception e) { 226 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 227 | } 228 | return new ArrayList<>(); 229 | } 230 | 231 | public Integer selectCount() { 232 | Integer total = null; 233 | try { 234 | total = (int) execute((statement) -> { 235 | ResultSet query = statement.executeQuery(SqlBuilder.buildSelectCountSql(getT())); 236 | return query.getInt("total"); 237 | }); 238 | } catch (Exception e) { 239 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 240 | } 241 | return total; 242 | } 243 | 244 | public Integer selectCount(T where) { 245 | Integer total = null; 246 | try { 247 | total = (int) execute((statement) -> { 248 | ResultSet query = statement.executeQuery(SqlBuilder.buildSelectCountSql(where)); 249 | return query.getInt("total"); 250 | }); 251 | } catch (Exception e) { 252 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 253 | } 254 | return total; 255 | } 256 | 257 | public Boolean exist(String field, String value) { 258 | Integer total = null; 259 | try { 260 | total = (int) execute((statement) -> { 261 | String sql = String.format("select count(*) as total from %s where %s = '%s'", this.tableName, field, value); 262 | ResultSet query = statement.executeQuery(sql); 263 | return query.getInt("total"); 264 | }); 265 | } catch (Exception e) { 266 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 267 | } 268 | if (null == total) { 269 | return null; 270 | } 271 | return total > 0; 272 | } 273 | 274 | public String getCreateTableSql() { 275 | return null; 276 | } 277 | 278 | } 279 | -------------------------------------------------------------------------------- /src/main/java/code/util/BytesUtil.java: -------------------------------------------------------------------------------- 1 | package code.util; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class BytesUtil { 6 | 7 | private final static int KB = 1024; 8 | private final static int MB = 1024 * 1024; 9 | private final static int GB = 1024 * 1024 * 1024; 10 | 11 | private static BigDecimal divide(long size, int unit) { 12 | return new BigDecimal(size).divide(new BigDecimal(unit)).setScale(2, BigDecimal.ROUND_DOWN); 13 | } 14 | 15 | public static String toDisplayStr(long size) { 16 | if (size < KB) { 17 | return size + "B"; 18 | } else if (size >= KB && size < MB) { 19 | return divide(size, KB) + "KB"; 20 | } else if (size >= MB && size < GB) { 21 | return divide(size, MB) + "MB"; 22 | } else if (size >= GB) { 23 | return divide(size, GB) + "GB"; 24 | } 25 | return null; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/code/util/CompressUtil.java: -------------------------------------------------------------------------------- 1 | package code.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.codehaus.plexus.archiver.tar.TarXZUnArchiver; 5 | 6 | import java.io.*; 7 | import java.util.zip.ZipEntry; 8 | import java.util.zip.ZipInputStream; 9 | 10 | @Slf4j 11 | public class CompressUtil { 12 | 13 | public static void unzip(String fileZip, File destDir) throws IOException { 14 | byte[] buffer = new byte[1024]; 15 | ZipInputStream zis = new ZipInputStream(new FileInputStream(fileZip)); 16 | try { 17 | ZipEntry zipEntry = zis.getNextEntry(); 18 | while (zipEntry != null) { 19 | File newFile = newFile(destDir, zipEntry); 20 | if (zipEntry.isDirectory()) { 21 | if (!newFile.isDirectory() && !newFile.mkdirs()) { 22 | throw new IOException("Failed to create directory " + newFile); 23 | } 24 | } else { 25 | // fix for Windows-created archives 26 | File parent = newFile.getParentFile(); 27 | if (!parent.isDirectory() && !parent.mkdirs()) { 28 | throw new IOException("Failed to create directory " + parent); 29 | } 30 | 31 | // write file content 32 | FileOutputStream fos = new FileOutputStream(newFile); 33 | int len; 34 | while ((len = zis.read(buffer)) > 0) { 35 | fos.write(buffer, 0, len); 36 | } 37 | fos.close(); 38 | } 39 | zipEntry = zis.getNextEntry(); 40 | } 41 | 42 | } finally { 43 | zis.closeEntry(); 44 | zis.close(); 45 | } 46 | } 47 | 48 | private static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException { 49 | File destFile = new File(destinationDir, zipEntry.getName()); 50 | 51 | String destDirPath = destinationDir.getCanonicalPath(); 52 | String destFilePath = destFile.getCanonicalPath(); 53 | 54 | if (!destFilePath.startsWith(destDirPath + File.separator)) { 55 | throw new IOException("Entry is outside of the target dir: " + zipEntry.getName()); 56 | } 57 | 58 | return destFile; 59 | } 60 | 61 | public static void tarXZUnArchiver(String in, String out) { 62 | final TarXZUnArchiver ua = new TarXZUnArchiver(); 63 | 64 | ua.setSourceFile(new File(in)); 65 | ua.setDestDirectory(new File(out)); 66 | ua.extract(); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/code/util/DownloadUtil.java: -------------------------------------------------------------------------------- 1 | package code.util; 2 | 3 | import code.config.RequestProxyConfig; 4 | import kong.unirest.GetRequest; 5 | import kong.unirest.HttpResponse; 6 | import kong.unirest.ProgressMonitor; 7 | import kong.unirest.Unirest; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.hc.client5.http.fluent.Request; 10 | import org.apache.hc.core5.util.Timeout; 11 | 12 | import java.io.File; 13 | import java.io.InputStream; 14 | import java.nio.file.StandardCopyOption; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | @Slf4j 18 | public class DownloadUtil { 19 | 20 | public static InputStream download(RequestProxyConfig requestProxyConfig, String url) { 21 | try { 22 | Request request = Request 23 | .get(url) 24 | .connectTimeout(Timeout.ofSeconds(30)) 25 | .responseTimeout(Timeout.ofSeconds(60)); 26 | requestProxyConfig.viaProxy(request); 27 | return request.execute().returnContent().asStream(); 28 | 29 | } catch (Exception e) { 30 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 31 | } 32 | return null; 33 | } 34 | 35 | public static boolean download(RequestProxyConfig requestProxyConfig, String url, String file, ProgressMonitor progressMonitor) { 36 | try { 37 | GetRequest request = Unirest 38 | .get(url) 39 | .downloadMonitor(progressMonitor) 40 | .connectTimeout((int) TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS)) 41 | ; 42 | requestProxyConfig.viaProxy(request); 43 | 44 | HttpResponse response = request.asFile(file, StandardCopyOption.REPLACE_EXISTING); 45 | return response.getStatus() == 200; 46 | } catch (Exception e) { 47 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 48 | } 49 | return false; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/code/util/ExceptionUtil.java: -------------------------------------------------------------------------------- 1 | package code.util; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintWriter; 5 | import java.io.StringWriter; 6 | 7 | public class ExceptionUtil { 8 | 9 | public static String getStackTraceWithCustomInfoToStr(Exception e, String description) { 10 | return String.format("description: %s, class name: %s, stacktrace: %s", description, e.getClass().getName(), getStackTraceToStr(e)); 11 | } 12 | 13 | public static String getStackTraceWithCustomInfoToStr(Exception e) { 14 | return String.format("class name: %s, stacktrace: %s", e.getClass().getName(), getStackTraceToStr(e)); 15 | } 16 | 17 | public static String getStackTraceToStr(Exception e) { 18 | StringWriter stringWriter = null; 19 | PrintWriter printWriter = null; 20 | try { 21 | stringWriter = new StringWriter(); 22 | printWriter = new PrintWriter(stringWriter); 23 | 24 | e.printStackTrace(printWriter); 25 | 26 | printWriter.flush(); 27 | stringWriter.flush(); 28 | } finally { 29 | if (null != stringWriter) { 30 | try { 31 | stringWriter.close(); 32 | } catch (IOException ioException) { 33 | ioException.printStackTrace(); 34 | } 35 | } 36 | if (null != printWriter) { 37 | printWriter.close(); 38 | } 39 | } 40 | return stringWriter.toString(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/code/util/FFmpegUtil.java: -------------------------------------------------------------------------------- 1 | package code.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.concurrent.*; 13 | 14 | @Slf4j 15 | public class FFmpegUtil { 16 | 17 | private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setDaemon(false).setNameFormat("ffmpeg-pool-%d").build(); 18 | 19 | private static ExecutorService fixedThreadPool = new ThreadPoolExecutor( 20 | 5, 21 | 20, 22 | 60, 23 | TimeUnit.SECONDS, 24 | new ArrayBlockingQueue<>(200), 25 | threadFactory, 26 | (Runnable r, ThreadPoolExecutor executor) -> { 27 | log.error(r.toString() + " is Rejected"); 28 | } 29 | ); 30 | public static void oggFileToMp3(String ffmpegPath, String filePath, String outPath) { 31 | List commend = new ArrayList(); 32 | commend.add(ffmpegPath); 33 | commend.add("-i"); 34 | commend.add(filePath); 35 | commend.add("-acodec"); 36 | commend.add("libmp3lame"); 37 | commend.add("-y"); 38 | commend.add(outPath); 39 | 40 | try { 41 | ProcessBuilder builder = new ProcessBuilder(commend); 42 | builder.command(commend); 43 | log.info("ffmpeg command: {}", String.join(" ", commend)); 44 | Process p = builder.start(); 45 | 46 | // 获取外部程序标准输出流 47 | fixedThreadPool.submit(new OutputHandlerRunnable(p.getInputStream(), false)); 48 | // 获取外部程序标准错误流 49 | fixedThreadPool.submit(new OutputHandlerRunnable(p.getErrorStream(), true)); 50 | int code = p.waitFor(); 51 | log.info("ffmpeg command: {} result: {}", String.join(" ", commend), code); 52 | 53 | p.destroy(); 54 | } catch (Exception e) { 55 | e.printStackTrace(); 56 | } 57 | } 58 | private static class OutputHandlerRunnable implements Runnable { 59 | private InputStream in; 60 | 61 | private boolean error; 62 | 63 | public OutputHandlerRunnable(InputStream in, boolean error) { 64 | this.in = in; 65 | this.error = error; 66 | } 67 | 68 | @Override 69 | public void run() { 70 | try (BufferedReader bufr = new BufferedReader(new InputStreamReader(this.in))) { 71 | String line = null; 72 | while ((line = bufr.readLine()) != null) { 73 | if (error) { 74 | System.out.println(line); 75 | } 76 | } 77 | } catch (IOException e) { 78 | e.printStackTrace(); 79 | } 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/code/util/GPTUtil.java: -------------------------------------------------------------------------------- 1 | package code.util; 2 | 3 | import code.config.RequestProxyConfig; 4 | import code.util.gpt.GPTCallback; 5 | import code.util.gpt.parameter.GPTChatParameter; 6 | import code.util.gpt.parameter.GPTCreateImageParameter; 7 | import code.util.gpt.parameter.GPTTranscriptionsParameter; 8 | import code.util.gpt.response.*; 9 | import com.alibaba.fastjson2.JSON; 10 | import com.alibaba.fastjson2.JSONReader; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.apache.commons.io.IOUtils; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.apache.hc.client5.http.entity.mime.HttpMultipartMode; 15 | import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; 16 | import org.apache.hc.client5.http.fluent.Content; 17 | import org.apache.hc.client5.http.fluent.Request; 18 | import org.apache.hc.client5.http.fluent.Response; 19 | import org.apache.hc.core5.http.ContentType; 20 | import org.apache.hc.core5.http.HttpEntity; 21 | import org.apache.hc.core5.util.Timeout; 22 | 23 | import java.io.BufferedInputStream; 24 | import java.io.BufferedReader; 25 | import java.io.InputStream; 26 | import java.io.InputStreamReader; 27 | import java.nio.charset.StandardCharsets; 28 | 29 | @Slf4j 30 | public class GPTUtil { 31 | 32 | public static GPTCreateImageResponse createImage(RequestProxyConfig requestProxyConfig, String token, String apiPrefix, GPTCreateImageParameter parameter) { 33 | GPTCreateImageResponse gptCreateImageResponse = new GPTCreateImageResponse(); 34 | gptCreateImageResponse.setOk(false); 35 | try { 36 | Request request = Request 37 | .post(apiPrefix + "/v1/images/generations") 38 | .setHeader("Authorization", "Bearer " + token) 39 | .bodyString(JSON.toJSONString(parameter), ContentType.APPLICATION_JSON) 40 | .connectTimeout(Timeout.ofSeconds(30)) 41 | .responseTimeout(Timeout.ofSeconds(60)); 42 | requestProxyConfig.viaProxy(request); 43 | Response response = request.execute(); 44 | 45 | Content content = response.returnContent(); 46 | String s = content.asString(); 47 | if (StringUtils.isNotBlank(s)) { 48 | GPTCreateImageResponse gptCreateImageResponse2 = JSON.parseObject(s, GPTCreateImageResponse.class, JSONReader.Feature.SupportSmartMatch); 49 | if (null != gptCreateImageResponse2 && null != gptCreateImageResponse2.getData() && !gptCreateImageResponse2.getData().isEmpty()) { 50 | gptCreateImageResponse2.setOk(true); 51 | return gptCreateImageResponse2; 52 | } 53 | } 54 | 55 | } catch (Exception e) { 56 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 57 | } 58 | return gptCreateImageResponse; 59 | } 60 | 61 | public static GPTTranscriptionsResponse transcriptions(RequestProxyConfig requestProxyConfig, String token, String apiPrefix, GPTTranscriptionsParameter parameter) { 62 | GPTTranscriptionsResponse gptTranscriptionsResponse = new GPTTranscriptionsResponse(); 63 | gptTranscriptionsResponse.setOk(false); 64 | try { 65 | MultipartEntityBuilder builder = MultipartEntityBuilder.create(); 66 | builder.setMode(HttpMultipartMode.LEGACY); 67 | builder.addBinaryBody("file", parameter.getFile(), ContentType.DEFAULT_BINARY, parameter.getFile().getName()); 68 | builder.addTextBody("model", parameter.getModel(), ContentType.DEFAULT_BINARY); 69 | HttpEntity entity = builder.build(); 70 | 71 | Request request = Request 72 | .post(apiPrefix + "/v1/audio/transcriptions") 73 | .body(entity) 74 | .setHeader("Authorization", "Bearer " + token) 75 | .connectTimeout(Timeout.ofSeconds(30)) 76 | .responseTimeout(Timeout.ofSeconds(60)); 77 | requestProxyConfig.viaProxy(request); 78 | Response response = request.execute(); 79 | 80 | Content content = response.returnContent(); 81 | String s = content.asString(StandardCharsets.UTF_8); 82 | if (StringUtils.isNotBlank(s)) { 83 | GPTTranscriptionsResponse gptTranscriptionsResponse2 = JSON.parseObject(s, GPTTranscriptionsResponse.class, JSONReader.Feature.SupportSmartMatch); 84 | if (null != gptTranscriptionsResponse2 && StringUtils.isNotBlank(gptTranscriptionsResponse2.getText())) { 85 | gptTranscriptionsResponse2.setOk(true); 86 | return gptTranscriptionsResponse2; 87 | } 88 | } 89 | 90 | } catch (Exception e) { 91 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 92 | } 93 | return gptTranscriptionsResponse; 94 | } 95 | 96 | public static GPTChatResponse chat(RequestProxyConfig requestProxyConfig, String token, String apiPrefix, GPTChatParameter parameter, GPTCallback callback) { 97 | GPTChatResponse chatResponse = new GPTChatResponse(); 98 | chatResponse.setOk(false); 99 | 100 | try { 101 | Request request = Request 102 | .post(apiPrefix + "/v1/chat/completions") 103 | .setHeader("Authorization", "Bearer " + token) 104 | .setHeader("accept", "text/event-stream") 105 | .bodyString(JSON.toJSONString(parameter), org.apache.hc.core5.http.ContentType.APPLICATION_JSON) 106 | .connectTimeout(Timeout.ofSeconds(30)) 107 | .responseTimeout(Timeout.ofSeconds(30)) 108 | ; 109 | requestProxyConfig.viaProxy(request); 110 | Response response = request.execute(); 111 | 112 | StringBuilder builder = new StringBuilder(); 113 | StringBuilder responseBuilder = new StringBuilder(); 114 | int code = response.handleResponse((classicHttpResponse) -> { 115 | InputStream inputStream = classicHttpResponse.getEntity().getContent(); 116 | 117 | try (BufferedInputStream in = IOUtils.buffer(inputStream)) { 118 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { 119 | String line = null; 120 | while((line = reader.readLine()) != null) { 121 | responseBuilder.append(line); 122 | String s = StringUtils.substringAfter(line, "data: "); 123 | if (StringUtils.isNotEmpty(s)) { 124 | if ("[DONE]".equals(s)) { 125 | GPTCallbackChatContent content = new GPTCallbackChatContent(); 126 | content.setDone(true); 127 | callback.call(content); 128 | } else { 129 | GPTCallbackChatContent content = JSON.parseObject(s, GPTCallbackChatContent.class, JSONReader.Feature.SupportSmartMatch); 130 | GPTChatContentChoicesDelta delta = content.getChoices().get(0).getDelta(); 131 | if (StringUtils.isNotEmpty(delta.getContent())) { 132 | builder.append(content.getChoices().get(0).getDelta().getContent()); 133 | content.setDone(false); 134 | content.setContent(builder.toString()); 135 | callback.call(content); 136 | } 137 | } 138 | } 139 | } 140 | } 141 | } catch (Exception e) { 142 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 143 | } 144 | 145 | return classicHttpResponse.getCode(); 146 | }); 147 | 148 | chatResponse.setStatusCode(code); 149 | 150 | String s = builder.toString(); 151 | chatResponse.setOk(StringUtils.isNotEmpty(s)); 152 | chatResponse.setContent(s); 153 | chatResponse.setResponse(responseBuilder.toString()); 154 | } catch (Exception e) { 155 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 156 | } 157 | 158 | return chatResponse; 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/code/util/GithubUtil.java: -------------------------------------------------------------------------------- 1 | package code.util; 2 | 3 | import code.config.RequestProxyConfig; 4 | import com.alibaba.fastjson2.JSON; 5 | import com.alibaba.fastjson2.JSONReader; 6 | import lombok.Data; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.hc.client5.http.fluent.Request; 9 | import org.apache.hc.client5.http.fluent.Response; 10 | import org.apache.hc.core5.http.ContentType; 11 | import org.apache.hc.core5.http.HttpResponse; 12 | import org.apache.hc.core5.util.Timeout; 13 | 14 | import java.nio.charset.Charset; 15 | import java.util.List; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | @Slf4j 19 | public class GithubUtil { 20 | 21 | private final static String ApiVersion = "2022-11-28"; 22 | 23 | @Data 24 | public static class LatestReleaseResponse { 25 | private boolean ok; 26 | private String htmlUrl; 27 | private String tagName; 28 | private String name; 29 | private String body; 30 | private List assets; 31 | } 32 | @Data 33 | public static class LatestReleaseAsset { 34 | private String name; 35 | private String browserDownloadUrl; 36 | } 37 | 38 | public static LatestReleaseResponse getLatestRelease(RequestProxyConfig requestProxyConfig, String owner, String repo) { 39 | String url = String.format("https://api.github.com/repos/%s/%s/releases/latest", owner, repo); 40 | try { 41 | Request request = Request 42 | .get(url) 43 | .setHeader("Accept", "application/vnd.github+json") 44 | .setHeader("X-GitHub-Api-Version", ApiVersion) 45 | .connectTimeout(Timeout.of(15, TimeUnit.SECONDS)) 46 | .responseTimeout(Timeout.of(60, TimeUnit.SECONDS)); 47 | requestProxyConfig.viaProxy(request); 48 | Response execute = request.execute(); 49 | 50 | LatestReleaseResponse releaseAssetResponse = JSON.parseObject(execute.returnContent().asString(Charset.forName("UTF-8")), LatestReleaseResponse.class, JSONReader.Feature.SupportSmartMatch); 51 | releaseAssetResponse.setOk(true); 52 | return releaseAssetResponse; 53 | } catch (Exception e) { 54 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 55 | } 56 | LatestReleaseResponse response = new LatestReleaseResponse(); 57 | response.setOk(false); 58 | return response; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/code/util/ProgramUtil.java: -------------------------------------------------------------------------------- 1 | package code.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.IOException; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | 9 | @Slf4j 10 | public class ProgramUtil { 11 | 12 | public static void restart(String processName) { 13 | try { 14 | String[] strings = {"java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/" + processName}; 15 | log.info(Stream.of(strings).collect(Collectors.joining(" "))); 16 | Runtime.getRuntime().exec(strings); 17 | System.exit(1); 18 | } catch (IOException e) { 19 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/code/util/SqliteUtil.java: -------------------------------------------------------------------------------- 1 | package code.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.File; 6 | import java.sql.Connection; 7 | import java.sql.DriverManager; 8 | import java.sql.SQLException; 9 | import java.sql.Statement; 10 | 11 | @Slf4j 12 | public class SqliteUtil { 13 | 14 | public interface SqliteInterface { 15 | Object execute(Statement statement) throws SQLException, IllegalAccessException, InstantiationException; 16 | } 17 | 18 | public static Object execute(String dbPath, SqliteInterface sqliteInterface) throws Exception { 19 | Connection connection = null; 20 | try { 21 | File file = new File(dbPath); 22 | boolean exists = file.exists(); 23 | if (!exists) { 24 | file.createNewFile(); 25 | } 26 | connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath); 27 | Statement statement = connection.createStatement(); 28 | statement.setQueryTimeout(30); 29 | return sqliteInterface.execute(statement); 30 | } catch (Exception e) { 31 | throw e; 32 | } finally { 33 | if (null != connection) { 34 | connection.close(); 35 | } 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/code/util/ThreadUtil.java: -------------------------------------------------------------------------------- 1 | package code.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.concurrent.TimeUnit; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | @Slf4j 9 | public class ThreadUtil { 10 | 11 | private static String name = "Thread-Util-"; 12 | private static AtomicInteger atomicInteger = new AtomicInteger(0); 13 | 14 | public interface IntervalCallback { 15 | void run(); 16 | } 17 | 18 | public static synchronized void newIntervalWithTryCatch(IntervalCallback callback, int unit, TimeUnit timeUnit) { 19 | new Thread(() -> { 20 | while (true) { 21 | try { 22 | callback.run(); 23 | } catch (Exception e) { 24 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 25 | } 26 | 27 | try { 28 | timeUnit.sleep(unit); 29 | } catch (InterruptedException e) { 30 | log.error(ExceptionUtil.getStackTraceWithCustomInfoToStr(e)); 31 | throw new RuntimeException(e); 32 | } 33 | } 34 | }, name + atomicInteger.incrementAndGet()).start(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/code/util/ffmpeg/FfmpegDownloadUrl.java: -------------------------------------------------------------------------------- 1 | package code.util.ffmpeg; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.Properties; 6 | 7 | @Getter 8 | public enum FfmpegDownloadUrl { 9 | 10 | Windows("https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-n6.0-latest-win64-gpl-6.0.zip"), 11 | 12 | LINUX_AMD64("https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz"), 13 | LINUX_I686("https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-i686-static.tar.xz"), 14 | LINUX_ARM64("https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-arm64-static.tar.xz"), 15 | LINUX_ARMHF("https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-armhf-static.tar.xz"), 16 | LINUX_ARMEL("https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-armel-static.tar.xz"), 17 | 18 | ; 19 | 20 | private String url; 21 | 22 | FfmpegDownloadUrl(String url) { 23 | this.url = url; 24 | } 25 | 26 | public static FfmpegDownloadUrl getFfmpegDownloadUrl() { 27 | Properties properties = System.getProperties(); 28 | String name = properties.getProperty("os.name"); 29 | String arch = properties.getProperty("os.arch"); 30 | 31 | if (name.toLowerCase().contains("windows")) { 32 | return Windows; 33 | } else { 34 | if ("amd64".equals(arch)) { 35 | return LINUX_AMD64; 36 | } else if ("i686".equals(arch)) { 37 | return LINUX_I686; 38 | } else if ("arm64".equals(arch)) { 39 | return LINUX_ARM64; 40 | } else if ("armhf".equals(arch)) { 41 | return LINUX_ARMHF; 42 | } else if ("armel".equals(arch)) { 43 | return LINUX_ARMEL; 44 | } else { 45 | return LINUX_AMD64; 46 | } 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/GPTCallback.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt; 2 | 3 | import code.util.gpt.response.GPTCallbackChatContent; 4 | 5 | public interface GPTCallback { 6 | 7 | void call(GPTCallbackChatContent content); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/GPTModel.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum GPTModel { 7 | 8 | Gpt3_5Turbo("gpt-3.5-turbo"), 9 | 10 | ; 11 | 12 | private String model; 13 | 14 | GPTModel(String model) { 15 | this.model = model; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/GPTRole.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt; 2 | 3 | import lombok.Getter; 4 | import lombok.ToString; 5 | 6 | @Getter 7 | public enum GPTRole { 8 | 9 | User("user"), 10 | System("system"), 11 | Assistant("assistant"), 12 | 13 | ; 14 | 15 | private String role; 16 | 17 | GPTRole(String role) { 18 | this.role = role; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/GPTTranscriptionsModel.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum GPTTranscriptionsModel { 7 | 8 | Whisper_1("whisper-1"), 9 | 10 | ; 11 | 12 | private String model; 13 | 14 | GPTTranscriptionsModel(String model) { 15 | this.model = model; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/parameter/GPTChatParameter.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt.parameter; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | @Data 8 | public class GPTChatParameter { 9 | 10 | private String model; 11 | private boolean stream; 12 | private List messages; 13 | private String user; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/parameter/GPTCreateImageParameter.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt.parameter; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class GPTCreateImageParameter { 7 | 8 | private String prompt; 9 | 10 | private String user; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/parameter/GPTMessage.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt.parameter; 2 | 3 | import code.util.gpt.GPTRole; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class GPTMessage { 8 | 9 | private String role; 10 | private String content; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/parameter/GPTTranscriptionsParameter.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt.parameter; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.File; 6 | 7 | @Data 8 | public class GPTTranscriptionsParameter { 9 | 10 | private File file; 11 | 12 | private String model; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/response/GPTCallbackChatContent.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt.response; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | @Data 8 | public class GPTCallbackChatContent { 9 | 10 | private boolean done; 11 | private String content; 12 | 13 | private String id; 14 | 15 | private String object; 16 | private Long created; 17 | private String model; 18 | private List choices; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/response/GPTChatContentChoices.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt.response; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class GPTChatContentChoices { 7 | 8 | private GPTChatContentChoicesDelta delta; 9 | 10 | private Integer index; 11 | private String finishReason; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/response/GPTChatContentChoicesDelta.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt.response; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class GPTChatContentChoicesDelta { 7 | 8 | private String role; 9 | 10 | private String content; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/response/GPTChatResponse.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt.response; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class GPTChatResponse { 7 | private boolean ok; 8 | 9 | private int statusCode; 10 | 11 | private String content; 12 | 13 | private String response; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/response/GPTCreateImage.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt.response; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class GPTCreateImage { 7 | 8 | private String url; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/response/GPTCreateImageResponse.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt.response; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | @Data 8 | public class GPTCreateImageResponse { 9 | 10 | private boolean ok; 11 | 12 | private Long created; 13 | 14 | private List data; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/code/util/gpt/response/GPTTranscriptionsResponse.java: -------------------------------------------------------------------------------- 1 | package code.util.gpt.response; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | @Data 8 | public class GPTTranscriptionsResponse { 9 | 10 | private boolean ok; 11 | 12 | private String text; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/resources/i18n/i18n_en.properties: -------------------------------------------------------------------------------- 1 | bot_start_succeed=Bot program start succeed! 2 | 3 | help_text=\ 4 | Document\n \ 5 | https://github.com/kylelin1998/ChatGPTForTelegram/blob/master/README_en.md 6 | 7 | invalid_command=Invalid command, you are not admin 8 | on=On 9 | off=Off 10 | test=Test 11 | update=Update 12 | not_found=Not found 13 | unknown_error=System unknown error. 14 | nothing_at_all=Nothing at all 15 | cancel_succeeded=Cancel succeed. 16 | confirm=Confirm 17 | cancel=Cancel 18 | delete=Delete 19 | exit_succeeded=Exit Succeeded 20 | getting=Getting... 21 | downloading=Downloading... 22 | update_config=Update Config 23 | restart=Restart 24 | upgrade=Upgrade 25 | update_succeeded=Updating succeeded. 26 | update_failed=Updating failed. 27 | open=Open 28 | close=Close 29 | processing=Processing... 30 | 31 | language_list=Please choose one language below. 32 | change_language_finish=Language changed success. 33 | 34 | this_chat_is_a_new_chat=This chat is a new chat. 35 | please_send_me_a_problem_that_you_want_to_ask=Please send me a problem that you want to ask... 36 | requesting_open_ai_api=%s\n---\n%s\nOrganizing language... 37 | the_current_mode_is_continuous_chat_mode=The current mode is continuous chat mode 38 | an_error_occurred_of_requesting_open_ai_api_failed=An error occurred of requesting open ai API failed. HTTP response status code: %s 39 | continue_this_chat=You can continue this chat if you\u2019d like, or use /exit command to exit chat. 40 | ask_chat_ended=Chat ended or use /ask command to ask again if you wish 41 | the_current_mode_is_chat_message_limit_mode=The current mode is chat message limit mode 42 | cml_chat_ended=Chat ended, current chat message count: %s / %s, or you can use /cml command to chat again if you want to. 43 | cml_continue_this_chat=You can continue this chat if you\u2019d like, current chat message count: %s / %s, or use /exit command to exit chat. 44 | the_current_mode_is_none_of_message_context_mode=The current mode is none of message context mode 45 | 46 | you_are_not_an_admin=Invalid use of this command because you are not an admin. 47 | are_you_sure_to_restart_right_now=Are you sure to restart the bot right now? 48 | restarting=Restarting... 49 | getting_update_data=Getting update data... 50 | are_you_sure_to_upgrade_this_bot_right_now=Are you sure to upgrade this bot right now ? 51 | are_you_sure_to_update_the_config=Are you sure to update the config ? 52 | please_send_me_config_content=Please send me the config content you want to set. 53 | update_config_fail=Updating the config failed. This is because the config content does not meet certain standards. 54 | target_version=Target version 55 | current_version=Current version 56 | update_logs=Update logs 57 | updating=Updating 58 | downloaded=Downloaded: %s, Total size: %s 59 | please_send_me_an_image_description=Please send me an image description of what you want to create 60 | image_description_text_character_count_more_than=Image description text character count more than 1000 characters. 61 | 62 | chat_has_too_many_conversations=Oops! This chat has too many conversations. Please invoke /chat command for a new chat again. 63 | 64 | set_open_status=Set open usage status 65 | choose_open_status=Open status:\n\ 66 | Anyone can use this bot.\n\n\ 67 | Close status:\n\ 68 | Only permission chat id list can use this bot.\n\n\ 69 | Please choose open status or close status, and you can choose cancel to cancel. 70 | 71 | model=Model 72 | change_model=Change Model 73 | please_send_me_the_model_you_want_to_change=\u8BF7\u53D1\u9001\u4F60\u60F3\u8981\u66F4\u6539\u7684\u6A21\u578B 74 | 75 | set_voice_status=Set voice usage status 76 | saving=Saving... 77 | save_succeeded=Save succeeded. 78 | save_failed=Save failed. 79 | unzipping=Unzipping... 80 | delete_succeeded=Delete succeeded. 81 | delete_failed=Delete failed. 82 | please_send_me_the_playback_alias=Please resend the command /p 83 | xxx_not_found='%s' not found. 84 | record_mode_opened=Recording mode has been activated. Please chat with this chatbot to record the conversation. If you want to end the recording, send "end_record" to me. If you only want to record a single dialogue, please include the variable "${content}". The recording will end immediately when this variable is detected. More explanation: When replaying the single dialogue, the variable "${content}" will be automatically replaced with your input text, minimizing the need to input characters each time. 85 | please_send_me_record_alias=Please send me this record chat alias. 86 | invalid_alias=Invalid alias. The alias length must less than 10. Please send me again. 87 | Please_send_me_record_explanations=Please send me the record chat explanations. 88 | invalid_explanations=Invalid explanations. The explanations length must less than 100. Please send me again. 89 | record_alias=Record Alias 90 | record_explanations=Record Explanations 91 | you_can_use_record_to_start_recoding=You haven't created any records yet. You can use the /record command to start recording. 92 | choose_voice_status=Please choose open status or close status, and you can choose cancel to cancel. 93 | downloading_xxx=Downloading %s program... 94 | please_send_me_chat_buttons=Please send me chat buttons. If you don't want to set them, you can send '-1' to me.\n\n\ 95 | The "<>" symbol is merely for emphasis. Please refrain from including this symbol when submitting formal data.\n\ 96 | Sample\uFF1A\n\ 97 | \n\ 98 |