├── .cee-repo ├── .clang-format ├── .gitattributes ├── .github └── workflows │ ├── gh_pages.yml │ └── test_build.yml ├── .gitignore ├── .vscode └── c_cpp_properties.json ├── LICENSE ├── Makefile ├── README.md ├── cee-utils ├── README.md ├── cee-data-sizes.h ├── cee-utils.c ├── cee-utils.h ├── clock.h ├── debug.h ├── greatest.h ├── jsmn.h ├── json-actor-boxed.c ├── json-actor-boxed.h ├── json-actor.c ├── json-actor.h ├── json-string.c ├── json-struct.c ├── log.c ├── log.h ├── logconf.c ├── logconf.h ├── ntl.c ├── ntl.h └── uthash.h ├── common ├── common.c ├── common.h ├── third-party │ ├── curl-websocket-utils.c │ ├── curl-websocket.c │ ├── curl-websocket.h │ ├── heap-inl.h │ ├── queue.h │ ├── sha1.c │ ├── sha1.h │ ├── threadpool.c │ └── threadpool.h ├── user-agent.c ├── user-agent.h ├── websockets.c ├── websockets.h ├── work.c └── work.h ├── config.json ├── discord-adapter-api.c ├── discord-adapter-ratelimit.c ├── discord-adapter.c ├── discord-client.c ├── discord-gateway.c ├── discord-internal.h ├── discord-misc.c ├── discord-voice-connections.c ├── discord-voice-connections.h ├── discord.h ├── docs ├── BUILDING_A_BOT.md ├── BUILDING_WITH_WINDOWS.md ├── CODING_GUIDELINES.md ├── CONTRIBUTING.md ├── DISCORD_ROADMAP.md ├── INTERNALS.md ├── PROJECT_OUTLINE.md └── SAIPHC.md ├── examples ├── .gitignore ├── Makefile ├── bot-audit-log.c ├── bot-ban.c ├── bot-channel.c ├── bot-components.c ├── bot-delete-messages.c ├── bot-echo.c ├── bot-embed.c ├── bot-emoji.c ├── bot-fetch-messages.c ├── bot-github-create-fork.c ├── bot-github-get-gist.c ├── bot-github-gist-starred.c ├── bot-github-gist.c ├── bot-guild-template.c ├── bot-guild.c ├── bot-invite.c ├── bot-manual-dm.c ├── bot-pin.c ├── bot-ping-pong.c ├── bot-presence.c ├── bot-reaction.c ├── bot-shell.c ├── bot-slash-commands.c ├── bot-slash-commands2.c ├── bot-voice.c └── bot-webhook.c ├── github-adapter.c ├── github-client.c ├── github-internal.h ├── github.h ├── licenses ├── LICENSE └── third-party │ ├── LICENSE.curl-websockets │ ├── LICENSE.jsmn │ ├── LICENSE.json-string │ ├── LICENSE.threadpool │ └── LICENSE.utf8 ├── my_bot ├── .gitignore ├── Makefile ├── config.json └── myBot.c ├── reddit-adapter.c ├── reddit-client.c ├── reddit-internal.h ├── reddit.h ├── scripts ├── diffuse_all.sh └── get-cee-utils.sh ├── slack-client.c ├── slack-internal.h ├── slack-socketmode.c ├── slack-webapi.c ├── slack.h ├── specs-code ├── discord │ ├── application.c │ ├── application_commands.c │ ├── application_commands.params.c │ ├── audit_log.c │ ├── audit_log.params.c │ ├── channel.c │ ├── channel.params.c │ ├── emoji.c │ ├── emoji.params.c │ ├── gateway.c │ ├── guild.c │ ├── guild.params.c │ ├── guild_template.c │ ├── guild_template.params.c │ ├── interaction.c │ ├── interaction.params.c │ ├── invite.c │ ├── invite.params.c │ ├── message_components.c │ ├── one-specs.h │ ├── permissions.c │ ├── stage_instance.c │ ├── stage_instance.params.c │ ├── sticker.c │ ├── sticker.params.c │ ├── user.c │ ├── user.params.c │ ├── voice-connections.c │ ├── voice.c │ ├── webhook.c │ └── webhook.params.c ├── github │ ├── gist.c │ ├── gist.params.c │ ├── one-specs.h │ ├── repository.c │ └── user.c ├── reddit │ ├── links_n_comments.c │ ├── oauth2.c │ ├── one-specs.h │ └── search.c └── slack │ ├── chat.params.c │ ├── one-specs.h │ └── users.params.c ├── specs ├── .gitignore ├── Makefile ├── discord │ ├── application.json │ ├── application_commands.json │ ├── application_commands.params.json │ ├── audit_log.json │ ├── audit_log.params.json │ ├── channel.json │ ├── channel.params.json │ ├── emoji.json │ ├── emoji.params.json │ ├── gateway.json │ ├── guild.json │ ├── guild.params.json │ ├── guild_template.json │ ├── guild_template.params.json │ ├── interaction.json │ ├── interaction.params.json │ ├── invite.json │ ├── invite.params.json │ ├── message_components.json │ ├── permissions.json │ ├── stage_instance.json │ ├── stage_instance.params.json │ ├── sticker.json │ ├── sticker.params.json │ ├── user.json │ ├── user.params.json │ ├── voice-connections.json │ ├── voice.json │ ├── webhook.json │ └── webhook.params.json ├── github │ ├── gist.json │ ├── gist.params.json │ ├── repository.json │ └── user.json ├── reddit │ ├── links_n_comments.json │ ├── oauth2.json │ └── search.json ├── slack │ ├── chat.params.json │ └── users.params.json └── specs-gen.c └── test ├── .gitignore ├── Makefile ├── test-discord-api.c ├── test-discord-async.c ├── test-discord-ws.c ├── test-git2.c ├── test-slack-ws.c ├── test-user-agent.c └── test-websockets.c /.cee-repo: -------------------------------------------------------------------------------- 1 | { 2 | "owner": "cee-studio", 3 | "repo": "orca", 4 | "default_branch": "master" 5 | } 6 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # ORCA-FORMATTING 2 | # Requires clang-format 10 (at least) 3 | 4 | Language: Cpp 5 | # BasedOnStyle: Mozilla 6 | AccessModifierOffset: -2 7 | AlignAfterOpenBracket: Align 8 | AlignConsecutiveAssignments: false 9 | AlignConsecutiveDeclarations: false 10 | AlignEscapedNewlinesLeft: false 11 | AlignOperands: true 12 | AlignTrailingComments: false 13 | AllowAllParametersOfDeclarationOnNextLine: true 14 | AllowShortBlocksOnASingleLine: false 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: Inline 17 | AllowShortIfStatementsOnASingleLine: true 18 | AllowShortLoopsOnASingleLine: false 19 | AlwaysBreakAfterReturnType: TopLevelDefinitions 20 | # AlwaysBreakAfterReturnType: None # enable for 'main' files 21 | AlwaysBreakBeforeMultilineStrings: false 22 | AlwaysBreakTemplateDeclarations: true 23 | BinPackArguments: true 24 | BinPackParameters: false 25 | BraceWrapping: 26 | AfterClass: true 27 | AfterControlStatement: MultiLine 28 | AfterEnum: false 29 | AfterFunction: true 30 | AfterNamespace: false 31 | AfterObjCDeclaration: false 32 | AfterStruct: false 33 | AfterUnion: false 34 | BeforeCatch: false 35 | BeforeElse: true 36 | IndentBraces: false 37 | BreakBeforeBinaryOperators: NonAssignment 38 | BreakBeforeBraces: Custom 39 | BreakBeforeTernaryOperators: true 40 | BreakConstructorInitializersBeforeComma: true 41 | ColumnLimit: 79 42 | CommentPragmas: '^ IWYU pragma:' 43 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 44 | ConstructorInitializerIndentWidth: 2 45 | ContinuationIndentWidth: 2 46 | Cpp11BracedListStyle: false 47 | DerivePointerAlignment: false 48 | DisableFormat: false 49 | ExperimentalAutoDetectBinPacking: false 50 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 51 | IncludeCategories: 52 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 53 | Priority: 2 54 | - Regex: '^(<|"(gtest|isl|json)/)' 55 | Priority: 3 56 | - Regex: '.*' 57 | Priority: 1 58 | IndentCaseLabels: false 59 | IndentWidth: 2 60 | IndentWrappedFunctionNames: false 61 | KeepEmptyLinesAtTheStartOfBlocks: true 62 | MacroBlockBegin: '' 63 | MacroBlockEnd: '' 64 | MaxEmptyLinesToKeep: 1 65 | NamespaceIndentation: None 66 | ObjCBlockIndentWidth: 2 67 | ObjCSpaceAfterProperty: true 68 | ObjCSpaceBeforeProtocolList: false 69 | PenaltyBreakBeforeFirstCallParameter: 19 70 | PenaltyBreakComment: 300 71 | PenaltyBreakFirstLessLess: 120 72 | PenaltyBreakString: 1000 73 | PenaltyExcessCharacter: 1000000 74 | PenaltyReturnTypeOnItsOwnLine: 200 75 | PointerAlignment: Right 76 | AlignConsecutiveMacros: true 77 | ReflowComments: true 78 | SortIncludes: false 79 | SpaceAfterCStyleCast: false 80 | SpaceBeforeAssignmentOperators: true 81 | SpaceInEmptyParentheses: false 82 | SpacesBeforeTrailingComments: 1 83 | SpacesInAngles: false 84 | SpacesInContainerLiterals: true 85 | SpacesInCStyleCastParentheses: false 86 | SpacesInParentheses: false 87 | SpacesInSquareBrackets: false 88 | Standard: Cpp11 89 | TabWidth: 8 90 | UseTab: Never 91 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Handle line endings automatically for files detected as text 2 | # and leave all files detected as binary untouched. 3 | * text=auto 4 | 5 | # 6 | # The above will handle all files NOT found below 7 | *.sh eol=lf -------------------------------------------------------------------------------- /.github/workflows/gh_pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' # Auto-trigger workflow everyday at 00:00 6 | push: 7 | branches: [master] 8 | pull_request: 9 | 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | 16 | - name: Dependencies 17 | run: | 18 | echo "Preparing files for documentation" 19 | make docs 20 | echo "Install Sphinx" 21 | pip install sphinx 22 | pip install pydata_sphinx_theme 23 | echo "Install Breathe" 24 | pip install breathe 25 | 26 | - name: Doxygen Action 27 | uses: mattnotmitt/doxygen-action@v1.3.1 28 | 29 | - name: Move XML to Orca-Docs 30 | run: sudo mv docs/xml orca-docs/docs 31 | 32 | - name: Breathe Action 33 | run: make -C orca-docs/docs html 34 | 35 | - name: Deploy 36 | uses: peaceiris/actions-gh-pages@v3.7.3 37 | if: github.ref == 'refs/heads/master' 38 | with: 39 | github_token: ${{ secrets.GITHUB_TOKEN }} 40 | publish_branch: gh-pages 41 | publish_dir: ./orca-docs/docs/build/html 42 | -------------------------------------------------------------------------------- /.github/workflows/test_build.yml: -------------------------------------------------------------------------------- 1 | name: test build 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | 8 | jobs: 9 | test-build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | 14 | - name: Dependencies 15 | run: | 16 | echo "Installing build-essential and wget" 17 | sudo apt-get install -y build-essential wget 18 | echo "Installing libcurl and libssl" 19 | sudo apt-get install -y libcurl4-openssl-dev libssl-dev 20 | 21 | - name: Run Makefile 22 | run: | 23 | echo "Cleanup" 24 | make purge 25 | echo "Building" 26 | make all 27 | echo "Building example bots" 28 | make examples 29 | echo "Building test files" 30 | make test 31 | 32 | - name: Run Makefile with parallelism 33 | run: | 34 | echo "Cleanup" 35 | make clean 36 | echo "Building with parallelism" 37 | make examples -j$(nproc) 38 | make test -j$(nproc) 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stensal* 2 | *.tar 3 | *.exe 4 | *.out 5 | .ccls-cache 6 | *.o 7 | *~ 8 | .idea 9 | cmake-build-debug 10 | *dev.json 11 | obj 12 | lib 13 | dump.* 14 | .cee-contributor 15 | mujs/build 16 | bots-2 17 | *dump.json 18 | add-ons 19 | *.log 20 | orca-docs 21 | Doxyfile 22 | botx 23 | *.db 24 | *.swp 25 | my_bot/mybot_config.json 26 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | ".", 7 | "common", 8 | "mujs", 9 | "sqlite3", 10 | "${workspaceFolder}/**" 11 | ], 12 | "defines": [], 13 | "compilerPath": "/usr/bin/gcc", 14 | "cStandard": "c99", 15 | "cppStandard": "c++03" 16 | } 17 | ], 18 | "version": 4 19 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 cee.studio 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 | -------------------------------------------------------------------------------- /cee-utils/README.md: -------------------------------------------------------------------------------- 1 | # cee-utils 2 | 3 | The code are supposed to be dowloaded to our source tree to be used. 4 | 5 | ``` 6 | wget https://raw.githubusercontent.com/cee-studio/cee-utils/master/scripts/get-cee-utils.sh 7 | ./get-cee-utils.sh 8 | ``` 9 | 10 | 11 | many GPL codec 12 | https://github.com/cantora/avr-crypto-lib 13 | 14 | 15 | single file C unit test 16 | https://github.com/silentbicycle/greatest 17 | -------------------------------------------------------------------------------- /cee-utils/cee-data-sizes.h: -------------------------------------------------------------------------------- 1 | #ifndef CEE_DATA_SIZES_H 2 | #define CEE_DATA_SIZES_H 3 | 4 | /* common known data sizes */ 5 | 6 | #define CEE_SHA1_DATA_LEN 40 7 | #define CEE_SHA1_STR_LEN 40 + 1 8 | #define CEE_MAX_IP_ADDR_STR_LEN 46 9 | 10 | #endif /* CEE_DATA_SIZES_H */ 11 | -------------------------------------------------------------------------------- /cee-utils/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef DEBUG_H 2 | #define DEBUG_H 3 | 4 | #ifdef __saiph__ /* for error tracing purposes */ 5 | # define ABORT() \ 6 | { \ 7 | static char *p = "abort"; \ 8 | *p = 0; /* force segfault with a backtrace */ \ 9 | } 10 | #else 11 | # define ABORT() abort() 12 | #endif 13 | 14 | /* Encode a string with ANSI color */ 15 | #ifdef LOG_USE_COLOR 16 | # define ANSICOLOR(str, color) "\x1b[" color "m" str "\x1b[0m" 17 | #else 18 | # define ANSICOLOR(str, color) str 19 | #endif 20 | 21 | #define ANSI_FG_BLACK "30" 22 | #define ANSI_FG_RED "31" 23 | #define ANSI_FG_GREEN "32" 24 | #define ANSI_FG_YELLOW "33" 25 | #define ANSI_FG_BLUE "34" 26 | #define ANSI_FG_MAGENTA "35" 27 | #define ANSI_FG_CYAN "36" 28 | #define ANSI_FG_WHITE "37" 29 | #define ANSI_FG_GRAY "90" 30 | #define ANSI_FG_BRIGHT_RED "91" 31 | #define ANSI_FG_BRIGHT_GREEN "92" 32 | #define ANSI_FG_BRIGHT_YELLOW "93" 33 | #define ANSI_FG_BRIGHT_BLUE "94" 34 | #define ANSI_FG_BRIGHT_MAGENTA "95" 35 | #define ANSI_FG_BRIGHT_CYAN "96" 36 | #define ANSI_FG_BRIGHT_WHITE "97" 37 | 38 | #define ANSI_BG_BLACK "40" 39 | #define ANSI_BG_RED "41" 40 | #define ANSI_BG_GREEN "42" 41 | #define ANSI_BG_YELLOW "43" 42 | #define ANSI_BG_BLUE "44" 43 | #define ANSI_BG_MAGENTA "45" 44 | #define ANSI_BG_CYAN "46" 45 | #define ANSI_BG_WHITE "47" 46 | #define ANSI_BG_GRAY "100" 47 | #define ANSI_BG_BRIGHT_RED "101" 48 | #define ANSI_BG_BRIGHT_GREEN "102" 49 | #define ANSI_BG_BRIGHT_YELLOW "103" 50 | #define ANSI_BG_BRIGHT_BLUE "104" 51 | #define ANSI_BG_BRIGHT_MAGENTA "105" 52 | #define ANSI_BG_BRIGHT_CYAN "106" 53 | #define ANSI_BG_BRIGHT_WHITE "107" 54 | 55 | #ifndef D_OUT 56 | # define D_OUT stderr 57 | #endif 58 | 59 | #if __STDC_VERSION__ >= 199901L 60 | # define D_FMT_PREFIX "[%s:%d] %s()\n\t" 61 | # define D_FMT_ARGS __FILE__, __LINE__, __func__ 62 | 63 | # define __PRINT(fmt, ...) \ 64 | fprintf(D_OUT, D_FMT_PREFIX fmt "\n%s", D_FMT_ARGS, __VA_ARGS__) 65 | # define PRINT(...) __PRINT(__VA_ARGS__, "") 66 | 67 | # ifdef LOG_H 68 | # define __ERR(fmt, ...) log_fatal(fmt "%s", __VA_ARGS__) 69 | # else 70 | # define __ERR(fmt, ...) __PRINT(fmt, __VA_ARGS__) 71 | # endif 72 | 73 | # define ERR(...) \ 74 | do { \ 75 | __ERR(__VA_ARGS__, ""); \ 76 | ABORT(); \ 77 | } while (0) 78 | 79 | /* THIS WILL ONLY WORK IF __VA_ARGS__ IS SET */ 80 | # define VASSERT_S(expr, fmt, ...) \ 81 | do { \ 82 | if (!(expr)) { \ 83 | ERR(ANSICOLOR("\n\tAssert Failed", ANSI_FG_RED)":\t"fmt"\n\t" \ 84 | ANSICOLOR("Expected", ANSI_FG_RED)":\t %s", __VA_ARGS__, #expr); \ 85 | } \ 86 | } while (0) 87 | 88 | #else 89 | # define D_FMT_PREFIX "[%s:%d]\n\t" 90 | # define D_FMT_ARGS __FILE__, __LINE__ 91 | 92 | static int PRINT(const char *format, ...) 93 | { 94 | va_list ap; 95 | int ret; 96 | 97 | fprintf(D_OUT, D_FMT_PREFIX, D_FMT_ARGS); 98 | 99 | va_start(ap, format); 100 | ret = vfprintf(D_OUT, format, ap); 101 | va_end(ap); 102 | 103 | return ret; 104 | } 105 | 106 | static void ERR(const char *format, ...) 107 | { 108 | va_list ap; 109 | 110 | fprintf(D_OUT, D_FMT_PREFIX, D_FMT_ARGS); 111 | 112 | va_start(ap, format); 113 | vfprintf(D_OUT, format, ap); 114 | va_end(ap); 115 | 116 | ABORT(); 117 | } 118 | 119 | #endif 120 | 121 | #define PUTS(msg) fprintf(D_OUT, D_FMT_PREFIX "%s\n", D_FMT_ARGS, msg) 122 | 123 | #define ASSERT_S(expr, msg) \ 124 | do { \ 125 | if (!(expr)) { \ 126 | ERR(ANSICOLOR("\n\tAssert Failed", ANSI_FG_RED)":\t%s\n\t" \ 127 | ANSICOLOR("Expected", ANSI_FG_RED)":\t"msg, #expr); \ 128 | } \ 129 | } while (0) 130 | 131 | #endif /* DEBUG_H */ 132 | -------------------------------------------------------------------------------- /cee-utils/json-actor-boxed.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "ntl.h" 7 | #include "json-actor-boxed.h" 8 | #include "json-actor.h" 9 | 10 | void 11 | ja_str_from_json(char *json, size_t len, ja_str *ja) 12 | { 13 | ja->value = malloc(len + 1); 14 | memcpy(ja->value, json, len); 15 | ja->value[len] = 0; 16 | } 17 | 18 | void 19 | ja_str_cleanup(ja_str *p) 20 | { 21 | if (p->value) free(p->value); 22 | } 23 | 24 | void 25 | ja_str_cleanup_v(void *p) 26 | { 27 | ja_str_cleanup((ja_str *)p); 28 | } 29 | 30 | void 31 | ja_str_init(ja_str *p) 32 | { 33 | memset(p, 0, sizeof(*p)); 34 | } 35 | void 36 | ja_str_init_v(void *p) 37 | { 38 | ja_str_init((ja_str *)p); 39 | } 40 | 41 | void 42 | ja_str_from_json_v(char *json, size_t len, void *p) 43 | { 44 | ja_str_from_json(json, len, (ja_str *)p); 45 | } 46 | 47 | size_t 48 | ja_str_to_json(char *json, size_t len, ja_str *p) 49 | { 50 | return snprintf(json, len, "\"%s\"", p->value); 51 | } 52 | size_t 53 | ja_str_to_json_v(char *json, size_t len, void *p) 54 | { 55 | return ja_str_to_json(json, len, (ja_str *)p); 56 | } 57 | 58 | size_t 59 | ja_str_to_query(char *json, size_t len, ja_str *p) 60 | { 61 | return snprintf(json, len, "\"%s\"", p->value); 62 | } 63 | 64 | size_t 65 | ja_str_to_query_v(char *json, size_t len, void *p) 66 | { 67 | return ja_str_to_query(json, len, (ja_str *)p); 68 | } 69 | 70 | void 71 | ja_str_list_free(ja_str **p) 72 | { 73 | ntl_free((ntl_t)p, &ja_str_cleanup_v); 74 | } 75 | 76 | void 77 | ja_str_list_free_v(void **p) 78 | { 79 | ja_str_list_free((ja_str **)p); 80 | } 81 | 82 | void 83 | ja_str_list_from_json(char *str, size_t len, ja_str ***p) 84 | { 85 | struct ntl_deserializer d; 86 | 87 | memset(&d, 0, sizeof(d)); 88 | d.elem_size = sizeof(ja_str); 89 | d.init_elem = ja_str_init_v; 90 | d.elem_from_buf = ja_str_from_json_v; 91 | d.ntl_recipient_p = (void ***)p; 92 | extract_ntl_from_json(str, len, &d); 93 | } 94 | 95 | void 96 | ja_str_list_from_json_v(char *str, size_t len, void *p) 97 | { 98 | ja_str_list_from_json(str, len, (ja_str ***)p); 99 | } 100 | 101 | size_t 102 | ja_str_list_to_json(char *str, size_t len, ja_str **p) 103 | { 104 | return ntl_to_buf(str, len, (void **)p, NULL, ja_str_to_json_v); 105 | } 106 | 107 | size_t 108 | ja_str_list_to_json_v(char *str, size_t len, void *p) 109 | { 110 | return ja_str_list_to_json(str, len, (ja_str **)p); 111 | } 112 | 113 | void 114 | ja_str_use_default_inject_settings(ja_str *p) 115 | { 116 | (void)p; 117 | } 118 | 119 | void 120 | ja_u64_from_json(char *json, size_t len, ja_u64 *ja) 121 | { 122 | (void)len; 123 | ja->value = strtoull(json, NULL, 10); 124 | } 125 | 126 | size_t 127 | ja_u64_to_json(char *json, size_t len, ja_u64 *ja) 128 | { 129 | int ret = snprintf(json, len, "\"%" PRIu64 "\"", ja->value); 130 | return (size_t)ret; 131 | } 132 | 133 | size_t 134 | ja_u64_to_query(char *json, size_t len, ja_u64 *p) 135 | { 136 | return snprintf(json, len, "\"%" PRIu64 "\"", p->value); 137 | } 138 | 139 | void 140 | ja_u64_init(ja_u64 *p) 141 | { 142 | p->value = 0; 143 | } 144 | 145 | void 146 | ja_u64_cleanup(ja_u64 *p) 147 | { 148 | (void)p; 149 | } 150 | 151 | void 152 | ja_u64_cleanup_v(void *p) 153 | { 154 | (void)p; 155 | } 156 | 157 | void 158 | ja_u64_init_v(void *p) 159 | { 160 | ja_u64_init((ja_u64 *)p); 161 | } 162 | 163 | void 164 | ja_u64_from_json_v(char *json, size_t len, void *p) 165 | { 166 | ja_u64_from_json(json, len, (ja_u64 *)p); 167 | } 168 | 169 | size_t 170 | ja_u64_to_json_v(char *json, size_t len, void *p) 171 | { 172 | return ja_u64_to_json(json, len, (ja_u64 *)p); 173 | } 174 | 175 | size_t 176 | ja_u64_to_query_v(char *json, size_t len, void *p) 177 | { 178 | return ja_u64_to_query(json, len, (ja_u64 *)p); 179 | } 180 | 181 | void 182 | ja_u64_list_free(ja_u64 **p) 183 | { 184 | ntl_free((ntl_t)p, NULL); 185 | } 186 | 187 | void 188 | ja_u64_list_free_v(void **p) 189 | { 190 | ja_u64_list_free((ja_u64 **)p); 191 | } 192 | 193 | void 194 | ja_u64_list_from_json(char *str, size_t len, ja_u64 ***p) 195 | { 196 | struct ntl_deserializer d; 197 | 198 | memset(&d, 0, sizeof(d)); 199 | d.elem_size = sizeof(ja_u64); 200 | d.init_elem = ja_u64_init_v; 201 | d.elem_from_buf = ja_u64_from_json_v; 202 | d.ntl_recipient_p = (void ***)p; 203 | extract_ntl_from_json(str, len, &d); 204 | } 205 | 206 | void 207 | ja_u64_list_append(NTL_T(ja_u64) * ntl_p, uint64_t *u64_p) 208 | { 209 | ntl_append2((ntl_t *)ntl_p, sizeof(ja_u64), u64_p); 210 | } 211 | 212 | void 213 | ja_u64_list_from_json_v(char *str, size_t len, void *p) 214 | { 215 | ja_u64_list_from_json(str, len, (ja_u64 ***)p); 216 | } 217 | 218 | size_t 219 | ja_u64_list_to_json(char *str, size_t len, ja_u64 **p) 220 | { 221 | return ntl_to_buf(str, len, (void **)p, NULL, ja_u64_to_json_v); 222 | } 223 | 224 | size_t 225 | ja_u64_list_to_json_v(char *str, size_t len, void *p) 226 | { 227 | return ja_u64_list_to_json(str, len, (ja_u64 **)p); 228 | } 229 | 230 | void 231 | ja_u64_use_default_inject_settings(ja_u64 *p) 232 | { 233 | (void)p; 234 | } 235 | -------------------------------------------------------------------------------- /cee-utils/json-actor-boxed.h: -------------------------------------------------------------------------------- 1 | #ifndef JSON_ACTOR_BOXED_H 2 | #define JSON_ACTOR_BOXED_H 3 | #include 4 | #include 5 | #include "ntl.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif /* __cplusplus */ 10 | 11 | typedef struct ja_str { 12 | char *value; 13 | } ja_str; /* json-actor's boxed built-in type */ 14 | 15 | typedef struct ja_u64 { 16 | uint64_t value; 17 | } ja_u64; /* json-actor's boxed built-in type */ 18 | 19 | void ja_str_cleanup_v(void *p); 20 | void ja_str_cleanup(struct ja_str *p); 21 | void ja_str_init_v(void *p); 22 | void ja_str_init(struct ja_str *p); 23 | void ja_str_from_json_v(char *json, size_t len, void *p); 24 | void ja_str_from_json(char *json, size_t len, struct ja_str *p); 25 | size_t ja_str_to_json_v(char *json, size_t len, void *p); 26 | size_t ja_str_to_json(char *json, size_t len, struct ja_str *p); 27 | size_t ja_str_to_query_v(char *json, size_t len, void *p); 28 | size_t ja_str_to_query(char *json, size_t len, struct ja_str *p); 29 | void ja_str_list_free_v(void **p); 30 | void ja_str_list_free(struct ja_str **p); 31 | void ja_str_list_from_json_v(char *str, size_t len, void *p); 32 | void ja_str_list_from_json(char *str, size_t len, struct ja_str ***p); 33 | size_t ja_str_list_to_json_v(char *str, size_t len, void *p); 34 | size_t ja_str_list_to_json(char *str, size_t len, struct ja_str **p); 35 | void ja_str_use_default_inject_settings(struct ja_str *p); 36 | 37 | void ja_u64_cleanup_v(void *p); 38 | void ja_u64_cleanup(struct ja_u64 *p); 39 | void ja_u64_init_v(void *p); 40 | void ja_u64_init(struct ja_u64 *p); 41 | void ja_u64_from_json_v(char *json, size_t len, void *p); 42 | void ja_u64_from_json(char *json, size_t len, struct ja_u64 *p); 43 | size_t ja_u64_to_json_v(char *json, size_t len, void *p); 44 | size_t ja_u64_to_json(char *json, size_t len, struct ja_u64 *p); 45 | size_t ja_u64_to_query_v(char *json, size_t len, void *p); 46 | size_t ja_u64_to_query(char *json, size_t len, struct ja_u64 *p); 47 | void ja_u64_list_free_v(void **p); 48 | void ja_u64_list_free(struct ja_u64 **p); 49 | void ja_u64_list_append(NTL_T(ja_u64) * ntl_p, uint64_t *new_p); 50 | void ja_u64_list_from_json_v(char *str, size_t len, void *p); 51 | void ja_u64_list_from_json(char *str, size_t len, struct ja_u64 ***p); 52 | size_t ja_u64_list_to_json_v(char *str, size_t len, void *p); 53 | size_t ja_u64_list_to_json(char *str, size_t len, struct ja_u64 **p); 54 | void ja_u64_use_default_inject_settings(struct ja_u64 *p); 55 | 56 | #ifdef __cplusplus 57 | } 58 | #endif /* __cplusplus */ 59 | 60 | #endif /*JSON_ACTOR_BOXED_H */ 61 | -------------------------------------------------------------------------------- /cee-utils/log.c: -------------------------------------------------------------------------------- 1 | /* Edited by Lucas Müller https://github.com/lcsmuller */ 2 | /* 3 | * Copyright (c) 2020 rxi 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 7 | * deal in the Software without restriction, including without limitation the 8 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | * sell 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 13 | * all 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 20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | * IN THE SOFTWARE. 22 | */ 23 | 24 | #include "log.h" 25 | #include 26 | 27 | log_Logger L; 28 | 29 | const char *level_strings[] = { 30 | "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" 31 | }; 32 | const char *level_colors[] = { 33 | "\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m" 34 | }; 35 | 36 | 37 | static void stdout_callback(log_Event *ev) { 38 | char buf[16]; 39 | buf[strftime(buf, sizeof(buf), "%H:%M:%S", ev->time)] = '\0'; 40 | #ifdef LOG_USE_COLOR 41 | fprintf( 42 | ev->udata, "%s|\x1b[90m%010u\x1b[0m %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ", 43 | buf, (unsigned)pthread_self(), level_colors[ev->level], level_strings[ev->level], 44 | ev->file, ev->line); 45 | #else 46 | fprintf( 47 | ev->udata, "%s|%010u %-5s %s:%d: ", 48 | buf, (unsigned)pthread_self(), level_strings[ev->level], ev->file, ev->line); 49 | #endif 50 | vfprintf(ev->udata, ev->fmt, ev->ap); 51 | fprintf(ev->udata, "\n"); 52 | fflush(ev->udata); 53 | } 54 | 55 | 56 | static void file_callback(log_Event *ev) { 57 | char buf[64]; 58 | buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0'; 59 | fprintf( 60 | ev->udata, "%s|%010u %-5s %s:%d: ", 61 | buf, (unsigned)pthread_self(), level_strings[ev->level], ev->file, ev->line); 62 | vfprintf(ev->udata, ev->fmt, ev->ap); 63 | fprintf(ev->udata, "\n"); 64 | fflush(ev->udata); 65 | } 66 | 67 | 68 | static void lock(log_Logger *L) { 69 | if (L->lock) { L->lock(true, L->udata); } 70 | } 71 | 72 | 73 | static void unlock(log_Logger *L) { 74 | if (L->lock) { L->lock(false, L->udata); } 75 | } 76 | 77 | 78 | const char* log_level_string(int level) { 79 | return level_strings[level]; 80 | } 81 | 82 | 83 | void _log_set_lock(log_Logger *L, log_LockFn fn, void *udata) { 84 | L->lock = fn; 85 | L->udata = udata; 86 | } 87 | 88 | 89 | void _log_set_level(log_Logger *L, int level) { 90 | L->level = level; 91 | } 92 | 93 | 94 | void _log_set_quiet(log_Logger *L, bool enable) { 95 | L->quiet = enable; 96 | } 97 | 98 | 99 | int _log_add_callback(log_Logger *L, log_LogFn fn, void *udata, int level) { 100 | int i; 101 | 102 | for (i = 0; i < LOG_MAX_CALLBACKS; i++) { 103 | if (!L->callbacks[i].fn) { 104 | L->callbacks[i] = (log_Callback) { fn, udata, level }; 105 | return 0; 106 | } 107 | } 108 | return -1; 109 | } 110 | 111 | 112 | int _log_add_fp(log_Logger *L, FILE *fp, int level) { 113 | return _log_add_callback(L, file_callback, fp, level); 114 | } 115 | 116 | 117 | static void init_event(log_Event *ev, void *udata) { 118 | if (!ev->time) { 119 | time_t t = time(NULL); 120 | ev->time = localtime(&t); 121 | } 122 | ev->udata = udata; 123 | } 124 | 125 | 126 | void _log_log(log_Logger *L, int level, const char *file, int line, const char *fmt, ...) { 127 | int i; 128 | log_Event ev = { 129 | .fmt = fmt, 130 | .file = file, 131 | .line = line, 132 | .level = level, 133 | }; 134 | 135 | lock(L); 136 | 137 | if (!L->quiet && level >= L->level) { 138 | init_event(&ev, stderr); 139 | va_start(ev.ap, fmt); 140 | stdout_callback(&ev); 141 | va_end(ev.ap); 142 | } 143 | 144 | for (i = 0; i < LOG_MAX_CALLBACKS && L->callbacks[i].fn; i++) { 145 | log_Callback *cb = &L->callbacks[i]; 146 | if (level >= cb->level) { 147 | init_event(&ev, cb->udata); 148 | va_start(ev.ap, fmt); 149 | cb->fn(&ev); 150 | va_end(ev.ap); 151 | } 152 | } 153 | 154 | unlock(L); 155 | } 156 | -------------------------------------------------------------------------------- /cee-utils/log.h: -------------------------------------------------------------------------------- 1 | /* Edited by Lucas Müller https://github.com/lcsmuller */ 2 | /** 3 | * Copyright (c) 2020 rxi 4 | * 5 | * This library is free software; you can redistribute it and/or modify it 6 | * under the terms of the MIT license. See `log.c` for details. 7 | */ 8 | 9 | #ifndef LOG_H 10 | #define LOG_H 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define LOG_VERSION "x.x.x modified" 18 | 19 | typedef struct { 20 | va_list ap; 21 | const char *fmt; 22 | const char *file; 23 | struct tm *time; 24 | void *udata; 25 | int line; 26 | int level; 27 | } log_Event; 28 | 29 | typedef void (*log_LogFn)(log_Event *ev); 30 | typedef void (*log_LockFn)(bool lock, void *udata); 31 | 32 | #define LOG_MAX_CALLBACKS 32 33 | 34 | typedef struct { 35 | log_LogFn fn; 36 | void *udata; 37 | int level; 38 | } log_Callback; 39 | 40 | typedef struct { 41 | void *udata; 42 | log_LockFn lock; 43 | int level; 44 | bool quiet; 45 | log_Callback callbacks[LOG_MAX_CALLBACKS]; 46 | } log_Logger; 47 | 48 | typedef enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL } log_Level; 49 | 50 | #define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__) 51 | #define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__) 52 | #define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__) 53 | #define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__) 54 | #define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__) 55 | #define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__) 56 | 57 | const char* log_level_string(int level); 58 | #define log_set_lock(fn, udata) _log_set_lock(&L, fn, udata); 59 | #define log_set_level(level) _log_set_level(&L, level); 60 | #define log_set_quiet(enable) _log_set_quiet(&L, enable) 61 | #define log_add_callback(fn, udata, level) _log_add_callback(&L, fn, udata, level) 62 | #define log_add_fp(fn, level) _log_add_fp(&L, fn, level) 63 | #define log_log(level, file, line, ...) _log_log(&L, level, file, line, __VA_ARGS__) 64 | 65 | void _log_set_lock(log_Logger *L, log_LockFn fn, void *udata); 66 | void _log_set_level(log_Logger *L, int level); 67 | void _log_set_quiet(log_Logger *L, bool enable); 68 | int _log_add_callback(log_Logger *L, log_LogFn fn, void *udata, int level); 69 | int _log_add_fp(log_Logger *L, FILE *fp, int level); 70 | void _log_log(log_Logger *L, int level, const char *file, int line, const char *fmt, ...); 71 | 72 | extern const char *level_strings[]; 73 | extern const char *level_colors[]; 74 | extern log_Logger L; 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /common/common.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "common.h" 5 | #include "work.h" 6 | #include "log.h" 7 | 8 | static _Bool once; 9 | 10 | const char * 11 | orca_strerror(ORCAcode code) 12 | { 13 | switch (code) { 14 | case ORCA_OK: 15 | return "Success: The request was a success"; 16 | case ORCA_HTTP_CODE: 17 | return "Failure: The request was a failure"; 18 | case ORCA_UNUSUAL_HTTP_CODE: 19 | return "Failure: The request was a failure"; 20 | case ORCA_BAD_PARAMETER: 21 | return "Failure: Bad value for parameter"; 22 | case ORCA_BAD_JSON: 23 | return "Failure: Internal failure when encoding or decoding JSON"; 24 | case ORCA_CURLE_INTERNAL: 25 | case ORCA_CURLM_INTERNAL: 26 | return "Failure: Libcurl's internal error"; 27 | default: 28 | return "Unknown: Code received doesn't match any description"; 29 | } 30 | } 31 | 32 | ORCAcode 33 | orca_global_init() 34 | { 35 | if (once) return ORCA_GLOBAL_INIT; 36 | 37 | if (0 != curl_global_init(CURL_GLOBAL_DEFAULT)) { 38 | log_warn("Couldn't start libcurl's globals"); 39 | return ORCA_GLOBAL_INIT; 40 | } 41 | if (work_global_init()) { 42 | log_warn("Attempt duplicate global initialization"); 43 | return ORCA_GLOBAL_INIT; 44 | } 45 | 46 | once = 1; 47 | 48 | return ORCA_OK; 49 | } 50 | 51 | void 52 | orca_global_cleanup() 53 | { 54 | curl_global_cleanup(); 55 | work_global_cleanup(); 56 | once = 0; 57 | } 58 | -------------------------------------------------------------------------------- /common/common.h: -------------------------------------------------------------------------------- 1 | /** @file common.h */ 2 | 3 | #ifndef COMMON_H 4 | #define COMMON_H 5 | 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif /* __cplusplus */ 11 | 12 | /** @defgroup OrcaTypes 13 | * @brief Commonly used datatypes across various APIs 14 | * 15 | * @note these datatypes are typedefs of C primitives, 16 | * its purpose is to facilitate identification 17 | * and "intent of use". 18 | * @{ */ 19 | 20 | /** 21 | * @brief Unix time in milliseconds 22 | */ 23 | typedef uint64_t u64_unix_ms_t; 24 | /** 25 | * @brief Snowflake datatype 26 | * 27 | * Used in APIs such as Twitter and Discord for their unique IDs 28 | */ 29 | typedef uint64_t u64_snowflake_t; 30 | 31 | /** 32 | * @brief Bitmask primitive 33 | * 34 | * Used for fields that may store values of, or perform bitwise operations 35 | */ 36 | typedef uint64_t u64_bitmask_t; 37 | 38 | /** 39 | * @brief Raw JSON string 40 | * 41 | * Used for fields that have dynamic or unreliable types. A string made out of 42 | * `json_char_t` should be used to keep a raw JSON, which can then be 43 | * parsed with the assistance of a JSON library. 44 | */ 45 | typedef char json_char_t; 46 | 47 | /** @} OrcaTypes */ 48 | 49 | /** @defgroup OrcaCodes 50 | * @brief Orca error values 51 | * @{ */ 52 | /** the error code datatype */ 53 | typedef int ORCAcode; 54 | /** request was a success */ 55 | #define ORCA_OK 0 56 | /** request wasn't succesful */ 57 | #define ORCA_HTTP_CODE -1 58 | /** no response came through from curl */ 59 | #define ORCA_CURL_NO_RESPONSE -2 60 | /** received a non-standard http code */ 61 | #define ORCA_UNUSUAL_HTTP_CODE -3 62 | /** bad value for parameter */ 63 | #define ORCA_BAD_PARAMETER -4 64 | /** internal failure when encoding or decoding JSON */ 65 | #define ORCA_BAD_JSON -5 66 | /** curl's easy handle internal error */ 67 | #define ORCA_CURLE_INTERNAL -6 68 | /** curl's multi handle internal error */ 69 | #define ORCA_CURLM_INTERNAL -7 70 | /** attempt to initialize globals more than once */ 71 | #define ORCA_GLOBAL_INIT -8 72 | /** @} OrcaCodes */ 73 | 74 | /** @defgroup OrcaLimits 75 | * @brief Limits discovered across the web 76 | * @{ */ 77 | #define ORCA_LIMITS_SHA256 1024 + 1 78 | #define ORCA_LIMITS_LOCALE 16 + 1 79 | #define ORCA_LIMITS_EMAIL 254 + 1 80 | #define ORCA_LIMITS_REGION 16 + 1 81 | /** @} OrcaLimits */ 82 | 83 | /** 84 | * @brief Get container `type` from a field `ptr` 85 | * 86 | * @param ptr the field contained in `type` 87 | * @param type the container datatype 88 | * @param path the path to the field from the container POV 89 | */ 90 | #define CONTAINEROF(ptr, type, path) \ 91 | ((type *)((char *)(ptr)-offsetof(type, path))) 92 | 93 | /** 94 | * @brief log and return `code` if `expect` condition is false 95 | * 96 | * @param expect the expected outcome 97 | * @param client the discord client 98 | * @param error return ORCAcode error 99 | * @param reason for return 100 | */ 101 | #define ORCA_EXPECT(client, expect, code, reason) \ 102 | do { \ 103 | if (!(expect)) { \ 104 | logconf_error(&(client)->conf, "Expected: " #expect ": " reason); \ 105 | return code; \ 106 | } \ 107 | } while (0) 108 | 109 | /** 110 | * @brief Return a generic meaning for ORCAcode 111 | * 112 | * @param code the ORCAcode to be explained 113 | * @return a string containing the code meaning 114 | */ 115 | const char *orca_strerror(ORCAcode code); 116 | 117 | /** 118 | * @brief Initialize global shared-resources not API-specific 119 | * 120 | * @return ORCA_OK on success, ORCA_GLOBAL_INIT on error 121 | */ 122 | ORCAcode orca_global_init(); 123 | 124 | /** @brief Cleanup global shared-resources */ 125 | void orca_global_cleanup(); 126 | 127 | #ifdef __cplusplus 128 | } 129 | #endif /* __cplusplus */ 130 | 131 | #endif /* COMMON_H */ 132 | -------------------------------------------------------------------------------- /common/third-party/sha1.h: -------------------------------------------------------------------------------- 1 | #ifndef SHA1_H 2 | #define SHA1_H 3 | 4 | /* 5 | SHA-1 in C 6 | By Steve Reid 7 | 100% Public Domain 8 | */ 9 | 10 | #include "stdint.h" 11 | 12 | typedef struct 13 | { 14 | uint32_t state[5]; 15 | uint32_t count[2]; 16 | unsigned char buffer[64]; 17 | } SHA1_CTX; 18 | 19 | void SHA1Transform( 20 | uint32_t state[5], 21 | const unsigned char buffer[64] 22 | ); 23 | 24 | void SHA1Init( 25 | SHA1_CTX * context 26 | ); 27 | 28 | void SHA1Update( 29 | SHA1_CTX * context, 30 | const unsigned char *data, 31 | uint32_t len 32 | ); 33 | 34 | void SHA1Final( 35 | unsigned char digest[20], 36 | SHA1_CTX * context 37 | ); 38 | 39 | void SHA1( 40 | char *hash_out, 41 | const char *str, 42 | int len); 43 | 44 | #endif /* SHA1_H */ 45 | -------------------------------------------------------------------------------- /common/third-party/threadpool.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016, Mathias Brossard . 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are 7 | * met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #ifndef _THREADPOOL_H_ 30 | #define _THREADPOOL_H_ 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | /** 37 | * @file threadpool.h 38 | * @brief Threadpool Header File 39 | */ 40 | 41 | /** 42 | * Increase this constants at your own risk 43 | * Large values might slow down your system 44 | */ 45 | #define MAX_THREADS 64 46 | #define MAX_QUEUE 65536 47 | 48 | typedef struct threadpool_t threadpool_t; 49 | 50 | typedef enum { 51 | threadpool_invalid = -1, 52 | threadpool_lock_failure = -2, 53 | threadpool_queue_full = -3, 54 | threadpool_shutdown = -4, 55 | threadpool_thread_failure = -5 56 | } threadpool_error_t; 57 | 58 | typedef enum { 59 | threadpool_graceful = 1 60 | } threadpool_destroy_flags_t; 61 | 62 | /** 63 | * @function threadpool_create 64 | * @brief Creates a threadpool_t object. 65 | * @param thread_count Number of worker threads. 66 | * @param queue_size Size of the queue. 67 | * @param flags Unused parameter. 68 | * @return a newly created thread pool or NULL 69 | */ 70 | threadpool_t *threadpool_create(int thread_count, int queue_size, int flags); 71 | 72 | /** 73 | * @function threadpool_add 74 | * @brief add a new task in the queue of a thread pool 75 | * @param pool Thread pool to which add the task. 76 | * @param function Pointer to the function that will perform the task. 77 | * @param argument Argument to be passed to the function. 78 | * @param flags Unused parameter. 79 | * @return 0 if all goes well, negative values in case of error (@see 80 | * threadpool_error_t for codes). 81 | */ 82 | int threadpool_add(threadpool_t *pool, void (*routine)(void *), 83 | void *arg, int flags); 84 | 85 | /** 86 | * @function threadpool_destroy 87 | * @brief Stops and destroys a thread pool. 88 | * @param pool Thread pool to destroy. 89 | * @param flags Flags for shutdown 90 | * 91 | * Known values for flags are 0 (default) and threadpool_graceful in 92 | * which case the thread pool doesn't accept any new tasks but 93 | * processes all pending tasks before shutdown. 94 | */ 95 | int threadpool_destroy(threadpool_t *pool, int flags); 96 | 97 | #ifdef __cplusplus 98 | } 99 | #endif 100 | 101 | #endif /* _THREADPOOL_H_ */ 102 | -------------------------------------------------------------------------------- /common/work.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "work.h" 7 | #include "threadpool.h" 8 | 9 | /** true after threadpool initialization */ 10 | static _Bool once; 11 | 12 | /** request thread and optional callback execution thread */ 13 | static threadpool_t *tpool; 14 | 15 | int 16 | work_global_init(void) 17 | { 18 | static int nthreads = 0; 19 | static int queue_size = 0; 20 | const char *val; 21 | char *p_end; 22 | 23 | if (once) return 1; 24 | 25 | /* get threadpool thread amount */ 26 | val = getenv("ORCA_THREADPOOL_SIZE"); 27 | if (val != NULL) { 28 | nthreads = (int)strtol(val, &p_end, 10); 29 | } 30 | if (nthreads < 2 || ERANGE == errno || p_end == val) { 31 | nthreads = 2; 32 | } 33 | /* get threadpool queue size */ 34 | val = getenv("ORCA_THREADPOOL_QUEUE_SIZE"); 35 | if (val != NULL) { 36 | queue_size = (int)strtol(val, &p_end, 10); 37 | } 38 | if (queue_size < 8 || ERANGE == errno || p_end == val) { 39 | queue_size = 8; 40 | } 41 | 42 | /* initialize threadpool */ 43 | tpool = threadpool_create(nthreads, queue_size, 0); 44 | 45 | once = 1; 46 | 47 | return 0; 48 | } 49 | 50 | int 51 | work_run(void (*callback)(void *data), void *data) 52 | { 53 | return threadpool_add(tpool, callback, data, 0); 54 | } 55 | 56 | void 57 | work_global_cleanup(void) 58 | { 59 | /* cleanup thread-pool manager */ 60 | threadpool_destroy(tpool, threadpool_graceful); 61 | once = 0; 62 | } 63 | -------------------------------------------------------------------------------- /common/work.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file work.h 3 | */ 4 | 5 | #ifndef WORK_H 6 | #define WORK_H 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif /* __cplusplus */ 11 | 12 | /** 13 | * @brief Initialize global threadpool and priority queue 14 | * @return `0` on success, `1` if it has already been initialized 15 | */ 16 | int work_global_init(void); 17 | 18 | /** 19 | * @brief Cleanup global threadpool and priority queue 20 | */ 21 | void work_global_cleanup(void); 22 | 23 | /** 24 | * @brief Run a callback from a worker thread 25 | * 26 | * @param callback user callback to be executed 27 | * @param data user data to be passed to callback 28 | * @return 0 if all goes well, negative values in case of error (see 29 | * threadpool.h for codes) 30 | */ 31 | int work_run(void (*callback)(void *data), void *data); 32 | 33 | #ifdef __cplusplus 34 | } 35 | #endif /* __cplusplus */ 36 | 37 | #endif /* WORK_H */ 38 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "logging": { 3 | "level": "trace", 4 | "filename": "bot.log", 5 | "quiet": true, 6 | "overwrite": false, 7 | "use_color": true, 8 | "http": { 9 | "enable": true, 10 | "filename": "http.log" 11 | }, 12 | "disable_modules": ["WEBSOCKETS", "USER_AGENT"] 13 | }, 14 | "discord": { 15 | "token": "YOUR-BOT-TOKEN", 16 | "default_prefix": { 17 | "enable": false, 18 | "prefix": "YOUR-COMMANDS-PREFIX" 19 | } 20 | }, 21 | "slack": { 22 | "app_token": "YOUR-APP-TOKEN", 23 | "bot_token": "YOUR-BOT-TOKEN", 24 | "user_token": "YOUR-USER-TOKEN", 25 | "client_id": "YOUR-CLIENT-ID", 26 | "client_secret": "YOUR-CLIENT-SECRET", 27 | "signing_secret": "YOUR-SIGNING-SECRET" 28 | }, 29 | "github": { 30 | "username": "YOUR-GITHUB-USERNAME", 31 | "token": "YOUR-PERSONAL-ACCESS-TOKEN" 32 | }, 33 | "reddit": { 34 | "username": "YOUR-REDDIT-USERNAME", 35 | "password": "YOUR-REDDIT-PASSWORD", 36 | "client_id": "REDDIT-CLIENT-ID", 37 | "client_secret": "REDDIT-CLIENT-SECRET" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/PROJECT_OUTLINE.md: -------------------------------------------------------------------------------- 1 | # Project Outline 2 | An explanation of important folders for developers who would like to contribute to Orca. 3 | 4 | | directory | meaning | 5 | |----------------------|:---------------------------------------------------------------------| 6 | | examples/ | Example bots for new users | 7 | | common/ | The backbone to wrap APIs using Orca | 8 | | docs/ | Guidelines, roadmap, and other important information regarding Orca | 9 | | licenses/ | License for Orca, and some other libraries used by Orca | 10 | | mujs/ | An embedded JavaScript engine. Unused at the moment. | 11 | | scripts/ | Cee-Studio specific scripts | 12 | | specs/ | Data used to generate types and structures used by Orca | 13 | | specs-code/ | Where files generated from ``specs/`` are dumped to | 14 | | test/ | Tests to make sure Orca is running smoothly are put here | 15 | 16 | -------------------------------------------------------------------------------- /docs/SAIPHC.md: -------------------------------------------------------------------------------- 1 | # Saiph-C 2 | 3 | [The Saiph-C SDK](https://www.cee.studio/get_sdk.html) is a compiler for C and C++ programs that allows users to diagnose segmentation faults in an easy and simple manner. 4 | 5 | ## Installing and Using Saiph-C for Orca 6 | 7 | Please run the following commands as *a normal user* (NOT ROOT). 8 | 9 | Follow the installation steps [here](https://www.cee.studio/get_sdk.html). After this, there are some additional steps required to compile Orca: 10 | 11 | 1. Download the additional libraries for Saiph-C from [here](https://github.com/cee-studio/packages) using the following command: 12 | ``` 13 | git clone https://github.com/cee-studio/packages.git 14 | ``` 15 | 2. For Orca, use these commands inside the above `packages` folder to install the libraries instead: 16 | ``` 17 | CC=sfc make bearssl curl 18 | ./install.sh 19 | ``` 20 | 3. To build Orca: 21 | ``` 22 | cd orca 23 | CC=sfc make 24 | ``` 25 | 26 | 27 | # Using Saiph-C to Diagnose Segfaults 28 | 29 | As of the current build of Orca, [Saiph-C](https://www.cee.studio/get_sdk.html) can be used to compile any bots made and will give easy-to-read instructions. 30 | 31 | Running the bot after doing [Using Saiph-C](#installing-and-using-saiph-c-for-orca) should be the same as any other C program. However, Saiph-C provides diagnostic information in the case of incorrect memory access. 32 | 33 | ## Parts of a Saiph-C error 34 | 35 | Examples of Saiph-C errors are shown [here](https://www.cee.studio/benefits.html). 36 | As Saiph-C covers many types of memory access errors/warnings, messages are varied, but they should have three parts: [the memory error type](#memory-error-type), [the general information](#general-information), [and the stack trace](#stack-trace). 37 | 38 | ### Memory Error Type 39 | 40 | #### Example: 41 | ``` 42 | Memory access error: writing to the outside of a memory block; abort! 43 | ``` 44 | 45 | The Memory Error Type Block of a Saiph-C message is the first part of a message and defines the type of memory access violation that has occured; it usually has this format: 46 | 47 | ``` 48 | Memory access : 49 | ; 50 | 51 | ``` 52 | 53 | Check [here](https://www.cee.studio/benefits.html) for a list of all the memory violation types. 54 | 55 | ### General Information 56 | 57 | #### Example: 58 | ``` 59 | # Writing 1 bytes to 0x929e010 may clobber other memory blocks. 60 | # 61 | # The memory-block-to-be-written (start:0x929e010, size:10 bytes) was allocated at 62 | # file:/prog.c::10, 14 63 | # [libc-start-main] 64 | # It has been freed at 65 | # file:/prog.c::11, 3 66 | # [libc-start-main] 67 | # The allocation/free locations could have been distorted by subsequent reuses. 68 | ``` 69 | 70 | The General Information Block of a Saiph-C message is the second part of a message and further specifies the memory violation that occurs. This can vary from memory violation to memory violation and can even be absent in specific ones. 71 | 72 | ### Stack Trace 73 | 74 | #### Example: 75 | ``` 76 | # Stack trace (most recent call first) of the read. 77 | # [0] file:/prog.c::7, 3 78 | # [1] [libc-start-main] 79 | ``` 80 | The Stack Trace portion of Saiph-C is the last part of a message and will give a stack trace of the functions that called the erroring line. This is pretty self-explainatory, but it should be noted that **the most recent call is the top-most function**. 81 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | # But these 4 | !.gitignore 5 | !*.c 6 | !Makefile 7 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | # This Makefile assumes the top folder has been built 2 | 3 | TOP = .. 4 | CC ?= gcc 5 | 6 | CEEUTILS_DIR := $(TOP)/cee-utils 7 | COMMON_DIR := $(TOP)/common 8 | 9 | BOT_DISCORD := bot-audit-log \ 10 | bot-ban \ 11 | bot-channel \ 12 | bot-components \ 13 | bot-delete-messages \ 14 | bot-echo \ 15 | bot-embed \ 16 | bot-emoji \ 17 | bot-fetch-messages \ 18 | bot-guild-template \ 19 | bot-guild \ 20 | bot-invite \ 21 | bot-manual-dm \ 22 | bot-pin \ 23 | bot-ping-pong \ 24 | bot-presence \ 25 | bot-reaction \ 26 | bot-shell \ 27 | bot-slash-commands \ 28 | bot-slash-commands2 \ 29 | bot-voice \ 30 | bot-webhook 31 | 32 | BOT_GITHUB := bot-github-create-fork \ 33 | bot-github-get-gist \ 34 | bot-github-gist-starred \ 35 | bot-github-gist 36 | 37 | BOTS := $(BOT_DISCORD) $(BOT_GITHUB) 38 | 39 | CFLAGS += -I$(TOP) -I$(CEEUTILS_DIR) -I$(COMMON_DIR) \ 40 | -I$(COMMON_DIR)/third-party \ 41 | -O0 -g -pthread -Wall 42 | LDFLAGS += -L$(TOP)/lib 43 | 44 | ifneq (,$(findstring $(CC),stensal-c sfc)) # ifeq stensal-c OR sfc 45 | __DEST := $(dir $(shell which $(CC))) 46 | PREFIX := $(__DEST:%/stensal/bin/=%/usr) 47 | LDFLAGS += -lcurl-bearssl -lbearssl -static 48 | else 49 | LDFLAGS += $(pkg-config --libs --cflags libcurl) -lcurl 50 | endif 51 | 52 | all: $(BOTS) 53 | 54 | $(BOT_DISCORD): %: %.c 55 | $(CC) $(CFLAGS) -o $@ $< -ldiscord $(LDFLAGS) 56 | $(BOT_GITHUB): %: %.c 57 | $(CC) $(CFLAGS) -o $@ $< -lgithub $(LDFLAGS) 58 | 59 | echo: 60 | @ echo -e 'CC: $(CC)\n' 61 | @ echo -e 'BOT_DISCORD: $(BOT_DISCORD)\n' 62 | @ echo -e 'BOT_GITHUB: $(BOT_GITHUB)\n' 63 | 64 | clean: 65 | rm -rf $(BOTS) 66 | 67 | .PHONY: all echo clean 68 | -------------------------------------------------------------------------------- /examples/bot-audit-log.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "discord.h" 10 | 11 | void on_ready(struct discord *client) 12 | { 13 | const struct discord_user *bot = discord_get_self(client); 14 | 15 | log_info("Audit-Log-Bot succesfully connected to Discord as %s#%s!", 16 | bot->username, bot->discriminator); 17 | } 18 | 19 | void on_log_guild_member_add(struct discord *client, 20 | u64_snowflake_t guild_id, 21 | const struct discord_guild_member *member) 22 | { 23 | log_info("%s#%s joined guild %" PRIu64, member->user->username, 24 | member->user->discriminator, guild_id); 25 | } 26 | 27 | void on_log_guild_member_update(struct discord *client, 28 | u64_snowflake_t guild_id, 29 | const struct discord_guild_member *member) 30 | { 31 | char nick[128] = ""; 32 | 33 | if (member->nick && *member->nick) 34 | snprintf(nick, sizeof(nick), " (%s)", member->nick); 35 | 36 | log_info("%s#%s%s updated (guild %" PRIu64 ")", member->user->username, 37 | member->user->discriminator, nick, guild_id); 38 | } 39 | 40 | void on_log_guild_member_remove(struct discord *client, 41 | u64_snowflake_t guild_id, 42 | const struct discord_user *user) 43 | { 44 | log_info("%s#%s left guild %" PRIu64, user->username, user->discriminator, 45 | guild_id); 46 | } 47 | 48 | void on_audit_channel_create(struct discord *client, 49 | const struct discord_message *msg) 50 | { 51 | if (msg->author->bot) return; 52 | 53 | struct discord_audit_log audit_log; 54 | discord_audit_log_init(&audit_log); 55 | 56 | ORCAcode code; 57 | code = discord_get_guild_audit_log( 58 | client, msg->guild_id, 59 | &(struct discord_get_guild_audit_log_params){ 60 | .user_id = msg->author->id, 61 | .action_type = DISCORD_AUDIT_LOG_CHANNEL_CREATE }, 62 | &audit_log); 63 | 64 | if (code != ORCA_OK) { 65 | log_error("%s", discord_strerror(code, client)); 66 | goto _error; 67 | } 68 | if (!audit_log.audit_log_entries) { 69 | goto _error; 70 | } 71 | 72 | struct discord_audit_log_entry *entry = audit_log.audit_log_entries[0]; 73 | if (!entry->user_id || !entry->target_id) { 74 | goto _error; 75 | } 76 | 77 | char text[1028]; // should be large enough 78 | sprintf(text, "<@!%" PRIu64 "> has created <#%s>!", entry->user_id, 79 | entry->target_id); 80 | struct discord_create_message_params params = { .content = text }; 81 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 82 | 83 | return; 84 | 85 | _error: 86 | discord_audit_log_cleanup(&audit_log); 87 | log_error("Couldn't retrieve audit log"); 88 | } 89 | 90 | int main(int argc, char *argv[]) 91 | { 92 | const char *config_file; 93 | if (argc > 1) 94 | config_file = argv[1]; 95 | else 96 | config_file = "../config.json"; 97 | 98 | setlocale(LC_ALL, ""); 99 | 100 | orca_global_init(); 101 | struct discord *client = discord_config_init(config_file); 102 | assert(NULL != client && "Couldn't initialize client"); 103 | 104 | discord_add_intents(client, 32767); // subscribe to all events 105 | 106 | discord_set_on_ready(client, &on_ready); 107 | discord_set_on_guild_member_add(client, &on_log_guild_member_add); 108 | discord_set_on_guild_member_update(client, &on_log_guild_member_update); 109 | discord_set_on_guild_member_remove(client, &on_log_guild_member_remove); 110 | 111 | discord_set_on_command(client, "!last_channel", &on_audit_channel_create); 112 | 113 | printf( 114 | "\n\nThis bot demonstrates how easy it is to log" 115 | " for certain events.\n" 116 | "1. Type '!last_channel' to check the most recent channel created by you\n" 117 | "\tsee: " 118 | "https://discord.com/developers/docs/resources/" 119 | "audit-log#audit-log-entry-object-audit-log-events\n" 120 | "\nTYPE ANY KEY TO START BOT\n"); 121 | fgetc(stdin); // wait for input 122 | 123 | discord_run(client); 124 | 125 | discord_cleanup(client); 126 | orca_global_cleanup(); 127 | } 128 | -------------------------------------------------------------------------------- /examples/bot-delete-messages.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "discord.h" 7 | #include "debug.h" 8 | 9 | char *SPAM[] = { 10 | "Yes I love to spam", // 1 11 | "Do you?", // 2 12 | "ROFL", // 3 13 | "What are you going to do about it?", // 4 14 | "Are you going to !clear me?", // 5 15 | "Good luck with that.", // 6 16 | "Many have tried but..", // 7 17 | "They all fail.", // 8 18 | "What makes you think", // 9 19 | "It should be any different with you?" // 10 20 | }; 21 | 22 | void on_spam(struct discord *client, const struct discord_message *msg) 23 | { 24 | if (msg->author->bot) return; 25 | 26 | struct discord_create_message_params params = { 0 }; 27 | for (size_t i = 0; i < 10; ++i) { 28 | params.content = SPAM[i]; 29 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 30 | } 31 | } 32 | 33 | void on_clear(struct discord *client, const struct discord_message *msg) 34 | { 35 | if (msg->author->bot) return; 36 | 37 | const struct discord_user *bot = discord_get_self(client); 38 | 39 | discord_delete_messages_by_author_id(client, msg->channel_id, bot->id); 40 | 41 | struct discord_create_message_params params = { 42 | .content = "Deleted 100 messages or less" 43 | }; 44 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 45 | } 46 | 47 | int main(int argc, char *argv[]) 48 | { 49 | const char *config_file; 50 | if (argc > 1) 51 | config_file = argv[1]; 52 | else 53 | config_file = "../config.json"; 54 | 55 | orca_global_init(); 56 | struct discord *client = discord_config_init(config_file); 57 | assert(NULL != client && "Couldn't initialize client"); 58 | 59 | discord_set_on_command(client, "!spam", &on_spam); 60 | discord_set_on_command(client, "!clear", &on_clear); 61 | 62 | printf("\n\nThis bot demonstrates how easy it is to delete" 63 | " messages.\n" 64 | "1. Type !spam to spam 10 random messages in chat\n" 65 | "2. Type !clear to delete spam messages\n" 66 | "\nTYPE ANY KEY TO START BOT\n"); 67 | fgetc(stdin); // wait for input 68 | 69 | discord_run(client); 70 | 71 | discord_cleanup(client); 72 | orca_global_cleanup(); 73 | } 74 | -------------------------------------------------------------------------------- /examples/bot-echo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "discord.h" 7 | 8 | void on_ready(struct discord *client) 9 | { 10 | const struct discord_user *bot = discord_get_self(client); 11 | 12 | log_info("Echo-Bot succesfully connected to Discord as %s#%s!", 13 | bot->username, bot->discriminator); 14 | } 15 | 16 | void on_reaction_add(struct discord *client, 17 | u64_snowflake_t user_id, 18 | u64_snowflake_t channel_id, 19 | u64_snowflake_t message_id, 20 | u64_snowflake_t guild_id, 21 | const struct discord_guild_member *member, 22 | const struct discord_emoji *emoji) 23 | { 24 | if (member->user->bot) return; 25 | 26 | discord_create_reaction(client, channel_id, message_id, emoji->id, 27 | emoji->name); 28 | } 29 | 30 | void on_message_create(struct discord *client, 31 | const struct discord_message *msg) 32 | { 33 | if (msg->author->bot) return; 34 | 35 | struct discord_create_message_params 36 | params = { .content = msg->content, 37 | .message_reference = 38 | !msg->referenced_message 39 | ? NULL 40 | : &(struct discord_message_reference){ 41 | .message_id = msg->referenced_message->id, 42 | .channel_id = msg->channel_id, 43 | .guild_id = msg->guild_id, 44 | } }; 45 | 46 | discord_async_next(client, NULL); 47 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 48 | } 49 | 50 | void on_message_update(struct discord *client, 51 | const struct discord_message *msg) 52 | { 53 | struct discord_create_message_params params = { 54 | .content = "I see what you did there." 55 | }; 56 | 57 | discord_async_next(client, NULL); 58 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 59 | } 60 | 61 | void on_message_delete(struct discord *client, 62 | u64_snowflake_t id, 63 | u64_snowflake_t channel_id, 64 | u64_snowflake_t guild_id) 65 | { 66 | struct discord_create_message_params params = { 67 | .content = "Did that message just disappear?" 68 | }; 69 | 70 | discord_async_next(client, NULL); 71 | discord_create_message(client, channel_id, ¶ms, NULL); 72 | } 73 | 74 | void on_message_delete_bulk(struct discord *client, 75 | const u64_snowflake_t **ids, 76 | u64_snowflake_t channel_id, 77 | u64_snowflake_t guild_id) 78 | { 79 | char text[128]; 80 | sprintf(text, "Where did those %zu messages go?", ntl_length((ntl_t)ids)); 81 | 82 | struct discord_create_message_params params = { .content = text }; 83 | 84 | discord_async_next(client, NULL); 85 | discord_create_message(client, channel_id, ¶ms, NULL); 86 | } 87 | 88 | int main(int argc, char *argv[]) 89 | { 90 | const char *config_file; 91 | if (argc > 1) 92 | config_file = argv[1]; 93 | else 94 | config_file = "../config.json"; 95 | 96 | orca_global_init(); 97 | struct discord *client = discord_config_init(config_file); 98 | assert(NULL != client && "Couldn't initialize client"); 99 | 100 | discord_set_on_ready(client, &on_ready); 101 | discord_set_on_message_create(client, &on_message_create); 102 | discord_set_on_message_update(client, &on_message_update); 103 | discord_set_on_message_delete(client, &on_message_delete); 104 | discord_set_on_message_reaction_add(client, &on_reaction_add); 105 | discord_set_on_message_delete_bulk(client, &on_message_delete_bulk); 106 | 107 | printf("\n\nThis bot demonstrates how easy it is to setup a bot that" 108 | " echoes user actions.\n" 109 | "1. Send a message in any chat\n" 110 | "2. Edit that message\n" 111 | "3. Delete that message\n" 112 | "4. Add a reaction to a message\n" 113 | "5. Have another bot bulk-delete messages\n" 114 | "\nTYPE ANY KEY TO START BOT\n"); 115 | fgetc(stdin); // wait for input 116 | 117 | discord_run(client); 118 | 119 | discord_cleanup(client); 120 | orca_global_cleanup(); 121 | } 122 | -------------------------------------------------------------------------------- /examples/bot-emoji.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include /* PRIu64, SCNu64 */ 4 | #include 5 | 6 | #include "discord.h" 7 | 8 | void on_ready(struct discord *client) 9 | { 10 | const struct discord_user *bot = discord_get_self(client); 11 | 12 | log_info("Emoji-Bot succesfully connected to Discord as %s#%s!", 13 | bot->username, bot->discriminator); 14 | } 15 | 16 | void on_list(struct discord *client, const struct discord_message *msg) 17 | { 18 | if (msg->author->bot) return; 19 | 20 | struct discord_emoji **emojis = NULL; 21 | char text[2000]; 22 | ORCAcode code; 23 | 24 | code = discord_list_guild_emojis(client, msg->guild_id, &emojis); 25 | 26 | if (code != ORCA_OK || !emojis) { 27 | sprintf(text, "No guild emojis found."); 28 | } 29 | else { 30 | char *cur = text; 31 | char *end = &text[sizeof(text) - 1]; 32 | char *prev; 33 | 34 | for (size_t i = 0; emojis[i]; ++i) { 35 | prev = cur; 36 | cur += snprintf(cur, end - cur, "<%s:%s:%" PRIu64 ">(%" PRIu64 ")\n", 37 | emojis[i]->animated ? "a" : "", emojis[i]->name, 38 | emojis[i]->id, emojis[i]->id); 39 | 40 | if (cur >= end) { // to make sure no emoji is skipped 41 | *prev = '\0'; // end string before truncation 42 | // reset for retry 43 | cur = text; 44 | --i; 45 | 46 | struct discord_create_message_params params = { .content = text }; 47 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 48 | 49 | continue; 50 | } 51 | } 52 | discord_emoji_list_free(emojis); 53 | } 54 | 55 | struct discord_create_message_params params = { .content = text }; 56 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 57 | } 58 | 59 | void on_get(struct discord *client, const struct discord_message *msg) 60 | { 61 | if (msg->author->bot) return; 62 | 63 | char text[DISCORD_MAX_MESSAGE_LEN]; 64 | u64_snowflake_t emoji_id = 0; 65 | 66 | sscanf(msg->content, "%" SCNu64, &emoji_id); 67 | 68 | if (!emoji_id) { 69 | sprintf(text, "Missing 'emoji_id'"); 70 | } 71 | else { 72 | struct discord_emoji emoji = { 0 }; 73 | 74 | discord_get_guild_emoji(client, msg->guild_id, emoji_id, &emoji); 75 | if (emoji.id) 76 | sprintf(text, "Here you go: <%s:%s:%" PRIu64 ">", 77 | emoji.animated ? "a" : "", emoji.name, emoji.id); 78 | else 79 | sprintf(text, "Unknown emoji"); 80 | 81 | discord_emoji_cleanup(&emoji); 82 | } 83 | 84 | struct discord_create_message_params params = { .content = text }; 85 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 86 | } 87 | 88 | int main(int argc, char *argv[]) 89 | { 90 | const char *config_file; 91 | if (argc > 1) 92 | config_file = argv[1]; 93 | else 94 | config_file = "../config.json"; 95 | 96 | orca_global_init(); 97 | struct discord *client = discord_config_init(config_file); 98 | assert(NULL != client && "Could not initialize client"); 99 | 100 | discord_set_on_ready(client, &on_ready); 101 | 102 | discord_set_prefix(client, "emoji."); 103 | discord_set_on_command(client, "list", &on_list); 104 | discord_set_on_command(client, "get", &on_get); 105 | 106 | printf("\n\n This bot demonstrates how easy it is to create/delete emojis\n" 107 | "1. Type 'emoji.list' to get a list of server emojis\n" 108 | "2. Type 'emoji.get ' to get the selected emoji\n" 109 | "\nTYPE ANY KEY TO START BOT\n"); 110 | fgetc(stdin); // wait for input 111 | 112 | discord_run(client); 113 | 114 | discord_cleanup(client); 115 | orca_global_cleanup(); 116 | } 117 | -------------------------------------------------------------------------------- /examples/bot-fetch-messages.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "discord.h" 7 | 8 | u64_snowflake_t select_guild(struct discord *client) 9 | { 10 | // get guilds bot is a part of 11 | struct discord_guild **guilds = NULL; 12 | discord_get_current_user_guilds(client, &guilds); 13 | assert(NULL != guilds && "Couldn't fetch guilds"); 14 | 15 | printf("\n\nSelect the guild that the user you wish to fetch messages from " 16 | "is part of"); 17 | int i = 0; 18 | while (guilds[i]) { 19 | printf("\n%d. %s", i + 1, guilds[i]->name); 20 | ++i; 21 | } 22 | 23 | do { 24 | puts("\n\nNUMBER >>"); 25 | char strnum[10]; // 10 digits should be more than enough.. 26 | fgets(strnum, sizeof(strnum), stdin); 27 | int num = strtol(strnum, NULL, 10); 28 | if (num > 0 && num <= i) { 29 | u64_snowflake_t guild_id = guilds[num - 1]->id; 30 | discord_guild_list_free(guilds); 31 | return guild_id; 32 | } 33 | printf("\nPlease, insert a value between 1 and %d", i); 34 | } while (1); 35 | } 36 | 37 | u64_snowflake_t select_member(struct discord *client, u64_snowflake_t guild_id) 38 | { 39 | // get guilds bot is a part of 40 | struct discord_guild_member **members = NULL; 41 | struct discord_list_guild_members_params params = { .limit = 1000, 42 | .after = 0 }; 43 | discord_list_guild_members(client, guild_id, ¶ms, &members); 44 | assert( 45 | NULL != members 46 | && "Guild is empty or bot needs to activate its privileged intents.\n\t" 47 | "See this guide to activate it: " 48 | "https://discordpy.readthedocs.io/en/latest/" 49 | "intents.html#privileged-intents"); 50 | 51 | printf("\n\nSelect the member that will have its messages fetched"); 52 | int i = 0; 53 | while (members[i]) { 54 | printf("\n%d. %s", i + 1, members[i]->user->username); 55 | if (members[i]->nick && *members[i]->nick) { // prints nick if available 56 | printf(" (%s)", members[i]->nick); 57 | } 58 | ++i; 59 | } 60 | 61 | do { 62 | puts("\n\nNUMBER >>"); 63 | char strnum[10]; // 10 digits should be more than enough.. 64 | fgets(strnum, sizeof(strnum), stdin); 65 | int num = strtol(strnum, NULL, 10); 66 | if (num > 0 && num <= i) { 67 | u64_snowflake_t user_id = members[num - 1]->user->id; 68 | discord_guild_member_list_free(members); 69 | return user_id; 70 | } 71 | printf("\nPlease, insert a value between 1 and %d", i); 72 | } while (1); 73 | } 74 | 75 | void fetch_member_msgs(struct discord *client, 76 | u64_snowflake_t guild_id, 77 | u64_snowflake_t user_id) 78 | { 79 | struct discord_channel **channels = NULL; 80 | discord_get_guild_channels(client, guild_id, &channels); 81 | assert(NULL != channels && "Couldn't fetch channels from guild"); 82 | 83 | struct discord_get_channel_messages_params params = { .limit = 100 }; 84 | 85 | for (int i = 0; channels[i]; ++i) { 86 | params.before = 0; 87 | 88 | int n_msg; 89 | struct discord_message **messages = NULL; 90 | do { 91 | discord_get_channel_messages(client, channels[i]->id, ¶ms, 92 | &messages); 93 | if (!messages) break; /* EARLY BREAK */ 94 | 95 | for (n_msg = 0; messages[n_msg]; ++n_msg) { 96 | if (user_id == messages[n_msg]->author->id 97 | && *messages[n_msg]->content) { 98 | printf("%s\n", messages[n_msg]->content); 99 | } 100 | } 101 | 102 | if (n_msg) { 103 | params.before = messages[n_msg - 1]->id; 104 | } 105 | 106 | discord_message_list_free(messages); 107 | 108 | } while (n_msg == params.limit); 109 | } 110 | 111 | discord_channel_list_free(channels); 112 | } 113 | 114 | int main(int argc, char *argv[]) 115 | { 116 | const char *config_file; 117 | if (argc > 1) 118 | config_file = argv[1]; 119 | else 120 | config_file = "../config.json"; 121 | 122 | orca_global_init(); 123 | struct discord *client = discord_config_init(config_file); 124 | assert(NULL != client && "Couldn't initialize client"); 125 | 126 | printf("\n\nThis bot demonstrates how easy it is to fetch" 127 | " messages from a particular user (without even connecting" 128 | " to Discord Gateway).\n" 129 | "\nTYPE ANY KEY TO START BOT\n"); 130 | fgetc(stdin); // wait for input 131 | 132 | u64_snowflake_t guild_id = select_guild(client); 133 | u64_snowflake_t user_id = select_member(client, guild_id); 134 | fetch_member_msgs(client, guild_id, user_id); 135 | 136 | discord_cleanup(client); 137 | orca_global_cleanup(); 138 | } 139 | -------------------------------------------------------------------------------- /examples/bot-github-create-fork.c: -------------------------------------------------------------------------------- 1 | /* 2 | * A bot that can create a fork of a repository. 3 | */ 4 | 5 | #include 6 | #include 7 | #include "github.h" 8 | 9 | void print_usage() 10 | { 11 | printf("bot-github-create-fork - create forks of a repository from the " 12 | "terminal\n"); 13 | printf("Usage: bot-github-create-fork.exe \n\n"); 14 | printf("Parameters:\n"); 15 | printf(" owner the owner of the repository\n"); 16 | printf(" repo the name of the repository\n"); 17 | } 18 | 19 | int main(int argc, char *argv[]) 20 | { 21 | struct github *client = github_config_init("../config.json", NULL); 22 | 23 | if (argc == 1) { 24 | print_usage(); 25 | exit(1); 26 | } 27 | else if (argc < 3) { 28 | printf("bot-github-create-fork expects 2 arguments. owner, and repo\n"); 29 | exit(1); 30 | } 31 | 32 | ORCAcode success = github_create_fork(client, argv[1], argv[2]); 33 | 34 | if (success == 0) { 35 | printf("Successfully created fork!"); 36 | } 37 | 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /examples/bot-github-get-gist.c: -------------------------------------------------------------------------------- 1 | /* 2 | * A bot to get information about a gist 3 | */ 4 | 5 | #include 6 | #include 7 | #include "github.h" 8 | 9 | void print_usage() 10 | { 11 | printf("bot-github-get-gist - a bot to get information about a gist\n"); 12 | printf("\nPositional arguments:\n"); 13 | printf(" gist_id the id of the gist to retrieve\n"); 14 | exit(0); 15 | } 16 | 17 | int main(int argc, char *argv[]) 18 | { 19 | if (argc == 1) { 20 | print_usage(); 21 | } 22 | else if (argc > 2) { 23 | printf("bot-github-get-gist: too many parameters"); 24 | } 25 | 26 | struct github *client = github_config_init("../config.json", NULL); 27 | struct github_gist gist; 28 | 29 | github_get_gist(client, argv[1], &gist); 30 | 31 | printf("Gist Id: %s\n", gist.id); 32 | printf("Gist Description: %s\n", gist.description); 33 | printf("Gist Comments: %i\n", gist.comments); 34 | } 35 | -------------------------------------------------------------------------------- /examples/bot-github-gist-starred.c: -------------------------------------------------------------------------------- 1 | /* 2 | * A bot to check if a gist is starred 3 | */ 4 | 5 | #include 6 | #include 7 | #include "github.h" 8 | 9 | void print_usage() 10 | { 11 | printf("bot-github-gist-starred - a bot to check if a gist is starred\n"); 12 | printf("\nPositional arguments:\n"); 13 | printf(" gist_id the id of the gist to check\n"); 14 | exit(0); 15 | } 16 | 17 | int main(int argc, char *argv[]) 18 | { 19 | if (argc == 1) { 20 | print_usage(); 21 | } 22 | else if (argc > 2) { 23 | printf("bot-github-gist-starred: too many parameters"); 24 | } 25 | 26 | struct github *client = github_config_init("../config.json", NULL); 27 | int is_starred = github_gist_is_starred(client, argv[1]); 28 | 29 | if (is_starred == ORCA_OK) { 30 | printf("'%s' is starred.\n", argv[1]); 31 | } 32 | else if (is_starred == ORCA_GITHUB_NOT_STARRED) { 33 | printf("'%s' is not starred.\n", argv[1]); 34 | } 35 | else { 36 | printf("Error occured for gist '%s', with status code: %i.\n", argv[1], 37 | is_starred); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/bot-github-gist.c: -------------------------------------------------------------------------------- 1 | /* 2 | * A bot that creates gists with a title, description, and body. 3 | */ 4 | 5 | #include 6 | #include 7 | #include "github.h" 8 | 9 | void print_usage() 10 | { 11 | printf("bot-github-gist - create gists from the terminal\n"); 12 | printf("Usage: bot-github-gist.exe <description> <body>\n\n"); 13 | printf("Parameters:\n"); 14 | printf(" title the title of the gist\n"); 15 | printf(" description the description of the gist\n"); 16 | printf(" body the body of the gist\n"); 17 | } 18 | 19 | int main(int argc, char *argv[]) 20 | { 21 | struct github *client = github_config_init("../config.json", NULL); 22 | 23 | if (argc == 1) { 24 | print_usage(); 25 | exit(1); 26 | } 27 | else if (argc < 4) { 28 | printf( 29 | "bot-github-gist expects 3 arguments. title, description, and body\n"); 30 | exit(1); 31 | } 32 | 33 | struct github_gist gist_info; 34 | struct github_gist_create_params params = { .title = argv[1], 35 | .description = argv[2], 36 | .contents = argv[3], 37 | .public = "false" }; 38 | 39 | github_create_gist(client, ¶ms, &gist_info); 40 | printf("Created gist \"%s\" at URL: %s\n", argv[1], gist_info.html_url); 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /examples/bot-guild-template.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Examples on how to use guild templates. 3 | */ 4 | 5 | #include <stdio.h> 6 | #include <stdlib.h> 7 | #include <string.h> 8 | #include <inttypes.h> /* PRIu64, SCNu64 */ 9 | #include <assert.h> 10 | 11 | #include "discord.h" 12 | 13 | void on_ready(struct discord *client) 14 | { 15 | const struct discord_user *bot = discord_get_self(client); 16 | 17 | log_info("Guild-Bot succesfully connected to Discord as %s#%s!", 18 | bot->username, bot->discriminator); 19 | } 20 | 21 | void on_get_guild_template(struct discord *client, 22 | const struct discord_message *msg) 23 | { 24 | struct discord_guild_template template; 25 | char buf[DISCORD_MAX_MESSAGE_LEN]; 26 | ORCAcode code; 27 | 28 | code = discord_get_guild_template(client, msg->content, &template); 29 | 30 | if (code == ORCA_OK) { 31 | snprintf(buf, sizeof(buf), 32 | "Here is some information about your guild template:\nName: " 33 | "'%s'\nDescription: '%s'\nCreator Id: %" PRIu64 "\n", 34 | template.name, template.description, template.creator_id); 35 | 36 | discord_guild_template_cleanup(&template); 37 | } 38 | else { 39 | snprintf(buf, sizeof(buf), 40 | "Could not retrieve guild template. Error: '%s'\n", 41 | discord_strerror(code, client)); 42 | } 43 | 44 | struct discord_create_message_params params = { .content = buf }; 45 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 46 | } 47 | 48 | void on_create_guild_template(struct discord *client, 49 | const struct discord_message *msg) 50 | { 51 | struct discord_guild_template template; 52 | char buf[DISCORD_MAX_MESSAGE_LEN]; 53 | ORCAcode code; 54 | 55 | struct discord_create_guild_template_params params = { 56 | .name = "New server template!", 57 | .description = "This is a new server templated created with Orca!" 58 | }; 59 | 60 | code = 61 | discord_create_guild_template(client, msg->guild_id, ¶ms, &template); 62 | 63 | if (code == ORCA_OK) { 64 | snprintf(buf, sizeof(buf), 65 | "Successfully synced the guild template! Here is some " 66 | "information about its current state guild template:\nName: " 67 | "'%s'\nDescription: '%s'\nCode: %s\nCreator Id: %" PRIu64 "\n", 68 | template.name, template.description, template.code, 69 | template.creator_id); 70 | 71 | discord_guild_template_cleanup(&template); 72 | } 73 | else { 74 | snprintf(buf, sizeof(buf), 75 | "Could not create guild template. Error: '%s'\n", 76 | discord_strerror(code, client)); 77 | } 78 | 79 | discord_create_message( 80 | client, msg->channel_id, 81 | &(struct discord_create_message_params){ .content = buf }, NULL); 82 | } 83 | 84 | void on_sync_guild_template(struct discord *client, 85 | const struct discord_message *msg) 86 | { 87 | struct discord_guild_template template; 88 | char buf[DISCORD_MAX_MESSAGE_LEN]; 89 | ORCAcode code; 90 | 91 | code = discord_sync_guild_template(client, msg->guild_id, msg->content, 92 | &template); 93 | 94 | if (code == ORCA_OK) { 95 | snprintf(buf, sizeof(buf), 96 | "Successfully synced the guild template! Here is some " 97 | "information about its current state guild template:\nName: " 98 | "'%s'\nDescription: '%s'\nCode: %s\nCreator Id: %" PRIu64 "\n", 99 | template.name, template.description, template.code, 100 | template.creator_id); 101 | 102 | discord_guild_template_cleanup(&template); 103 | } 104 | else { 105 | snprintf(buf, sizeof(buf), "Could not create sync template. Error: '%s'\n", 106 | discord_strerror(code, client)); 107 | } 108 | 109 | struct discord_create_message_params params = { .content = buf }; 110 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 111 | } 112 | 113 | int main(int argc, char *argv[]) 114 | { 115 | const char *config_file; 116 | if (argc > 1) 117 | config_file = argv[1]; 118 | else 119 | config_file = "../config.json"; 120 | 121 | orca_global_init(); 122 | struct discord *client = discord_config_init(config_file); 123 | assert(NULL != client && "Couldn't initialize client"); 124 | 125 | discord_set_on_ready(client, &on_ready); 126 | discord_set_prefix(client, "guild-template."); 127 | discord_set_on_command(client, "get", on_get_guild_template); 128 | discord_set_on_command(client, "create", on_create_guild_template); 129 | discord_set_on_command(client, "sync", on_sync_guild_template); 130 | 131 | printf("\n\nThis bot demonstrates how easy it is to manipulate guild" 132 | " template endpoints.\n" 133 | "1. Type 'guild-template.get <code>' to get a guild template's " 134 | "information\n" 135 | "2. Type 'guild-template.create' to create a new guild template\n" 136 | "3. Type 'guild-template.sync' to sync the guild template\n" 137 | "\nTYPE ANY KEY TO START BOT\n"); 138 | fgetc(stdin); // wait for input 139 | 140 | discord_run(client); 141 | 142 | discord_cleanup(client); 143 | orca_global_cleanup(); 144 | } 145 | -------------------------------------------------------------------------------- /examples/bot-invite.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <inttypes.h> /* PRIu64 */ 4 | #include <assert.h> 5 | 6 | #include "discord.h" 7 | 8 | void on_ready(struct discord *client) 9 | { 10 | const struct discord_user *bot = discord_get_self(client); 11 | 12 | log_info("Invite-Bot succesfully connected to Discord as %s#%s!", 13 | bot->username, bot->discriminator); 14 | } 15 | 16 | void on_invite_get(struct discord *client, const struct discord_message *msg) 17 | { 18 | if (msg->author->bot) return; 19 | 20 | struct discord_invite invite = { 0 }; 21 | char text[DISCORD_MAX_MESSAGE_LEN]; 22 | ORCAcode code; 23 | 24 | code = discord_get_invite(client, msg->content, 25 | &(struct discord_get_invite_params){ 26 | .with_counts = true, 27 | .with_expiration = true, 28 | }, 29 | &invite); 30 | 31 | if (ORCA_OK == code) 32 | sprintf(text, "https://discord.gg/%s", invite.code); 33 | else 34 | sprintf(text, "Couldn't get invite."); 35 | 36 | struct discord_create_message_params params = { .content = text }; 37 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 38 | 39 | discord_invite_cleanup(&invite); 40 | } 41 | 42 | void on_invite_delete(struct discord *client, 43 | const struct discord_message *msg) 44 | { 45 | if (msg->author->bot) return; 46 | 47 | char *text; 48 | 49 | if (ORCA_OK == discord_delete_invite(client, msg->content, NULL)) 50 | text = "Succesfully deleted invite."; 51 | else 52 | text = "Couldn't delete invite"; 53 | 54 | struct discord_create_message_params params = { .content = text }; 55 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 56 | } 57 | 58 | int main(int argc, char *argv[]) 59 | { 60 | const char *config_file; 61 | if (argc > 1) 62 | config_file = argv[1]; 63 | else 64 | config_file = "../config.json"; 65 | 66 | orca_global_init(); 67 | struct discord *client = discord_config_init(config_file); 68 | assert(NULL != client && "Could not initialize client"); 69 | 70 | discord_set_on_ready(client, &on_ready); 71 | 72 | discord_set_prefix(client, "invite."); 73 | discord_set_on_command(client, "get", &on_invite_get); 74 | discord_set_on_command(client, "delete", &on_invite_delete); 75 | 76 | printf("\n\nThis bot demonstrates how easy it is to fetch/delete invites\n" 77 | "1. Type 'invite.get <invite_code>' to get a invite object from its " 78 | "particular code\n" 79 | "2. Type 'invite.delete <invite_code>' to delete a invite object by " 80 | "its particular code\n" 81 | "\nTYPE ANY KEY TO START BOT\n"); 82 | fgetc(stdin); // wait for input 83 | 84 | discord_run(client); 85 | 86 | discord_cleanup(client); 87 | orca_global_cleanup(); 88 | } 89 | -------------------------------------------------------------------------------- /examples/bot-manual-dm.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <string.h> 4 | #include <inttypes.h> 5 | #include <pthread.h> 6 | #include <assert.h> 7 | 8 | #include "discord.h" 9 | 10 | void on_ready(struct discord *client) 11 | { 12 | const struct discord_user *bot = discord_get_self(client); 13 | 14 | log_info("ManualDM-Bot succesfully connected to Discord as %s#%s!", 15 | bot->username, bot->discriminator); 16 | } 17 | 18 | void on_dm_receive(struct discord *client, const struct discord_message *msg) 19 | { 20 | if (msg->author->bot) return; 21 | 22 | printf("%s:%s\n", msg->author->username, msg->content); 23 | } 24 | 25 | void *read_input(void *p_client) 26 | { 27 | struct discord *client = p_client; 28 | char buf[32 + DISCORD_MAX_MESSAGE_LEN]; 29 | char msg[DISCORD_MAX_MESSAGE_LEN]; 30 | u64_snowflake_t recipient_id; 31 | u64_snowflake_t dm_channel_id; 32 | 33 | pthread_detach(pthread_self()); 34 | 35 | while (1) { 36 | memset(buf, 0, sizeof(buf)); 37 | fgets(buf, sizeof(buf), stdin); 38 | 39 | if (!*buf) continue; // is empty 40 | 41 | memset(msg, 0, sizeof(msg)); 42 | recipient_id = 0; 43 | sscanf(buf, "%" PRIu64 ":%[^\n]", &recipient_id, msg); 44 | 45 | if (!recipient_id || !*msg) { 46 | sscanf(buf, "%[^\n]", msg); 47 | 48 | if (!*msg) { 49 | printf("Expected format: <*recipient_id>:<message>"); 50 | continue; 51 | } 52 | } 53 | else { /* reset active chat */ 54 | struct discord_channel dm_channel = { 0 }; 55 | struct discord_create_dm_params params = { 56 | .recipient_id = recipient_id, 57 | }; 58 | 59 | discord_create_dm(client, ¶ms, &dm_channel); 60 | 61 | dm_channel_id = dm_channel.id; 62 | 63 | discord_channel_cleanup(&dm_channel); 64 | } 65 | 66 | struct discord_create_message_params params = { .content = msg }; 67 | discord_create_message(client, dm_channel_id, ¶ms, NULL); 68 | } 69 | 70 | pthread_exit(NULL); 71 | } 72 | 73 | int main(int argc, char *argv[]) 74 | { 75 | const char *config_file; 76 | if (argc > 1) 77 | config_file = argv[1]; 78 | else 79 | config_file = "../config.json"; 80 | 81 | orca_global_init(); 82 | struct discord *client = discord_config_init(config_file); 83 | assert(NULL != client && "Couldn't initialize client"); 84 | 85 | discord_set_on_ready(client, &on_ready); 86 | discord_set_on_message_create(client, &on_dm_receive); 87 | 88 | /* Keep just DISCORD_GATEWAY_DIRECT_MESSAGES */ 89 | discord_remove_intents(client, DISCORD_GATEWAY_GUILD_MESSAGES); 90 | 91 | printf("\n\nThis bot demonstrates how easy it is to start a DM" 92 | " with someone and talk without leaving the terminal\n" 93 | "1. Type at the terminal <recipient_id>:<message> to start your " 94 | "conversation\n" 95 | "\tex: 1232232312321232123:Hello there friend!\n" 96 | "2. For successive messages to the same person, you can just type " 97 | "the message" 98 | " without the need of specifying the recipient_id everytime\n" 99 | "3. If you wish to start a new conversation, repeat the #1 format\n" 100 | "\nTYPE ANY KEY TO START BOT\n"); 101 | fgetc(stdin); // wait for input 102 | 103 | pthread_t tid; 104 | pthread_create(&tid, NULL, &read_input, client); 105 | 106 | discord_run(client); 107 | 108 | discord_cleanup(client); 109 | orca_global_cleanup(); 110 | } 111 | -------------------------------------------------------------------------------- /examples/bot-pin.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <inttypes.h> /* PRIu64, SCNu64 */ 4 | #include <assert.h> 5 | 6 | #include "discord.h" 7 | 8 | void on_ready(struct discord *client) 9 | { 10 | const struct discord_user *bot = discord_get_self(client); 11 | 12 | log_info("Pin-Bot succesfully connected to Discord as %s#%s!", bot->username, 13 | bot->discriminator); 14 | } 15 | 16 | void on_pin(struct discord *client, const struct discord_message *msg) 17 | { 18 | if (msg->author->bot) return; 19 | 20 | u64_snowflake_t msg_id = 0; 21 | 22 | sscanf(msg->content, "%" SCNu64, &msg_id); 23 | 24 | if (!msg_id) { 25 | if (!msg->referenced_message) return; 26 | 27 | msg_id = msg->referenced_message->id; 28 | } 29 | 30 | discord_pin_message(client, msg->channel_id, msg_id); 31 | } 32 | 33 | void on_unpin(struct discord *client, const struct discord_message *msg) 34 | { 35 | if (msg->author->bot) return; 36 | 37 | u64_snowflake_t msg_id = 0; 38 | 39 | sscanf(msg->content, "%" SCNu64, &msg_id); 40 | 41 | if (!msg_id) { 42 | if (!msg->referenced_message) return; 43 | 44 | msg_id = msg->referenced_message->id; 45 | } 46 | 47 | discord_unpin_message(client, msg->channel_id, msg_id); 48 | } 49 | 50 | void on_get_pins(struct discord *client, const struct discord_message *msg) 51 | { 52 | if (msg->author->bot) return; 53 | 54 | struct discord_message **msgs = NULL; 55 | char text[DISCORD_MAX_MESSAGE_LEN]; 56 | 57 | discord_get_pinned_messages(client, msg->channel_id, &msgs); 58 | 59 | if (!msgs) { 60 | sprintf(text, "No pinned messages in <#%" PRIu64 ">", msg->channel_id); 61 | } 62 | else { 63 | char *cur = text; 64 | char *end = &text[sizeof(text) - 1]; 65 | 66 | for (size_t i = 0; msgs[i]; ++i) { 67 | cur += snprintf(cur, end - cur, 68 | "https://discord.com/channels/%" PRIu64 "/%" PRIu64 69 | "/%" PRIu64 "\n", 70 | msg->guild_id, msg->channel_id, msgs[i]->id); 71 | if (cur >= end) break; 72 | } 73 | 74 | discord_message_list_free(msgs); 75 | } 76 | 77 | struct discord_create_message_params params = { .content = text }; 78 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 79 | } 80 | 81 | int main(int argc, char *argv[]) 82 | { 83 | const char *config_file; 84 | if (argc > 1) 85 | config_file = argv[1]; 86 | else 87 | config_file = "../config.json"; 88 | 89 | orca_global_init(); 90 | struct discord *client = discord_config_init(config_file); 91 | assert(NULL != client && "Couldn't initialize client"); 92 | 93 | discord_set_on_ready(client, &on_ready); 94 | 95 | discord_set_prefix(client, "!"); 96 | discord_set_on_command(client, "pin", &on_pin); 97 | discord_set_on_command(client, "unpin", &on_unpin); 98 | discord_set_on_command(client, "get_pins", &on_get_pins); 99 | 100 | printf( 101 | "\n\nThis bot demonstrates how easy it is to have a" 102 | " message be pinned.\n" 103 | "1. Reply to a message with '!pin' or type '!pin <message_id> to pin it\n" 104 | "2. Reply to a message with '!unpin' or type '!unpin <message_id> to " 105 | "unpin it\n" 106 | "3. Type '!get_pins' to get a id list of pinned messages\n" 107 | "\nTYPE ANY KEY TO START BOT\n"); 108 | fgetc(stdin); // wait for input 109 | 110 | discord_run(client); 111 | 112 | discord_cleanup(client); 113 | orca_global_cleanup(); 114 | } 115 | -------------------------------------------------------------------------------- /examples/bot-ping-pong.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | 4 | #include "discord.h" 5 | 6 | void on_ready(struct discord *client) 7 | { 8 | const struct discord_user *bot = discord_get_self(client); 9 | 10 | log_info("PingPong-Bot succesfully connected to Discord as %s#%s!", 11 | bot->username, bot->discriminator); 12 | } 13 | 14 | void on_ping(struct discord *client, const struct discord_message *msg) 15 | { 16 | if (msg->author->bot) return; 17 | 18 | struct discord_create_message_params params = { .content = "pong" }; 19 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 20 | } 21 | 22 | void on_pong(struct discord *client, const struct discord_message *msg) 23 | { 24 | if (msg->author->bot) return; 25 | 26 | struct discord_create_message_params params = { .content = "ping" }; 27 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 28 | } 29 | 30 | int main(int argc, char *argv[]) 31 | { 32 | const char *config_file; 33 | if (argc > 1) 34 | config_file = argv[1]; 35 | else 36 | config_file = "../config.json"; 37 | 38 | orca_global_init(); 39 | struct discord *client = discord_config_init(config_file); 40 | 41 | discord_set_on_ready(client, &on_ready); 42 | discord_set_on_command(client, "ping", &on_ping); 43 | discord_set_on_command(client, "pong", &on_pong); 44 | 45 | printf("\n\nThis bot demonstrates a simple ping-pong response.\n" 46 | "1. Type 'pong' in chat\n" 47 | "2. Type 'ping' in chat\n" 48 | "\nTYPE ANY KEY TO START BOT\n"); 49 | fgetc(stdin); // wait for input 50 | 51 | discord_run(client); 52 | 53 | discord_cleanup(client); 54 | orca_global_cleanup(); 55 | } 56 | -------------------------------------------------------------------------------- /examples/bot-presence.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <string.h> 4 | #include <assert.h> 5 | 6 | #include "discord.h" 7 | 8 | void on_ready(struct discord *client) 9 | { 10 | const struct discord_user *bot = discord_get_self(client); 11 | 12 | log_info("Presence-Bot succesfully connected to Discord as %s#%s!", 13 | bot->username, bot->discriminator); 14 | 15 | discord_set_presence(client, &(struct discord_presence_status){ 16 | .activities = 17 | (struct discord_activity *[]){ 18 | &(struct discord_activity){ 19 | .name = "with Orca", 20 | .type = DISCORD_ACTIVITY_GAME, 21 | .details = "Fixing some bugs", 22 | }, 23 | NULL // END OF ACTIVITY ARRAY 24 | }, 25 | .status = "idle", 26 | .afk = false, 27 | .since = discord_timestamp(client), 28 | }); 29 | } 30 | 31 | int main(int argc, char *argv[]) 32 | { 33 | const char *config_file; 34 | if (argc > 1) 35 | config_file = argv[1]; 36 | else 37 | config_file = "../config.json"; 38 | 39 | orca_global_init(); 40 | struct discord *client = discord_config_init(config_file); 41 | assert(NULL != client && "Couldn't initialize client"); 42 | 43 | discord_set_on_ready(client, &on_ready); 44 | 45 | printf("\n\nThis bot demonstrates how easy it is to set the bot presence.\n" 46 | "1. Login\n" 47 | "2. Check the bot status\n" 48 | "\nTYPE ANY KEY TO START BOT\n"); 49 | fgetc(stdin); // wait for input 50 | 51 | discord_run(client); 52 | 53 | discord_cleanup(client); 54 | orca_global_cleanup(); 55 | } 56 | -------------------------------------------------------------------------------- /examples/bot-shell.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <string.h> 4 | #include <unistd.h> 5 | #include <assert.h> 6 | #include <locale.h> 7 | #include <limits.h> 8 | #include <errno.h> 9 | #include <inttypes.h> /* SCNu64 */ 10 | 11 | #include "discord.h" 12 | 13 | u64_snowflake_t g_sudo_id; 14 | 15 | void on_ready(struct discord *client) 16 | { 17 | const struct discord_user *bot = discord_get_self(client); 18 | 19 | log_info("Shell-Bot succesfully connected to Discord as %s#%s!", 20 | bot->username, bot->discriminator); 21 | } 22 | 23 | void on_cd(struct discord *client, const struct discord_message *msg) 24 | { 25 | if (msg->author->id != g_sudo_id) return; 26 | 27 | chdir(*msg->content ? msg->content : "."); 28 | 29 | char path[PATH_MAX]; 30 | struct discord_create_message_params params = { 31 | .content = getcwd(path, sizeof(path)), 32 | }; 33 | 34 | discord_async_next(client, NULL); 35 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 36 | } 37 | 38 | void on_less_like(struct discord *client, const struct discord_message *msg) 39 | { 40 | if (msg->author->id != g_sudo_id) return; 41 | 42 | struct discord_create_message_params params = { 0 }; 43 | char buf[512]; 44 | 45 | if (!msg->content) { 46 | params.content = "No file specified"; 47 | } 48 | else { 49 | snprintf(buf, sizeof(buf), "attachment://%s", msg->content); 50 | 51 | params.embeds = (struct discord_embed *[]){ 52 | &(struct discord_embed){ .title = msg->content }, 53 | NULL // end of array 54 | }; 55 | 56 | params.attachments = (struct discord_attachment *[]){ 57 | &(struct discord_attachment){ .filename = msg->content }, 58 | NULL // end of array 59 | }; 60 | } 61 | 62 | discord_async_next(client, NULL); 63 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 64 | } 65 | 66 | void on_fallback(struct discord *client, const struct discord_message *msg) 67 | { 68 | const size_t MAX_FSIZE = 5e6; // 5 mb 69 | 70 | if (msg->author->id != g_sudo_id) return; 71 | 72 | FILE *fp = popen(msg->content, "r"); 73 | if (NULL == fp) { 74 | printf("Failed to run command"); 75 | return; 76 | } 77 | 78 | char *path = calloc(1, MAX_FSIZE); 79 | char *pathtmp = calloc(1, MAX_FSIZE); 80 | 81 | while (NULL != fgets(path, MAX_FSIZE, fp)) { 82 | strncat(pathtmp, path, MAX_FSIZE - 1); 83 | } 84 | 85 | const size_t fsize = strlen(pathtmp); 86 | struct discord_create_message_params params = { 0 }; 87 | 88 | if (fsize <= DISCORD_MAX_MESSAGE_LEN) { 89 | params.content = pathtmp; 90 | } 91 | else { 92 | params.attachments = (struct discord_attachment *[]){ 93 | &(struct discord_attachment){ 94 | .content = pathtmp, 95 | .size = fsize, 96 | }, 97 | NULL // end of array 98 | }; 99 | } 100 | 101 | discord_async_next(client, NULL); 102 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 103 | 104 | pclose(fp); 105 | free(path); 106 | free(pathtmp); 107 | } 108 | 109 | int main(int argc, char *argv[]) 110 | { 111 | setlocale(LC_ALL, ""); 112 | 113 | const char *config_file; 114 | if (argc > 1) 115 | config_file = argv[1]; 116 | else 117 | config_file = "../config.json"; 118 | 119 | orca_global_init(); 120 | struct discord *client = discord_config_init(config_file); 121 | assert(NULL != client && "Couldn't initialize client"); 122 | 123 | discord_set_prefix(client, "$"); 124 | discord_set_on_command(client, NULL, &on_fallback); 125 | discord_set_on_command(client, "cd", &on_cd); 126 | discord_set_on_commands(client, &on_less_like, "less", "cat", "hexdump", 127 | NULL); 128 | 129 | printf("\n\nThis bot allows navigating its host machine like" 130 | " a shell terminal.\n\n" 131 | "DISCLAIMER: This bot is potentially dangerous if not" 132 | " used with care.\nOnly give admin privileges to yourself" 133 | " or someone trustworthy.\n\n\n"); 134 | 135 | do { 136 | printf("User ID to have sudo privileges\n"); 137 | fscanf(stdin, "%" SCNu64, &g_sudo_id); 138 | } while (!g_sudo_id || errno == ERANGE); 139 | 140 | discord_run(client); 141 | 142 | discord_cleanup(client); 143 | orca_global_cleanup(); 144 | } 145 | -------------------------------------------------------------------------------- /examples/bot-voice.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <string.h> 4 | #include <inttypes.h> 5 | #include <assert.h> 6 | 7 | #include "discord.h" 8 | #include "discord-internal.h" 9 | 10 | void on_ready(struct discord *client) 11 | { 12 | const struct discord_user *bot = discord_get_self(client); 13 | 14 | log_info("Voice-Bot succesfully connected to Discord as %s#%s!", 15 | bot->username, bot->discriminator); 16 | } 17 | 18 | void on_list_voice_regions(struct discord *client, 19 | const struct discord_message *msg) 20 | { 21 | if (msg->author->bot) return; 22 | 23 | struct discord_voice_region **voice_regions = NULL; 24 | 25 | discord_list_voice_regions(client, &voice_regions); 26 | 27 | if (!voice_regions) { 28 | log_error("Could not obtain voice regions"); 29 | return; 30 | } 31 | 32 | struct discord_create_message_params params = { 0 }; 33 | for (size_t i = 0; voice_regions[i]; ++i) { 34 | params.content = voice_regions[i]->name; 35 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 36 | } 37 | 38 | discord_voice_region_list_free(voice_regions); 39 | } 40 | 41 | void on_voice_join(struct discord *client, const struct discord_message *msg) 42 | { 43 | if (msg->author->bot) return; 44 | 45 | struct discord_channel vchannel; 46 | int position = -1; 47 | 48 | sscanf(msg->content, "%d", &position); 49 | 50 | discord_get_channel_at_pos(client, msg->guild_id, 51 | DISCORD_CHANNEL_GUILD_VOICE, position - 1, 52 | &vchannel); 53 | 54 | if (vchannel.id != 0) { // founds voice channel at pos 55 | discord_voice_join(client, msg->guild_id, vchannel.id, false, false); 56 | } 57 | else { // couldn't find a voice channel at pos 58 | struct discord_create_message_params params = { 59 | .content = "Invalid channel position" 60 | }; 61 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 62 | } 63 | 64 | discord_channel_cleanup(&vchannel); 65 | } 66 | 67 | void on_voice_kick(struct discord *client, const struct discord_message *msg) 68 | { 69 | if (msg->author->bot) return; 70 | 71 | char text[DISCORD_MAX_MESSAGE_LEN]; 72 | u64_snowflake_t user_id = 0; 73 | 74 | sscanf(msg->content, "%" SCNu64, &user_id); 75 | 76 | if (!user_id) { 77 | sprintf(text, "Couldn't find user"); 78 | } 79 | else { 80 | discord_disconnect_guild_member(client, msg->guild_id, user_id, NULL); 81 | snprintf(text, sizeof(text), "<@!%" PRIu64 "> has been kicked from VC", 82 | user_id); 83 | } 84 | 85 | struct discord_create_message_params params = { .content = text }; 86 | discord_create_message(client, msg->channel_id, ¶ms, NULL); 87 | } 88 | 89 | void log_on_voice_state_update(struct discord *client, 90 | const struct discord_voice_state *vs) 91 | { 92 | log_info("User <@!%" PRIu64 "> has joined <#%" PRIu64 ">!", vs->user_id, 93 | vs->channel_id); 94 | } 95 | 96 | int main(int argc, char *argv[]) 97 | { 98 | const char *config_file; 99 | if (argc > 1) 100 | config_file = argv[1]; 101 | else 102 | config_file = "../config.json"; 103 | 104 | orca_global_init(); 105 | struct discord *client = discord_config_init(config_file); 106 | assert(NULL != client && "Couldn't initialize client"); 107 | 108 | discord_set_on_voice_state_update(client, &log_on_voice_state_update); 109 | discord_set_prefix(client, "voice."); 110 | discord_set_on_command(client, "list_regions", &on_list_voice_regions); 111 | discord_set_on_command(client, "join", &on_voice_join); 112 | discord_set_on_command(client, "kick", &on_voice_kick); 113 | 114 | printf("\n\nThis bot is a work in progress, it should demonstrate some " 115 | "Voice related utilities\n" 116 | "1. Type 'voice.list_regions' to list regions that can be used when " 117 | "creating servers\n" 118 | "2. Type 'voice.join <channel position>' to join a particular voice " 119 | "channel by its position\n" 120 | "3. Type 'voice.kick <user id>' to kick a particular user from the " 121 | "voice channel he's at\n" 122 | "\nTYPE ANY KEY TO START BOT\n"); 123 | fgetc(stdin); // wait for input 124 | 125 | discord_run(client); 126 | 127 | discord_cleanup(client); 128 | orca_global_cleanup(); 129 | } 130 | -------------------------------------------------------------------------------- /examples/bot-webhook.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <string.h> 4 | #include <unistd.h> 5 | #include <assert.h> 6 | 7 | #include "discord.h" 8 | 9 | static void print_usage(char *prog) 10 | { 11 | fprintf(stderr, "Usage: %s -i webhook-id -h webhook-token\n", prog); 12 | exit(EXIT_FAILURE); 13 | } 14 | 15 | int main(int argc, char *argv[]) 16 | { 17 | u64_snowflake_t webhook_id = 0; 18 | char *webhook_token = NULL; 19 | int opt; 20 | 21 | while (-1 != (opt = getopt(argc, argv, "i:t:"))) { 22 | switch (opt) { 23 | case 't': webhook_token = strdup(optarg); break; 24 | case 'i': webhook_id = strtoull(optarg, NULL, 10); break; 25 | default: print_usage(argv[0]); break; 26 | } 27 | } 28 | if (!webhook_token || !webhook_id) print_usage(argv[0]); 29 | 30 | printf("\n\nThis bot demonstrates how to use webhook endpoints which " 31 | "require no authentication token\n" 32 | "\nTYPE ANY KEY TO START BOT\n"); 33 | fgetc(stdin); // wait for input 34 | 35 | orca_global_init(); 36 | struct discord *client = discord_init(NULL); 37 | assert(NULL != client && "Couldn't initialize client"); 38 | 39 | struct discord_webhook webhook; 40 | discord_webhook_init(&webhook); 41 | 42 | discord_get_webhook_with_token(client, webhook_id, webhook_token, &webhook); 43 | discord_webhook_cleanup(&webhook); 44 | 45 | discord_execute_webhook( 46 | client, webhook_id, webhook_token, 47 | &(struct discord_execute_webhook_params){ .content = "Hello world!" }, 48 | NULL); 49 | 50 | free(webhook_token); 51 | discord_cleanup(client); 52 | orca_global_cleanup(); 53 | 54 | return EXIT_SUCCESS; 55 | } 56 | -------------------------------------------------------------------------------- /github-client.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <string.h> 4 | #include <math.h> 5 | #include <errno.h> 6 | 7 | #include "cee-utils.h" 8 | #include "cee-utils/ntl.h" 9 | #include "json-actor.h" 10 | 11 | #include "github.h" 12 | #include "github-internal.h" 13 | 14 | static void 15 | _github_presets_init(struct github_presets *presets, 16 | const struct sized_buffer *username, 17 | const struct sized_buffer *token, 18 | const char *repo_config) 19 | { 20 | 21 | presets->owner = NULL; 22 | presets->repo = NULL; 23 | presets->default_branch = NULL; 24 | 25 | /* Optionally fill in the repo_config. Can be 26 | * done later with github_fill_repo_config. */ 27 | if (repo_config) { 28 | size_t len = 0; 29 | char *json = cee_load_whole_file(repo_config, &len); 30 | 31 | json_extract(json, len, "(owner):?s,(repo):?s,(default_branch):?s", 32 | &presets->owner, &presets->repo, &presets->default_branch); 33 | 34 | free(json); 35 | } 36 | 37 | cee_strndup(username->start, username->size, &presets->username); 38 | cee_strndup(token->start, token->size, &presets->token); 39 | } 40 | 41 | void 42 | github_write_json(char *json, size_t len, void *user_obj) 43 | { 44 | struct sized_buffer *new_user_obj = user_obj; 45 | 46 | new_user_obj->size = cee_strndup(json, len, &new_user_obj->start); 47 | } 48 | 49 | ORCAcode 50 | github_fill_repo_config(struct github *client, char *repo_config) 51 | { 52 | size_t len = 0; 53 | char *json; 54 | 55 | ORCA_EXPECT(client, !IS_EMPTY_STRING(repo_config), ORCA_BAD_PARAMETER, ""); 56 | 57 | json = cee_load_whole_file(repo_config, &len); 58 | 59 | json_extract(json, len, "(owner):?s,(repo):?s,(default_branch):?s", 60 | &client->presets.owner, &client->presets.repo, 61 | &client->presets.default_branch); 62 | 63 | free(json); 64 | 65 | return ORCA_OK; 66 | } 67 | 68 | struct github * 69 | github_init(const char username[], 70 | const char token[], 71 | const char repo_config[]) 72 | { 73 | const struct sized_buffer _username = { (char *)username, strlen(username) }; 74 | const struct sized_buffer _token = { (char *)token, strlen(token) }; 75 | struct github *new_client; 76 | 77 | new_client = calloc(1, sizeof *new_client); 78 | logconf_setup(&new_client->conf, "GITHUB", NULL); 79 | 80 | _github_presets_init(&new_client->presets, &_username, &_token, repo_config); 81 | 82 | github_adapter_init(&new_client->adapter, &new_client->conf, 83 | &new_client->presets); 84 | 85 | return new_client; 86 | } 87 | 88 | struct github * 89 | github_config_init(const char config_file[], const char repo_config[]) 90 | { 91 | struct sized_buffer username, token; 92 | struct github *new_client; 93 | FILE *fp; 94 | 95 | fp = fopen(config_file, "rb"); 96 | VASSERT_S(fp != NULL, "Couldn't open '%s': %s", config_file, 97 | strerror(errno)); 98 | 99 | new_client = calloc(1, sizeof *new_client); 100 | logconf_setup(&new_client->conf, "GITHUB", fp); 101 | 102 | fclose(fp); 103 | 104 | username = logconf_get_field(&new_client->conf, "github.username"); 105 | token = logconf_get_field(&new_client->conf, "github.token"); 106 | 107 | _github_presets_init(&new_client->presets, &username, &token, repo_config); 108 | 109 | github_adapter_init(&new_client->adapter, &new_client->conf, 110 | &new_client->presets); 111 | 112 | return new_client; 113 | } 114 | -------------------------------------------------------------------------------- /github-internal.h: -------------------------------------------------------------------------------- 1 | #ifndef GITHUB_INTERNAL_H 2 | #define GITHUB_INTERNAL_H 3 | 4 | #include "user-agent.h" 5 | #include "cee-utils.h" 6 | #include "json-actor.h" 7 | 8 | struct github_presets { 9 | char *owner; 10 | char *username; 11 | char *token; 12 | char *repo; 13 | char *default_branch; 14 | }; 15 | 16 | struct github_request_attr { 17 | /** the object itself */ 18 | void *obj; 19 | /** size of `obj` in bytes */ 20 | size_t size; 21 | /** initialize `obj` fields */ 22 | void (*init)(void *obj); 23 | /** callback for filling `obj` with JSON values */ 24 | void (*from_json)(char *json, size_t len, void *obj); 25 | /** perform a cleanup on `obj` */ 26 | void (*cleanup)(void *obj); 27 | }; 28 | 29 | struct github_adapter { 30 | /** GITHUB_HTTP logging module */ 31 | struct logconf conf; 32 | struct user_agent *ua; 33 | }; 34 | 35 | void github_adapter_init(struct github_adapter *adapter, 36 | struct logconf *conf, 37 | struct github_presets *presets); 38 | 39 | ORCAcode github_adapter_run(struct github_adapter *adapter, 40 | struct github_request_attr *attr, 41 | struct sized_buffer *body, 42 | enum http_method method, 43 | char endpoint_fmt[], 44 | ...); 45 | 46 | struct github { 47 | struct logconf conf; 48 | struct github_adapter adapter; 49 | struct github_presets presets; 50 | }; 51 | 52 | #endif /* GITHUB_INTERNAL_H */ 53 | -------------------------------------------------------------------------------- /github.h: -------------------------------------------------------------------------------- 1 | #ifndef GITHUB_H 2 | #define GITHUB_H 3 | 4 | #include <stdbool.h> 5 | #include "json-actor-boxed.h" 6 | #include "common.h" 7 | #include "logconf.h" 8 | 9 | /* see specs/github/ for specs */ 10 | #include "specs-code/github/one-specs.h" 11 | 12 | #define GITHUB_BASE_API_URL "https://api.github.com" 13 | 14 | /* This limit is fairly arbitrary, find the maximum at a later time. */ 15 | #define GITHUB_MAXIMUM_REPO_TOPICS 30 16 | 17 | /* Github error codes */ 18 | #define ORCA_GITHUB_JSON 1 19 | #define ORCA_GITHUB_BAD_AUTH 2 20 | #define ORCA_GITHUB_NOT_STARRED 404 21 | 22 | struct github_file { 23 | char *path; 24 | char *sha; 25 | }; 26 | 27 | struct github *github_init(const char username[], 28 | const char token[], 29 | const char repo_config[]); 30 | 31 | struct github *github_config_init(const char config_file[], 32 | const char repo_config[]); 33 | 34 | void github_write_json(char *json, size_t len, void *user_obj); 35 | 36 | ORCAcode github_fill_repo_config(struct github *client, char *repo_config); 37 | 38 | ORCAcode github_get_repository(struct github *client, 39 | char *owner, 40 | char *repo, 41 | struct sized_buffer *ret); 42 | 43 | ORCAcode github_create_fork(struct github *client, char *owner, char *repo); 44 | 45 | ORCAcode github_update_my_fork(struct github *client, char **p_sha); 46 | 47 | ORCAcode github_get_head_commit(struct github *client, char **p_sha); 48 | 49 | ORCAcode github_get_tree_sha(struct github *client, 50 | char *commit_sha, 51 | char **p_sha); 52 | 53 | ORCAcode github_create_blobs(struct github *client, 54 | struct github_file **files); 55 | 56 | ORCAcode github_create_tree(struct github *client, 57 | char *base_tree_sha, 58 | struct github_file **files, 59 | char **p_tree_sha); 60 | 61 | ORCAcode github_create_a_commit(struct github *client, 62 | char *tree_sha, 63 | char *parent_commit_sha, 64 | char *commit_msg, 65 | char **p_commit_sha); 66 | 67 | ORCAcode github_create_a_branch(struct github *client, 68 | char *head_commit_sha, 69 | char *branch); 70 | 71 | ORCAcode github_update_a_commit(struct github *client, 72 | char *branch, 73 | char *commit_sha); 74 | 75 | ORCAcode github_create_a_pull_request(struct github *client, 76 | char *branch, 77 | char *pull_msg); 78 | 79 | ORCAcode github_get_user(struct github *client, 80 | char *username, 81 | struct github_user *ret); 82 | 83 | ORCAcode github_get_gist(struct github *client, 84 | char *id, 85 | struct github_gist *ret); 86 | 87 | ORCAcode github_create_gist(struct github *client, 88 | struct github_gist_create_params *params, 89 | struct github_gist *ret); 90 | 91 | ORCAcode github_gist_is_starred(struct github *client, char *id); 92 | 93 | #endif /* GITHUB_H */ 94 | -------------------------------------------------------------------------------- /licenses/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Stensal Inc 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 | -------------------------------------------------------------------------------- /licenses/third-party/LICENSE.curl-websockets: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016 Gustavo Sverzut Barbieri 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | https://opensource.org/licenses/MIT 23 | -------------------------------------------------------------------------------- /licenses/third-party/LICENSE.jsmn: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Serge A. Zaitsev 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /licenses/third-party/LICENSE.json-string: -------------------------------------------------------------------------------- 1 | the algorithms of json-string.c are ported and customized from 2 | https://github.com/artyom-beilis/cppcms' json code 3 | 4 | Copyright (c) 2018 Artyom Beilis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /licenses/third-party/LICENSE.threadpool: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Mathias Brossard. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /licenses/third-party/LICENSE.utf8: -------------------------------------------------------------------------------- 1 | https://github.com/legitparty/jsmn 2 | -------------------------------------------------------------------------------- /my_bot/.gitignore: -------------------------------------------------------------------------------- 1 | myBot 2 | -------------------------------------------------------------------------------- /my_bot/Makefile: -------------------------------------------------------------------------------- 1 | # This Makefile assumes the top folder has been built 2 | 3 | # Default build: 4 | # $ make 5 | # Cleanup build: 6 | # $ make clean 7 | # Print this Makefile variables 8 | # $ make echo 9 | 10 | # If you are a Makefile newbie we recommend that you reading the following to 11 | # better understand this file: 12 | # 13 | # Automatic Variables: 14 | # gnu.org/software/make/manual/html_node/Automatic-Variables.html 15 | # Prerequisite Types: 16 | # gnu.org/software/make/manual/html_node/Prerequisite-Types.html 17 | 18 | .SILENT: config 19 | 20 | # Orca's relative location to this Makefile 21 | ORCA = .. 22 | 23 | # default compiler is gcc, but can be changed at the CLI 24 | # ex: '$ CC=clang make' 25 | CC ?= gcc 26 | 27 | LIBDIR := $(ORCA)/lib 28 | OBJDIR := obj 29 | 30 | # add your source files here 31 | SRC := myBot.c 32 | # this will match each individual .c file to a .o 33 | OBJS := $(SRC:%.c=$(OBJDIR)/%.o) 34 | # your bot executable's name 35 | MAIN := myBot 36 | # your bot config file 37 | CONF := config.json 38 | 39 | # -O0 no optimization 40 | # -Wall emit 'all' warnings 41 | # -g keep this for human-readable debugging 42 | # -pthread tell the compiler to link the pthread library 43 | # aswell configure compilation for threads 44 | # -I add header folder to compiler search path 45 | CFLAGS := -O0 -g -Wall -pthread \ 46 | -I. -I$(ORCA) -I$(ORCA)/cee-utils \ 47 | -I$(ORCA)/common -I$(ORCA)/common/third-party 48 | 49 | # -L add library folder to compiler search path 50 | # -ldiscord links against libdiscord.a 51 | # -lcurl links against libcurl, a orca dependency 52 | LDFLAGS := -L$(LIBDIR) -ldiscord -lcurl 53 | 54 | all: $(MAIN) $(CONF) 55 | 56 | # Compile the bot executable with the given the object files 57 | $(MAIN): $(OBJS) 58 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 59 | 60 | # Make sure the obj directory exists before building the object files 61 | $(OBJS): | $(OBJDIR) 62 | 63 | $(OBJDIR): 64 | @ mkdir -p $(OBJDIR) 65 | 66 | # Match each .o file to its .c counterpart (foo.o: foo.c) 67 | $(OBJDIR)/%.o: %.c 68 | $(CC) $(CFLAGS) -c -o $@ $< 69 | 70 | $(CONF): 71 | cp -n $(ORCA)/config.json $(CONF) 72 | echo "A template config file was copied into this folder." 73 | echo "Replace 'YOUR-BOT-TOKEN' with an actual token at $(CONF)" 74 | 75 | clean: 76 | rm -rf $(MAIN) 77 | rm -rf $(OBJDIR) 78 | 79 | echo: 80 | @ echo -e 'CC: $(CC)\n' 81 | @ echo -e 'ORCA: $(ORCA)\n' 82 | @ echo -e 'SRC: $(SRC)\n' 83 | @ echo -e 'OBJS: $(OBJS)\n' 84 | @ echo -e 'MAIN: $(MAIN)\n' 85 | 86 | .PHONY: all clean config 87 | -------------------------------------------------------------------------------- /my_bot/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "logging": { 3 | "level": "trace", 4 | "filename": "bot.log", 5 | "quiet": false, 6 | "overwrite": true, 7 | "use_color": true, 8 | "http": { 9 | "enable": true, 10 | "filename": "http.log" 11 | }, 12 | "disable_modules": ["WEBSOCKETS", "USER_AGENT", "DISCORD_GATEWAY"] 13 | }, 14 | "discord": { 15 | "token": "YOUR-BOT-TOKEN", 16 | "default_prefix": { 17 | "enable": false, 18 | "prefix": "!" 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /my_bot/myBot.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include "discord.h" 3 | 4 | void on_ready(struct discord* client) 5 | { 6 | const struct discord_user *bot = discord_get_self(client); 7 | 8 | log_info("Logged in as %s!", bot->username); 9 | } 10 | 11 | int main(void) 12 | { 13 | struct discord *client = discord_config_init("config.json"); 14 | 15 | discord_set_on_ready(client, &on_ready); 16 | discord_run(client); 17 | 18 | discord_cleanup(client); 19 | 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /reddit-client.c: -------------------------------------------------------------------------------- 1 | #include <stdlib.h> 2 | #include <string.h> 3 | #include <errno.h> 4 | 5 | #include "reddit.h" 6 | #include "reddit-internal.h" 7 | 8 | static void 9 | _reddit_init(struct reddit *new_client) 10 | { 11 | reddit_adapter_init(&new_client->adapter, &new_client->conf); 12 | } 13 | 14 | struct reddit * 15 | reddit_init(const char username[], 16 | const char password[], 17 | const char client_id[], 18 | const char client_secret[]) 19 | { 20 | struct reddit *new_client; 21 | 22 | new_client = calloc(1, sizeof *new_client); 23 | logconf_setup(&new_client->conf, "REDDIT", NULL); 24 | 25 | /* TODO: fix memory leak */ 26 | new_client->username.size = 27 | cee_strndup(username, strlen(username), &new_client->username.start); 28 | new_client->password.size = 29 | cee_strndup(password, strlen(password), &new_client->password.start); 30 | new_client->client_id.size = 31 | cee_strndup(client_id, strlen(client_id), &new_client->client_id.start); 32 | new_client->client_secret.size = cee_strndup( 33 | client_secret, strlen(client_secret), &new_client->client_secret.start); 34 | 35 | _reddit_init(new_client); 36 | 37 | return new_client; 38 | } 39 | 40 | struct reddit * 41 | reddit_config_init(const char config_file[]) 42 | { 43 | struct reddit *new_client = calloc(1, sizeof *new_client); 44 | FILE *fp; 45 | 46 | fp = fopen(config_file, "rb"); 47 | VASSERT_S(fp != NULL, "Couldn't open '%s': %s", config_file, 48 | strerror(errno)); 49 | 50 | new_client = calloc(1, sizeof *new_client); 51 | logconf_setup(&new_client->conf, "REDDIT", fp); 52 | fclose(fp); 53 | 54 | new_client->username = 55 | logconf_get_field(&new_client->conf, "reddit.username"); 56 | new_client->password = 57 | logconf_get_field(&new_client->conf, "reddit.password"); 58 | new_client->client_id = 59 | logconf_get_field(&new_client->conf, "reddit.client_id"); 60 | new_client->client_secret = 61 | logconf_get_field(&new_client->conf, "reddit.client_secret"); 62 | 63 | _reddit_init(new_client); 64 | 65 | return new_client; 66 | } 67 | 68 | void 69 | reddit_cleanup(struct reddit *client) 70 | { 71 | logconf_cleanup(&client->conf); 72 | reddit_adapter_cleanup(&client->adapter); 73 | free(client); 74 | } 75 | -------------------------------------------------------------------------------- /reddit-internal.h: -------------------------------------------------------------------------------- 1 | #ifndef REDDIT_INTERNAL_H 2 | #define REDDIT_INTERNAL_H 3 | 4 | #include "json-actor.h" 5 | #include "json-actor-boxed.h" 6 | 7 | #include "user-agent.h" 8 | #include "websockets.h" 9 | #include "cee-utils.h" 10 | 11 | struct reddit_request_attr { 12 | /** the object itself */ 13 | void *obj; 14 | /** size of `obj` in bytes */ 15 | size_t size; 16 | /** initialize `obj` fields */ 17 | void (*init)(void *obj); 18 | /** callback for filling `obj` with JSON values */ 19 | void (*from_json)(char *json, size_t len, void *obj); 20 | /** perform a cleanup on `obj` */ 21 | void (*cleanup)(void *obj); 22 | /** override default URL */ 23 | char *base_url; 24 | }; 25 | 26 | struct reddit_adapter { 27 | struct logconf conf; 28 | struct user_agent *ua; 29 | char *auth; 30 | }; 31 | 32 | void reddit_adapter_init(struct reddit_adapter *adapter, struct logconf *conf); 33 | void reddit_adapter_cleanup(struct reddit_adapter *adapter); 34 | 35 | ORCAcode reddit_adapter_run(struct reddit_adapter *adapter, 36 | struct reddit_request_attr *attr, 37 | struct sized_buffer *body, 38 | enum http_method method, 39 | char endpoint[], 40 | ...); 41 | 42 | struct reddit { 43 | struct logconf conf; 44 | 45 | struct sized_buffer username; 46 | struct sized_buffer password; 47 | struct sized_buffer client_id; 48 | struct sized_buffer client_secret; 49 | 50 | struct reddit_adapter adapter; 51 | }; 52 | 53 | #endif /* REDDIT_INTERNAL_H */ 54 | -------------------------------------------------------------------------------- /reddit.h: -------------------------------------------------------------------------------- 1 | #ifndef REDDIT_H 2 | #define REDDIT_H 3 | 4 | #include <stdbool.h> 5 | #include "json-actor-boxed.h" 6 | #include "common.h" 7 | #include "logconf.h" 8 | 9 | /* see specs/reddit/ for specs */ 10 | #include "specs-code/reddit/one-specs.h" 11 | 12 | #define REDDIT_BASE_API_URL "https://www.reddit.com" 13 | #define REDDIT_BASE_OAUTH_URL "https://oauth.reddit.com" 14 | 15 | struct reddit *reddit_init(const char username[], 16 | const char password[], 17 | const char client_id[], 18 | const char client_secret[]); 19 | 20 | struct reddit *reddit_config_init(const char config_file[]); 21 | 22 | void reddit_cleanup(struct reddit *client); 23 | 24 | ORCAcode reddit_access_token(struct reddit *client, 25 | struct reddit_access_token_params *params, 26 | struct sized_buffer *ret); 27 | 28 | ORCAcode reddit_comment(struct reddit *client, 29 | struct reddit_comment_params *params, 30 | struct sized_buffer *ret); 31 | 32 | ORCAcode reddit_search(struct reddit *client, 33 | struct reddit_search_params *params, 34 | char subreddit[], 35 | struct sized_buffer *ret); 36 | 37 | #endif /* REDDIT_H */ 38 | -------------------------------------------------------------------------------- /scripts/diffuse_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | while [ $# -ne 0 ]; do 4 | case $1 in 5 | "--unstage") 6 | unstage="Y" 7 | ;; 8 | "--help") 9 | echo "Usage: $0 [--unstage]" 10 | exit 1 11 | ;; 12 | *) 13 | echo "Usage: $0 [--unstage]" 14 | exit 1 15 | ;; 16 | esac 17 | shift 18 | done 19 | 20 | for i in $(git status -s | awk '{ print $2'}); do 21 | diffuse $i 22 | if [ -z "unstage" ]; then 23 | read -p "STAGE '$i' ? y/[n]:" ans 24 | if [ "$ans" = "y" ] || [ "$ans" = "Y" ]; then 25 | echo "git add $i" 26 | git add $i 27 | fi 28 | else 29 | read -p "UNSTAGE '$i' ? y/[n]:" ans 30 | if [ "$ans" = "y" ] || [ "$ans" = "Y" ]; then 31 | echo "git reset HEAD $i" 32 | git reset HEAD $i 33 | fi 34 | fi 35 | done 36 | -------------------------------------------------------------------------------- /scripts/get-cee-utils.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -o pipefail 4 | 5 | mypath=$(dirname $(readlink -f $0)) 6 | url="https://raw.githubusercontent.com/cee-studio/cee-utils/master" 7 | 8 | list="README.md 9 | debug.h 10 | jsmn.h 11 | json-actor-boxed.c 12 | json-actor-boxed.h 13 | json-actor.c 14 | json-actor.h 15 | json-string.c 16 | json-struct.c 17 | log.c 18 | logconf.c 19 | logconf.h 20 | log.h 21 | ntl.c 22 | ntl.h 23 | cee-utils.c 24 | cee-utils.h 25 | greatest.h 26 | uthash.h 27 | cee-data-sizes.h 28 | clock.h 29 | " 30 | 31 | mkdir -p $mypath/../cee-utils 32 | pushd $mypath/../cee-utils 33 | for i in $list; do 34 | echo "getting $i" 35 | echo "$url/$i" 36 | wget $url/$i -O $i 37 | done 38 | popd 39 | -------------------------------------------------------------------------------- /slack-client.c: -------------------------------------------------------------------------------- 1 | #include <stdlib.h> 2 | #include <string.h> 3 | #include <errno.h> 4 | 5 | #include "slack.h" 6 | #include "slack-internal.h" 7 | 8 | struct slack * 9 | slack_config_init(const char config_file[]) 10 | { 11 | struct slack *new_client; 12 | FILE *fp; 13 | 14 | orca_global_init(); 15 | 16 | fp = fopen(config_file, "rb"); 17 | VASSERT_S(fp != NULL, "Couldn't open '%s': %s", config_file, 18 | strerror(errno)); 19 | 20 | new_client = calloc(1, sizeof *new_client); 21 | logconf_setup(&new_client->conf, "SLACK", fp); 22 | 23 | fclose(fp); 24 | 25 | new_client->bot_token = 26 | logconf_get_field(&new_client->conf, "slack.bot_token"); 27 | new_client->app_token = 28 | logconf_get_field(&new_client->conf, "slack.app_token"); 29 | 30 | slack_webapi_init(&new_client->webapi, &new_client->conf, 31 | &new_client->bot_token); 32 | slack_sm_init(&new_client->sm, &new_client->conf); 33 | 34 | return new_client; 35 | } 36 | 37 | void 38 | slack_cleanup(struct slack *client) 39 | { 40 | logconf_cleanup(&client->conf); 41 | slack_webapi_cleanup(&client->webapi); 42 | slack_sm_cleanup(&client->sm); 43 | 44 | free(client); 45 | } 46 | 47 | void 48 | slack_run(struct slack *client) 49 | { 50 | slack_sm_run(&client->sm); 51 | } 52 | 53 | void 54 | slack_set_event_scheduler(struct slack *client, slack_on_scheduler callback) 55 | { 56 | client->sm.cbs.scheduler = callback; 57 | } 58 | 59 | void 60 | slack_set_on_idle(struct slack *client, slack_on_event callback) 61 | { 62 | client->sm.cbs.on_hello = callback; 63 | } 64 | 65 | void 66 | slack_set_on_hello(struct slack *client, slack_on_event callback) 67 | { 68 | client->sm.cbs.on_hello = callback; 69 | } 70 | 71 | void 72 | slack_set_on_message(struct slack *client, slack_on_event callback) 73 | { 74 | client->sm.cbs.on_message = callback; 75 | } 76 | 77 | void 78 | slack_set_on_block_actions(struct slack *client, slack_on_event callback) 79 | { 80 | client->sm.cbs.on_block_actions = callback; 81 | } 82 | 83 | void 84 | slack_set_on_message_actions(struct slack *client, slack_on_event callback) 85 | { 86 | client->sm.cbs.on_message_actions = callback; 87 | } 88 | 89 | void 90 | slack_set_on_view_closed(struct slack *client, slack_on_event callback) 91 | { 92 | client->sm.cbs.on_view_closed = callback; 93 | } 94 | 95 | void 96 | slack_set_on_view_submission(struct slack *client, slack_on_event callback) 97 | { 98 | client->sm.cbs.on_view_submission = callback; 99 | } 100 | -------------------------------------------------------------------------------- /slack-internal.h: -------------------------------------------------------------------------------- 1 | #ifndef SLACK_INTERNAL_H 2 | #define SLACK_INTERNAL_H 3 | 4 | #include <pthread.h> 5 | 6 | #include "json-actor.h" 7 | #include "json-actor-boxed.h" 8 | 9 | #include "user-agent.h" 10 | #include "websockets.h" 11 | #include "cee-utils.h" 12 | 13 | /** @brief Get client from its nested field */ 14 | #define CLIENT(ptr, path) CONTAINEROF(ptr, struct slack, path) 15 | 16 | struct slack_request_attr { 17 | /** the object itself */ 18 | void *obj; 19 | /** size of `obj` in bytes */ 20 | size_t size; 21 | /** initialize `obj` fields */ 22 | void (*init)(void *obj); 23 | /** callback for filling `obj` with JSON values */ 24 | void (*from_json)(char *json, size_t len, void *obj); 25 | /** perform a cleanup on `obj` */ 26 | void (*cleanup)(void *obj); 27 | 28 | /** client token-level */ 29 | enum { 30 | SLACK_TOKEN_BOT = 0, 31 | SLACK_TOKEN_APP, 32 | } token_level; 33 | 34 | /** override default content-type */ 35 | char *content_type; 36 | }; 37 | 38 | struct slack_webapi { 39 | struct user_agent *ua; 40 | struct logconf conf; 41 | }; 42 | 43 | /* ADAPTER PRIVATE FUNCTIONS */ 44 | void slack_webapi_init(struct slack_webapi *webapi, 45 | struct logconf *conf, 46 | struct sized_buffer *token); 47 | 48 | void slack_webapi_cleanup(struct slack_webapi *webapi); 49 | 50 | ORCAcode slack_webapi_run(struct slack_webapi *webapi, 51 | struct slack_request_attr *attr, 52 | struct sized_buffer *body, 53 | enum http_method method, 54 | char endpoint_fmt[], 55 | ...); 56 | 57 | struct slack_sm { 58 | struct websockets *ws; 59 | struct logconf conf; 60 | CURLM *mhandle; 61 | 62 | bool is_ready; 63 | 64 | /* SOCKETMODE HEARTBEAT STRUCT */ 65 | struct { 66 | uint64_t tstamp; 67 | long interval_ms; 68 | } hbeat; 69 | 70 | /* CALLBACKS STRUCTURE */ 71 | struct { 72 | /** trigers in every event loop iteration */ 73 | slack_on_event on_idle; 74 | /** triggers when connections first establishes */ 75 | slack_on_event on_hello; 76 | 77 | /* EVENT API CALLBACKS */ 78 | /** triggers when a message is sent */ 79 | slack_on_event on_message; 80 | 81 | /* INTERACTION CALLBACKS */ 82 | /** triggers when a block_action interaction occurs */ 83 | slack_on_event on_block_actions; 84 | /** triggers when a message_action interaction occurs */ 85 | slack_on_event on_message_actions; 86 | /** triggers when a view_closed interaction occurs */ 87 | slack_on_event on_view_closed; 88 | /** triggers when a view_submission interaction occurs */ 89 | slack_on_event on_view_submission; 90 | 91 | /** 92 | * Handle context on how each event callback is executed 93 | * @see slack_set_scheduler() */ 94 | slack_on_scheduler scheduler; 95 | } cbs; 96 | }; 97 | 98 | void slack_sm_init(struct slack_sm *sm, struct logconf *conf); 99 | void slack_sm_cleanup(struct slack_sm *sm); 100 | void slack_sm_run(struct slack_sm *sm); 101 | 102 | struct slack { 103 | struct sized_buffer bot_token; 104 | struct sized_buffer app_token; 105 | 106 | struct slack_webapi webapi; 107 | struct slack_sm sm; 108 | 109 | struct logconf conf; 110 | }; 111 | 112 | struct slack_event { 113 | /** a copy of payload data */ 114 | struct sized_buffer data; 115 | /** the sm client */ 116 | struct slack_sm *sm; 117 | char str_type[64]; 118 | enum slack_sm_types type; 119 | slack_on_event on_event; 120 | }; 121 | 122 | #endif /* SLACK_INTERNAL_H */ 123 | -------------------------------------------------------------------------------- /slack.h: -------------------------------------------------------------------------------- 1 | #ifndef SLACK_H 2 | #define SLACK_H 3 | 4 | #include <stdbool.h> 5 | #include "json-actor-boxed.h" 6 | #include "common.h" 7 | #include "logconf.h" 8 | 9 | /* see specs/slack/ for specs */ 10 | #include "specs-code/slack/one-specs.h" 11 | 12 | #define SLACK_BASE_API_URL "https://slack.com/api" 13 | 14 | struct slack; /* forward declaration */ 15 | 16 | /** @todo generate as specs */ 17 | enum slack_sm_types { 18 | SLACK_SOCKETMODE_TYPE_NONE = 0, 19 | /* EVENTS API ENUMS */ 20 | SLACK_SOCKETMODE_TYPE_MESSAGE, 21 | /* INTERACTION ENUMS */ 22 | SLACK_SOCKETMODE_TYPE_BLOCK_ACTIONS, 23 | SLACK_SOCKETMODE_TYPE_MESSAGE_ACTIONS, 24 | SLACK_SOCKETMODE_TYPE_VIEW_CLOSED, 25 | SLACK_SOCKETMODE_TYPE_VIEW_SUBMISSION 26 | }; 27 | 28 | typedef void (*slack_on_event)(struct slack *client, 29 | const char payload[], 30 | size_t len); 31 | 32 | struct slack *slack_config_init(const char config_file[]); 33 | void slack_cleanup(struct slack *client); 34 | 35 | void slack_set_on_idle(struct slack *client, slack_on_event callback); 36 | void slack_set_on_hello(struct slack *client, slack_on_event callback); 37 | void slack_set_on_message(struct slack *client, slack_on_event callback); 38 | void slack_set_on_block_actions(struct slack *client, slack_on_event callback); 39 | void slack_set_on_message_actions(struct slack *client, 40 | slack_on_event callback); 41 | void slack_set_on_view_closed(struct slack *client, slack_on_event callback); 42 | void slack_set_on_view_submission(struct slack *client, 43 | slack_on_event callback); 44 | 45 | void slack_run(struct slack *client); 46 | 47 | ORCAcode slack_apps_connections_open(struct slack *client, 48 | struct sized_buffer *ret); 49 | 50 | ORCAcode slack_auth_test(struct slack *client, struct sized_buffer *ret); 51 | 52 | ORCAcode slack_chat_post_message(struct slack *client, 53 | struct slack_chat_post_message_params *params, 54 | struct sized_buffer *ret); 55 | 56 | ORCAcode slack_users_info(struct slack *client, 57 | struct slack_users_info_params *params, 58 | struct sized_buffer *ret); 59 | 60 | typedef enum slack_event_scheduler { 61 | /** this event has been handled */ 62 | SLACK_EVENT_IGNORE, 63 | /** handle this event in main thread */ 64 | SLACK_EVENT_MAIN_THREAD, 65 | /** handle this event in a worker thread */ 66 | SLACK_EVENT_WORKER_THREAD 67 | } slack_event_scheduler_t; 68 | 69 | typedef slack_event_scheduler_t (*slack_on_scheduler)( 70 | struct slack *client, 71 | struct sized_buffer *event_data, 72 | enum slack_sm_types type); 73 | 74 | void slack_set_event_scheduler(struct slack *client, 75 | slack_on_scheduler callback); 76 | 77 | #endif /* SLACK_H */ 78 | -------------------------------------------------------------------------------- /specs-code/github/repository.c: -------------------------------------------------------------------------------- 1 | /* This file is generated from github/repository.json, Please don't edit it. */ 2 | /** 3 | * @file specs-code/github/repository.c 4 | * @see https://docs.github.com/en/rest/reference/repos#get-all-repository-topics 5 | */ 6 | 7 | #include <stdbool.h> 8 | #include <stdlib.h> 9 | #include <string.h> 10 | #include <strings.h> 11 | #include "json-actor.h" 12 | #include "json-actor-boxed.h" 13 | #include "cee-utils.h" 14 | #include "github.h" 15 | 16 | void github_topic_from_json_p(char *json, size_t len, struct github_topic **pp) 17 | { 18 | if (!*pp) *pp = malloc(sizeof **pp); 19 | github_topic_from_json(json, len, *pp); 20 | } 21 | void github_topic_from_json(char *json, size_t len, struct github_topic *p) 22 | { 23 | github_topic_init(p); 24 | json_extract(json, len, 25 | /* github/repository.json:12:28 26 | '{ "name": "names", "type":{ "base":"ja_str", "dec":"ntl"}}' */ 27 | "(names):F,", 28 | /* github/repository.json:12:28 29 | '{ "name": "names", "type":{ "base":"ja_str", "dec":"ntl"}}' */ 30 | ja_str_list_from_json, &p->names); 31 | } 32 | 33 | size_t github_topic_to_json(char *json, size_t len, struct github_topic *p) 34 | { 35 | size_t r; 36 | void *arg_switches[1]={NULL}; 37 | /* github/repository.json:12:28 38 | '{ "name": "names", "type":{ "base":"ja_str", "dec":"ntl"}}' */ 39 | arg_switches[0] = p->names; 40 | 41 | r=json_inject(json, len, 42 | /* github/repository.json:12:28 43 | '{ "name": "names", "type":{ "base":"ja_str", "dec":"ntl"}}' */ 44 | "(names):F," 45 | "@arg_switches:b", 46 | /* github/repository.json:12:28 47 | '{ "name": "names", "type":{ "base":"ja_str", "dec":"ntl"}}' */ 48 | ja_str_list_to_json, p->names, 49 | arg_switches, sizeof(arg_switches), true); 50 | return r; 51 | } 52 | 53 | 54 | void github_topic_cleanup_v(void *p) { 55 | github_topic_cleanup((struct github_topic *)p); 56 | } 57 | 58 | void github_topic_init_v(void *p) { 59 | github_topic_init((struct github_topic *)p); 60 | } 61 | 62 | void github_topic_from_json_v(char *json, size_t len, void *p) { 63 | github_topic_from_json(json, len, (struct github_topic*)p); 64 | } 65 | 66 | size_t github_topic_to_json_v(char *json, size_t len, void *p) { 67 | return github_topic_to_json(json, len, (struct github_topic*)p); 68 | } 69 | 70 | void github_topic_list_free_v(void **p) { 71 | github_topic_list_free((struct github_topic**)p); 72 | } 73 | 74 | void github_topic_list_from_json_v(char *str, size_t len, void *p) { 75 | github_topic_list_from_json(str, len, (struct github_topic ***)p); 76 | } 77 | 78 | size_t github_topic_list_to_json_v(char *str, size_t len, void *p){ 79 | return github_topic_list_to_json(str, len, (struct github_topic **)p); 80 | } 81 | 82 | 83 | void github_topic_cleanup(struct github_topic *d) { 84 | /* github/repository.json:12:28 85 | '{ "name": "names", "type":{ "base":"ja_str", "dec":"ntl"}}' */ 86 | if (d->names) 87 | ja_str_list_free(d->names); 88 | } 89 | 90 | void github_topic_init(struct github_topic *p) { 91 | memset(p, 0, sizeof(struct github_topic)); 92 | /* github/repository.json:12:28 93 | '{ "name": "names", "type":{ "base":"ja_str", "dec":"ntl"}}' */ 94 | 95 | } 96 | void github_topic_list_free(struct github_topic **p) { 97 | ntl_free((void**)p, (void(*)(void*))github_topic_cleanup); 98 | } 99 | 100 | void github_topic_list_from_json(char *str, size_t len, struct github_topic ***p) 101 | { 102 | struct ntl_deserializer d; 103 | memset(&d, 0, sizeof(d)); 104 | d.elem_size = sizeof(struct github_topic); 105 | d.init_elem = NULL; 106 | d.elem_from_buf = (void(*)(char*,size_t,void*))github_topic_from_json_p; 107 | d.ntl_recipient_p= (void***)p; 108 | extract_ntl_from_json2(str, len, &d); 109 | } 110 | 111 | size_t github_topic_list_to_json(char *str, size_t len, struct github_topic **p) 112 | { 113 | return ntl_to_buf(str, len, (void **)p, NULL, (size_t(*)(char*,size_t,void*))github_topic_to_json); 114 | } 115 | 116 | -------------------------------------------------------------------------------- /specs/.gitignore: -------------------------------------------------------------------------------- 1 | specs-gen 2 | specs-code 3 | obj 4 | -------------------------------------------------------------------------------- /specs/Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | 3 | MAIN := specs-gen 4 | 5 | # https://github.com/cee-studio/cee-utils 6 | CEEUTILS_DIR ?= ../cee-utils 7 | 8 | WDIR := specs-code 9 | 10 | JSON := $(sort $(wildcard */*.json)) 11 | APIS := $(sort $(patsubst %/, %, $(dir $(JSON)))) 12 | 13 | # specs code-generator dependencies 14 | DEPS := $(CEEUTILS_DIR)/cee-utils.c \ 15 | $(CEEUTILS_DIR)/json-actor.c \ 16 | $(CEEUTILS_DIR)/ntl.c \ 17 | $(CEEUTILS_DIR)/json-string.c \ 18 | $(CEEUTILS_DIR)/log.c 19 | 20 | # default CFLAGS 21 | CFLAGS += -O0 -g -Wall -Wno-unused-function -I$(CEEUTILS_DIR) 22 | 23 | # for inserting newlines at the end of each foreach 24 | # see https://stackoverflow.com/questions/29651388/insert-a-new-line-in-a-makefile-foreach-loop 25 | blank := 26 | define \n 27 | 28 | $(blank) 29 | endef 30 | 31 | define generate_source 32 | # Generate source files (specs-code/%/*.c) 33 | $(foreach VAR, $(JSON), ./$(MAIN) \ 34 | -c \ 35 | -o $(patsubst %, $(WDIR)/%, $(VAR:%.json=%.c)) \ 36 | -i $(filter $(APIS), $(subst /, ,$(dir $(VAR)))).h \ 37 | $(VAR)$(\n)) 38 | endef 39 | 40 | define generate_headers 41 | # Generate header files (specs-code/%/*.h) 42 | $(foreach VAR, $(JSON), ./$(MAIN) \ 43 | -h \ 44 | -o $(patsubst %, $(WDIR)/%, $(VAR:%.json=%.h)) \ 45 | $(VAR)$(\n)) 46 | endef 47 | 48 | define generate_headers_amalgamation 49 | @ rm -rf $(WDIR)/*/one-specs.h 50 | # Generate single header (specs-code/%/one-specs.h) 51 | $(foreach VAR, $(JSON), ./$(MAIN) \ 52 | -O \ 53 | -a \ 54 | -o $(patsubst %, $(WDIR)/%, $(dir $(VAR))one-specs.h) \ 55 | $(VAR)$(\n)) 56 | $(foreach VAR, $(JSON), ./$(MAIN) \ 57 | -E \ 58 | -a \ 59 | -o $(patsubst %, $(WDIR)/%, $(dir $(VAR))one-specs.h) \ 60 | $(VAR)$(\n)) 61 | $(foreach VAR, $(JSON), ./$(MAIN) \ 62 | -S \ 63 | -a \ 64 | -o $(patsubst %, $(WDIR)/%, $(dir $(VAR))one-specs.h) \ 65 | $(VAR)$(\n)) 66 | $(foreach VAR, $(JSON), ./$(MAIN) \ 67 | -F \ 68 | -a \ 69 | -o $(patsubst %, $(WDIR)/%, $(dir $(VAR))one-specs.h) \ 70 | $(VAR)$(\n)) 71 | endef 72 | 73 | all: $(CEEUTILS_DIR) 74 | $(MAKE) gen_source gen_headers gen_headers_amalgamation 75 | 76 | gen_source: | $(WDIR) 77 | $(generate_source) 78 | 79 | gen_headers: | $(WDIR) 80 | $(generate_headers) 81 | 82 | gen_headers_amalgamation: | $(WDIR) 83 | $(generate_headers_amalgamation) 84 | 85 | $(WDIR): $(APIS) | $(MAIN) 86 | mkdir -p $(addprefix $(WDIR)/, $(APIS)) 87 | 88 | $(APIS): ; 89 | 90 | $(MAIN): $(MAIN).c $(DEPS) 91 | $(CC) $(CFLAGS) -o $@ $^ -lm 92 | 93 | echo: 94 | @ echo -e 'JSON: $(JSON)\n' 95 | @ echo -e 'APIS: $(APIS)\n' 96 | @ echo -e 'MAIN: $(MAIN)\n' 97 | @ echo -e 'CFLAGS: $(CFLAGS)\n' 98 | @ echo -e 'LDFLAGS: $(LDFLAGS)\n' 99 | 100 | clean: 101 | rm -rf $(WDIR) $(MAIN) 102 | 103 | .PHONY : all echo clean 104 | -------------------------------------------------------------------------------- /specs/discord/application.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment":"https://discord.com/developers/docs/resources/application", 4 | "defs": 5 | [ 6 | { 7 | "title":"Application Structure", 8 | "comment": "https://discord.com/developers/docs/resources/application#application-object-application-structure", 9 | "struct":"application", 10 | "fields": 11 | [ 12 | { "name": "id", "type":{ "base":"char", "dec":"*", "converter":"snowflake"}, "comment":"the id of the app" }, 13 | { "name": "name", "type":{ "base":"char", "dec":"*"}, "comment":"the name of the app" }, 14 | { "name": "icon", "type":{ "base":"char", "dec":"*"}, "comment":"the icon hash of the app", "inject_if_not":null }, 15 | { "name": "description", "type":{ "base":"char", "dec":"*"}, "comment":"the description of the app" }, 16 | { "name": "rpc_origins", "type":{ "base":"ja_str", "dec":"ntl"}, "comment":"an array of rpc origin urls, if rpc is enabled", "inject_if_not":null }, 17 | { "name":"bot_public","type":{"base":"bool"}, "comment":"when false only app owner can join the app's bot to guilds"}, 18 | { "name":"bot_require_code_grant","type":{"base":"bool"}, "comment":"when true the app's bot will only join upon completion of the full oauth2 code grant flow"}, 19 | { "name": "term_of_service_url", "type":{ "base":"char", "dec":"*"}, "comment":"the url of the app's terms of service", "inject_if_not":null }, 20 | { "name": "privacy_policy_url", "type":{ "base":"char", "dec":"*"}, "comment":"the url of the app's privacy policy", "inject_if_not":null }, 21 | { "name":"team","type":{"base":"struct discord_team", "dec":"*"}, "comment":"if the application belongs to a team, this will be a list of the members of that team", "inject_if_not":null, "todo":true }, 22 | { "name": "guild_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake"}, "comment":"if this application is a game sold on Discord, this field will be the guild on which it has been linked", "inject_if_not":0 }, 23 | { "name": "primary_sku_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake"}, "comment":"if this application is a game sold on Discord, this field will be the id of the \"Game SKU\" that is created, if exists", "inject_if_not":0 }, 24 | { "name": "slug", "type":{ "base":"char", "dec":"*"}, "comment":"if this application is a game sold on Discord, this field will be the URL slug that links to the store page", "inject_if_not":null }, 25 | { "name": "flags", "type":{ "base":"int", "int_alias":"enum discord_application_flags" }, "comment":"the application's public flags", "inject_if_not":0 } 26 | ] 27 | }, 28 | { 29 | "title":"Application Flags", 30 | "comment": "https://discord.com/developers/docs/resources/application#application-object-application-flags", 31 | "namespace":["application"], 32 | "enum":"flags", 33 | "items": 34 | [ 35 | { "name":"GATEWAY_PRESENCE","value":4096, "comment":"1 << 12"}, 36 | { "name":"GATEWAY_PRESENCE_LIMITED","value":8192, "comment":"1 << 13"}, 37 | { "name":"GATEWAY_GUILD_MEMBERS","value":16384, "comment":"1 << 14"}, 38 | { "name":"GATEWAY_GUILD_MEMBERS_LIMITED","value":32768, "comment":"1 << 15"}, 39 | { "name":"VERIFICATION_PENDING_GUILD_LIMIT","value":65536, "comment":"1 << 16"}, 40 | { "name":"EMBEDDED","value":131072, "comment":"1 << 17"} 41 | ] 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /specs/discord/application_commands.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace":["discord"], 3 | "comment":"https://discord.com/developers/docs/interactions/application-commands", 4 | "defs": 5 | [ 6 | { 7 | "title":"Create Global Application Command", 8 | "namespace": ["create_global_application_command"], 9 | "comment":"https://discord.com/developers/docs/interactions/application-commands#create-global-application-command", 10 | "struct":"params", 11 | "fields": 12 | [ 13 | { "name": "name", "type":{ "base":"char", "dec":"*" }, "comment":"1-32 lowercase character name"}, 14 | { "name": "description", "type":{ "base":"char", "dec":"*" }, "comment":"1-100 character description"}, 15 | { "name": "options", "type":{"base":"struct discord_application_command_option", "dec":"ntl"}, "comment":"the parameters for the command", "inject_if_not":null}, 16 | { "name": "default_permission", "type":{"base":"bool", "default_value":true}, "comment":"whether the command is enabled by default when the app is added to a guild"}, 17 | { "name": "type", "type":{"base":"int", "int_alias":"enum discord_application_command_types", "inject_if_not":0}} 18 | ] 19 | }, 20 | { 21 | "title":"Edit Global Application Command", 22 | "namespace": ["edit_global_application_command"], 23 | "comment": "https://discord.com/developers/docs/interactions/application-commands#edit-global-application-command", 24 | "struct": "params", 25 | "fields": 26 | [ 27 | { "name": "name", "type":{ "base":"char", "dec":"*" }, "comment":"1-32 lowercase character name", "inject_if_not":null}, 28 | { "name": "description", "type":{ "base":"char", "dec":"*" }, "comment":"1-100 character description", "inject_if_not":null}, 29 | { "name": "options", "type":{"base":"struct discord_application_command_option", "dec":"ntl"}, "comment":"the parameters for the command", "inject_if_not":null}, 30 | { "name": "default_permission", "type":{"base":"bool", "default_value":true}, "comment":"whether the command is enabled by default when the app is added to a guild"} 31 | ] 32 | }, 33 | { 34 | "title":"Create Guild Application Command", 35 | "namespace": ["create_guild_application_command"], 36 | "comment": "https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command", 37 | "struct": "params", 38 | "fields": 39 | [ 40 | { "name": "name", "type":{ "base":"char", "dec":"*" }, "comment":"1-32 lowercase character name"}, 41 | { "name": "description", "type":{ "base":"char", "dec":"*" }, "comment":"1-100 character description"}, 42 | { "name": "options", "type":{"base":"struct discord_application_command_option", "dec":"ntl"}, "comment":"the parameters for the command", "inject_if_not":null}, 43 | { "name": "default_permission", "type":{"base":"bool", "default_value":true}, "comment":"whether the command is enabled by default when the app is added to a guild"}, 44 | { "name": "type", "type":{"base":"int", "int_alias":"enum discord_application_command_types", "inject_if_not":0}} 45 | ] 46 | }, 47 | { 48 | "title":"Edit Guild Application Command", 49 | "namespace": ["edit_guild_application_command"], 50 | "comment": "https://discord.com/developers/docs/interactions/application-commands#edit-guild-application-command", 51 | "struct": "params", 52 | "fields": 53 | [ 54 | { "name": "name", "type":{ "base":"char", "dec":"*" }, "comment":"1-32 lowercase character name", "inject_if_not":null}, 55 | { "name": "description", "type":{ "base":"char", "dec":"*" }, "comment":"1-100 character description", "inject_if_not":null}, 56 | { "name": "options", "type":{"base":"struct discord_application_command_option", "dec":"ntl"}, "comment":"the parameters for the command", "inject_if_not":null}, 57 | { "name": "default_permission", "type":{"base":"bool", "default_value":true}, "comment":"whether the command is enabled by default when the app is added to a guild"} 58 | ] 59 | }, 60 | { 61 | "title":"Edit Application Command Permissions", 62 | "namespace": ["edit_application_command_permissions"], 63 | "comment": "https://discord.com/developers/docs/interactions/application-commands#edit-application-command-permissions", 64 | "struct": "params", 65 | "fields": 66 | [ 67 | { "name": "permissions", "type":{"base": "struct discord_application_command_permissions", "dec":"ntl"}, "comment":"the permissions for the command in the guild"} 68 | ] 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /specs/discord/audit_log.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord", "get_guild_audit_log"], 3 | "comment": "https://discord.com/developers/docs/resources/audit-log", 4 | "defs": 5 | [ 6 | { 7 | "struct":"params", 8 | "fields": 9 | [ 10 | { "name": "user_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake" }, "comment":"filter the log for actions made by a user", "inject_if_not":0 }, 11 | { "name": "action_type", "type":{ "base":"int", "int_alias":"enum discord_audit_log_events" }, "comment":"the type of audit log event", "inject_if_not":0 }, 12 | { "name": "before", "type":{ "base":"char", "dec":"*", "converter":"snowflake" }, "comment":"filter the log before a certain entry id", "inject_if_not":0 }, 13 | { "name": "limit", "type":{ "base":"int" }, "default_value":50, "comment":"how many entries are returned (default 50, minimum 1, maximum 100)", "inject_if_not":0 } 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /specs/discord/emoji.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment": "https://discord.com/developers/docs/resources/emoji", 4 | "defs": 5 | [ 6 | { 7 | "title":"Emoji Structure", 8 | "comment": "https://discord.com/developers/docs/resources/emoji#emoji-object-emoji-structure", 9 | "struct":"emoji", 10 | "fields": 11 | [ 12 | { "name": "id", "type":{ "base":"char", "dec":"*", "converter":"snowflake"}, "comment":"emoji id"}, 13 | { "name": "name", "type":{ "base":"char", "dec":"*"}, "comment":"emoji name"}, 14 | { "name": "roles", "type":{ "base":"struct discord_role", "dec":"ntl"}, "option":true, "comment":"roles allowed to use this emoji" }, 15 | { "name": "user", "type":{ "base":"struct discord_user", "dec":"*" }, "option":true, "comment":"user that created this emoji" }, 16 | { "name": "require_colons", "type":{ "base":"bool" }, "option":true, "comment":"whether this emoji must be wrapped in colons" }, 17 | { "name": "managed", "type":{ "base":"bool" }, "option":true, "comment":"whether this emoji is managed" }, 18 | { "name": "animated", "type":{ "base":"bool" }, "option":true, "comment":"whether this emoji is animated" }, 19 | { "name": "available", "type":{ "base":"bool" }, "option":true, "whether this emoji can be used, may be false due to loss of Server Boosts" } 20 | ] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /specs/discord/emoji.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment": "", 4 | "defs": 5 | [ 6 | { 7 | "title":"Create Guild Emoji", 8 | "namespace": ["create_guild_emoji"], 9 | "struct":"params", 10 | "fields": 11 | [ 12 | { "name": "name", "type":{ "base":"char", "dec":"*"}}, 13 | { "name": "image", "type":{ "base":"char", "dec":"*"}, "comment":"Base64 Encoded Image Data"}, 14 | { "name": "roles", "type":{ "base":"ja_u64", "dec":"ntl" }, "comment":"roles for which this emoji will be whitelisted"} 15 | ] 16 | }, 17 | { 18 | "title":"Modify Guild Emoji", 19 | "namespace": ["modify_guild_emoji"], 20 | "struct":"params", 21 | "fields": 22 | [ 23 | { "name": "name", "type":{ "base":"char", "dec":"*"}}, 24 | { "name": "roles", "type":{ "base":"ja_u64", "dec":"ntl" }, "comment":"roles for which this emoji will be whitelisted"} 25 | ] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /specs/discord/guild_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment": "https://discord.com/developers/docs/resources/guild-template", 4 | "defs": 5 | [ 6 | { 7 | "title":"Guild Template Structure", 8 | "comment": "https://discord.com/developers/docs/resources/guild-template#guild-template-object-guild-template-structure", 9 | "struct":"guild_template", 10 | "fields": 11 | [ 12 | { "name": "code", "type":{ "base":"char", "dec":"*"}}, 13 | { "name": "name", "type":{ "base":"char", "dec":"*"}}, 14 | { "name": "description", "type":{ "base":"char", "dec":"*" }}, 15 | { "name": "usage_count", "type":{ "base":"int"}}, 16 | { "name": "creator_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake" }}, 17 | { "name": "creator", "type":{ "base":"struct discord_user", "dec":"*" }}, 18 | { "name": "created_at", "type":{ "base":"char", "dec":"*", "converter":"iso8601" }}, 19 | { "name": "updated_at", "type":{ "base":"char", "dec":"*", "converter":"iso8601" }}, 20 | { "name": "source_guild_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake" }}, 21 | { "name": "serialized_source_guild", "type":{ "base":"struct discord_guild", "dec":"*" }}, 22 | { "name": "is_dirty", "type":{ "base":"char", "dec":"*", "converter":"mixed"}} 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /specs/discord/guild_template.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment": "https://discord.com/developers/docs/resources/guild-template", 4 | "defs": 5 | [ 6 | { 7 | "title":"Create Guild From Guild Template", 8 | "namespace": ["create_guild_from_guild_template"], 9 | "struct":"params", 10 | "fields": 11 | [ 12 | { "name": "name", "type":{ "base":"char", "dec":"*"}, "comment":"name of the guild"}, 13 | { "name": "icon", "type":{ "base":"char", "dec":"*" }, "comment":"base64 128x128 image for the guild icon", "inject_if_not": null} 14 | ] 15 | }, 16 | { 17 | "title":"Create Guild Template", 18 | "namespace": ["create_guild_template"], 19 | "struct":"params", 20 | "fields": 21 | [ 22 | { "name": "name", "type":{ "base":"char", "dec":"*"}, "comment":"name of the guild"}, 23 | { "name": "description", "type":{ "base":"char", "dec":"*" }, "comment":"description for the template (0-120) chars", "inject_if_not": null} 24 | ] 25 | }, 26 | { 27 | "title":"Modify Guild Template", 28 | "namespace": ["modify_guild_template"], 29 | "struct":"params", 30 | "fields": 31 | [ 32 | { "name": "name", "type":{ "base":"char", "dec":"*"}, "comment":"name of the guild", "inject_if_not": null}, 33 | { "name": "description", "type":{ "base":"char", "dec":"*" }, "comment":"description for the template (0-120) chars", "inject_if_not": null} 34 | ] 35 | } 36 | ] 37 | } 38 | 39 | -------------------------------------------------------------------------------- /specs/discord/interaction.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment": "https://discord.com/developers/docs/interactions/receiving-and-responding", 4 | "defs": 5 | [ 6 | { 7 | "title":"Edit Original Interaction Response", 8 | "namespace": ["edit_original_interaction_response"], 9 | "struct":"params", 10 | "fields": 11 | [ 12 | { "name": "content", "type":{ "base":"char", "dec":"*" }, "comment":"name of the webhook(1-2000) chars", "inject_if_not":null }, 13 | { "name": "embeds", "type":{ "base":"struct discord_embed", "dec":"ntl" }, "comment":"array of up to 10 embeds objects", "inject_if_not":null }, 14 | { "name": "payload_json", "type":{ "base":"char", "dec":"*" }, "comment":"JSON encoded body of non-file params (multipart/form-data only)", "inject_if_not":null }, 15 | { "name": "allowed_mentions", "type":{ "base":"struct discord_allowed_mentions", "dec":"*" }, "comment":"allowed mentions for the message", "inject_if_not":null }, 16 | { "name": "attachments", "type":{ "base":"struct discord_attachment", "dec":"ntl" }, "comment":"attached files to keep", "inject_if_not":null }, 17 | { "name": "components", "type":{ "base":"struct discord_component", "dec":"ntl" }, "comment":"the components to include with the message", "inject_if_not":null } 18 | ] 19 | }, 20 | { 21 | "title":"Create Followup Message", 22 | "namespace": ["create_followup_message"], 23 | "struct":"params", 24 | "fields": 25 | [ 26 | 27 | { "name": "wait", "type":{ "base":"bool"}, "loc":"query", "comment":" waits for server confirmation of message send before response, and returns the created message body (defaults to false; when false a message that is not saved does not return an error)", "default_value":true }, 28 | { "name": "thread_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake"}, "loc":"query", "comment":"Send a message to the specified thread withing a webhook's channel. The thread will automatically be unarchived", "inject_if_not":0 }, 29 | { "name": "content", "type":{ "base":"char", "dec":"*" }, "comment":"the message contents (up to 2000 characters)", "inject_if_not": null }, 30 | { "name": "username", "type":{ "base":"char", "dec":"*" }, "comment":"override the default username of the webhook", "inject_if_not": null }, 31 | { "name": "avatar_url", "type":{ "base":"char", "dec":"*" }, "comment":"override the default avatar of the webhook", "inject_if_not": null }, 32 | { "name": "tts", "type":{ "base":"bool" }, "comment":"true if this is a TTS message", "inject_if_not":false }, 33 | { "name": "attachments", "type":{ "base":"struct discord_attachment", "dec":"ntl" }, "comment":"attached files to keep", "inject_if_not":null }, 34 | { "name": "embeds", "type":{ "base":"struct discord_embed", "dec":"*" }, "comment":"embedded rich content", "inject_if_not":null }, 35 | { "name": "payload_json", "type":{ "base":"char", "dec":"*" }, "comment":"JSON encoded body of non-file params", "inject_if_not": null }, 36 | { "name": "allowed_mentions", "type":{ "base":"struct discord_allowed_mentions", "dec":"*" }, "comment":"allowed mentions for the message", "inject_if_not": null }, 37 | { "name": "components", "type":{ "base":"struct discord_component", "dec":"ntl" }, "comment":"the components to include with the message", "inject_if_not": null }, 38 | { "name": "flags", "type":{ "base":"int" }, "comment":"can be set to 64 to send a ephemeral message", "inject_if_not": 0 } 39 | ] 40 | }, 41 | { 42 | "title":"Edit Followup Message", 43 | "namespace": ["edit_followup_message"], 44 | "struct":"params", 45 | "fields": 46 | [ 47 | { "name": "content", "type":{ "base":"char", "dec":"*" }, "comment":"name of the webhook(1-2000) chars", "inject_if_not":null }, 48 | { "name": "embeds", "type":{ "base":"struct discord_embed", "dec":"ntl" }, "comment":"array of up to 10 embeds objects", "inject_if_not":null }, 49 | { "name": "payload_json", "type":{ "base":"char", "dec":"*" }, "comment":"JSON encoded body of non-file params (multipart/form-data only)", "inject_if_not":null }, 50 | { "name": "allowed_mentions", "type":{ "base":"struct discord_allowed_mentions", "dec":"*" }, "comment":"allowed mentions for the message", "inject_if_not":null }, 51 | { "name": "attachments", "type":{ "base":"struct discord_attachment", "dec":"ntl" }, "comment":"attached files to keep", "inject_if_not":null }, 52 | { "name": "components", "type":{ "base":"struct discord_component", "dec":"ntl" }, "comment":"the components to include with the message", "inject_if_not":null } 53 | ] 54 | } 55 | ] 56 | } 57 | 58 | -------------------------------------------------------------------------------- /specs/discord/invite.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment": "https://discord.com/developers/docs/resources/invite", 4 | "defs": 5 | [ 6 | { 7 | "title":"Target User Types", 8 | "comment":"https://discord.com/developers/docs/resources/invite#invite-object-target-user-types", 9 | "namespace":["invite"], 10 | "enum":"target_user_types", 11 | "items": 12 | [ 13 | {"name":"STREAM", "value":1} 14 | ] 15 | }, 16 | { 17 | "title":"Invite Structure", 18 | "comment":"https://discord.com/developers/docs/resources/invite#invite-object-invite-structure", 19 | "struct":"invite", 20 | "fields": 21 | [ 22 | { "name": "code", "type":{ "base":"char", "dec":"*" }}, 23 | { "name": "guild", "type":{ "base":"struct discord_guild", "dec":"*"}, "comment":"partial guild object"}, 24 | { "name": "channel", "type":{ "base":"struct discord_channel", "dec":"*"}, "comment":"partial channel object"}, 25 | { "name": "inviter", "type":{ "base":"struct discord_user", "dec":"*"}}, 26 | { "name": "target_user", "type":{ "base":"struct discord_user", "dec":"*"}, "comment":"partial user object"}, 27 | { "name": "target_user_type", "type":{ "base":"int", "int_alias":"enum discord_invite_target_user_types" }}, 28 | { "name": "approximate_presence_count", "type":{ "base":"int" }}, 29 | { "name": "approximate_member_count", "type":{ "base":"int" }} 30 | ] 31 | }, 32 | { 33 | "comment":"https://discord.com/developers/docs/resources/invite#invite-metadata-object", 34 | "title":"Invite Metadata Structure", 35 | "namespace":["invite"], 36 | "struct":"metadata", 37 | "fields": 38 | [ 39 | { "name": "user", "type":{ "base":"int" }}, 40 | { "name": "max_uses", "type":{ "base":"int" }}, 41 | { "name": "max_age", "type":{ "base":"int" }}, 42 | { "name": "temporary", "type":{ "base":"int" }}, 43 | { "name": "created_at", "type":{ "base":"char", "dec":"*", "converter":"iso8601"}} 44 | ] 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /specs/discord/invite.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment": "https://discord.com/developers/docs/resources/invite", 4 | "defs": 5 | [ 6 | { 7 | "title":"Get Invite", 8 | "namespace": ["get_invite"], 9 | "struct":"params", 10 | "fields": 11 | [ 12 | { "name": "with_counts", "type":{ "base":"bool" }, "comment":"whether the invite should contain approximate member counts"}, 13 | { "name": "with_expiration", "type":{ "base":"bool" }, "comment":"whether the invite should contain the expiration date"} 14 | ] 15 | } 16 | ] 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /specs/discord/stage_instance.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "defs": 4 | [ 5 | { 6 | "title":"Privacy Level", 7 | "namespace":["stage_instance"], 8 | "enum":"privacy_level", 9 | "items": 10 | [ 11 | { "name": "PUBLIC", "value":1, "comment":"The Stage instance is visible publicly, such as on Stage Discovery."}, 12 | { "name": "GUILD_ONLY", "value":2, "comment":"The Stage instance is visible to only guild members."} 13 | ] 14 | }, 15 | { 16 | "title":"Stage Instance Structure", 17 | "struct":"stage_instance", 18 | "fields": 19 | [ 20 | {"name":"id", "type":{"base":"char", "dec":"*", "converter":"snowflake"}, "comment":"The id of this Stage instance"}, 21 | {"name":"guild_id", "type":{"base":"char", "dec":"*", "converter":"snowflake"}, "comment":"The guild id of the associated Stage channel"}, 22 | {"name":"channel_id", "type":{"base":"char", "dec":"*", "converter":"snowflake"}, "comment":"The id of the associated Stage channel"}, 23 | {"name":"topic", "type":{"base":"char", "dec":"*"}, "comment":"The topic of the Stage instance (1-120 characters)"}, 24 | {"name":"privacy_level", "type":{"base":"int", "int_alias":"enum discord_stage_instance_privacy_level"}, "comment":"The privacy level of the Stage instance"}, 25 | {"name":"discoverable_disabled", "type":{"base":"bool", "comment":"Whether or not Stage Discovery is disabled"}} 26 | ] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /specs/discord/stage_instance.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace":["discord"], 3 | "defs": 4 | [ 5 | { 6 | "title":"Create Stage Instance", 7 | "namespace":["create_stage_instance"], 8 | "struct":"params", 9 | "fields": 10 | [ 11 | {"name":"channel_id", "type":{"base":"char", "dec":"*", "converter":"snowflake"}, "comment":"The id of the Stage channel"}, 12 | {"name":"topic", "type":{"base":"char", "dec":"*"}, "comment":"The topic of the Stage instance (1-120 characters)"}, 13 | {"name":"privacy_level", "type":{"base":"int", "int_alias":"enum discord_stage_instance_privacy_level", "comment":"The privacy level of the Stage instance (default GUILD_ONLY)"}, "inject_if_not":0} 14 | ] 15 | }, 16 | { 17 | "title":"Modify Stage Instance", 18 | "namespace":["modify_stage_instance"], 19 | "struct":"params", 20 | "fields": 21 | [ 22 | {"name":"topic", "type":{"base":"char", "dec":"*"}, "comment":"The topic of the Stage instance (1-120 characters)"}, 23 | {"name":"privacy_level", "type":{"base":"int", "int_alias":"enum discord_stage_instance_privacy_level", "comment":"The privacy level of the Stage instance (default GUILD_ONLY)"}, "inject_if_not":0} 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /specs/discord/sticker.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "defs": 4 | [ 5 | { 6 | "title":"Sticker Types", 7 | "namespace":["sticker"], 8 | "enum":"types", 9 | "items": 10 | [ 11 | { "name": "STANDARD", "value":1, "comment":"an official sticker in a pack, part of Nitro or in a removed purchasable pack"}, 12 | { "name": "GUILD", "value":2, "comment":"a sticker uploaded to a Boosted guild for the guild's members"} 13 | ] 14 | }, 15 | { 16 | "title":"Sticker Format Types", 17 | "namespace":["sticker"], 18 | "enum":"format_types", 19 | "items": 20 | [ 21 | { "name": "PNG", "value":1}, 22 | { "name": "APNG", "value":2}, 23 | { "name": "LOTTIE", "value":3} 24 | ] 25 | }, 26 | { 27 | "title":"Sticker Structure", 28 | "struct":"sticker", 29 | "fields": 30 | [ 31 | {"name":"id", "type":{"base":"char", "dec":"*", "converter":"snowflake"}, "comment":"id of the sticker"}, 32 | {"name":"pack_id", "type":{"base":"char", "dec":"*", "converter":"snowflake"}, "inject_if_not":0, "comment":"for standard stickers, id of the pack the sticker is from"}, 33 | {"name":"name", "type":{"base":"char", "dec":"*"}, "comment":"name of the sticker"}, 34 | {"name":"description", "type":{"base":"char", "dec":"*"}, "comment":"description of the sticker"}, 35 | {"name":"tags", "type":{"base":"char", "dec":"*"}, "comment":"autocomplete/suggestion tags for the sticker (max 200 characters)"}, 36 | {"name":"asset", "type":{"base":"char", "dec":"*"}, "comment":"Deprecated previously the sticker asset hash, now an empty string"}, 37 | {"name":"type", "type":{"base":"int", "int_alias":"enum discord_sticker_types"}, "comment":"type of sticker"}, 38 | {"name":"format_type", "type":{"base":"int", "int_alias":"enum discord_sticker_format_types"}, "comment":"type of sticker format"}, 39 | {"name":"available", "type":{"base":"bool"}, "inject_if_not":false, "comment":"whether this guild sticker can be used, may be false due to loss of Server Boosts"}, 40 | {"name":"guild_id", "type":{"base":"char", "dec":"*", "converter":"snowflake"}, "inject_if_not":0, "comment":"id of the guild that owns this sticker"}, 41 | {"name":"user", "type":{"base":"struct discord_user", "dec":"*"}, "inject_if_not":null, "comment":"the user that uploaded the guild sticker"}, 42 | {"name":"sort_value", "type":{"base":"int"}, "comment":"the standard sticker's sort order within its pack"} 43 | ] 44 | }, 45 | { 46 | "title":"Sticker Item Structure", 47 | "struct":"sticker_item", 48 | "fields": 49 | [ 50 | {"name":"id", "type":{"base":"char", "dec":"*", "converter":"snowflake"}, "comment":"id of the sticker"}, 51 | {"name":"name", "type":{"base":"char", "dec":"*"}, "comment":"name of the sticker"}, 52 | {"name":"format_type", "type":{"base":"int", "int_alias":"enum discord_sticker_format_types"}, "comment":"type of sticker format"} 53 | ] 54 | }, 55 | { 56 | "title":"Sticker Pack Structure", 57 | "struct":"sticker_pack", 58 | "fields": 59 | [ 60 | {"name":"id", "type":{"base":"char", "dec":"*", "converter":"snowflake"}, "comment":"id of the sticker pack"}, 61 | {"name":"stickers", "type":{"base":"struct discord_sticker", "dec":"ntl"}, "comment":"the stickers in the pack"}, 62 | {"name":"name", "type":{"base":"char", "dec":"*"}, "comment":"name of the sticker pack"}, 63 | {"name":"sku_id", "type":{"base":"char", "dec":"*", "converter":"snowflake"}, "comment":"id of the pack's SKU"}, 64 | {"name":"cover_sticker_id", "type":{"base":"char", "dec":"*", "converter":"snowflake"}, "inject_if_not":0, "comment":"id of a sticker in the pack which is shown as the pack's icon"}, 65 | {"name":"description", "type":{"base":"char", "dec":"*"}, "comment":"description of the sticker pack"}, 66 | {"name":"banner_asset_id", "type":{"base":"char", "dec":"*", "converter":"snowflake"}, "comment":"id of the sticker pack's banner image"} 67 | ] 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /specs/discord/sticker.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace":["discord"], 3 | "defs": 4 | [ 5 | { 6 | "title":"List Nitro Sticker Packs", 7 | "namespace":["list_nitro_sticker_packs"], 8 | "struct":"response", 9 | "fields": 10 | [ 11 | {"name":"sticker_packs", "type":{"base":"struct discord_sticker_pack", "dec":"ntl"}, "comment":"array of sticker pack objects"} 12 | ] 13 | }, 14 | { 15 | "title":"Create Guild Sticker", 16 | "namespace":["create_guild_sticker"], 17 | "struct":"params", 18 | "fields": 19 | [ 20 | {"name":"name", "type":{"base":"char", "dec":"*"}, "comment":"name of the sticker (2-30 characters)"}, 21 | {"name":"description", "type":{"base":"char", "dec":"*"}, "comment":"description of the sticker (empty or 2-100 characters)"}, 22 | {"name":"file", "type":{ "base":"struct discord_attachment", "dec":"*" }, "loc":"multipart", "comment":"the sticker file to upload, must be a PNG, APNG, or Lottie JSON file, max 500 KB"}, 23 | {"name":"tags", "type":{"base":"char", "dec":"*"}, "comment":"autocomplete/suggestion tags for the sticker (max 200 characters)"} 24 | ] 25 | }, 26 | { 27 | "title":"Modify Guild Sticker", 28 | "namespace":["modify_guild_sticker"], 29 | "struct":"params", 30 | "fields": 31 | [ 32 | {"name":"name", "type":{"base":"char", "dec":"*"}, "comment":"name of the sticker (2-30 characters)"}, 33 | {"name":"description", "type":{"base":"char", "dec":"*"}, "comment":"description of the sticker (empty or 2-100 characters)"}, 34 | {"name":"tags", "type":{"base":"char", "dec":"*"}, "comment":"autocomplete/suggestion tags for the sticker (max 200 characters)"} 35 | ] 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /specs/discord/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment": "https://discord.com/developers/docs/resources/user", 4 | "defs": 5 | [ 6 | { 7 | "title":"User Flags", 8 | "comment":"https://discord.com/developers/docs/resources/user#user-object-user-flags", 9 | "namespace": ["user"], 10 | "enum":"flags", 11 | "items": 12 | [ 13 | {"name":"DISCORD_EMPLOYEE", "value":1, "comment":"1 << 0" }, 14 | {"name":"PARTNERED_SERVER_OWNER", "value":2, "comment":"1 << 2" }, 15 | {"name":"HYPESQUAD_EVENTS", "value":4, "comment":"1 << 3" }, 16 | {"name":"BUG_HUNTER_LEVEL_1", "value":8, "comment":"1 << 4"}, 17 | {"name":"HOUSE_BRAVERY", "value":32, "comment":"1 << 6"}, 18 | {"name":"HOUSE_BRILLIANCE", "value":64, "comment":"1 << 7"}, 19 | {"name":"HOUSE_BALANCE", "value":128, "comment":"1 << 8"}, 20 | {"name":"EARLY_SUPPORTER", "value":256, "comment":"1 << 9"}, 21 | {"name":"TEAM_USER", "value":512, "comment":"1 << 10"}, 22 | {"name":"SYSTEM", "value":4096, "comment":"1 << 12"}, 23 | {"name":"BUG_HUNTER_LEVEL_2", "value":16384, "comment":"1 << 14"}, 24 | {"name":"VERIFIED_BOT", "value":65536, "comment":"1 << 16"}, 25 | {"name":"EARLY_VERIFIED_BOT_DEVELOPER", "value":131072, "comment":"1 << 17"} 26 | ] 27 | }, 28 | { 29 | "title":"Premium Types", 30 | "comment":"https://discord.com/developers/docs/resources/user#user-object-premium-types", 31 | "namespace":["user"], 32 | "enum":"premium_types", 33 | "items": 34 | [ 35 | {"name":"NITRO_CLASSIC"}, 36 | {"name":"NITRO"} 37 | ] 38 | }, 39 | { 40 | "title":"User Structure", 41 | "struct":"user", 42 | "fields": 43 | [ 44 | { "name": "id", "type":{ "base":"char", "dec":"*", "converter":"snowflake"} }, 45 | { "name": "username", "type":{ "base":"char", "dec":"*"}}, 46 | { "name": "discriminator", "type":{ "base":"char", "dec":"*" }}, 47 | { "name": "avatar", "type":{ "base":"char", "dec":"*" }}, 48 | { "name": "bot", "type":{ "base":"bool" }}, 49 | { "name": "System", "json_key": "system", "type":{ "base":"bool" }}, 50 | { "name": "mfa_enabled", "type":{ "base":"bool" }}, 51 | { "name": "locale", "type":{ "base":"char", "dec":"*" }}, 52 | { "name": "verified", "type":{ "base":"bool" }}, 53 | { "name": "email", "type":{ "base":"char", "dec":"*" }}, 54 | { "name": "flags", "type":{ "base":"int", "int_alias": "enum discord_user_flags" }}, 55 | { "name": "banner", "type":{ "base":"char", "dec":"*" }}, 56 | { "name": "premium_type", "type":{ "base":"int", "int_alias": "enum discord_user_premium_types" }}, 57 | { "name": "public_flags", "type":{ "base":"int", "int_alias": "enum discord_user_flags" }} 58 | ] 59 | }, 60 | { 61 | "title":"Visbility Types", 62 | "comment":"https://discord.com/developers/docs/resources/user#connection-object-visibility-types", 63 | "namespace": ["visibility"], 64 | "enum":"types", 65 | "items": 66 | [ 67 | {"name":"NONE", "value":0}, 68 | {"name":"EVERYONE", "value":1} 69 | ] 70 | }, 71 | { 72 | "title":"Connection Structure", 73 | "comment":"https://discord.com/developers/docs/resources/user#connection-object-connection-structure", 74 | "struct":"connection", 75 | "fields": 76 | [ 77 | { "name": "id", "type":{ "base":"char", "dec":"*" }}, 78 | { "name": "name", "type":{ "base":"char", "dec":"*"}}, 79 | { "name": "type", "type":{ "base":"char", "dec":"*"}}, 80 | { "name": "revoked", "type":{ "base":"bool"}}, 81 | { "name": "integrations", "type": {"base":"struct discord_integration", "dec":"ntl"}}, 82 | { "name": "verified", "type":{ "base":"bool" }}, 83 | { "name": "friend_sync", "type":{ "base":"bool" }}, 84 | { "name": "show_activity", "type":{ "base":"bool" }}, 85 | { "name": "visibility", "type":{ "base":"int", "int_alias":"enum discord_visibility_types" }} 86 | ] 87 | } 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /specs/discord/user.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment": "https://discord.com/developers/docs/resources/user", 4 | "defs": 5 | [ 6 | { 7 | "title":"Modify Current User", 8 | "namespace": ["modify_current_user"], 9 | "struct":"params", 10 | "fields": 11 | [ 12 | { "name": "username", "type":{ "base":"char", "dec":"*" }}, 13 | { "name": "avatar", "type":{ "base":"char", "dec":"*"}, "comment":"base64 encoded image data"} 14 | ] 15 | }, 16 | { 17 | "title":"Create DM", 18 | "namespace": ["create_dm"], 19 | "struct":"params", 20 | "fields": 21 | [ 22 | { "name": "recipient_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake" }, "comment":"the recipient to open a DM channel with", "inject_if_not":0 } 23 | ] 24 | }, 25 | { 26 | "title":"Create Group DM", 27 | "namespace": ["create_group_dm"], 28 | "struct":"params", 29 | "fields": 30 | [ 31 | { "name": "access_tokens", "type":{ "base":"ja_str", "dec":"ntl" }, 32 | "comment":"access tokens of users that have granted your app the gdm.join scope"}, 33 | { "name":"nicks", "type":{ "base":"ja_u64", "dec":"ntl"}, 34 | "comment":"a dictionary of user ids to their respective nicknames"} 35 | ] 36 | } 37 | ] 38 | } 39 | 40 | -------------------------------------------------------------------------------- /specs/discord/voice-connections.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment":"https://discord.com/developers/docs/topics/voice-connections", 4 | "defs": 5 | [ 6 | { 7 | "title":"Voice Close Event Codes", 8 | "comment": "https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-close-event-codes", 9 | "namespace": ["voice", "close_event"], 10 | "enum":"codes", 11 | "items": 12 | [ 13 | { "name":"UNKNOWN_OPCODE", "value":4001 }, 14 | { "name":"DECODE_ERROR", "value":4002 }, 15 | { "name":"NOT_AUTHENTICATED", "value":4003 }, 16 | { "name":"AUTHENTICATION_FAILED", "value":4004 }, 17 | { "name":"ALREADY_AUTHENTICATED", "value":4005 }, 18 | { "name":"INVALID_SESSION", "value":4006 }, 19 | { "name":"SESSION_TIMED_OUT", "value":4009 }, 20 | { "name":"SERVER_NOT_FOUND", "value":4011 }, 21 | { "name":"UNKNOWN_PROTOCOL", "value":4012 }, 22 | { "name":"DISCONNECTED", "value":4014 }, 23 | { "name":"SERVER_CRASH", "value":4015 }, 24 | { "name":"UNKNOWN_ENCRYPTION_MODE", "value":4016 } 25 | ] 26 | }, 27 | { 28 | "title":"Voice Opcodes", 29 | "comment": "https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice-voice-opcodes", 30 | "namespace": ["voice"], 31 | "enum":"opcodes", 32 | "items": 33 | [ 34 | { "name":"IDENTIFY","value":0}, 35 | { "name":"SELECT_PROTOCOL","value":1}, 36 | { "name":"READY","value":2}, 37 | { "name":"HEARTBEAT","value":3}, 38 | { "name":"SESSION_DESCRIPTION","value":4}, 39 | { "name":"SPEAKING","value":5}, 40 | { "name":"HEARTBEAT_ACK","value":6}, 41 | { "name":"RESUME","value":7}, 42 | { "name":"HELLO","value":8}, 43 | { "name":"RESUMED","value":9}, 44 | { "name":"CLIENT_DISCONNECT","value":13}, 45 | { "name":"CODEC","value":14} 46 | ] 47 | }, 48 | { 49 | "title":"Voice Speaking Flags", 50 | "comment": "https://discord.com/developers/docs/topics/voice-connections#speaking", 51 | "namespace": ["voice"], 52 | "enum":"speaking_flags", 53 | "items": 54 | [ 55 | { "name":"MICROPHONE","value":1, "comment":"1 << 0"}, 56 | { "name":"SOUNDSHARE","value":2, "comment":"1 << 1"}, 57 | { "name":"PRIORITY","value":4, "comment":"1 << 2"} 58 | ] 59 | } 60 | ] 61 | } 62 | 63 | -------------------------------------------------------------------------------- /specs/discord/voice.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment":"https://discord.com/developers/docs/resources/voice", 4 | "defs": 5 | [ 6 | { 7 | "title":"Voice State Structure", 8 | "comment": "https://discord.com/developers/docs/resources/voice#voice-state-object-voice-state-structure", 9 | "namespace": ["voice"], 10 | "struct":"state", 11 | "fields": 12 | [ 13 | { "name": "guild_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake" }}, 14 | { "name": "channel_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake"}}, 15 | { "name": "user_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake" }}, 16 | { "name": "member", "type":{ "base":"struct discord_guild_member", "dec":"*" }}, 17 | { "name": "session_id", "type":{ "base":"char", "dec":"*" }}, 18 | { "name": "deaf", "type":{ "base":"bool" }}, 19 | { "name": "mute", "type":{ "base":"bool" }}, 20 | { "name": "self_deaf", "type":{ "base":"bool" }}, 21 | { "name": "self_mute", "type":{ "base":"bool" }}, 22 | { "name": "self_stream", "type":{ "base":"bool" }}, 23 | { "name": "self_video", "type":{ "base":"bool" }}, 24 | { "name": "supress", "type":{ "base":"bool" }} 25 | ] 26 | }, 27 | { 28 | "title":"Voice Region Structure", 29 | "comment":"https://discord.com/developers/docs/resources/voice#voice-region-object-voice-region-structure", 30 | "namespace": ["voice"], 31 | "struct":"region", 32 | "fields": 33 | [ 34 | { "name": "id", "type":{ "base":"char", "dec":"*" }}, 35 | { "name": "name", "type":{ "base":"char", "dec":"*" }}, 36 | { "name": "vip", "type":{ "base":"bool" }}, 37 | { "name": "optimal", "type":{ "base":"bool" }}, 38 | { "name": "deprecated", "type":{ "base":"bool" }}, 39 | { "name": "custom", "type":{ "base":"bool" }} 40 | ] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /specs/discord/webhook.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment": "https://discord.com/developers/docs/resources/webhook", 4 | "defs": 5 | [ 6 | { 7 | "title":"Webhook Structure", 8 | "comment": "https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-structure", 9 | "struct":"webhook", 10 | "fields": 11 | [ 12 | { "name": "id", "type":{ "base":"char", "dec":"*", "converter":"snowflake" }, "comment":"the id of the webhook" }, 13 | { "name": "type", "type":{ "base":"int", "int_alias":"enum discord_webhook_types" }, "comment":"the type of the webhook" }, 14 | { "name": "guild_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake" }, "comment":"the guild id this webhook is for, if any", "inject_if_not":0 }, 15 | { "name": "channel_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake" }, "comment":"the channel id this webhook is for, if any", "inject_if_not":0 }, 16 | { "name": "user", "type":{ "base":"struct discord_user", "dec":"*" }, "comment":"the user this webhook was created by (not returned when getting a webhook with its token", "inject_if_not":null }, 17 | { "name": "name", "type":{ "base":"char", "dec":"*", "comment":"the default name of the webhook", "inject_if_not":null }}, 18 | { "name": "avatar", "type":{ "base":"char", "dec":"*" }, "comment":"the default user avatar has of the webhook", "inject_if_not":null }, 19 | { "name": "token", "type":{ "base":"char", "dec":"*" }, "comment":"the secure token of the webhook (returned for Incoming Webhooks)", "inject_if_not":null }, 20 | { "name": "application_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake" }, "comment":"the bot/OAuth2 application that created this webhook", "inject_if_not":0 }, 21 | { "name": "source_guild", "type":{ "base":"struct discord_guild", "dec":"*" }, "comment":"the guild of the channel that this webhook is following (returned for Channel Follower Webhook)", "inject_if_not":null }, 22 | { "name": "source_channel", "type":{ "base":"struct discord_channel", "dec":"*" }, "comment":"the channel that this webhook is following (returned for Channel Follower Webhooks)", "inject_if_not":null }, 23 | { "name": "url", "type":{ "base":"char", "dec":"*" }, "comment":"the url used for executing the webhook (returned by the webhooks OAuth2 flow)", "inject_if_not":null } 24 | ] 25 | }, 26 | { 27 | "title":"Webhook Types", 28 | "comment":"https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types", 29 | "namespace":["webhook"], 30 | "enum":"types", 31 | "items": 32 | [ 33 | {"name": "INCOMING", "value":1, "comment":"Incoming Webhooks can post messages to channels with a generated token" }, 34 | {"name": "CHANNEL_FOLLOWER", "value":2, "comment":"Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages int channels" }, 35 | {"name": "APPLICATION", "value":3, "comment":"Application webhooks are webhooks used with interactions" } 36 | ] 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /specs/discord/webhook.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["discord"], 3 | "comment": "https://discord.com/developers/docs/resources/webhook", 4 | "defs": 5 | [ 6 | { 7 | "title":"Create Webhook", 8 | "namespace": ["create_webhook"], 9 | "struct":"params", 10 | "fields": 11 | [ 12 | { "name": "name", "type":{ "base":"char", "dec":"*" }, "comment":"name of the webhook(1-80) chars" }, 13 | { "name": "avatar", "type":{ "base":"char", "dec":"*" }, "inject_if_not":null, "comment":"base64 image for the default webhook avatar" } 14 | ] 15 | }, 16 | { 17 | "title":"Modify Webhook", 18 | "namespace": ["modify_webhook"], 19 | "struct":"params", 20 | "fields": 21 | [ 22 | { "name": "name", "type":{ "base":"char", "dec":"*" }, "inject_if_not":null, "comment":"name of the webhook(1-80) chars" }, 23 | { "name": "avatar", "type":{ "base":"char", "dec":"*" }, "inject_if_not":null, "comment":"base64 image for the default webhook avatar" }, 24 | { "name": "channel_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake" }, "inject_if_not":0, "comment":"the new channel id this webhook should be moved to" } 25 | ] 26 | }, 27 | { 28 | "title":"Modify Webhook with Token", 29 | "namespace": ["modify_webhook_with_token"], 30 | "struct":"params", 31 | "fields": 32 | [ 33 | { "name": "name", "type":{ "base":"char", "dec":"*" }, "inject_if_not":null, "comment":"name of the webhook(1-80) chars" }, 34 | { "name": "avatar", "type":{ "base":"char", "dec":"*" }, "inject_if_not":null, "comment":"base64 image for the default webhook avatar" } 35 | ] 36 | }, 37 | { 38 | "title":"Execute Webhook", 39 | "namespace": ["execute_webhook"], 40 | "struct":"params", 41 | "fields": 42 | [ 43 | 44 | { "name": "wait", "type":{ "base":"bool"}, "loc":"query", "comment":" waits for server confirmation of message send before response, and returns the created message body (defaults to false; when false a message that is not saved does not return an error)" }, 45 | { "name": "thread_id", "type":{ "base":"char", "dec":"*", "converter":"snowflake"}, "loc":"query", "comment":"Send a message to the specified thread withing a webhook's channel. The thread will automatically be unarchived", "inject_if_not":0 }, 46 | { "name": "content", "type":{ "base":"char", "dec":"*" }, "comment":"the message contents (up to 2000 characters)", "inject_if_not": null }, 47 | { "name": "username", "type":{ "base":"char", "dec":"*" }, "comment":"override the default username of the webhook", "inject_if_not": null }, 48 | { "name": "avatar_url", "type":{ "base":"char", "dec":"*" }, "comment":"override the default avatar of the webhook", "inject_if_not": null }, 49 | { "name": "tts", "type":{ "base":"bool" }, "comment":"true if this is a TTS message", "inject_if_not":false }, 50 | { "name": "embeds", "type":{ "base":"struct discord_embed", "dec":"*" }, "comment":"embedded rich content", "inject_if_not":null }, 51 | { "name": "allowed_mentions", "type":{ "base":"struct discord_allowed_mentions", "dec":"*" }, "comment":"allowed mentions for the message", "inject_if_not": null }, 52 | { "name": "components", "type":{ "base":"struct discord_component", "dec":"ntl" }, "comment":"the components to include with the message", "inject_if_not": null }, 53 | { "name": "attachments", "type":{ "base":"struct discord_attachment", "dec":"ntl" }, "comment":"attached files to keep", "inject_if_not":null } 54 | ] 55 | }, 56 | { 57 | "title":"Edit Webhook Message", 58 | "namespace": ["edit_webhook_message"], 59 | "struct":"params", 60 | "fields": 61 | [ 62 | { "name": "content", "type":{ "base":"char", "dec":"*" }, "comment":"name of the webhook(1-2000) chars", "inject_if_not":null }, 63 | { "name": "embeds", "type":{ "base":"struct discord_embed", "dec":"ntl" }, "comment":"array of up to 10 embeds objects", "inject_if_not":null }, 64 | { "name": "allowed_mentions", "type":{ "base":"struct discord_allowed_mentions", "dec":"*" }, "comment":"allowed mentions for the message", "inject_if_not":null }, 65 | { "name": "attachments", "type":{ "base":"struct discord_attachment", "dec":"ntl" }, "comment":"attached files to keep", "inject_if_not":null }, 66 | { "name": "components", "type":{ "base":"struct discord_component", "dec":"ntl" }, "comment":"the components to include with the message", "inject_if_not":null } 67 | ] 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /specs/github/gist.json: -------------------------------------------------------------------------------- 1 | { 2 | "title":"Gist Object", 3 | "namespace": ["github"], 4 | "comment": "https://docs.github.com/en/rest/reference/gists#create-a-gist", 5 | "defs": 6 | [ 7 | { 8 | "title": "Gist Structure", 9 | "struct": "gist", 10 | "fields": 11 | [ 12 | { "name": "url", "type":{ "base":"char", "dec":"*"}}, 13 | { "name": "id", "type":{ "base":"char", "dec":"*"}}, 14 | { "name": "node_id", "type":{ "base":"char", "dec":"*"}}, 15 | { "name": "html_url", "type":{ "base":"char", "dec":"*"}}, 16 | { "name": "created_at", "type":{ "base":"char", "dec":"*"}}, 17 | { "name": "updated_at", "type":{ "base":"char", "dec":"*"}}, 18 | { "name": "description", "type":{ "base":"char", "dec":"*"}}, 19 | { "name": "comments", "type":{ "base":"int"}} 20 | ] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /specs/github/gist.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["github"], 3 | "comment": "https://docs.github.com/en/rest/reference/gists", 4 | "defs": 5 | [ 6 | { 7 | "title":"Gist Create", 8 | "comment":"https://docs.github.com/en/rest/reference/gists#create-a-gist--parameters", 9 | "namespace": ["gist_create"], 10 | "struct":"params", 11 | "fields": 12 | [ 13 | { "name": "description", "type":{ "base":"char", "dec":"*" }}, 14 | { "name": "title", "type":{ "base":"char", "dec":"*" }}, 15 | { "name": "contents", "type":{ "base":"char", "dec":"*" }}, 16 | { "name": "public", "type":{ "base":"char", "dec":"*" }} 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /specs/github/repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "title":"Topics Object", 3 | "namespace": ["github"], 4 | "comment": "https://docs.github.com/en/rest/reference/repos#get-all-repository-topics", 5 | "defs": 6 | [ 7 | { 8 | "title": "Topic Structure", 9 | "struct": "topic", 10 | "fields": 11 | [ 12 | { "name": "names", "type":{ "base":"ja_str", "dec":"ntl"}} 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /specs/github/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "title":"User Object", 3 | "namespace": ["github"], 4 | "comment": "https://docs.github.com/en/rest/reference/users#get-a-user", 5 | "defs": 6 | [ 7 | { 8 | "title": "User Structure", 9 | "struct": "user", 10 | "fields": 11 | [ 12 | { "name": "login", "type":{ "base":"char", "dec":"*"}}, 13 | { "name": "id", "type":{ "base":"int"}}, 14 | { "name": "node_id", "type":{ "base":"char", "dec":"*"}}, 15 | { "name": "avatar_url", "type":{ "base":"char", "dec":"*"}}, 16 | { "name": "gravatar_id", "type":{ "base":"char", "dec":"*"}}, 17 | { "name": "html_url", "type":{ "base":"char", "dec":"*"}}, 18 | { "name": "type", "type":{ "base":"char", "dec":"*"}}, 19 | { "name": "site_admin", "type":{ "base":"bool"}}, 20 | { "name": "name", "type":{ "base":"char", "dec":"*"}}, 21 | { "name": "company", "type":{ "base":"char", "dec":"*"}}, 22 | { "name": "blog", "type":{ "base":"char", "dec":"*"}}, 23 | { "name": "location", "type":{ "base":"char", "dec":"*"}}, 24 | { "name": "email", "type":{ "base":"char", "dec":"*"}}, 25 | { "name": "hireable", "type":{ "base":"char", "dec":"*"}}, 26 | { "name": "bio", "type":{ "base":"char", "dec":"*"}}, 27 | { "name": "public_repos", "type":{ "base":"int"}}, 28 | { "name": "public_gists", "type":{ "base":"int"}}, 29 | { "name": "followers", "type":{ "base":"int"}}, 30 | { "name": "following", "type":{ "base":"int"}}, 31 | { "name": "created_at", "type":{ "base":"char", "dec":"*"}}, 32 | { "name": "updated_at", "type":{ "base":"char", "dec":"*"}} 33 | ] 34 | } 35 | ] 36 | } 37 | 38 | -------------------------------------------------------------------------------- /specs/reddit/links_n_comments.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["reddit"], 3 | "comment": "", 4 | "defs": 5 | [ 6 | { 7 | "title":"Comment", 8 | "namespace":["comment"], 9 | "comment":"https://www.reddit.com/dev/api/#POST_api_comment", 10 | "struct":"params", 11 | "fields": 12 | [ 13 | { "name": "api_type", "type":{ "base":"char", "dec":"*" }, "comment":"the string json" }, 14 | { "name": "return_rtjson", "type":{ "base":"bool" }, "comment":"boolean value" }, 15 | { "name": "richtext_json", "type":{ "base":"char", "dec":"*" }, "comment":"JSON data" }, 16 | { "name": "text", "type":{ "base":"char", "dec":"*" }, "comment":"raw markdown text" }, 17 | { "name": "thing_id", "type":{ "base":"char", "dec":"*" }, "comment":"fullname of parent thing" }, 18 | { "name": "uh", "type":{ "base":"char", "dec":"*" }, "comment":"a modhash" } 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /specs/reddit/oauth2.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["reddit"], 3 | "comment": "https://github.com/reddit-archive/reddit/wiki/OAuth2-Quick-Start-Example", 4 | "defs": 5 | [ 6 | { 7 | "title":"Access Token", 8 | "namespace":["access_token"], 9 | "struct":"params", 10 | "fields": 11 | [ 12 | { "name": "grant_type", "type":{ "base":"char", "dec":"*" }, "comment":"'password' for script type apps, 'refresh_token' for renewing access token and 'authorization_code' for webapps"}, 13 | { "name": "username", "type":{ "base":"char", "dec":"*" }, "comment":"username for script app"}, 14 | { "name": "password", "type":{ "base":"char", "dec":"*" }, "comment":"password for script app"}, 15 | { "name": "code", "type":{ "base":"char", "dec":"*" }, "comment":"the code retrieved by the webapp"}, 16 | { "name": "redirect_uri", "type":{ "base":"char", "dec":"*" }, "comment":"redirect uri for webapp"} 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /specs/reddit/search.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["reddit"], 3 | "comment": "", 4 | "defs": 5 | [ 6 | { 7 | "title":"Search", 8 | "namespace":["search"], 9 | "comment":"https://www.reddit.com/dev/api/#GET_search", 10 | "struct":"params", 11 | "fields": 12 | [ 13 | { "name": "after", "type":{ "base":"char", "dec":"*" }, "comment":"fullname of a thing"}, 14 | { "name": "before", "type":{ "base":"char", "dec":"*" }, "comment":"fullname of a thing"}, 15 | { "name": "category", "type":{ "base":"char", "dec":"*" }, "comment":"a string no longer than 5 character"}, 16 | { "name": "count", "type":{ "base":"int" }, "comment":"a positive integer (default: 0)"}, 17 | { "name": "include_facets", "type":{ "base":"bool" }, "comment":"boolean value"}, 18 | { "name": "limit", "type":{ "base":"int" }, "comment":"the maximum number of items desired (default: 25, maximum: 100)"}, 19 | { "name": "q", "type":{ "base":"char", "dec":"*" }, "comment":"a string no longer than 512 characters"}, 20 | { "name": "restrict_sr", "type":{ "base":"bool" }, "comment":"boolean value"}, 21 | { "name": "show", "type":{ "base":"char", "dec":"*" }, "comment":"(optional)the string all"}, 22 | { "name": "sort", "type":{ "base":"char", "dec":"*" }, "comment":"one of(relevance, hot, top, new, comments)"}, 23 | { "name": "sr_detail", "type":{ "base":"char", "dec":"*" }, "comment":"expand subreddits"}, 24 | { "name": "t", "type":{ "base":"char", "dec":"*" }, "comment":"one of(hour, day, week, month, year, all)"}, 25 | { "name": "type", "type":{ "base":"char", "dec":"*" }, "comment":"(optional) comma-delimited list of result types (sr, link, user)"} 26 | ] 27 | } 28 | ] 29 | } 30 | 31 | -------------------------------------------------------------------------------- /specs/slack/chat.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["slack", "chat"], 3 | "comment": "https://api.slack.com/methods?filter=chat", 4 | "defs": 5 | [ 6 | { 7 | "title":"Post Message", 8 | "namespace": ["post_message"], 9 | "struct":"params", 10 | "fields": 11 | [ 12 | { "name": "token", "type":{ "base":"char", "dec":"*" }, "comment":"Authentication token bearing required scopes. Tokens should be passed as an HTTP Authorization header or alternatively, as a POST parameter.", "inject_if_not":null }, 13 | { "name": "channel", "type":{ "base":"char", "dec":"*" }, "comment":"Channel, private group, or IM channel to send message to. Can be an encoded ID, or a name. See below for more details.", "inject_if_not":null }, 14 | { "name": "attachments", "type":{ "base":"char", "dec":"*" }, "comment":"A JSON-based array of structured attachments, presented as a URL-encoded string.", "inject_if_not":null }, 15 | { "name": "blocks", "type":{ "base":"char", "dec":"*" }, "comment":"A JSON-based array of structured blocks, presented as a URL-encoded string.", "inject_if_not":null }, 16 | { "name": "text", "type":{ "base":"char", "dec":"*" }, "comment":"The formatted text of the message to be published. If blocks are included, this will become the fallback text used in notifications.", "inject_if_not":null }, 17 | { "name": "as_user", "type":{ "base":"bool" }, "comment":"Pass true to post the message as the authed user, instead of as a bot. Defaults to false.", "inject_if_not":false }, 18 | { "name": "icon_emoji", "type":{ "base":"char", "dec":"*" }, "comment":"Emoji to use as the icon for this message. Overrides icon_url. Must be used in conjunction with as_user set to false, otherwise ignored.", "inject_if_not":null }, 19 | { "name": "icon_url", "type":{ "base":"char", "dec":"*" }, "comment":"URL to an image to use as the icon for this message. Must be used in conjunction with as_user set to false, otherwise ignored.", "inject_if_not":null }, 20 | { "name": "link_names", "type":{ "base":"bool" }, "comment":"Find and link channel names and usernames.", "inject_if_not":false }, 21 | { "name": "mrkdwn", "type":{ "base":"bool" }, "default_value":true, "comment":"Disable Slack markup parsing by setting to false. Enabled by default.", "inject_if_not":true }, 22 | { "name": "parse", "type":{ "base":"char", "dec":"*" }, "comment":"Change how messages are treated. Defaults to none", "inject_if_not":null }, 23 | { "name": "reply_broadcast", "type":{ "base":"bool" }, "comment":"Used in conjunction with thread_ts and indicates whether reply should be made visible to everyone in the channel or conversation. Defaults to false.", "inject_if_not":false }, 24 | { "name": "thread_ts", "type":{ "base":"char", "dec":"*" }, "comment":"Provide another message's ts value to make this message a reply. Avoid using a reply's ts value; use its parent instead.", "inject_if_not":null }, 25 | { "name": "unfurl_links", "type":{ "base":"bool" }, "comment":"Pass true to enable unfurling of primarily text-based content.", "inject_if_not":false }, 26 | { "name": "unfurl_media", "type":{ "base":"bool" }, "default_value":true, "comment":"Pass false to disable unfurling of media content.", "inject_if_not":true }, 27 | { "name": "username", "type":{ "base":"char", "dec":"*" }, "comment":"Set your bot's user name. Must be used in conjunction with as_user set to false, otherwise ignored.", "inject_if_not":null } 28 | ] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /specs/slack/users.params.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": ["slack", "users"], 3 | "comment": "https://api.slack.com/methods?filter=users", 4 | "defs": 5 | [ 6 | { 7 | "title":"Users Info", 8 | "namespace": ["info"], 9 | "struct":"params", 10 | "fields": 11 | [ 12 | { "name": "token", "type":{ "base":"char", "dec":"*" }, "comment":"Authentication token bearing required scopes. Tokens should be passed as an HTTP Authorization header or alternatively, as a POST parameter.", "inject_if_not":null }, 13 | { "name": "user", "type":{ "base":"char", "dec":"*" }, "comment":"User to get info onUser to get info on", "inject_if_not":null }, 14 | { "name": "include_locale", "type":{ "base":"bool" }, "comment":"Set this to true to receive the locale for this user. Defaults to false", "inject_if_not":false } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /specs/specs-gen.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include <stdio.h> 3 | #include <string.h> 4 | #include <stdlib.h> 5 | #include <unistd.h> 6 | 7 | #include "json-struct.c" 8 | 9 | static void print_usage(char *prog) 10 | { 11 | fprintf( 12 | stderr, 13 | "Usage: %s [-h|-c|-d|-f] -o output-file -i include-headers input-file \n" 14 | " -h generate header\n" 15 | " -c generate data and function definitions\n" 16 | " -d generate data and function declarations\n" 17 | " -S generate struct declarations\n" 18 | " -E generate enum declarations\n" 19 | " -F generate function declarations\n" 20 | " -f generate function definitions\n" 21 | " -O generate all opaque struct declarations\n" 22 | " -a append to output\n", 23 | prog); 24 | exit(EXIT_FAILURE); 25 | } 26 | 27 | int main(int argc, char **argv) 28 | { 29 | size_t len = 0; 30 | char *s; 31 | 32 | char *config_file = NULL; 33 | struct emit_option eo = { .type = FILE_SINGLE_FILE }; 34 | 35 | char *open_mode = "w"; 36 | NTL_T(name_t) incl_headers = NULL; 37 | 38 | int opt; 39 | while (-1 != (opt = getopt(argc, argv, "ahcdfSEFOo:i:"))) { 40 | switch (opt) { 41 | case 'a': 42 | open_mode = "a"; 43 | break; 44 | case 'o': 45 | config_file = strdup(optarg); 46 | break; 47 | case 'i': { 48 | name_t header = ""; 49 | snprintf(header, sizeof(name_t), "%s", optarg); 50 | ntl_append2((ntl_t *)&incl_headers, sizeof(name_t), &header); 51 | break; 52 | } 53 | case 'h': 54 | eo.type = FILE_HEADER; 55 | break; 56 | case 'c': 57 | eo.type = FILE_CODE; 58 | break; 59 | case 'd': 60 | eo.type = FILE_DECLARATION; 61 | break; 62 | case 'f': 63 | eo.type = FILE_DEFINITION; 64 | break; 65 | case 'S': 66 | eo.type = FILE_STRUCT_DECLARATION; 67 | break; 68 | case 'E': 69 | eo.type = FILE_ENUM_DECLARATION; 70 | break; 71 | case 'F': 72 | eo.type = FILE_FUN_DECLARATION; 73 | break; 74 | case 'O': 75 | eo.type = FILE_OPAQUE_STRUCT_DECLARATION; 76 | break; 77 | default: /* '?' */ 78 | print_usage(argv[0]); 79 | } 80 | } 81 | 82 | if (!config_file) print_usage(argv[0]); 83 | 84 | char *file = argv[optind]; 85 | s = cee_load_whole_file(file, &len); 86 | spec_name = file; 87 | spec_buffer.start = s; 88 | spec_buffer.size = len; 89 | 90 | struct jc_definition d; 91 | memset(&d, 0, sizeof(d)); 92 | definition_from_json(s, len, &d); 93 | d.spec_name = file; 94 | d.incl_headers = incl_headers; 95 | gen_definition(config_file, open_mode, &eo, &d); 96 | 97 | return EXIT_SUCCESS; 98 | } 99 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all 2 | * 3 | # But these 4 | !.gitignore 5 | !*.c 6 | !Makefile 7 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | # This Makefile assumes the top folder has been built 2 | 3 | TOP = .. 4 | CC ?= gcc 5 | 6 | CEEUTILS_DIR := $(TOP)/cee-utils 7 | COMMON_DIR := $(TOP)/common 8 | 9 | TEST_DISCORD := test-discord-api test-discord-async test-discord-ws 10 | TEST_GITHUB := test-git2 11 | TEST_COMMON := test-user-agent test-websockets 12 | 13 | EXES := $(TEST_DISCORD) $(TEST_GITHUB) $(TEST_COMMON) 14 | 15 | CFLAGS += -I$(TOP) -I$(CEEUTILS_DIR) -I$(COMMON_DIR) \ 16 | -I$(COMMON_DIR)/third-party \ 17 | -O0 -g -pthread -Wall 18 | LDFLAGS += -L$(TOP)/lib 19 | 20 | ifneq (,$(findstring $(CC),stensal-c sfc)) # ifeq stensal-c OR sfc 21 | __DEST := $(dir $(shell which $(CC))) 22 | PREFIX := $(__DEST:%/stensal/bin/=%/usr) 23 | LDFLAGS += -lcurl-bearssl -lbearssl -static 24 | else 25 | LDFLAGS += $(pkg-config --libs --cflags libcurl) -lcurl 26 | endif 27 | 28 | all: $(EXES) 29 | 30 | $(TEST_DISCORD): %: %.c 31 | $(CC) $(CFLAGS) -o $@ $< -ldiscord $(LDFLAGS) 32 | $(TEST_GITHUB): %: %.c 33 | $(CC) $(CFLAGS) -o $@ $< -lgithub $(LDFLAGS) 34 | $(TEST_COMMON): %: %.c 35 | $(CC) $(CFLAGS) -o $@ $< -ldiscord $(LDFLAGS) 36 | 37 | echo: 38 | @ echo -e 'CC: $(CC)\n' 39 | @ echo -e 'EXES: $(EXES)\n' 40 | 41 | clean: 42 | rm -rf $(EXES) 43 | 44 | .PHONY: all echo clean 45 | -------------------------------------------------------------------------------- /test/test-discord-api.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <inttypes.h> 4 | #include <assert.h> 5 | 6 | #include "discord.h" 7 | 8 | int main(int argc, char *argv[]) 9 | { 10 | const char *config_file; 11 | if (argc > 1) 12 | config_file = argv[1]; 13 | else 14 | config_file = "../config.json"; 15 | 16 | discord_global_init(); 17 | 18 | struct discord *client = discord_config_init(config_file); 19 | assert(NULL != client); 20 | 21 | struct discord_user me; 22 | discord_user_init(&me); 23 | 24 | assert(ORCA_OK == discord_get_current_user(client, &me)); 25 | printf("Greetings, %s#%s!\n", me.username, me.discriminator); 26 | 27 | struct discord_guild **guilds = NULL; 28 | assert(ORCA_OK == discord_get_current_user_guilds(client, &guilds)); 29 | for (size_t i = 0; guilds[i]; ++i) 30 | fprintf(stderr, "Guild[%s] id:\n\t%" PRIu64 "\n", guilds[i]->name, 31 | guilds[i]->id); 32 | 33 | // Test discord_strerror() 34 | ORCAcode code; 35 | code = discord_delete_channel(client, 123, NULL); 36 | fprintf(stderr, "%s\n", discord_strerror(code, client)); 37 | code = discord_modify_channel(client, 123, NULL, NULL); 38 | fprintf(stderr, "%s\n", discord_strerror(code, client)); 39 | 40 | discord_guild_list_free(guilds); 41 | discord_user_cleanup(&me); 42 | 43 | discord_cleanup(client); 44 | 45 | discord_global_cleanup(); 46 | } 47 | -------------------------------------------------------------------------------- /test/test-git2.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <unistd.h> 4 | #include <string.h> 5 | #include <curl/curl.h> 6 | 7 | #include "github.h" 8 | #include "cee-utils.h" 9 | 10 | static void print_usage(char *prog) 11 | { 12 | fprintf(stderr, 13 | "Usage: %s [-c config] [-m <commit-message>] file file ...\n", prog); 14 | exit(EXIT_FAILURE); 15 | } 16 | 17 | int main(int argc, char **argv) 18 | { 19 | int opt; 20 | char *commit_msg = NULL, *config_file = NULL; 21 | 22 | while ((opt = getopt(argc, argv, "c:m:")) != -1) { 23 | switch (opt) { 24 | case 'c': 25 | config_file = strdup(optarg); 26 | break; 27 | case 'm': 28 | commit_msg = strdup(optarg); 29 | break; 30 | default: /* '?' */ 31 | print_usage(argv[0]); 32 | } 33 | } 34 | 35 | if (NULL == config_file) { 36 | fprintf(stderr, "Using .cee-contributor as the user config\n"); 37 | config_file = ".cee-contributor"; 38 | } 39 | else if (NULL == commit_msg) { 40 | fprintf(stderr, "Please specify: -m \"commit message\"\n"); 41 | exit(EXIT_FAILURE); 42 | } 43 | else if (optind >= argc) { 44 | fprintf(stderr, "Expected files\n"); 45 | exit(EXIT_FAILURE); 46 | } 47 | 48 | ORCAcode code; 49 | struct github_file **files = 50 | (void *)ntl_calloc(argc - optind, sizeof(struct github_file)); 51 | for (int i = 0; files[i]; ++i) 52 | files[i]->path = argv[optind + i]; 53 | 54 | curl_global_init(CURL_GLOBAL_ALL); 55 | struct github *client = github_config_init(config_file, ".cee-repo"); 56 | 57 | code = github_update_my_fork(client, NULL); 58 | if (code != ORCA_OK) return EXIT_FAILURE; 59 | 60 | code = github_create_blobs(client, files); 61 | if (code != ORCA_OK) return EXIT_FAILURE; 62 | 63 | char *head_commit_sha = NULL, *base_tree_sha = NULL, *tree_sha = NULL, 64 | *commit_sha = NULL; 65 | code = github_get_head_commit(client, &head_commit_sha); 66 | if (code != ORCA_OK) return EXIT_FAILURE; 67 | code = github_get_tree_sha(client, head_commit_sha, &base_tree_sha); 68 | if (code != ORCA_OK) return EXIT_FAILURE; 69 | code = github_create_tree(client, base_tree_sha, files, &tree_sha); 70 | if (code != ORCA_OK) return EXIT_FAILURE; 71 | code = github_create_a_commit(client, tree_sha, head_commit_sha, commit_msg, 72 | &commit_sha); 73 | if (code != ORCA_OK) return EXIT_FAILURE; 74 | 75 | char new_branch[256]; 76 | snprintf(new_branch, sizeof(new_branch), "n%ld", time(NULL)); 77 | code = github_create_a_branch(client, head_commit_sha, new_branch); 78 | if (code != ORCA_OK) return EXIT_FAILURE; 79 | code = github_update_a_commit(client, new_branch, commit_sha); 80 | if (code != ORCA_OK) return EXIT_FAILURE; 81 | code = github_create_a_pull_request(client, new_branch, commit_msg); 82 | if (code != ORCA_OK) return EXIT_FAILURE; 83 | 84 | return EXIT_SUCCESS; 85 | } 86 | -------------------------------------------------------------------------------- /test/test-slack-ws.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <assert.h> 4 | 5 | #include "slack.h" 6 | 7 | void on_hello(struct slack *client, const char *text, const size_t len) 8 | { 9 | fputs("\n\nSuccesfully connected to Slack!\n\n", stderr); 10 | } 11 | 12 | int main(int argc, char *argv[]) 13 | { 14 | const char *config_file; 15 | if (argc > 1) 16 | config_file = argv[1]; 17 | else 18 | config_file = "bot.config"; 19 | 20 | struct slack *client = slack_config_init(config_file); 21 | assert(NULL != client); 22 | 23 | slack_set_on_hello(client, &on_hello); 24 | 25 | slack_run(client); 26 | 27 | slack_cleanup(client); 28 | } 29 | -------------------------------------------------------------------------------- /test/test-user-agent.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <string.h> 3 | 4 | #include "user-agent.h" 5 | 6 | void load(char *str, size_t len, void *ptr) 7 | { 8 | fprintf(stderr, "%.*s\n", (int)len, str); 9 | } 10 | 11 | int commit(char *base_url, struct logconf *conf) 12 | { 13 | struct ua_attr ua_attr = { 0 }; 14 | struct user_agent *ua; 15 | 16 | struct ua_resp_handle handle = { .ok_cb = load, .ok_obj = NULL }; 17 | struct sized_buffer body = { .start = "{ }", .size = 3 }; 18 | struct ua_conn_attr conn_attr = { 0 }; 19 | struct ua_info info = { 0 }; 20 | 21 | curl_global_init(CURL_GLOBAL_ALL); 22 | 23 | /* base url */ 24 | ua_attr.conf = conf; 25 | ua = ua_init(&ua_attr); 26 | ua_set_url(ua, base_url); 27 | 28 | conn_attr.body = &body; 29 | 30 | conn_attr.method = HTTP_POST; 31 | conn_attr.endpoint = "/echo?m=POST"; 32 | ua_easy_run(ua, &info, &handle, &conn_attr); 33 | 34 | conn_attr.method = HTTP_PATCH; 35 | conn_attr.endpoint = "/echo?m=PATCH"; 36 | ua_easy_run(ua, &info, &handle, &conn_attr); 37 | 38 | conn_attr.method = HTTP_GET; 39 | conn_attr.endpoint = "/echo?m=GET"; 40 | ua_easy_run(ua, &info, &handle, &conn_attr); 41 | 42 | conn_attr.method = HTTP_PUT; 43 | conn_attr.endpoint = "/echo?m=PUT"; 44 | ua_easy_run(ua, &info, &handle, &conn_attr); 45 | 46 | conn_attr.method = HTTP_DELETE; 47 | conn_attr.endpoint = "/echo?m=DELETE"; 48 | ua_easy_run(ua, &info, &handle, &conn_attr); 49 | 50 | curl_global_cleanup(); 51 | 52 | return 0; 53 | } 54 | 55 | int main(int argc, char *argv[]) 56 | { 57 | char *config_file; 58 | if (argc > 1) 59 | config_file = argv[1]; 60 | else 61 | config_file = "../config.json"; 62 | 63 | struct logconf conf; 64 | FILE *fp = fopen(config_file, "rb"); 65 | logconf_setup(&conf, "CEE_HTTP", fp); 66 | fclose(fp); 67 | 68 | commit("https://cee.studio", &conf); 69 | 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /test/test-websockets.c: -------------------------------------------------------------------------------- 1 | #include <stdio.h> 2 | #include <stdlib.h> 3 | #include <stdint.h> 4 | #include <string.h> 5 | #include <unistd.h> 6 | 7 | #include "websockets.h" 8 | #include "log.h" 9 | 10 | void print_usage(char *prog) 11 | { 12 | fprintf(stderr, 13 | "Usage: %s -u base-url -s start_test -e end-test -c config-file\n\n" 14 | "First install autobahn test suite (pip2 is required):\n" 15 | "\tpip2 install autobahntestsuite\n" 16 | "Then start autobahn:\n" 17 | "\twstest -m fuzzingserver\n\n" 18 | "Example:\n" 19 | "\t%s -u ws://localhost:9001 -s 1 -e 260\n" 20 | "\t%s -u wss://localhost:9001 -s 1 -e 10\n\n", 21 | prog, prog, prog); 22 | exit(EXIT_FAILURE); 23 | } 24 | 25 | void on_connect_cb(void *data, 26 | struct websockets *ws, 27 | struct ws_info *info, 28 | const char *ws_protocols) 29 | { 30 | (void)data; 31 | (void)ws; 32 | (void)info; 33 | log_info("Connected, WS-Protocols: '%s'", ws_protocols); 34 | } 35 | 36 | void on_text_cb(void *data, 37 | struct websockets *ws, 38 | struct ws_info *info, 39 | const char *text, 40 | size_t len) 41 | { 42 | (void)data; 43 | (void)ws; 44 | (void)info; 45 | log_trace("RECEIVE:\n%.*s", (int)len, text); 46 | } 47 | 48 | void on_ping_cb(void *data, 49 | struct websockets *ws, 50 | struct ws_info *info, 51 | const char *reason, 52 | size_t len) 53 | { 54 | (void)data; 55 | (void)ws; 56 | (void)info; 57 | log_trace("PING:\n%.*s", (int)len, reason); 58 | ws_pong(ws, NULL, "just pong", SIZE_MAX); 59 | } 60 | 61 | void on_pong_cb(void *data, 62 | struct websockets *ws, 63 | struct ws_info *info, 64 | const char *reason, 65 | size_t len) 66 | { 67 | (void)data; 68 | (void)ws; 69 | (void)info; 70 | log_trace("PONG:\n%.*s", (int)len, reason); 71 | ws_close(ws, WS_CLOSE_REASON_NORMAL, "close it!", SIZE_MAX); 72 | } 73 | 74 | void on_close_cb(void *data, 75 | struct websockets *ws, 76 | struct ws_info *info, 77 | enum ws_close_reason wscode, 78 | const char *reason, 79 | size_t len) 80 | { 81 | (void)data; 82 | (void)ws; 83 | (void)info; 84 | log_info("Closed connection (%d) : %.*s", wscode, (int)len, reason); 85 | } 86 | 87 | int main(int argc, char *argv[]) 88 | { 89 | char *config_file = "../config.json"; 90 | struct ws_callbacks cbs = { 91 | .on_connect = &on_connect_cb, 92 | .on_text = &on_text_cb, 93 | .on_ping = &on_ping_cb, 94 | .on_pong = &on_pong_cb, 95 | .on_close = &on_close_cb, 96 | }; 97 | struct websockets *ws; 98 | struct ws_attr attr = { 0 }; 99 | CURLM *mhandle = NULL; 100 | struct logconf conf; 101 | uint64_t tstamp; 102 | 103 | char *url = NULL; 104 | int opt; 105 | FILE *fp; 106 | 107 | while (-1 != (opt = getopt(argc, argv, "hu:c:"))) { 108 | switch (opt) { 109 | case 'u': url = strdup(optarg); break; 110 | case 'c': config_file = strdup(optarg); break; 111 | case 'h': 112 | default: print_usage(argv[0]); break; 113 | } 114 | } 115 | if (!url) print_usage(argv[0]); 116 | 117 | /* init logging */ 118 | fp = fopen(config_file, "rb"); 119 | logconf_setup(&conf, "TEST", fp); 120 | 121 | /* init websockets handle */ 122 | mhandle = curl_multi_init(); 123 | attr.conf = &conf; 124 | ws = ws_init(&cbs, mhandle, &attr); 125 | ws_set_url(ws, url, NULL); 126 | 127 | /* run the event-loop */ 128 | ws_start(ws); 129 | while (true == ws_easy_run(ws, 5, &tstamp)) 130 | ; 131 | ws_end(ws); 132 | 133 | ws_cleanup(ws); 134 | curl_multi_cleanup(mhandle); 135 | logconf_cleanup(&conf); 136 | fclose(fp); 137 | } 138 | --------------------------------------------------------------------------------