diff --git a/documentation/en/docs/index.md b/documentation/en/docs/index.md new file mode 100644 index 0000000..c0f7462 --- /dev/null +++ b/documentation/en/docs/index.md @@ -0,0 +1,85 @@ +--- +title: Personal ChatGPT Telegram Bot +hide: +- navigation +--- + +# Personal ChatGPT Telegram Bot +> Introducing the ultimate AI-powered chatbot for Telegram - the perfect companion for anyone looking for quick, accurate answers to their questions. Our bot uses state-of-the-art machine learning technology powered by the ChatGPT model, allowing it to understand natural language inputs and respond with human-like precision. +> +> With our bot, you can ask anything you want, from simple queries to complex questions. Whether you're looking for information on the latest news, need help with a math problem, or just want to chat with a virtual friend, our bot has you covered. +> +> What's more, our bot is designed to be easy to use and user-friendly. Simply type in your question or query, and the bot will do the rest, providing you with a quick and accurate response in seconds. No more scrolling through endless search results or struggling to find the information you need - our bot does all the hard work for you. +> +> So why wait? Try out our ChatGPT-powered Telegram bot today and experience the future of AI-powered chatbots for yourself! +> +> (с) Generated by ChatGPT + +But seriously, the project uses your ChatGPT token to access the ChatGPT API and let you chat with ChatGPT directly in Telegram. + +## Work example + + + +## Privacy +See my last name in the domain? [I'm a developer](https://mark.struchkov.dev), [blogger](https://struchkov.dev/blog/ru/), and publicly active. I don't have the benefit of getting dirty under my own name. I assure you that your data is not transferred to third parties, even I do not know about your requests to ChatGPT. You can see for yourself by examining the code, it's opensource. + +## Preparing + +* You must have [Docker installed](https://docs.docker.com/engine/install/). You can run the project both on the PC and on the server. +* You must [register a bot in Telegram](https://t.me/BotFather) and get an access token. +* You also need [ChatGPT access token](https://platform.openai.com/account/api-keys). +* You must know your telegramId. [You can find it out here.](https://t.me/myidbot). + +## Docker Run + +``` bash +docker run --name chatgpt-telegram-bot upagge/chatgpt-telegram-bot:develop \ + -e TELEGRAM_BOT_TOKEN= \ + -e TELEGRAM_BOT_USERNAME= \ # (1)! + -e TELEGRAM_PERSON_ID= \ + -e CHAT_GPT_TOKEN= +``` + +1. Specify a name with the ending bot here, not a public name. + +### Telegram Proxy +If you have Telegram blocked, you can specify proxy settings to connect. + +``` bash +docker run --name chatgpt-telegram-bot upagge/chatgpt-telegram-bot:develop \ + -e TELEGRAM_BOT_TOKEN= \ + -e TELEGRAM_BOT_USERNAME= \ + -e TELEGRAM_PERSON_ID= \ + -e CHAT_GPT_TOKEN= \ + -e TELEGRAM_PROXY_ENABLE=true \ + -e TELEGRAM_PROXY_HOST= \ + -e TELEGRAM_PROXY_PORT= \ + -e TELEGRAM_PROXY_TYPE=SOCKS5 \ # (1)! + -e TELEGRAM_PROXY_USERNAME= \ # (2)! + -e TELEGRAM_PROXY_PASSWORD= # (2)! +``` + +1. Available options `SOCKS5`, `SOCKS4`, `HTTP`. +2. Optional. If there is no authorization, you can leave it blank. + +## :heart:{ .heart } Support Develop + +Sponsorship makes a project sustainable because it pays for the time of the maintainers of that project, a very scarce resource that is spent on developing new features, fixing bugs, improving stability, solving problems, and general support. The biggest bottleneck in Open Source is time. + +- Bank card for residents of the Russian Federation: [https://www.tinkoff.ru/cf/4iU6NB3uzqx](https://www.tinkoff.ru/cf/4iU6NB3uzqx) +- Crypto: + * TON: `struchkov-mark.ton` + * BTC (Taproot): `bc1pt49vnp43c4mktk6309zlq3020dzd0p89gc8d90zzn4sgjvck56xs0t86vy` + * ETH: `0x7668C802Bd71Be965671D4Bbb1AD90C7f7f32921` + * USDT (ERC-20): `0x7668C802Bd71Be965671D4Bbb1AD90C7f7f32921` + * DAI (ERC-20): `0x7668C802Bd71Be965671D4Bbb1AD90C7f7f32921` + * BNB: `0xDa41aC95f606850f2E01ba775e521Cd385AA7D03` + * USD: `0xDa41aC95f606850f2E01ba775e521Cd385AA7D03` + * DAI: `0xDa41aC95f606850f2E01ba775e521Cd385AA7D03` + +## FAQ + +??? question "What model is used?" + + For now the `gpt-3.5-turbo` model is used. In future versions you will be able to choose the model. \ No newline at end of file diff --git a/documentation/en/docs/stylesheets/extra.css b/documentation/en/docs/stylesheets/extra.css new file mode 100644 index 0000000..841f6aa --- /dev/null +++ b/documentation/en/docs/stylesheets/extra.css @@ -0,0 +1,77 @@ +.md-typeset .admonition, .md-typeset details { + font-size: 0.75rem; +} + +.md-typeset h1, .md-typeset h2 { + font-weight: 500; +} + +.md-typeset h2 { + margin-top: 4rem; +} + +.md-typeset h3 { + margin-top: 2.2rem; +} + +.md-typeset mark { + background-color: #fff3bc; + margin: -4px -4px -6px; + padding: 4px 4px 6px; +} + +@keyframes heart { + 0%, 40%, 80%, 100% { + transform: scale(1); + } + 20%, 60% { + transform: scale(1.15); + } +} +.heart { + animation: heart 1000ms infinite; +} + +@keyframes ninja-disappear { + 0% { + opacity: 1; + } + 50% { + opacity: 0.3; + } + 100% { + opacity: 1; + } +} + +.ninja-disappear { + animation: ninja-disappear 3000ms infinite; + animation-delay: 1500ms; +} + +@keyframes jingle-bell-swing { + 0% { + transform: rotate(0deg); + } + 50% { + transform: rotate(10deg); + } + 100% { + transform: rotate(0deg); + } +} + +.jingle-bell { + animation: jingle-bell-swing 2s ease-in-out infinite; + transform-origin: center; +} + + +@media(min-width: 768px) { + .frontpage-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + column-gap: 2em; + row-gap: 1em; + } +} \ No newline at end of file diff --git a/documentation/en/mkdocs.yml b/documentation/en/mkdocs.yml new file mode 100644 index 0000000..94ba6e9 --- /dev/null +++ b/documentation/en/mkdocs.yml @@ -0,0 +1,119 @@ +site_name: ChatGPT Telegram Bot +site_url: https://docs.struchkov.dev/chatgpt-telegram-bot/en + +repo_url: https://github.com/uPagge/openai-chatgpt-telegram-bot +repo_name: uPagge/openai-chatgpt-telegram-bot + +#edit_uri: edit/master/documentation/docs + +nav: + - Home: + - index.md + +theme: + name: material + custom_dir: overrides + language: en +# favicon: assets/favicon.ico + icon: + logo: simple/openai + repo: fontawesome/brands/github + palette: + - media: "(prefers-color-scheme)" + primary: deep purple + accent: indigo + scheme: default + toggle: + icon: material/brightness-auto + name: Switch to light mode + - media: "(prefers-color-scheme: light)" + primary: deep purple + accent: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to system preference + + features: + - navigation.instant + - navigation.indexes + - navigation.sections + - navigation.tracking + - navigation.footer + - navigation.tabs.sticky + - navigation.prune +# - search +# - search.suggest +# - search.highlight +# - content.action.edit + - content.code.copy + - content.code.annotate + - content.tooltips + - toc.follow +# - toc.integrate +plugins: + - social + - typeset + # - blog: + # blog_toc: true + # post_date_format: short + # post_url_format: "{slug}" + # archive_toc: true + # categories: false + # authors: false + # - optimize: + # optimize_jpg_quality: 90 +# - tags +# - search: +# lang: ru +markdown_extensions: + - pymdownx.details + - pymdownx.superfences + - pymdownx.inlinehilite + - pymdownx.snippets: + auto_append: + - includes/abbreviations.md + - pymdownx.critic + - pymdownx.caret + - pymdownx.keys + - pymdownx.mark + - pymdownx.tilde + - attr_list + - def_list + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.tabbed: + alternate_style: true + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + - admonition + - abbr + - md_in_html + - footnotes + - toc: + toc_depth: 3 + title: Table of content + permalink: ⚓︎ + +extra_css: + - stylesheets/extra.css +extra: + generator: false + analytics: + provider: custom + property: foobar + version: + provider: mike +# alternate: +# - name: Русский +# link: /ru/ +# lang: ru \ No newline at end of file diff --git a/documentation/en/overrides/partials/comments.html b/documentation/en/overrides/partials/comments.html new file mode 100644 index 0000000..11806e0 --- /dev/null +++ b/documentation/en/overrides/partials/comments.html @@ -0,0 +1,17 @@ +{% if "comments" in page.meta.hide %} +{% else %} +

