├── .clang-format ├── .dockerignore ├── .flooignore ├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README.md ├── extern.cc ├── image.jpg ├── include.hh ├── lib └── .gitkeep ├── main.cc └── token.eg.dat /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # We'll use defaults from the LLVM style, but with 4 columns indentation. 3 | BasedOnStyle: LLVM 4 | IndentWidth: 4 5 | --- 6 | Language: Cpp 7 | # Force pointers to the type for C++. 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Just the bare essentials 2 | ** 3 | !lib/** 4 | !CMakeLists.txt 5 | !main.cc 6 | -------------------------------------------------------------------------------- /.flooignore: -------------------------------------------------------------------------------- 1 | extern 2 | node_modules 3 | tmp 4 | vendor 5 | .idea/workspace.xml 6 | .idea/misc.xml 7 | cmake-build-debug/ 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [UndarkAido] 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: [push] 4 | 5 | env: 6 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 7 | BUILD_TYPE: Release 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] # windows-latest, , macOS-latest 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2.1.0 20 | with: 21 | submodules: recursive 22 | 23 | - name: Dependencies 24 | run: sudo apt install build-essential libssl-dev libboost-all-dev 25 | 26 | - name: Create Build Environment 27 | # Some projects don't allow in-source building, so create a separate build directory 28 | # We'll use this as our working directory for all subsequent commands 29 | run: cmake -E make_directory ${{runner.workspace}}/build 30 | 31 | - name: Configure CMake 32 | # Use a bash shell so we can use the same syntax for environment variable 33 | # access regardless of the host operating system 34 | shell: bash 35 | working-directory: ${{runner.workspace}}/build 36 | # Note the current convention is to use the -S and -B options here to specify source 37 | # and build directories, but this is only available with CMake 3.13 and higher. 38 | # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 39 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE 40 | 41 | - name: Build 42 | working-directory: ${{runner.workspace}}/build 43 | shell: bash 44 | # Execute the build. You can specify a specific target with "--target " 45 | run: cmake --build . --config $BUILD_TYPE 46 | 47 | - uses: actions/upload-artifact@v1 48 | with: 49 | name: echo_bot 50 | path: ${{runner.workspace}}/build/echo_bot 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build*/ 2 | .floo 3 | token.dat 4 | cmake-build-*/ 5 | scratch.txt 6 | 7 | # Prerequisites 8 | *.d 9 | 10 | # Compiled Object files 11 | *.slo 12 | *.lo 13 | *.o 14 | *.obj 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Compiled Dynamic libraries 21 | *.so 22 | *.dylib 23 | *.dll 24 | 25 | # Fortran module files 26 | *.mod 27 | *.smod 28 | 29 | # Compiled Static libraries 30 | *.lai 31 | *.la 32 | *.a 33 | *.lib 34 | 35 | # Executables 36 | *.exe 37 | *.out 38 | *.app 39 | 40 | # Created by https://www.gitignore.io/api/jetbrains+all 41 | 42 | ### JetBrains+all ### 43 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 44 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 45 | 46 | # User-specific stuff: 47 | .idea/**/workspace.xml 48 | .idea/**/tasks.xml 49 | .idea/dictionaries 50 | 51 | # Sensitive or high-churn files: 52 | .idea/**/dataSources/ 53 | .idea/**/dataSources.ids 54 | .idea/**/dataSources.xml 55 | .idea/**/dataSources.local.xml 56 | .idea/**/sqlDataSources.xml 57 | .idea/**/dynamic.xml 58 | .idea/**/uiDesigner.xml 59 | 60 | # Gradle: 61 | .idea/**/gradle.xml 62 | .idea/**/libraries 63 | 64 | # CMake 65 | cmake-build-debug/ 66 | 67 | # Mongo Explorer plugin: 68 | .idea/**/mongoSettings.xml 69 | 70 | ## File-based project format: 71 | *.iws 72 | 73 | ## Plugin-specific files: 74 | 75 | # IntelliJ 76 | /out/ 77 | 78 | # mpeltonen/sbt-idea plugin 79 | .idea_modules/ 80 | 81 | # JIRA plugin 82 | atlassian-ide-plugin.xml 83 | 84 | # Cursive Clojure plugin 85 | .idea/replstate.xml 86 | 87 | # Crashlytics plugin (for Android Studio and IntelliJ) 88 | com_crashlytics_export_strings.xml 89 | crashlytics.properties 90 | crashlytics-build.properties 91 | fabric.properties 92 | 93 | ### JetBrains+all Patch ### 94 | # Ignores the whole idea folder 95 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 96 | 97 | .idea/ 98 | 99 | # End of https://www.gitignore.io/api/jetbrains+all 100 | 101 | # Created by https://www.gitignore.io/api/visualstudiocode 102 | # Edit at https://www.gitignore.io/?templates=visualstudiocode 103 | 104 | ### VisualStudioCode ### 105 | .vscode/* 106 | !.vscode/settings.json 107 | !.vscode/tasks.json 108 | !.vscode/launch.json 109 | !.vscode/extensions.json 110 | 111 | ### VisualStudioCode Patch ### 112 | # Ignore all local history of files 113 | .history 114 | 115 | # End of https://www.gitignore.io/api/visualstudiocode -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/plugin-overload"] 2 | path = lib/plugin-overload 3 | url = https://github.com/DiscordPP/plugin-overload.git 4 | [submodule "lib/plugin-ratelimit"] 5 | path = lib/plugin-ratelimit 6 | url = https://github.com/DiscordPP/plugin-ratelimit.git 7 | [submodule "lib/plugin-responder"] 8 | path = lib/plugin-responder 9 | url = https://github.com/DiscordPP/plugin-responder.git 10 | [submodule "lib/websocket-simpleweb"] 11 | path = lib/websocket-simpleweb 12 | url = https://github.com/DiscordPP/websocket-simpleweb.git 13 | [submodule "lib/rest-simpleweb"] 14 | path = lib/rest-simpleweb 15 | url = https://github.com/DiscordPP/rest-simpleweb.git 16 | [submodule "lib/plugin-interactionhandler"] 17 | path = lib/plugin-interactionhandler 18 | url = https://github.com/DiscordPP/plugin-interactionhandler.git 19 | [submodule "lib/discordpp"] 20 | path = lib/discordpp 21 | url = https://github.com/DiscordPP/discordpp.git 22 | [submodule "lib/plugin-native"] 23 | path = lib/plugin-native 24 | url = https://github.com/DiscordPP/plugin-native.git 25 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(echo_bot) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | set(DISCORDPP_USE_BOOST OFF CACHE BOOL "Override option" FORCE) 7 | 8 | add_subdirectory(lib/discordpp) 9 | add_subdirectory(lib/rest-simpleweb) 10 | add_subdirectory(lib/websocket-simpleweb) 11 | add_subdirectory(lib/plugin-native) 12 | add_subdirectory(lib/plugin-overload) 13 | add_subdirectory(lib/plugin-responder) 14 | add_subdirectory(lib/plugin-interactionhandler) 15 | add_subdirectory(lib/plugin-ratelimit) 16 | 17 | CREATE_DISCORDPP_DEFINITIONS() 18 | CREATE_DISCORDPP_INCLUDE() 19 | 20 | set(SOURCE_FILES main.cc extern.cc) 21 | add_executable(${PROJECT_NAME} ${SOURCE_FILES}) 22 | target_precompile_headers(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include.hh) 23 | 24 | set(THREADS_PREFER_PTHREAD_FLAG ON) 25 | 26 | if(${DISCORDPP_USE_BOOST}) 27 | find_package(Boost 1.71.0 REQUIRED system date_time) 28 | endif() 29 | find_package(Threads REQUIRED) 30 | find_package(OpenSSL REQUIRED) 31 | 32 | if(WIN32) 33 | add_definitions(-D_WIN32_WINNT=0x0601) # Windows 7 34 | endif() 35 | 36 | if(MSVC) 37 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj /wd4250 /wd4244 /Zc:preprocessor") 38 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /bigobj") 39 | add_definitions(-D_CRT_SECURE_NO_WARNINGS) 40 | else() 41 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic") 42 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=address -fno-omit-frame-pointer") 43 | endif() 44 | 45 | TARGET_LINK_LIBRARIES( 46 | ${PROJECT_NAME} 47 | ${Boost_LIBRARIES} 48 | Threads::Threads 49 | ${OPENSSL_LIBRARIES} 50 | ${ALL_DISCORDPP_LIBRARIES} 51 | ) 52 | 53 | TARGET_INCLUDE_DIRECTORIES( 54 | ${PROJECT_NAME} PUBLIC 55 | ${DISCORDPP_INCLUDE_GENERATED} 56 | ) 57 | 58 | function(SYNC_FILE FILE) 59 | if (EXISTS ${CMAKE_SOURCE_DIR}/${FILE}) 60 | configure_file(${FILE} ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) 61 | elseif (EXISTS ${CMAKE_CURRENT_BINARY_DIR}/${FILE}) 62 | file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/${FILE}) 63 | endif () 64 | endfunction(SYNC_FILE) 65 | 66 | SYNC_FILE(token.dat) 67 | SYNC_FILE(image.jpg) 68 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use -e BOT_TOKEN="Bot exampletoken" as an environment variable when running 2 | FROM ubuntu:20.04 AS build 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | RUN apt update && apt install -y \ 5 | libboost-all-dev \ 6 | cmake \ 7 | build-essential \ 8 | libssl-dev \ 9 | && rm -rf /var/lib/apt/lists/* 10 | WORKDIR /echo_bot 11 | COPY . . 12 | RUN mkdir build && cd build && cmake .. && make 13 | 14 | FROM alpine:latest 15 | RUN apk --no-cache add libgcc libstdc++ libc6-compat 16 | WORKDIR /echo_bot 17 | COPY --from=build /echo_bot/build/echo_bot . 18 | CMD ["./echo_bot"] 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Aidan Edwards 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 | [![CMake](https://github.com/DiscordPP/echo-bot/workflows/CMake/badge.svg)](https://github.com/DiscordPP/echo-bot/actions?query=workflow%3ACMake) 2 | 3 | # Discord++ example bot, EchoEchoEcho 4 | 5 | ## Community & Support Some incredibly nice people here! 6 | [Discord++ Discord Server](https://discord.gg/4stCdkDHw6) 7 | 8 | ## Usage 9 | 10 | See [the Wiki](https://github.com/DiscordPP/discordpp/wiki) for setup and configuration 11 | 12 | ## Contributing 13 | * Echo is pretty simple and probably doesn't need much, but if you have a suggestion or spot an issue, make a pull request on Github! 14 | * Also, this repo needs better jokes. 15 | -------------------------------------------------------------------------------- /extern.cc: -------------------------------------------------------------------------------- 1 | #define ECHO_EXTERN 2 | #include "include.hh" 3 | 4 | template class MY_BOT_DEF; 5 | -------------------------------------------------------------------------------- /image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DiscordPP/echo-bot/254ea02aa6131fedd1d7bdc95b2f0fc19d7ebaac/image.jpg -------------------------------------------------------------------------------- /include.hh: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Aidan on 10/2/2020. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #define MY_BOT_DEF BOT_DEF(ALL_DISCORDPP_PLUGINS) 10 | 11 | #ifndef ECHO_EXTERN 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef ASIO_STANDALONE 18 | #include 19 | #else 20 | #include 21 | #endif 22 | 23 | // Put more non-plugin `include`s here 24 | 25 | #endif 26 | 27 | #include 28 | 29 | #ifndef ECHO_EXTERN 30 | 31 | extern template class MY_BOT_DEF; 32 | using DppBot = MY_BOT_DEF; 33 | 34 | #undef DPPBOTDEF 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /lib/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DiscordPP/echo-bot/254ea02aa6131fedd1d7bdc95b2f0fc19d7ebaac/lib/.gitkeep -------------------------------------------------------------------------------- /main.cc: -------------------------------------------------------------------------------- 1 | #include "include.hh" 2 | 3 | #ifndef ASIO_STANDALONE 4 | namespace asio = boost::asio; 5 | #endif 6 | using json = nlohmann::json; 7 | namespace dpp = discordpp; 8 | 9 | std::string getToken(); 10 | 11 | std::istream &safeGetline(std::istream &is, std::string &t); 12 | 13 | void filter(std::string &target, const std::string &pattern); 14 | 15 | int main() { 16 | dpp::log::filter = dpp::log::info; 17 | dpp::log::out = &std::cerr; 18 | 19 | std::cout << "Howdy, and thanks for trying out Discord++!\n" 20 | << "Feel free to drop into the official server at " 21 | "https://discord.gg/4stCdkDHw6 if you have any questions.\n\n" 22 | << std::flush; 23 | 24 | std::cout << "Starting bot...\n\n"; 25 | 26 | std::string token = getToken(); 27 | if (token.empty()) { 28 | std::cerr << "CRITICAL: " 29 | << "There is no valid way for Echo to obtain a token! Use " 30 | "one of the following ways:" 31 | << std::endl 32 | << "(1) Fill the BOT_TOKEN environment variable with the " 33 | "token (e.g. 'Bot 123456abcdef')." 34 | << std::endl 35 | << "(2) Copy the example `token.eg.dat` as `token.dat` and " 36 | "write your own token to it.\n"; 37 | exit(1); 38 | } 39 | 40 | // Create Bot object 41 | auto bot = std::make_shared(); 42 | 43 | // Don't complain about unhandled events 44 | bot->debugUnhandled = true; 45 | 46 | // Declare the intent to receive guild messages 47 | // You don't need `NONE` it's just to show you how to declare multiple 48 | bot->intents = dpp::intents::NONE | dpp::intents::GUILD_MESSAGES; 49 | 50 | /*/ 51 | * Create handler for the READY payload, this may be handled by the bot in 52 | the future. 53 | * The `self` object contains all information about the 'bot' user. 54 | /*/ 55 | dpp::User self; 56 | bot->handlers.insert( 57 | {"READY", [&self](dpp::ReadyEvent ready) { self = *ready.user; }}); 58 | 59 | bot->prefix = "~"; 60 | 61 | bot->respond("help", "Mention me and I'll echo your message back!"); 62 | 63 | bot->respond("about", [&bot](dpp::MessageCreateEvent msg) { 64 | std::ostringstream content; 65 | content << "Sure thing, " 66 | << *(msg.member->nick ? msg.author->username : msg.member->nick) 67 | << "!\n" 68 | << "I'm a simple bot meant to demonstrate the " 69 | "Discord++ library.\n" 70 | << "You can learn more about Discord++ at " 71 | "https://discord.gg/4stCdkDHw6"; 72 | bot->createMessage() 73 | ->channel_id(*msg.channel_id) 74 | ->content(content.str()) 75 | ->run(); 76 | }); 77 | 78 | bot->respond("lookatthis", [&bot](dpp::MessageCreateEvent msg) { 79 | std::ifstream ifs("image.jpg", std::ios::binary); 80 | if (!ifs) { 81 | std::cerr << "Couldn't load file 'image.jpg'!\n"; 82 | return; 83 | } 84 | ifs.seekg(0, std::ios::end); 85 | std::ifstream::pos_type fileSize = ifs.tellg(); 86 | ifs.seekg(0, std::ios::beg); 87 | auto file = std::make_shared(fileSize, '\0'); 88 | ifs.read(file->data(), fileSize); 89 | 90 | bot->createMessage() 91 | ->channel_id(*msg.channel_id) 92 | ->content("Look at this photograph") 93 | ->filename("image.jpg") 94 | ->filetype("image/jpg") 95 | ->file(file) 96 | ->run(); 97 | }); 98 | 99 | bot->respond("channelinfo", [&bot](dpp::MessageCreateEvent msg) { 100 | bot->getChannel() 101 | ->channel_id(*msg.channel_id) 102 | ->onRead([&bot, msg](bool error, json res) { 103 | bot->createMessage() 104 | ->channel_id(*msg.channel_id) 105 | ->content("```json\n" + res["body"].dump(4) + "\n```") 106 | ->run(); 107 | }) 108 | ->run(); 109 | }); 110 | 111 | bot->respond("registerslash", [&bot, &self](dpp::MessageCreateEvent msg) { 112 | if (*msg.author->id == 106615803402547200) { 113 | bot->createGuildApplicationCommand() 114 | ->application_id(*self.id) 115 | ->guild_id(*msg.guild_id) 116 | ->name("echo") 117 | ->description("Echoes what you say") 118 | ->options({dpp::ApplicationCommandOption( 119 | dpp::ApplicationCommandOptionType::STRING, 120 | std::string("message"), dpp::omitted, std::string("The message to echo"), 121 | dpp::omitted, true)}) 122 | ->command_type(dpp::ApplicationCommandType::CHAT_INPUT) 123 | ->onRead([](bool error, json res) { 124 | std::cout << res.dump(4) << std::endl; 125 | }) 126 | ->run(); 127 | } 128 | }); 129 | 130 | bot->interactionHandlers.insert( 131 | {881674285683470376, [&bot](dpp::Interaction msg) { 132 | bot->createResponse() 133 | ->interaction_id(*msg.id) 134 | ->interaction_token(*msg.token) 135 | ->interaction_type( 136 | dpp::InteractionCallbackType::CHANNEL_MESSAGE_WITH_SOURCE) 137 | ->data({{ 138 | "content", 139 | *std::get(*msg.data).options->at(0).value 140 | }}) 141 | ->run(); 142 | }}); 143 | 144 | // Create handler for the MESSAGE_CREATE payload, this receives all messages 145 | // sent that the bot can see. 146 | bot->handlers.insert( 147 | {"MESSAGE_CREATE", [&bot, &self](const dpp::MessageCreateEvent msg) { 148 | // Ignore messages from other bots 149 | if (msg.webhook_id || (msg.author->bot && *msg.author->bot)) { 150 | return; 151 | } 152 | 153 | // Scan through mentions in the message for self 154 | bool mentioned = false; 155 | for (const dpp::User &mention : *msg.mentions) { 156 | mentioned = mentioned || (*mention.id == *self.id); 157 | } 158 | if (mentioned) { 159 | // Identify and remove mentions of self from the message 160 | std::string content = *msg.content; 161 | unsigned int oldlength, length = content.length(); 162 | do { 163 | oldlength = length; 164 | content = std::regex_replace( 165 | content, 166 | std::regex(R"(<@!?)" + std::to_string(*self.id) + 167 | R"(> ?)"), 168 | ""); 169 | length = content.length(); 170 | } while (oldlength > length); 171 | 172 | // Get the target user's display name 173 | std::string name = *(msg.member->nick ? msg.member->nick 174 | : msg.author->username); 175 | 176 | std::cout << "Echoing " << name << '\n'; 177 | 178 | // Echo the created message 179 | bot->createMessage() 180 | ->channel_id(*msg.channel_id) 181 | ->content(content) 182 | ->run(); 183 | 184 | // Set status to Playing "with [author]" 185 | bot->send(3, 186 | {{"game", {{"name", "with " + name}, {"type", 0}}}, 187 | {"status", "online"}, 188 | {"afk", false}, 189 | {"since", "null"}}); 190 | } 191 | }}); 192 | 193 | // Create Asio context, this handles async stuff. 194 | auto aioc = std::make_shared(); 195 | 196 | // Set the bot up 197 | bot->initBot(9, token, aioc); 198 | 199 | // Run the bot! 200 | bot->run(); 201 | 202 | return 0; 203 | } 204 | 205 | std::string getToken() { 206 | std::string token; 207 | 208 | /* 209 | First attempt to read the token from the BOT_TOKEN 210 | environment variable. 211 | */ 212 | char const *env = std::getenv("BOT_TOKEN"); 213 | if (env != nullptr) { 214 | token = std::string(env); 215 | } else { 216 | /*/ 217 | * Read token from token file. 218 | * Tokens are required to communicate with Discord, and hardcoding 219 | tokens is a bad idea. 220 | * If your bot is open source, make sure it's ignore by git in your 221 | .gitignore file. 222 | /*/ 223 | std::ifstream tokenFile("token.dat"); 224 | if (!tokenFile) { 225 | return ""; 226 | } 227 | safeGetline(tokenFile, token); 228 | tokenFile.close(); 229 | } 230 | return token; 231 | } 232 | 233 | /*/ 234 | * Source: https://stackoverflow.com/a/6089413/1526048 235 | /*/ 236 | std::istream &safeGetline(std::istream &is, std::string &t) { 237 | t.clear(); 238 | 239 | // The characters in the stream are read one-by-one using a std::streambuf. 240 | // That is faster than reading them one-by-one using the std::istream. 241 | // Code that uses streambuf this way must be guarded by a sentry object. 242 | // The sentry object performs various tasks, 243 | // such as thread synchronization and updating the stream state. 244 | 245 | std::istream::sentry se(is, true); 246 | std::streambuf *sb = is.rdbuf(); 247 | 248 | for (;;) { 249 | int c = sb->sbumpc(); 250 | switch (c) { 251 | case '\n': 252 | return is; 253 | case '\r': 254 | if (sb->sgetc() == '\n') { 255 | sb->sbumpc(); 256 | } 257 | return is; 258 | case std::streambuf::traits_type::eof(): 259 | // Also handle the case when the last line has no line ending 260 | if (t.empty()) { 261 | is.setstate(std::ios::eofbit); 262 | } 263 | return is; 264 | default: 265 | t += (char)c; 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /token.eg.dat: -------------------------------------------------------------------------------- 1 | Bot En2Ga1S3sj8I49G2fbs1gurTAfnzGSWdU6rhQ9L6GS05qaWtsl3nxUXNV96 2 | --------------------------------------------------------------------------------