{{ lang.t("meta.comments") }}

+
+ + +{% endif %} \ No newline at end of file diff --git a/documentation/en/overrides/partials/integrations/analytics/custom.html b/documentation/en/overrides/partials/integrations/analytics/custom.html new file mode 100644 index 0000000..b4e0e91 --- /dev/null +++ b/documentation/en/overrides/partials/integrations/analytics/custom.html @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/src/main/java/dev/struchkov/example/bot/unit/PersonalChatGPTUnit.java b/src/main/java/dev/struchkov/example/bot/unit/PersonalChatGPTUnit.java index 09e43b5..4753e80 100644 --- a/src/main/java/dev/struchkov/example/bot/unit/PersonalChatGPTUnit.java +++ b/src/main/java/dev/struchkov/example/bot/unit/PersonalChatGPTUnit.java @@ -6,12 +6,14 @@ import dev.struchkov.example.bot.util.UnitName; import dev.struchkov.godfather.main.domain.annotation.Unit; import dev.struchkov.godfather.main.domain.content.Attachment; import dev.struchkov.godfather.main.domain.content.Mail; +import dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton; import dev.struchkov.godfather.simple.domain.BoxAnswer; import dev.struchkov.godfather.simple.domain.unit.AnswerText; import dev.struchkov.godfather.telegram.domain.ChatAction; import dev.struchkov.godfather.telegram.domain.ClientBotCommand; import dev.struchkov.godfather.telegram.domain.attachment.ButtonClickAttachment; import dev.struchkov.godfather.telegram.domain.attachment.CommandAttachment; +import dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard; import dev.struchkov.godfather.telegram.main.context.MailPayload; import dev.struchkov.godfather.telegram.main.core.util.Attachments; import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending; @@ -29,6 +31,7 @@ import dev.struchkov.openai.domain.response.GptResponse; import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component; +import java.text.MessageFormat; import java.util.List; import java.util.Optional; @@ -36,6 +39,7 @@ import static dev.struchkov.example.bot.util.UnitName.CLEAR_CONTEXT; import static dev.struchkov.example.bot.util.UnitName.GPT_UNIT; import static dev.struchkov.godfather.simple.domain.BoxAnswer.boxAnswer; import static dev.struchkov.godfather.simple.domain.BoxAnswer.replaceBoxAnswer; +import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.DISABLE_WEB_PAGE_PREVIEW; @Component public class PersonalChatGPTUnit implements PersonUnitConfiguration { @@ -108,7 +112,15 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration { @Unit(value = GPT_UNIT, main = true) public AnswerText chatGpt() { return AnswerText.builder() - .triggerCheck(mail -> mail.getFromPersonId().equals(appProperty.getTelegramId())) + .triggerCheck(mail -> { + if (mail.getFromPersonId().equals(appProperty.getTelegramId())) { + final Optional firstCommand = Attachments.findFirstCommand(mail.getAttachments()); + if (firstCommand.isPresent()) { + return !firstCommand.get().getCommandType().equals("/start"); + } + } + return false; + }) .answer(message -> { telegramService.executeAction(message.getFromPersonId(), ChatAction.TYPING); @@ -157,6 +169,44 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration { .build(); } + @Unit(value = UnitName.START, main = true) + public AnswerText startMessage() { + return AnswerText.builder() + .triggerCheck( + mail -> { + if (mail.getFromPersonId().equals(appProperty.getTelegramId())) { + final List attachments = mail.getAttachments(); + final Optional optCommand = Attachments.findFirstCommand(attachments); + if (optCommand.isPresent()) { + final CommandAttachment command = optCommand.get(); + return Cmd.START.equals(command.getCommandType()); + } + } + return false; + } + ) + .answer(message -> { + return BoxAnswer.builder() + .message(MessageFormat.format( + """ + Hello 👋 + Your personal ChatGPT bot has been successfully launched. + + Use the help command to find out about the possibilities 🚀 + -- -- -- -- -- + 🤘 Version: {0} + 👨‍💻 Developer: [Struchkov Mark](https://mark.struchkov.dev/) + 💊 Docs: https://docs.struchkov.dev/chatgpt-telegram-bot + """, + appProperty.getVersion() + )) + .keyBoard(InlineKeyBoard.inlineKeyBoard(SimpleButton.simpleButton("❤️ Support Develop", "support"))) + .payload(DISABLE_WEB_PAGE_PREVIEW, true) + .build(); + }) + .build(); + } + @Unit(value = UnitName.PROMPT, main = true) public AnswerText prompt() { return AnswerText.builder() @@ -221,6 +271,8 @@ public class PersonalChatGPTUnit implements PersonUnitConfiguration { ❤️ *Support Develop* Sponsorship makes a project sustainable because it pays for the time of the maintainers of that project, a very scarce resource that is spent on developing new features, fixing bugs, improving stability, solving problems, and general support. *The biggest bottleneck in Open Source is time.* + + Bank card (Russia): [https://www.tinkoff.ru/cf/4iU6NB3uzqx](https://www.tinkoff.ru/cf/4iU6NB3uzqx) TON: `struchkov-mark.ton` diff --git a/src/main/java/dev/struchkov/example/bot/unit/StartNotify.java b/src/main/java/dev/struchkov/example/bot/unit/StartNotify.java index e432945..8fc6111 100644 --- a/src/main/java/dev/struchkov/example/bot/unit/StartNotify.java +++ b/src/main/java/dev/struchkov/example/bot/unit/StartNotify.java @@ -3,21 +3,33 @@ package dev.struchkov.example.bot.unit; import dev.struchkov.example.bot.conf.AppProperty; import dev.struchkov.godfather.main.domain.keyboard.button.SimpleButton; import dev.struchkov.godfather.simple.domain.BoxAnswer; +import dev.struchkov.godfather.simple.domain.SentBox; import dev.struchkov.godfather.telegram.domain.keyboard.InlineKeyBoard; import dev.struchkov.godfather.telegram.simple.context.service.TelegramSending; +import dev.struchkov.godfather.telegram.simple.context.service.TelegramService; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import org.springframework.stereotype.Component; import java.text.MessageFormat; +import java.util.Optional; import static dev.struchkov.godfather.telegram.main.context.BoxAnswerPayload.DISABLE_WEB_PAGE_PREVIEW; +import static dev.struchkov.haiti.utils.Checker.checkNotBlank; +@Slf4j @Component @RequiredArgsConstructor public class StartNotify { + private final OkHttpClient client = new OkHttpClient(); + private final TelegramSending sending; + private final TelegramService telegramService; private final AppProperty appProperty; @PostConstruct @@ -31,7 +43,7 @@ public class StartNotify { Use the help command to find out about the possibilities 🚀 -- -- -- -- -- 🤘 Version: {0} - 👨‍💻 Developer: [https://mark.struchkov.dev/](Struchkov Mark) + 👨‍💻 Developer: [Struchkov Mark](https://mark.struchkov.dev/) 💊 Docs: https://docs.struchkov.dev/chatgpt-telegram-bot """, appProperty.getVersion() @@ -41,6 +53,36 @@ public class StartNotify { .build(); boxAnswer.setRecipientIfNull(appProperty.getTelegramId()); sending.send(boxAnswer); + sendNotice(); + } + + /** + * Используется для уведомления пользователя о выходе новой версии. + */ + private void sendNotice() { + final String requestUrl = "https://metrika.struchkov.dev/gitlab-notify/start-notice/chatgpt?version=" + appProperty.getVersion(); + final Request request = new Request.Builder().get().url(requestUrl).build(); + try { + final Response response = client.newCall(request).execute(); + if (response.code() == 200) { + final String noticeMessage = response.body().string(); + if (checkNotBlank(noticeMessage)) { + final BoxAnswer notice = BoxAnswer.builder() + .message(noticeMessage) + .recipientPersonId(appProperty.getTelegramId()) + .payload(DISABLE_WEB_PAGE_PREVIEW, true) + .build(); + final Optional optSentBox = sending.send(notice); + if (optSentBox.isPresent()) { + final SentBox sentBox = optSentBox.get(); + final String messageId = sentBox.getMessageId(); + telegramService.pinMessage(appProperty.getTelegramId(), messageId); + } + } + } + } catch (Exception e) { + log.warn(e.getMessage()); + } } } diff --git a/src/main/java/dev/struchkov/example/bot/util/Cmd.java b/src/main/java/dev/struchkov/example/bot/util/Cmd.java index df0594e..aee71ff 100644 --- a/src/main/java/dev/struchkov/example/bot/util/Cmd.java +++ b/src/main/java/dev/struchkov/example/bot/util/Cmd.java @@ -10,5 +10,6 @@ public class Cmd { public static final String GPT = "gpt"; public static final String HELP = "help"; public static final String SUPPORT_DEV = "support"; + public static final String START = "start"; } diff --git a/src/main/java/dev/struchkov/example/bot/util/UnitName.java b/src/main/java/dev/struchkov/example/bot/util/UnitName.java index 4d1e0e2..6199bc8 100644 --- a/src/main/java/dev/struchkov/example/bot/util/UnitName.java +++ b/src/main/java/dev/struchkov/example/bot/util/UnitName.java @@ -10,7 +10,8 @@ public class UnitName { public static final String HELP = "HELP"; public static final String PROMPT = "PROMPT"; public static final String ACCESS_ERROR = "ACCESS_ERROR"; - public static final String SUPPORT = "SUPPORT"; + public static final String START = "START"; + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e051fe0..e77c66d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,16 +1,18 @@ app: - telegram-id: ${TELEGRAM_ID} + telegram-id: ${TELEGRAM_PERSON_ID} version: 0.0.1-SNAPShOT telegram: bot: username: ${TELEGRAM_BOT_USERNAME} token: ${TELEGRAM_BOT_TOKEN} + autoresponder: + threads: ${AUTORESPONDER_THREADS:8} proxy: - enable: ${PROXY_ENABLE:false} - host: ${PROXY_HOST:} - port: ${PROXY_PORT:} - type: ${PROXY_TYPE:SOCKS5} - user: ${PROXY_USERNAME:} - password: ${PROXY_PASSWORD:} + enable: ${TELEGRAM_PROXY_ENABLE:false} + host: ${TELEGRAM_PROXY_HOST:} + port: ${TELEGRAM_PROXY_PORT:} + type: ${TELEGRAM_PROXY_TYPE:SOCKS5} + user: ${TELEGRAM_PROXY_USERNAME:} + password: ${TELEGRAM_PROXY_PASSWORD:} openai: token: ${CHAT_GPT_TOKEN} \ No newline at end of file