Compare commits

...

151 Commits
0.2.0 ... main

Author SHA1 Message Date
tastytea c48f1dc3d0
add maintenance mode notice
continuous-integration/drone/push Build is passing Details
2022-11-13 06:24:35 +01:00
tastytea 9a25cd9178
add Windows section to readme
Reported-by: Ben S.
2022-11-13 06:19:51 +01:00
tastytea e74828c19e
don't add -fsanitize=undefined if MinGW is used
Reported-by: Ben S.
2022-11-13 06:17:34 +01:00
tastytea 7255df01e0
add support for testing with catch 3
continuous-integration/drone/push Build is passing Details
2022-08-01 14:21:06 +02:00
tastytea 7de644d841
Add repology badge.
continuous-integration/drone/push Build is passing Details
2021-05-17 23:31:50 +02:00
tastytea 52a849870f
Make direction a string_view in answer_type::parse_pagination(). 2021-02-08 17:28:46 +01:00
tastytea aabcb46602
Update .clang-tidy. 2020-11-30 20:52:56 +01:00
tastytea cca3a4a239
Update Gentoo package installation.
continuous-integration/drone/push Build is passing Details
2020-11-29 05:07:04 +01:00
tastytea 5ec7a119dc
Add hyperlink to AUR.
continuous-integration/drone/push Build is passing Details
2020-11-27 14:46:58 +01:00
tastytea cf4302248f
Typo in CI recipe.
continuous-integration/drone/push Build is passing Details
2020-11-20 22:52:57 +01:00
tastytea cadf0d777e
Version bump 0.5.7.
Forgot to bump the version in the CMake recipe, packages couldn't be
built.
2020-11-13 15:07:41 +01:00
tastytea aeb7396961
Generate API documentation with CMake.
continuous-integration/drone/push Build is passing Details
2020-11-13 14:25:22 +01:00
tastytea f4bd5abd01
Fix some warnings.
Avoid copy, initialize members in header, initialize variables.
2020-11-13 14:17:44 +01:00
tastytea c9211e621e
Reformat examples. 2020-11-13 14:05:28 +01:00
tastytea 3a93aec941
Reformat tests. 2020-11-13 14:01:09 +01:00
tastytea 9efc8d2dfd
Reformat source files. 2020-11-13 14:00:03 +01:00
tastytea 8c7493e68e
Reformat header files. 2020-11-13 13:45:59 +01:00
tastytea 63d2497966
Update .clang-tidy, add .clang-format. 2020-11-13 13:41:13 +01:00
tastytea 4da6929392
Add note about scopes in documentation for ObtainToken::step_1().
continuous-integration/drone/push Build is passing Details
2020-11-13 13:33:24 +01:00
tastytea 24cb2d523d
ObtainToken: Change grant_type to authorization_code.
continuous-integration/drone/push Build is passing Details
client_credentials worked before but not anymore. 🤷
2020-11-13 13:03:49 +01:00
tastytea 41f470d2aa
Add read to scopes in obtain-token example.
Without that, we cannot very our credentials. Mastodon needs
read::accounts, Pleroma needs read.
2020-11-13 13:02:52 +01:00
tastytea e563731efe
Avoid copies, fix warnings. 2020-11-13 12:01:18 +01:00
tastytea ef11508ca1
Bump maximum CMake version to 3.16.
continuous-integration/drone/push Build is passing Details
2020-05-19 19:33:34 +02:00
tastytea c2bc1b5e7b
Only use add_compile_definitions with CMake >= 3.12. 2020-05-19 19:32:34 +02:00
tastytea 3c23f6d1a0
Version bump 0.5.5. 2020-05-19 19:26:51 +02:00
tastytea 3909187f7a
Add -DNDEBUG for non-debug builds.
continuous-integration/drone/push Build is failing Details
2020-05-19 19:24:21 +02:00
tastytea 2ada3b406b
Merge branch 'develop' into main
continuous-integration/drone/push Build is passing Details
2020-04-18 15:44:12 +02:00
tastytea d20818221d
Add Support for Pleroma 2.0.2.
continuous-integration/drone/push Build is passing Details
Add endpoint: /api/pleroma/admin/users/:nickname/update_credentials
2020-04-18 15:25:49 +02:00
tastytea f16e6d61e0
Remove unused using declaration. 2020-04-18 15:25:49 +02:00
tastytea 576c84aa36
Version bump 0.5.4.
continuous-integration/drone/push Build is passing Details
2020-03-21 12:26:42 +01:00
tastytea aea4b0f492
Merge branch 'develop' into main
continuous-integration/drone/push Build was killed Details
2020-03-21 12:25:54 +01:00
tastytea 5bf1e9bf25
Define copy constructor for instance.
continuous-integration/drone/push Build is passing Details
Needed because the underlying CURLWrapper is not copied but freshly created, so
access_token, proxy, cainfo and useragent have to be set.
2020-03-21 11:38:34 +01:00
tastytea 251d8a975c
Set access token in CURLWrapper too if it is set in Instance. 2020-03-21 11:26:23 +01:00
tastytea b5144fd9ce
Ensure that the first parameter in all GET calls is prefaced with ?.
continuous-integration/drone/push Build is passing Details
All calls to CURLWrapper::add_parameters_to_uri() but the first used & for all
parameters, because the boolean keeping track was implemented wrong.
2020-03-20 16:29:16 +01:00
tastytea 0bbc6ac4c7
Merge branch 'develop' into main
continuous-integration/drone/push Build is passing Details
2020-03-20 14:48:59 +01:00
tastytea abd7079c5a
Allow argument in constructor of Connection to be const.
continuous-integration/drone/push Build is running Details
2020-03-20 14:47:44 +01:00
tastytea ce27005e9c
Version bump 0.5.2.
continuous-integration/drone/push Build is passing Details
2020-03-20 13:53:13 +01:00
tastytea 778b7c3a5f
Merge branch 'develop' into main 2020-03-20 13:52:59 +01:00
tastytea 9d37bebdc8
Add missing constructors and assignment operators
continuous-integration/drone/push Build is passing Details
… to Instance and Connection.
2020-03-20 13:49:30 +01:00
tastytea fc32e8ac0a
Add copy constructor for CURLWrapper.
The copy constructor does the same as the constructor. A new CURL handle is used
for the “copy”.
2020-03-20 13:49:20 +01:00
tastytea d1b3455584
Version bump 0.5.1.
continuous-integration/drone/push Build is passing Details
2020-03-12 12:15:39 +01:00
tastytea f983acb910
Merge branch 'develop' into main 2020-03-12 12:15:19 +01:00
tastytea da1c2ba409
Handle more than one replacement in replace_parameter_in_uri().
continuous-integration/drone/push Build is passing Details
2020-03-12 12:12:31 +01:00
tastytea 00056c224e
Update parameter replacements for Pleroma 2.0.0.
continuous-integration/drone/push Build is passing Details
2020-03-12 11:40:11 +01:00
tastytea 854d2f67af
Updated Pleroma endpoints to version 2.0.0.
* v1::
  * pleroma_conversations_id_read
  * pleroma_accounts_id_scrobbles
  * pleroma_scrobble
  * pleroma_statuses_id_reactions_emoji
  * pleroma_statuses_id_reactions

* pleroma::
  * admin_users_nickname_toggle_activation
  * admin_users_permission_group_permission_group
  * admin_users_activate
  * admin_users_deactivate
  * admin_instances_instance_statuses
  * admin_statuses
  * admin_users_force_password_reset
  * admin_grouped_reports
  * admin_reports_id_notes
  * admin_reports_report_id_notes_id
  * admin_restart
  * admin_config_descriptions
  * admin_moderation_log
  * admin_reload_emoji
  * admin_users_confirm_email
  * admin_users_resend_confirm_email
  * admin_stats
  * emoji_packs
  * emoji_packs_name
  * emoji_packs_name_update_file
  * emoji_packs_name_update_metadata
  * emoji_packs_download_from
  * emoji_packs_list_from
  * emoji_packs_name_download_shared
2020-03-12 11:38:41 +01:00
tastytea e1a2b4e843
Merge branch 'develop' into main
continuous-integration/drone/push Build is passing Details
2020-02-16 14:09:01 +01:00
tastytea b80e40ada1
Update contribution guidelines.
continuous-integration/drone/push Build is passing Details
2020-02-16 14:08:17 +01:00
tastytea 1c3efd3589
Typo: A space too much. 2020-02-12 20:22:47 +01:00
tastytea ac07daddce
Make map of named entities into 2 columns.
continuous-integration/drone/push Build is passing Details
2020-02-12 17:13:17 +01:00
tastytea 58aebfc4c0
Mark unescape_html() nodiscard.
continuous-integration/drone/push Build is passing Details
2020-02-03 13:06:01 +01:00
tastytea 965780fbba
Version bump 0.5.0.
continuous-integration/drone/push Build is passing Details
2020-02-02 15:26:43 +01:00
tastytea 95696ba5ca
Merge branch 'develop' into main
continuous-integration/drone/push Build is passing Details
2020-02-02 15:18:31 +01:00
tastytea 3bbc24ba57
Decrease loops drastically in unescape_html().
continuous-integration/drone/push Build is passing Details
2020-02-02 15:02:19 +01:00
tastytea af1993c71f
Add hyperlinks to git commands in contributing guidelines. 2020-02-02 15:01:50 +01:00
tastytea fa1cbbeb91
Merge branch 'develop' into main
continuous-integration/drone/push Build is passing Details
2020-01-31 03:57:55 +01:00
tastytea 185ab91978
Rename buffer_mutex → _buffer_mutex.
continuous-integration/drone/push Build is passing Details
2020-01-28 08:11:56 +01:00
tastytea cdd7a465d2
Update .clang-tidy. 2020-01-28 08:03:16 +01:00
tastytea 2bbead14e5
Add .clang-tidy. 2020-01-27 03:19:47 +01:00
tastytea 56cabf48a8
Add clang-tidy exceptions for http_method. 2020-01-27 01:09:20 +01:00
tastytea 4dc7dbb4e6
CMake: We are already in the build directory. 2020-01-27 01:08:45 +01:00
tastytea 0ccef6773b
Merge branch 'develop' into main
continuous-integration/drone/push Build is passing Details
2020-01-26 23:26:36 +01:00
tastytea deeff83410
Make some private members of CURLWrapper static.
continuous-integration/drone/push Build is passing Details
replace_parameter_in_uri(), add_parameters_to_uri() and add_mime_part().
2020-01-26 08:50:05 +01:00
tastytea 3876cf3f01
Add WITH_CLANG-TIDY to CMake recipe. 2020-01-26 05:20:05 +01:00
tastytea 151103a9a1
Catch JSON exceptions in nlohmann-json example. 2020-01-26 05:19:58 +01:00
tastytea 25c75a6211
Setting -DNDEBUG is redundant. 2020-01-26 05:12:00 +01:00
tastytea b82d779119
Use brace initialization for _stream_cancelled. 2020-01-26 04:10:59 +01:00
tastytea fe5ac46d52
Explicitly call CURLWrapper::set_useragent() in setup_curl(). 2020-01-26 03:51:32 +01:00
tastytea 65ff76312b
Add nlohmann-json example.
continuous-integration/drone/push Build is passing Details
2020-01-26 00:49:49 +01:00
tastytea f0b38dae10
Make the introduction more true. 2020-01-25 20:23:52 +01:00
tastytea 28b2668426
Add Mastodon and Pleroma hyperlinks to readme. 2020-01-25 20:22:33 +01:00
tastytea e6c2db91fd
Merge branch 'develop' into main
continuous-integration/drone/push Build is passing Details
2020-01-25 03:47:05 +01:00
tastytea 02a22d773c
Make set_proxy(), set_cainfo(), set_useragent() virtual.
continuous-integration/drone/push Build is passing Details
2020-01-24 22:06:47 +01:00
tastytea bb088e6d70
Mark CURLWrapper::get_buffer() inline. 2020-01-24 21:57:15 +01:00
tastytea ac4150a3aa
Fix decumentation for CURLWrapper destructor. 2020-01-24 21:50:51 +01:00
tastytea 8955442b0d
Setting -g for debug builds is redundant.
continuous-integration/drone/push Build is passing Details
2020-01-24 21:23:48 +01:00
tastytea c9ea49ed91
Enhance documentation for Instance and Connection.
continuous-integration/drone/push Build is passing Details
2020-01-24 02:18:54 +01:00
tastytea 9523de8788
Mark buffer variables const.
continuous-integration/drone/push Build is passing Details
2020-01-20 02:46:00 +01:00
tastytea 2c13f03a1c
Clarify documentation for Instance::ObtainToken::step_2(). 2020-01-20 02:32:48 +01:00
tastytea 3d614afa72
Instance is not a function. 2020-01-20 02:29:01 +01:00
tastytea 60f26dc864
Report right headerfile location for Instance::ObtainToken. 2020-01-20 02:26:15 +01:00
tastytea cbdc438ccf
80 char rule. 2020-01-19 20:52:55 +01:00
tastytea 161554e677
Allow examples to build outside mastodonpp.
continuous-integration/drone/push Build is passing Details
2020-01-18 23:51:34 +01:00
tastytea 9bc3fe6fe2
Fix header location in readme example. 2020-01-18 23:38:11 +01:00
tastytea e40848fb1e
typo.
continuous-integration/drone/push Build is passing Details
2020-01-18 23:32:51 +01:00
tastytea e650e4c787
Merge branch 'develop' into main
continuous-integration/drone/push Build was killed Details
2020-01-18 23:25:08 +01:00
tastytea c56d8115e4
Mention “make package” in readme.
continuous-integration/drone/push Build is passing Details
2020-01-18 23:24:15 +01:00
tastytea 8e5e0435b6
Update feature list.
continuous-integration/drone/push Build is passing Details
2020-01-18 18:09:58 +01:00
tastytea 837524661c
Indentation fix. 2020-01-18 18:09:49 +01:00
tastytea 5d78051161
Merge branch 'develop' into main
continuous-integration/drone/push Build is passing Details
2020-01-17 17:32:13 +01:00
tastytea c75aefbe2a
Fix header names in documentation.
continuous-integration/drone/push Build was killed Details
2020-01-17 17:30:31 +01:00
tastytea 393c179c00
Make main page of reference more precise. 2020-01-17 17:30:31 +01:00
tastytea d8582f33c1
Mention the dependency on C++17.
continuous-integration/drone/push Build was killed Details
2020-01-17 17:29:44 +01:00
tastytea dc706ab41f
Fix clang URI.
continuous-integration/drone/push Build is passing Details
2020-01-16 23:50:09 +01:00
tastytea be71510316
Version bump 0.4.0.
continuous-integration/drone/push Build is passing Details
2020-01-16 18:25:32 +01:00
tastytea 9d44f30f8e
Merge branch 'develop' into main
continuous-integration/drone/push Build is passing Details
2020-01-16 18:21:07 +01:00
tastytea c01ba95f5a
Remove useless else. 2020-01-16 18:20:37 +01:00
tastytea bdd328ac91
Remove useless const. 2020-01-16 18:19:47 +01:00
tastytea 5e612210ef
Log proxy changes when debugging.
continuous-integration/drone/push Build is passing Details
2020-01-16 18:17:29 +01:00
tastytea 18c0b41a20
Add test for unescape_html(). 2020-01-16 18:16:02 +01:00
tastytea 8cfba1f16b
Add example to unescape_html() documentation. 2020-01-16 18:06:14 +01:00
tastytea cf2ff119b4
Revert "Add -fsanitize=address to debug flags."
This reverts commit fad9f87391.

Output was unusable.
2020-01-16 18:05:22 +01:00
tastytea 343fe2adca
Add unescape_html().
continuous-integration/drone/push Build is passing Details
2020-01-15 19:09:43 +01:00
tastytea fad9f87391
Add -fsanitize=address to debug flags.
continuous-integration/drone/push Build is passing Details
2020-01-15 14:25:36 +01:00
tastytea 11608abed8
Merge branch 'develop' into main
continuous-integration/drone/push Build is passing Details
2020-01-14 23:50:05 +01:00
tastytea 7ac55d6868
Update tested dpkg version.
continuous-integration/drone/push Build is passing Details
2020-01-14 23:49:15 +01:00
tastytea 99f00e331a
Silence false-positive clang-tidy warning,
continuous-integration/drone/push Build was killed Details
and one I can't do anything about.
2020-01-14 23:46:42 +01:00
tastytea 0596406113
Silence false positive clang-tidy warnings in log.hpp. 2020-01-14 23:45:39 +01:00
tastytea a237d74a81
Use bracket initialization in example code. 2020-01-14 23:34:50 +01:00
tastytea 9ccd724693
Mark parse_pagination() nodiscard. 2020-01-14 23:32:04 +01:00
tastytea be42f30631
Removed superfluous const from parameter for readability. 2020-01-14 23:31:39 +01:00
tastytea 7afc8d19a9
Add documentation for set_useragent().
continuous-integration/drone/push Build is passing Details
2020-01-14 23:28:20 +01:00
tastytea 968863301a
Mark ObtainToken constructor explicit. 2020-01-14 23:27:30 +01:00
tastytea df51d4fdcc
Change website in example08 to example.com. 2020-01-14 23:22:50 +01:00
tastytea 31df2a703d
Use localhost-IP for proxy in example. 2020-01-14 23:13:09 +01:00
tastytea 7c81f5f4cc
Version bump 0.3.0.
continuous-integration/drone/push Build is passing Details
2020-01-14 22:54:06 +01:00
tastytea f2594d2c46
Merge branch 'develop' into main 2020-01-14 22:53:44 +01:00
tastytea 59a9747c06
Add account_id and list_id to list of parameters to replace in URIs.
continuous-integration/drone/push Build is passing Details
2020-01-14 22:50:14 +01:00
tastytea 0d8caf93e2
Fixed endpoint translations. 2020-01-14 22:47:23 +01:00
tastytea c192f352f0
Extract post formats via regex.
continuous-integration/drone/push Build is passing Details
2020-01-14 22:28:14 +01:00
tastytea bd7952e901
Fixed the minimum libcurl version for rpms. 2020-01-14 22:21:09 +01:00
tastytea 0d8b2e8490
Use regex to extract NodeInfo addresses. 2020-01-14 21:53:42 +01:00
tastytea 223db7b255
Use regex to extract max_toot_chars. 2020-01-14 21:03:04 +01:00
tastytea aafb55e7eb
Tick boxes in readme.
continuous-integration/drone/push Build is passing Details
2020-01-14 20:44:58 +01:00
tastytea a8e342202d
Move types into types.hpp and add answer_type::next() and prev(). 2020-01-14 20:44:08 +01:00
tastytea bd490e788c
Remove test for Instance::get_proxy().
continuous-integration/drone/push Build is passing Details
2020-01-12 17:54:40 +01:00
tastytea c049af3212
Merge branch 'develop' into main 2020-01-12 17:41:36 +01:00
tastytea 9ae50917dd
Remove Instance::get_proxy() and Instance::get_cainfo().
continuous-integration/drone/push Build is failing Details
We can't get the values that were set using environment variables or build-time
options. We don't need them anymore for initializing “Connection”s.
2020-01-12 17:39:39 +01:00
tastytea 1b4ad05acb
Add set_useragent(). 2020-01-12 17:37:06 +01:00
tastytea 04526f37cc
Make argument of CURLWrapper::set_cafile() const. 2020-01-12 17:27:40 +01:00
tastytea 975fe57677
Simplify connection setup.
By adding CURLWrapper::setup_connection_properties.
2020-01-12 17:24:02 +01:00
tastytea b4428ed823
Mark CURLWrapper::escape_url() and unescape_url() nodiscard. 2020-01-12 17:01:09 +01:00
tastytea 78ef177e58
Hide CURLWrapper::set_proxy(). 2020-01-12 17:00:49 +01:00
tastytea b192bc70c7
Fix @since-tags that point to nonexisting versions.
continuous-integration/drone/push Build is passing Details
2020-01-12 16:30:20 +01:00
tastytea 7af01ffb17
Add ObtainToken-example.
continuous-integration/drone/push Build is passing Details
2020-01-12 16:28:11 +01:00
tastytea 02b0b05aa1
Add Instance::ObtainToken. 2020-01-12 16:28:04 +01:00
tastytea deeffc5adf
Bugfix in endpoint translation. 2020-01-12 16:06:44 +01:00
tastytea 79c5087ca5
Marked some functions noexcept.
continuous-integration/drone/push Build is failing Details
2020-01-12 15:28:26 +01:00
tastytea 7fc19639b1
Add missing header. 2020-01-12 15:25:04 +01:00
tastytea 20410ece28
Removed excess comma. 2020-01-12 15:24:05 +01:00
tastytea e669c65d2e
Merge branch 'develop' into main
continuous-integration/drone/push Build is passing Details
2020-01-12 13:38:44 +01:00
tastytea 97eb269865
Add Instance::set_cainfo(). 2020-01-12 13:37:53 +01:00
tastytea cd31af94a6
Bugfix in endpoint translation. 2020-01-12 11:45:15 +01:00
tastytea b2f370a727
Add escape_url() and unescape_url() to CURLWrapper. 2020-01-12 01:04:23 +01:00
tastytea fd5275f4a3
Return default max chars on exception in get_max_chars(). 2020-01-11 23:04:40 +01:00
tastytea 1a99e8a276
Made code in Connection::get_new_events() clearer. 2020-01-11 22:16:37 +01:00
tastytea a6b328c2d1
Add Instance::get_post_formats().
continuous-integration/drone/push Build is passing Details
2020-01-11 20:05:11 +01:00
tastytea b9bf28c264
Add Instance::get_nodeinfo(). 2020-01-11 19:51:16 +01:00
tastytea acd6da37f2
Add set_proxy() to readme example. 2020-01-11 18:16:38 +01:00
tastytea 40d985f9af
Add missing header. 2020-01-11 18:13:15 +01:00
tastytea 597cf41668
Don't check errors if it can't fail.
continuous-integration/drone/push Build is passing Details
2020-01-11 18:08:33 +01:00
tastytea d749e3aa23
Improve documentation for get_max_chars(). 2020-01-11 17:59:14 +01:00
42 changed files with 2005 additions and 502 deletions

131
.clang-format Normal file
View File

@ -0,0 +1,131 @@
# -*- mode: yaml -*-
# Written for clang-format 10.
# https://releases.llvm.org/10.0.0/tools/clang/docs/ClangFormatStyleOptions.html
---
DisableFormat: false
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
# AlignConsecutiveBitFields: false # clang-format 11
AlignConsecutiveDeclarations: false
AlignConsecutiveMacros: false
AlignEscapedNewlines: DontAlign
AlignOperands: true # clang-format 11: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
# AllowShortEnumsOnASingleLine: false # clang-format 11
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: Inline
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping: # If BreakBeforeBraces is set to Custom.
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: true
BeforeElse: true
# BeforeLambdaBody: true # clang-format 11
# BeforeWhile: true # clang-format 11
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
BreakInheritanceList: BeforeComma
BreakStringLiterals: true
ColumnLimit: 80
# CommentPragmas: 'regex'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
FixNamespaceComments: true
ForEachMacros:
- FOREACH
- RANGES_FOR
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Regroup
IncludeCategories: # stdlib headers into own group.
- Regex: '^[^\.]+$'
Priority: 4
# IndentCaseBlocks: false # clang-format 11
IndentCaseLabels: false
# IndentExternBlock: NoIndent # clang-format 11
IndentGotoLabels: false
IndentPPDirectives: AfterHash
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: true
# MacroBlockBegin: 'string'
# MacroBlockEnd: 'string'
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
# NamespaceMacros: 'string'
PenaltyBreakAssignment: 250
PenaltyBreakBeforeFirstCallParameter: 300
# PenaltyBreakComment: 300
# PenaltyBreakFirstLessLess: 120
# PenaltyBreakString: 1000
# PenaltyBreakTemplateDeclaration: 10
# PenaltyExcessCharacter: 1000000
# PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right
# RawStringFormats: # <YAML>
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
# StatementMacros:
# - Q_UNUSED
# - QT_REQUIRE_VERSION
TabWidth: 4
# TypenameMacros:
# - STACK_OF
# - LIST
UseCRLF: false
UseTab: Never
# WhitespaceSensitiveMacros: ['string', 'string'] # clang-format 11
...

45
.clang-tidy Normal file
View File

@ -0,0 +1,45 @@
# -*- mode: conf; fill-column: 100; -*-
# Written for clang-tidy 11.
---
Checks: '*,
-cppcoreguidelines-non-private-member-variables-in-classes,
-fuchsia-default-arguments-calls,
-fuchsia-default-arguments-declarations,
-fuchsia-default-arguments,
-llvm-include-order,
-llvm-header-guard,
-misc-non-private-member-variables-in-classes,
-fuchsia-overloaded-operator,
-cppcoreguidelines-avoid-magic-numbers,
-readability-magic-numbers,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-hicpp-no-array-decay,
-modernize-avoid-c-arrays,
-cppcoreguidelines-avoid-c-arrays,
-hicpp-avoid-c-arrays,
-google-build-using-namespace,
-readability-named-parameter,
-google-runtime-references,
-hicpp-avoid-goto,
-hicpp-vararg,
-fuchsia-statically-constructed-objects,
-google-readability-todo,
-modernize-use-trailing-return-type,
-fuchsia-multiple-inheritance,
-llvmlibc*'
FormatStyle: file # Use .clang-format.
CheckOptions: # ↓ Clashes with static private member prefix. (static int _var;) ↓
- { key: readability-identifier-naming.VariableCase, value: lower_case }
- { key: readability-identifier-naming.MemberCase, value: lower_case }
- { key: readability-identifier-naming.PrivateMemberCase, value: lower_case }
- { key: readability-identifier-naming.PrivateMemberPrefix, value: _ }
- { key: readability-identifier-naming.ProtectedMemberCase, value: lower_case }
- { key: readability-identifier-naming.ProtectedMemberPrefix, value: _ }
- { key: readability-identifier-naming.ClassCase, value: CamelCase }
- { key: readability-identifier-naming.StructCase, value: lower_case }
- { key: readability-identifier-naming.EnumCase, value: lower_case }
- { key: readability-identifier-naming.FunctionCase, value: lower_case }
- { key: readability-identifier-naming.ParameterCase, value: lower_case }
...

View File

@ -141,7 +141,7 @@ steps:
- apt-get install -qq build-essential cmake lsb-release
- apt-get install -qq libcurl4-openssl-dev
- rm -rf build && mkdir -p build && cd build
- cmake -G "Unix Makefiles" -SCMAKE_INSTALL_PREFIX=/usr -DWITH_DEB=YES ..
- cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr -DWITH_DEB=YES ..
- make
- make install DESTDIR=install
- make package

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
/build/
/doc/
/update_doc.sh
/examples/example99*

View File

@ -1,6 +1,6 @@
# Support version 3.9 and above, but use policy settings up to 3.14.
# 3.9 is needed for project description.
cmake_minimum_required(VERSION 3.9...3.14)
cmake_minimum_required(VERSION 3.9...3.16)
# Ranges are supported from 3.12, set policy to current for < 3.12.
if(${CMAKE_VERSION} VERSION_LESS 3.12)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
@ -10,8 +10,17 @@ endif()
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "The type of build.")
option(BUILD_SHARED_LIBS "Build shared libraries." YES)
# Not every non-debug build type adds -DNDEBUG.
if(NOT ${CMAKE_BUILD_TYPE} MATCHES "Debug")
if(${CMAKE_VERSION} VERSION_LESS 3.12)
add_definitions("-DNDEBUG")
else()
add_compile_definitions("NDEBUG")
endif()
endif()
project(mastodonpp
VERSION 0.2.0
VERSION 0.5.7
DESCRIPTION "C++ wrapper for the Mastodon and Pleroma APIs."
LANGUAGES CXX)
@ -20,8 +29,10 @@ list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
# Project build options.
option(WITH_TESTS "Compile tests." NO)
option(WITH_EXAMPLES "Compile examples." NO)
option(WITH_DOC "Generate API documentation." NO)
option(WITH_DEB "Prepare for the building of .deb packages." NO)
option(WITH_RPM "Prepare for the building of .rpm packages." NO)
option(WITH_CLANG-TIDY "Check sourcecode with clang-tidy while compiling." NO)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -29,9 +40,11 @@ set(CMAKE_CXX_EXTENSIONS OFF)
include(debug_flags)
# Disable debug log.
if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug")
add_definitions("-DNDEBUG")
if(WITH_CLANG-TIDY)
set(CMAKE_CXX_CLANG_TIDY
"clang-tidy"
"-header-filter=${PROJECT_SOURCE_DIR}"
"-quiet")
endif()
add_subdirectory(src)
@ -48,4 +61,11 @@ if(WITH_EXAMPLES)
add_subdirectory(examples)
endif()
if(WITH_DOC)
include(cmake/Doxygen.cmake)
enable_doxygen(
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_SOURCE_DIR}/src")
endif()
include(cmake/packages.cmake)

View File

@ -4,6 +4,8 @@
:contact-email: tastytea@tastytea.de
:contact-xmpp: {contact-email}
:contact-fediverse: https://likeable.space/users/tastytea
:uri-git-format-patch: https://git-scm.com/docs/git-format-patch
:uri-git-send-email: https://git-scm.com/docs/git-send-email
== How to contribute
@ -23,7 +25,10 @@ don't want to open an account.
=== Pull requests
Please use similar coding conventions as the rest of the project. The basic rule
to remember is to write code in the same style as the existing/surrounding code.
to remember is to write code in the same style as the existing/surrounding
code. Add a copyright line with the year, your name and your email address to
the files you edited, unless you don't want to.
You can also send me your patches via mailto:{contact-email}[E-Mail], ideally
using `git format-patch` or `git send-email`.
using link:{uri-git-format-patch}[git format-patch] or
link:{uri-git-send-email}[git send-email].

View File

@ -1,27 +0,0 @@
# -*- mode: conf-unix -*-
INPUT = src/ include/
RECURSIVE = YES
STRIP_FROM_INC_PATH = "include"
EXAMPLE_PATH = examples/
EXAMPLE_RECURSIVE = YES
GENERATE_HTML = YES
HTML_OUTPUT = doc/html
GENERATE_LATEX = NO
ALLOW_UNICODE_NAMES = YES
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ALWAYS_DETAILED_SEC = YES
INLINE_INHERITED_MEMB = NO
INHERIT_DOCS = YES
SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 4
MARKDOWN_SUPPORT = YES
AUTOLINK_SUPPORT = YES
INLINE_SIMPLE_STRUCTS = NO
QUIET = NO
WARNINGS = YES
BUILTIN_STL_SUPPORT = YES
VERBATIM_HEADERS = YES
INLINE_SOURCES = YES
SEARCHENGINE = YES
SHOW_FILES = YES

View File

@ -3,37 +3,49 @@
:project: mastodonpp
:uri-base: https://schlomp.space/tastytea/{project}
:uri-branch-main: {uri-base}/src/branch/main
:uri-wp-mastodon: https://en.wikipedia.org/wiki/Mastodon_(software)
:uri-pleroma: https://pleroma.social/
:uri-mastodon-cpp: https://schlomp.space/tastytea/mastodon-cpp
:uri-reference: https://doc.schlomp.space/{project}/
:uri-gcc: https://gcc.gnu.org/
:uti-clang: https://clang.llvm.org/
:uri-clang: https://clang.llvm.org/
:uri-cmake: https://cmake.org/
:uri-doxygen: http://www.doxygen.nl/
:uri-catch: https://github.com/catchorg/Catch2
:uri-dpkg: https://packages.qa.debian.org/dpkg
:uri-rpm-build: http://www.rpm.org
:uri-libcurl: https://curl.haxx.se/libcurl/
:uri-nodeinfo: https://nodeinfo.diaspora.software/
:uri-clang-tidy: https://clang.llvm.org/extra/clang-tidy/
*{project}* is a C++ wrapper for the Mastodon and Pleroma APIs. It replaces
*{project}* is a C++ wrapper for the link:{uri-wp-mastodon}[Mastodon] and
link:{uri-pleroma}[Pleroma] APIs. It replaces
link:{uri-mastodon-cpp}[mastodon-cpp].
[IMPORTANT]
mastodonpp is in maintenance mode. I will continue to fix bugs, but won't add
new features. If you'd like to adopt this project, please get in touch.
We aim to create a library that is comfortable, yet minimal. All API endpoints
from Mastodon and Pleroma are stored in ``enum class``es, to counteract typos and
make your life easier. The network-facing code is built on
from Mastodon and Pleroma are stored in ``enum class``es, to counteract typos
and make your life easier. The network-facing code is built on
link:{uri-libcurl}[libcurl], a mature and stable library that is available on
virtually every operating system. The library does not parse the responses
itself, but returns to you the raw data, because we know everyone has their
favorite JSON library and we don't want to impose our choice on you!
most operating systems. The library does not parse the responses itself, but
returns to you the raw data, because we know everyone has their favorite JSON
library and we don't want to impose our choice on you!
== Features
This is still a work in progress; here is a rough overview of the features:
Here is a rough overview of the features:
* [x] `GET`, Streaming `GET`, `POST`, `PATCH`, `PUT` and `DELETE` requests.
* [x] `GET`, Streaming `GET`, `POST`, `PATCH`, `PUT` and `DELETE` requests.
* [x] Comfortable access to pagination headers.
* [x] Report maximum allowed character per post.
* [ ] Comfortable access to pagination headers.
* [ ] Comfortable function to register a new “app” (get an access token).
* [ ] Report which mime types are allowed for posting statuses.
* [x] Simple function to register a new “app” (get an access token).
* [x] Report which mime types are allowed for posting statuses.
* [x] Find and retrieve link:{uri-nodeinfo}[NodeInfo].
* [x] Easy access to the libcurl handle for maximum configurability.
* [x] Set proxy server, User-Agent and the path to the CA bundle.
== Usage
@ -43,12 +55,13 @@ Have a look at the link:{uri-reference}[reference].
[source,cpp]
--------------------------------------------------------------------------------
#include "mastodonpp.hpp"
#include <mastodonpp/mastodonpp.hpp>
#include <iostream>
int main()
{
mastodonpp::Instance instance{"example.com", "123AccessToken123"};
instance.set_proxy("socks4a://[::1]:9050");
mastodonpp::Connection connection{instance};
const mastodonpp::parametermap parameters
@ -70,16 +83,24 @@ link:{uri-reference}/examples.html[More examples] are included in the reference.
== Install
[alt="Packaging status" link=https://repology.org/project/mastodonpp/versions]
image::https://repology.org/badge/vertical-allrepos/mastodonpp.svg[]
=== Gentoo
[source,shell]
--------------------------------------------------------------------------------
eselect repository enable tastytea
eselect repository enable guru
echo 'dev-cpp/mastodonpp' >> /etc/portage/package.accept_keywords/mastodonpp
emaint sync -r tastytea
emaint sync -r guru
emerge -a dev-cpp/mastodonpp
--------------------------------------------------------------------------------
=== Arch
The git-version is available via the AUR:
<https://aur.archlinux.org/packages/mastodonpp-git/>.
=== Debian and Ubuntu
We automatically generate packages for Debian buster (10) and Ubuntu bionic
@ -106,13 +127,14 @@ yum install ./libmastodonpp*.rpm
==== Dependencies
* Tested OS: Linux
* C++ compiler (tested: link:{uri-gcc}[GCC] 7/8/9, link:{uri-lang}[clang] 6/7)
* C\++ compiler with C++17 support (tested: link:{uri-gcc}[GCC] 7/8/9,
link:{uri-clang}[clang] 6/7)
* link:{uri-cmake}[CMake] (at least: 3.9)
* link:{uri-libcurl}[libcurl] (at least: 7.56)
* Optional
** Documentation: link:{uri-doxygen}[Doxygen] (tested: 1.8)
** Tests: link:{uri-catch}[Catch] (tested: 2.5 / 1.2)
** DEB package: link:{uri-dpkg}[dpkg] (tested: 1.18)
** DEB package: link:{uri-dpkg}[dpkg] (tested: 1.19)
** RPM package: link:{uri-rpm-build}[rpm-build] (tested: 4.11)
==== Get sourcecode
@ -141,8 +163,19 @@ cmake --build . -- -j$(nproc --ignore=1)
* `-DCMAKE_BUILD_TYPE=Debug` for a debug build.
* `-DWITH_TESTS=YES` if you want to compile the tests.
* `-DWITH_EXAMPLES=YES` if you want to compile the examples.
* `-DWITH_DOC=YES` if you want to generate the API documentation.
* `-DWITH_CLANG-TIDY=YES` to check the sourcecode with
link:{uri-clang-tidy}[clang-tidy] while compiling.
* One of:
** `-DWITH_DEB=YES` if you want to be able to generate a deb-package.
** `-DWITH_RPM=YES` if you want to be able to generate an rpm-package.
To create a deb or rpm package, run `make package` after compiling.
===== Windows
mastodonpp has been reported to compile with MinGW GCC, but
`http_method::DELETE` has to be renamed, because Windows headers define a
`DELETE` macro.
include::{uri-base}/raw/branch/main/CONTRIBUTING.adoc[]

View File

@ -1,11 +0,0 @@
#!/bin/sh
project="$(realpath --relative-base=.. .)"
version="$(grep -Eo '[0-9]+.[0-9]+.[0-9]+$' CMakeLists.txt)"
if [[ -f Doxyfile ]]; then
mkdir -p doc
(doxygen -s -g - && cat Doxyfile &&
echo "PROJECT_NAME = ${project}" &&
echo "PROJECT_NUMBER = ${version}") | doxygen -
fi

43
cmake/Doxygen.cmake Normal file
View File

@ -0,0 +1,43 @@
include(GNUInstallDirs)
function(enable_doxygen)
find_package(Doxygen REQUIRED dot)
set(DOXYGEN_RECURSIVE YES)
set(DOXYGEN_STRIP_FROM_INC_PATH "include")
if (WITH_EXAMPLES)
set(DOXYGEN_EXAMPLE_PATH "examples/")
set(DOXYGEN_EXAMPLE_RECURSIVE YES)
endif()
set(DOXYGEN_GENERATE_HTML YES)
set(DOXYGEN_HTML_OUTPUT "doc/html")
set(DOXYGEN_GENERATE_LATEX NO)
set(DOXYGEN_ALLOW_UNICODE_NAMES YES)
set(DOXYGEN_BRIEF_MEMBER_DESC YES)
set(DOXYGEN_REPEAT_BRIEF YES)
set(DOXYGEN_ALWAYS_DETAILED_SEC YES)
set(DOXYGEN_INLINE_INHERITED_MEMB NO)
set(DOXYGEN_INHERIT_DOCS YES)
set(DOXYGEN_SEPARATE_MEMBER_PAGES NO)
set(DOXYGEN_TAB_SIZE 4)
set(DOXYGEN_MARKDOWN_SUPPORT YES)
set(DOXYGEN_AUTOLINK_SUPPORT YES)
set(DOXYGEN_INLINE_SIMPLE_STRUCTS NO)
set(DOXYGEN_QUIET YES)
set(DOXYGEN_WARNINGS YES)
set(DOXYGEN_WARN_IF_UNDOCUMENTED YES)
set(DOXYGEN_BUILTIN_STL_SUPPORT YES)
set(DOXYGEN_VERBATIM_HEADERS YES)
set(DOXYGEN_INLINE_SOURCES YES)
set(DOXYGEN_SEARCHENGINE YES)
set(DOXYGEN_SHOW_FILES YES)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/doc")
doxygen_add_docs(${PROJECT_NAME}_doxygen "${ARGV}")
# Make sure doxygen is run with every build.
add_custom_target(${PROJECT_NAME}_docs ALL DEPENDS ${PROJECT_NAME}_doxygen)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc/html
DESTINATION "${CMAKE_INSTALL_DOCDIR}")
endfunction()

View File

@ -22,8 +22,6 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang"
"-Wdouble-promotion"
"-Wformat=2"
"-ftrapv"
"-fsanitize=undefined"
"-g"
"-Og"
"-fno-omit-frame-pointer")
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
@ -41,15 +39,21 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang"
endif()
endif()
endif()
if(NOT MINGW)
list(APPEND DEBUG_CXXFLAGS
"-fsanitize=undefined")
endif()
add_compile_options("$<$<CONFIG:Debug>:${DEBUG_CXXFLAGS}>")
set(DEBUG_LDFLAGS
"-fsanitize=undefined")
# add_link_options was introduced in version 3.13.
if(${CMAKE_VERSION} VERSION_LESS 3.13)
set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${DEBUG_LDFLAGS}")
else()
add_link_options("$<$<CONFIG:Debug>:${DEBUG_LDFLAGS}>")
if(NOT MINGW)
set(DEBUG_LDFLAGS
"-fsanitize=undefined")
# add_link_options was introduced in version 3.13.
if(${CMAKE_VERSION} VERSION_LESS 3.13)
set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${DEBUG_LDFLAGS}")
else()
add_link_options("$<$<CONFIG:Debug>:${DEBUG_LDFLAGS}>")
endif()
endif()
else()
message(STATUS

View File

@ -38,7 +38,7 @@ if(WITH_RPM)
set(CPACK_GENERATOR "RPM")
set(CPACK_RPM_PACKAGE_LICENSE "AGPL-3")
set(CPACK_RPM_PACKAGE_URL "https://schlomp.space/tastytea/${PROJECT_NAME}")
set(CPACK_RPM_PACKAGE_REQUIRES "libcurl >= 7.32")
set(CPACK_RPM_PACKAGE_REQUIRES "libcurl >= 7.56")
list(APPEND CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION
"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/cmake"
"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/pkgconfig")

View File

@ -15,7 +15,11 @@
// Print information about an instance (/api/v1/instance).
#include "mastodonpp.hpp"
#if __has_include("mastodonpp.hpp")
# include "mastodonpp.hpp" // We're building mastodonpp.
#else
# include <mastodonpp/mastodonpp.hpp> // We're building outside mastodonpp.
#endif
#include <iostream>
#include <string>
@ -23,11 +27,11 @@
#include <vector>
namespace masto = mastodonpp;
using std::cout;
using std::cerr;
using std::cout;
using std::endl;
using std::to_string;
using std::string_view;
using std::to_string;
using std::vector;
int main(int argc, char *argv[])

View File

@ -15,7 +15,11 @@
// Print new public events (/api/v1/streaming/public).
#include "mastodonpp.hpp"
#if __has_include("mastodonpp.hpp")
# include "mastodonpp.hpp" // We're building mastodonpp.
#else
# include <mastodonpp/mastodonpp.hpp> // We're building outside mastodonpp.
#endif
#include <chrono>
#include <iostream>
@ -26,14 +30,14 @@
namespace masto = mastodonpp;
using namespace std::chrono_literals;
using std::cout;
using std::cerr;
using std::cout;
using std::endl;
using std::to_string;
using std::string_view;
using std::thread;
using std::this_thread::sleep_for;
using std::to_string;
using std::vector;
using std::this_thread::sleep_for;
int main(int argc, char *argv[])
{
@ -57,8 +61,10 @@ int main(int argc, char *argv[])
if (answer && answer.body == "OK")
{
// Make a thread, get all public events.
// clang-format off
thread stream_thread{[&]
{
// clang-format on
answer = connection.get(masto::API::v1::streaming_public);
}};
@ -69,8 +75,8 @@ int main(int argc, char *argv[])
for (const auto &event : connection.get_new_events())
{
// Print type of event and the beginning of the data.
cout << event.type << ": "
<< event.data.substr(0, 70) << "" << endl;
cout << event.type << ": " << event.data.substr(0, 70)
<< "" << endl;
}
}

View File

@ -15,7 +15,11 @@
// Post a status (/api/v1/status).
#include "mastodonpp.hpp"
#if __has_include("mastodonpp.hpp")
# include "mastodonpp.hpp" // We're building mastodonpp.
#else
# include <mastodonpp/mastodonpp.hpp> // We're building outside mastodonpp.
#endif
#include <iostream>
#include <string>
@ -23,11 +27,11 @@
#include <vector>
namespace masto = mastodonpp;
using std::cout;
using std::cerr;
using std::cout;
using std::endl;
using std::to_string;
using std::string_view;
using std::to_string;
using std::vector;
int main(int argc, char *argv[])
@ -47,12 +51,12 @@ int main(int argc, char *argv[])
// Set up the parameters.
constexpr auto poll_seconds{60 * 60 * 24 * 2}; // 2 days.
const masto::parametermap parameters
{
{"status", "How is the weather?"},
{"poll[options]", vector<string_view>{"Nice", "not nice"}},
{"poll[expires_in]", to_string(poll_seconds)}
};
const masto::parametermap parameters{{"status", "How is the weather?"},
{"poll[options]",
vector<string_view>{"Nice",
"not nice"}},
{"poll[expires_in]",
to_string(poll_seconds)}};
// Post the status.
auto answer{connection.post(masto::API::v1::statuses, parameters)};

View File

@ -15,7 +15,11 @@
// Post a status (/api/v1/status) with an attachment (/api/v1/media).
#include "mastodonpp.hpp"
#if __has_include("mastodonpp.hpp")
# include "mastodonpp.hpp" // We're building mastodonpp.
#else
# include <mastodonpp/mastodonpp.hpp> // We're building outside mastodonpp.
#endif
#include <iostream>
#include <string>
@ -23,12 +27,12 @@
#include <vector>
namespace masto = mastodonpp;
using std::cout;
using std::cerr;
using std::cout;
using std::endl;
using std::string;
using std::to_string;
using std::string_view;
using std::to_string;
using std::vector;
int main(int argc, char *argv[])
@ -50,10 +54,8 @@ int main(int argc, char *argv[])
// Create attachment.
auto answer{connection.post(masto::API::v1::media,
{
{"file", string("@file:") += filename},
{"description", "Test."}
})};
{{"file", string("@file:") += filename},
{"description", "Test."}})};
// Get the ID of the attachment.
// You normally would use a JSON parser, of course. I don't use one
@ -65,11 +67,9 @@ int main(int argc, char *argv[])
// Post the status. Note that “media_ids” always has to be a vector.
answer = connection.post(masto::API::v1::statuses,
{
{"status", "Attachment test."},
{"media_ids",
vector<string_view>{media_id}}
});
{{"status", "Attachment test."},
{"media_ids",
vector<string_view>{media_id}}});
if (answer)
{
cout << "Successfully posted " << filename << ".\n";

View File

@ -15,7 +15,11 @@
// Update notification settings (/api/pleroma/notification_settings).
#include "mastodonpp.hpp"
#if __has_include("mastodonpp.hpp")
# include "mastodonpp.hpp" // We're building mastodonpp.
#else
# include <mastodonpp/mastodonpp.hpp> // We're building outside mastodonpp.
#endif
#include <iostream>
#include <string>
@ -23,11 +27,11 @@
#include <vector>
namespace masto = mastodonpp;
using std::cout;
using std::cerr;
using std::cout;
using std::endl;
using std::to_string;
using std::string_view;
using std::to_string;
using std::vector;
int main(int argc, char *argv[])
@ -46,14 +50,14 @@ int main(int argc, char *argv[])
masto::Connection connection{instance};
// Update the settings.
const auto answer{connection.put(
masto::API::pleroma::notification_settings,
{
{"followers", "true"},
{"follows", "true"},
{"remote", "true"},
{"local", "true"},
})};
const auto answer{
connection.put(masto::API::pleroma::notification_settings,
{
{"followers", "true"},
{"follows", "true"},
{"remote", "true"},
{"local", "true"},
})};
if (answer)
{
cout << answer << endl;

View File

@ -15,7 +15,11 @@
// Update account display name settings (/api/v1/accounts/update_credentials).
#include "mastodonpp.hpp"
#if __has_include("mastodonpp.hpp")
# include "mastodonpp.hpp" // We're building mastodonpp.
#else
# include <mastodonpp/mastodonpp.hpp> // We're building outside mastodonpp.
#endif
#include <iostream>
#include <string>
@ -23,11 +27,11 @@
#include <vector>
namespace masto = mastodonpp;
using std::cout;
using std::cerr;
using std::cout;
using std::endl;
using std::to_string;
using std::string_view;
using std::to_string;
using std::vector;
int main(int argc, char *argv[])
@ -48,11 +52,9 @@ int main(int argc, char *argv[])
masto::Connection connection{instance};
// Update the settings.
const auto answer{connection.patch(
masto::API::v1::accounts_update_credentials,
{
{"display_name", name},
})};
const auto answer{
connection.patch(masto::API::v1::accounts_update_credentials,
{{"display_name", name}})};
if (answer)
{
cout << "Successfully changed display name.\n";

View File

@ -15,7 +15,11 @@
// Post a status (/api/v1/status), then delete it (/api/v1/statuses/:id).
#include "mastodonpp.hpp"
#if __has_include("mastodonpp.hpp")
# include "mastodonpp.hpp" // We're building mastodonpp.
#else
# include <mastodonpp/mastodonpp.hpp> // We're building outside mastodonpp.
#endif
#include <chrono>
#include <iostream>
@ -26,13 +30,13 @@
namespace masto = mastodonpp;
using namespace std::chrono_literals;
using std::cout;
using std::cerr;
using std::cout;
using std::endl;
using std::to_string;
using std::string_view;
using std::this_thread::sleep_for;
using std::to_string;
using std::vector;
using std::this_thread::sleep_for;
int main(int argc, char *argv[])
{

View File

@ -0,0 +1,117 @@
/* This file is part of mastodonpp.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Obtain an access token and verify that it works.
#if __has_include("mastodonpp.hpp")
# include "mastodonpp.hpp" // We're building mastodonpp.
#else
# include <mastodonpp/mastodonpp.hpp> // We're building outside mastodonpp.
#endif
#include <cstdlib>
#include <iostream>
#include <string>
#include <string_view>
#include <vector>
namespace masto = mastodonpp;
using std::cerr;
using std::cin;
using std::cout;
using std::endl;
using std::exit;
using std::string;
using std::string_view;
using std::to_string;
using std::vector;
void handle_error(const masto::answer_type &answer);
int main(int argc, char *argv[])
{
const vector<string_view> args(argv, argv + argc);
if (args.size() <= 1)
{
cerr << "Usage: " << args[0] << " <instance hostname>\n";
return 1;
}
try
{
// Initialize Instance and Instance::ObtainToken.
masto::Instance instance{args[1], {}};
masto::Instance::ObtainToken token{instance};
// Create an “Application” (/api/v1/apps),
// and get URI for the authorization code (/oauth/authorize).
// NOTE: Mastodon only needs read:accounts for verify_credentials but
// Pleroma needs the full read scope.
auto answer{token.step_1("Testclient", "read write:favourites",
"https://example.com/")};
if (!answer)
{
handle_error(answer);
}
cout << "Please visit " << answer << "\nand paste the code here: ";
string code;
cin >> code;
// Obtain the token (/oauth/token).
answer = token.step_2(code);
if (!answer)
{
handle_error(answer);
}
cout << "Your access token is: " << answer << endl;
// Test if the token works.
masto::Connection connection{instance};
answer = connection.get(masto::API::v1::apps_verify_credentials);
if (!answer)
{
handle_error(answer);
}
cout << answer << endl;
}
catch (const masto::CURLException &e)
{
// Only libcurl errors that are not network errors will be thrown.
// There went probably something wrong with the initialization.
cerr << e.what() << endl;
}
return 0;
}
void handle_error(const masto::answer_type &answer)
{
if (answer.curl_error_code == 0)
{
// If it is no libcurl error, it must be an HTTP error.
cerr << "HTTP status: " << answer.http_status << endl;
}
else
{
// Network errors like “Couldn't resolve host.”.
cerr << "libcurl error " << to_string(answer.curl_error_code) << ": "
<< answer.error_message << endl;
}
exit(1);
}

View File

@ -0,0 +1,145 @@
/* This file is part of mastodonpp.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Get the last 4 public statuses of an instance and process them with
// nlohmann-json. <https://github.com/nlohmann/json>
#if __has_include("mastodonpp.hpp")
# include "mastodonpp.hpp" // We're building mastodonpp.
#else
# include <mastodonpp/mastodonpp.hpp> // We're building outside mastodonpp.
#endif
// Don't compile if nlohmann-json can't be found.
#if __has_include(<nlohmann/json.hpp>)
# include <nlohmann/json.hpp>
# include <cstdlib>
# include <iostream>
# include <string>
# include <string_view>
# include <vector>
namespace masto = mastodonpp;
using json = nlohmann::json;
using std::cerr;
using std::cout;
using std::exit;
using std::string_view;
using std::to_string;
using std::vector;
void handle_error(const masto::answer_type &answer);
int main(int argc, char *argv[])
{
const vector<string_view> args(argv, argv + argc);
if (args.size() <= 1)
{
cerr << "Usage: " << args[0] << " <instance hostname>\n";
return 1;
}
try
{
// Initialize Instance and Connection.
masto::Instance instance{args[1], {}};
masto::Connection connection{instance};
// Get the last 4 public statuses of the instance.
auto answer{connection.get(masto::API::v1::timelines_public,
{{"limit", "4"}, {"local", "true"}})};
if (answer)
{
// Parse JSON string.
auto statuses{json::parse(answer.body)};
for (const auto &status : statuses)
{
// Extract the info we want and print it.
const auto acct{status["account"]["acct"].get<string_view>()};
const auto content{status["content"].get<string_view>()};
const auto id{status["id"].get<string_view>()};
cout << acct << " wrote status " << id << ": \n";
cout << " " << content.substr(0, 76) << "\n";
// Print tags if there are any.
const auto tags{status["tags"]};
if (!tags.empty())
{
cout << " Tags: ";
for (const auto &tag : tags)
{
cout << '#' << tag["name"].get<string_view>() << " ";
}
cout << '\n';
}
// Print the number of attachments.
const auto n_attachments{status["media_attachments"].size()};
if (n_attachments > 0)
{
cout << " " << n_attachments << " attachment";
if (n_attachments > 1)
{
cout << "s";
}
cout << ".\n";
}
}
}
else
{
handle_error(answer);
}
}
catch (const masto::CURLException &e)
{
// Only libcurl errors that are not network errors will be thrown.
// There went probably something wrong with the initialization.
cerr << e.what() << '\n';
}
catch (const nlohmann::detail::exception &e)
{
cerr << "JSON exception: " << e.what() << '\n';
}
}
void handle_error(const masto::answer_type &answer)
{
if (answer.curl_error_code == 0)
{
// If it is no libcurl error, it must be an HTTP error.
cerr << "HTTP status: " << answer.http_status << '\n';
}
else
{
// Network errors like “Couldn't resolve host.”.
cerr << "libcurl error " << to_string(answer.curl_error_code) << ": "
<< answer.error_message << '\n';
}
exit(1);
}
#else
# include <iostream>
int main()
{
std::cout << "Example could not be compiled "
"because nlohmann-json was not found.\n";
}
#endif // __has_include(<nlohmann/json.hpp>)

View File

@ -31,7 +31,7 @@ using std::variant;
/*!
* @brief Holds %API endpoints.
*
* Supported %API endpoints: Mastodon 3.0.1, Pleroma 1.1.7.
* Supported %API endpoints: Mastodon 3.0.1, Pleroma 2.0.2.
*
* @since 0.1.0
*
@ -43,7 +43,7 @@ public:
/*!
* @brief An enumeration of all v1 %API endpoints.
*
* The original `/` are substituted with `_`.
* The original `/` are substituted with `_`. `:` are omitted.
*
* @since 0.1.0
*/
@ -184,7 +184,6 @@ public:
admin_reports_id_reopen,
pleroma_notifications_read,
pleroma_accounts_id_subscribe,
pleroma_accounts_id_unsubscribe,
pleroma_accounts_id_favourites,
@ -192,17 +191,20 @@ public:
pleroma_accounts_update_banner,
pleroma_accounts_update_background,
pleroma_accounts_confirmation_resend,
pleroma_mascot,
pleroma_conversations_id_statuses,
pleroma_conversations_id,
pleroma_conversations_id_read,
pleroma_accounts_id_scrobbles,
pleroma_scrobble,
pleroma_statuses_id_reactions_emoji,
pleroma_statuses_id_reactions,
};
/*!
* @brief An enumeration of all v2 %API endpoints.
*
* The original `/` are substituted with `_`.
* The original `/` are substituted with `_`. `:` are omitted.
*
* @since 0.1.0
*/
@ -214,7 +216,7 @@ public:
/*!
* @brief An enumeration of all oauth %API endpoints.
*
* The original `/` are substituted with `_`.
* The original `/` are substituted with `_`. `:` are omitted.
*
* @since 0.1.0
*/
@ -230,7 +232,7 @@ public:
*
* These endpoints are directly under `/api/`.
*
* The original `/` are substituted with `_`.
* The original `/` are substituted with `_`. `:` are omitted.
*
* @since 0.1.0
*/
@ -243,7 +245,7 @@ public:
/*!
* @brief An enumeration of all pleroma %API endpoints.
*
* The original `/` are substituted with `_`.
* The original `/` are substituted with `_`. `:` are omitted.
*
* @since 0.1.0
*/
@ -252,38 +254,66 @@ public:
admin_users,
admin_users_follow,
admin_users_unfollow,
admin_users_nickname,
admin_users_nickname_toggle_activation,
admin_users_tag,
admin_users_nickname_permission_group,
admin_users_nickname_permission_group_permission_group,
admin_users_nickname_activation_status,
admin_users_permission_group_permission_group,
admin_users_activate,
admin_users_deactivate,
admin_users_nickname_or_id,
admin_users_nickname_or_id_statuses,
admin_instances_instance_statuses,
admin_statuses,
admin_relay,
admin_users_invite_token,
admin_users_invites,
admin_users_revoke_invite,
admin_users_email_invite,
admin_users_nickname_password_reset,
admin_users_nickname_update_credentials,
admin_users_force_password_reset,
admin_reports,
admin_grouped_reports,
admin_reports_id,
admin_reports_id_respond,
admin_reports_id_notes,
admin_reports_report_id_notes_id,
admin_statuses_id,
admin_restart,
admin_config,
admin_config_descriptions,
admin_moderation_log,
admin_reload_emoji,
admin_users_confirm_email,
admin_users_resend_confirm_email,
admin_stats,
// No longer documented for Pleroma 2.0.0.
admin_users_nickname,
admin_users_nickname_activation_status,
admin_reports_id_respond,
admin_config_migrate_to_db,
admin_config_migrate_from_db,
admin_config,
emoji,
follow_import,
captcha,
delete_account,
disable_account,
account_register,
notification_settings,
healthcheck,
change_email
change_email,
emoji_packs,
emoji_packs_name,
emoji_packs_name_update_file,
emoji_packs_name_update_metadata,
emoji_packs_download_from,
emoji_packs_list_from,
emoji_packs_name_download_shared,
// No longer documented for Pleroma 2.0.0.
account_register,
};
/*!
@ -292,7 +322,7 @@ public:
*
* @since 0.1.0
*/
using endpoint_type = variant<v1,v2,oauth,other,pleroma>;
using endpoint_type = variant<v1, v2, oauth, other, pleroma>;
/*!
* @brief Constructs an API object. You should never need this.
@ -309,17 +339,16 @@ public:
*
* @since 0.1.0
*/
[[nodiscard]]
inline string_view to_string_view() const
[[nodiscard]] inline string_view to_string_view() const
{
return _endpoint_map.at(_endpoint);
}
private:
const endpoint_type _endpoint;
static const map<endpoint_type,string_view> _endpoint_map;
static const map<endpoint_type, string_view> _endpoint_map;
};
} // namespace mastodonpp
#endif // MASTODONPP_API_HPP
#endif // MASTODONPP_API_HPP

View File

@ -17,10 +17,10 @@
#ifndef MASTODONPP_CONNECTION_HPP
#define MASTODONPP_CONNECTION_HPP
#include "answer.hpp"
#include "api.hpp"
#include "curl_wrapper.hpp"
#include "instance.hpp"
#include "types.hpp"
#include <string>
#include <string_view>
@ -40,7 +40,7 @@ using std::vector;
*
* @since 0.1.0
*/
using endpoint_variant = variant<API::endpoint_type,string_view>;
using endpoint_variant = variant<API::endpoint_type, string_view>;
/*!
* @brief A stream event.
@ -68,6 +68,9 @@ struct event_type
/*!
* @brief Represents a connection to an instance. Used for requests.
*
* Do not make 2 requests with the same Connection at the same time. You can
* create as many Connection%s as you want from one Instance.
*
* @since 0.1.0
*
* @headerfile connection.hpp mastodonpp/connection.hpp
@ -82,7 +85,31 @@ public:
*
* @since 0.1.0
*/
explicit Connection(Instance &instance);
explicit Connection(const Instance &instance)
: _instance{instance}
, _baseuri{instance.get_baseuri()}
{
_instance.copy_connection_properties(*this);
}
/*!
* @brief Copy constructor. A new CURLWrapper is constructed.
*
* @since 0.5.2
*/
Connection(const Connection &other) = default;
//! Move constructor
Connection(Connection &&other) noexcept = delete;
//! Destructor
~Connection() noexcept override = default;
//! Copy assignment operator
Connection &operator=(const Connection &other) = delete;
//! Move assignment operator
Connection &operator=(Connection &&other) noexcept = delete;
/*!
* @brief Make a HTTP GET call with parameters.
@ -102,9 +129,8 @@ public:
*
* @since 0.1.0
*/
[[nodiscard]]
answer_type get(const endpoint_variant &endpoint,
const parametermap &parameters);
[[nodiscard]] answer_type get(const endpoint_variant &endpoint,
const parametermap &parameters);
/*!
* @brief Make a HTTP GET call.
@ -118,8 +144,7 @@ public:
*
* @since 0.1.0
*/
[[nodiscard]]
inline answer_type get(const endpoint_variant &endpoint)
[[nodiscard]] inline answer_type get(const endpoint_variant &endpoint)
{
return get(endpoint, {});
}
@ -144,9 +169,8 @@ public:
*
* @since 0.1.0
*/
[[nodiscard]]
answer_type post(const endpoint_variant &endpoint,
const parametermap &parameters);
[[nodiscard]] answer_type post(const endpoint_variant &endpoint,
const parametermap &parameters);
/*!
* @brief Make a HTTP POST call.
@ -155,8 +179,7 @@ public:
*
* @since 0.1.0
*/
[[nodiscard]]
inline answer_type post(const endpoint_variant &endpoint)
[[nodiscard]] inline answer_type post(const endpoint_variant &endpoint)
{
return post(endpoint, {});
}
@ -170,9 +193,8 @@ public:
*
* @since 0.2.0
*/
[[nodiscard]]
answer_type patch(const endpoint_variant &endpoint,
const parametermap &parameters);
[[nodiscard]] answer_type patch(const endpoint_variant &endpoint,
const parametermap &parameters);
/*!
* @brief Make a HTTP PATCH call.
@ -181,8 +203,7 @@ public:
*
* @since 0.2.0
*/
[[nodiscard]]
inline answer_type patch(const endpoint_variant &endpoint)
[[nodiscard]] inline answer_type patch(const endpoint_variant &endpoint)
{
return patch(endpoint, {});
}
@ -196,9 +217,8 @@ public:
*
* @since 0.2.0
*/
[[nodiscard]]
answer_type put(const endpoint_variant &endpoint,
const parametermap &parameters);
[[nodiscard]] answer_type put(const endpoint_variant &endpoint,
const parametermap &parameters);
/*!
* @brief Make a HTTP PUT call.
@ -207,8 +227,7 @@ public:
*
* @since 0.2.0
*/
[[nodiscard]]
inline answer_type put(const endpoint_variant &endpoint)
[[nodiscard]] inline answer_type put(const endpoint_variant &endpoint)
{
return put(endpoint, {});
}
@ -222,9 +241,8 @@ public:
*
* @since 0.2.0
*/
[[nodiscard]]
answer_type del(const endpoint_variant &endpoint,
const parametermap &parameters);
[[nodiscard]] answer_type del(const endpoint_variant &endpoint,
const parametermap &parameters);
/*!
* @brief Make a HTTP DELETE call.
@ -233,8 +251,7 @@ public:
*
* @since 0.2.0
*/
[[nodiscard]]
inline answer_type del(const endpoint_variant &endpoint)
[[nodiscard]] inline answer_type del(const endpoint_variant &endpoint)
{
return del(endpoint, {});
}
@ -264,14 +281,15 @@ public:
{
CURLWrapper::cancel_stream();
}
private:
Instance &_instance;
const Instance &_instance;
const string_view _baseuri;
[[nodiscard]]
string endpoint_to_uri(const endpoint_variant &endpoint) const;
[[nodiscard]] string
endpoint_to_uri(const endpoint_variant &endpoint) const;
};
} // namespace mastodonpp
#endif // MASTODONPP_CONNECTION_HPP
#endif // MASTODONPP_CONNECTION_HPP

View File

@ -17,28 +17,19 @@
#ifndef MASTODONPP_CURL_WRAPPER_HPP
#define MASTODONPP_CURL_WRAPPER_HPP
#include "answer.hpp"
#include "curl/curl.h"
#include "types.hpp"
#include <map>
#include <mutex>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
namespace mastodonpp
{
using std::map;
using std::mutex;
using std::string;
using std::string_view;
using std::pair;
using std::variant;
using std::vector;
/*!
* @brief The HTTP method.
@ -47,43 +38,13 @@ using std::vector;
*/
enum class http_method
{
GET,
POST,
PATCH,
PUT,
DELETE
GET, // NOLINT(readability-identifier-naming)
POST, // NOLINT(readability-identifier-naming)
PATCH, // NOLINT(readability-identifier-naming)
PUT, // NOLINT(readability-identifier-naming)
DELETE // NOLINT(readability-identifier-naming)
};
/*!
* @brief `std::map` of parameters for %API calls.
*
* Note that arrays always have to be specified as vectors, even if they have
* only 1 element. To send a file, use <tt>\@file:</tt> followed by the file
* name as value.
*
* Example:
* @code
* parametermap parameters
* {
* {"poll[expires_in]", "86400"},
* {"poll[options]", vector<string_view>{"Yes", "No", "Maybe"}},
* {"status", "How is the weather?"}
* };
* @endcode
*
* @since 0.1.0
*/
using parametermap =
map<string_view, variant<string_view, vector<string_view>>>;
/*!
* @brief A single parameter of a parametermap.
*
* @since 0.1.0
*/
using parameterpair =
pair<string_view, variant<string_view, vector<string_view>>>;
/*!
* @brief Handles the details of network connections.
*
@ -108,8 +69,12 @@ public:
*/
CURLWrapper();
//! Copy constructor
CURLWrapper(const CURLWrapper &other) = delete;
/*!
* @brief Copy constructor. Does the same as the Constructor.
*
* @since 0.5.2
*/
CURLWrapper(const CURLWrapper &);
//! Move constructor
CURLWrapper(CURLWrapper &&other) noexcept = delete;
@ -117,7 +82,7 @@ public:
/*!
* @brief Cleans up curl and connection.
*
* Calls `curl_global_cleanup`, which is not thread-safe. For more
* May call `curl_global_cleanup`, which is not thread-safe. For more
* information consult [curl_global_cleanup(3)]
* (https://curl.haxx.se/libcurl/c/curl_global_cleanup.html).
*
@ -126,10 +91,10 @@ public:
virtual ~CURLWrapper() noexcept;
//! Copy assignment operator
CURLWrapper& operator=(const CURLWrapper &other) = delete;
CURLWrapper &operator=(const CURLWrapper &other) = delete;
//! Move assignment operator
CURLWrapper& operator=(CURLWrapper &&other) noexcept = delete;
CURLWrapper &operator=(CURLWrapper &&other) noexcept = delete;
/*!
* @brief Returns pointer to the CURL easy handle.
@ -146,16 +111,57 @@ public:
}
/*!
* @brief Set the proxy to use.
* @brief URL encodes the given string.
*
* See [CURLOPT_PROXY(3)]
* (https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html).
* For more information consult [curl_easy_escape(3)]
* (https://curl.haxx.se/libcurl/c/curl_easy_escape.html).
*
* @param proxy Examples: "socks4a://127.0.0.1:9050", "http://[::1]:3128".
* @param url String to escape.
*
* @since 0.1.0
* @return The escaped string or {} if it failed.
*
* @since 0.3.0
*/
void set_proxy(string_view proxy);
[[nodiscard]] inline string escape_url(const string_view url) const
{
char *cbuf{curl_easy_escape(_connection, url.data(),
static_cast<int>(url.size()))};
string sbuf{cbuf};
curl_free(cbuf);
return sbuf;
}
/*!
* @brief URL decodes the given string.
*
* For more information consult [curl_easy_unescape(3)]
* (https://curl.haxx.se/libcurl/c/curl_easy_unescape.html).
*
* @param url String to unescape.
*
* @return The unescaped string or {} if it failed.
*
* @since 0.3.0
*/
[[nodiscard]] inline string unescape_url(const string_view url) const
{
char *cbuf{curl_easy_unescape(_connection, url.data(),
static_cast<int>(url.size()), nullptr)};
string sbuf{cbuf};
curl_free(cbuf);
return sbuf;
}
/*!
* @brief Set some properties of the connection.
*
* Meant for internal use. See Instance::copy_connection_properties().
*
* @since 0.3.0
*/
void setup_connection_properties(string_view proxy,
string_view access_token,
string_view cainfo, string_view useragent);
protected:
/*!
@ -166,7 +172,7 @@ protected:
*
* @since 0.1.0
*/
mutex buffer_mutex;
mutex _buffer_mutex;
/*!
* @brief Make a HTTP request.
@ -177,17 +183,16 @@ protected:
*
* @since 0.1.0
*/
[[nodiscard]]
answer_type make_request(const http_method &method, string uri,
const parametermap &parameters);
[[nodiscard]] answer_type make_request(const http_method &method,
string uri,
const parametermap &parameters);
/*!
* @brief Returns a reference to the buffer libcurl writes into.
*
* @since 0.1.0
*/
[[nodiscard]]
string &get_buffer()
[[nodiscard]] inline string &get_buffer()
{
return _curl_buffer_body;
}
@ -206,19 +211,52 @@ protected:
_stream_cancelled = true;
}
/*!
* @brief Set the proxy to use.
*
* See [CURLOPT_PROXY(3)]
* (https://curl.haxx.se/libcurl/c/CURLOPT_PROXY.html).
*
* @param proxy Examples: "socks4a://127.0.0.1:9050", "http://[::1]:3128".
*
* @since 0.1.0
*/
virtual void set_proxy(string_view proxy);
/*!
* @brief Set OAuth 2.0 Bearer Access Token.
*
* @since 0.1.0
*/
void set_access_token(const string_view access_token);
void set_access_token(string_view access_token);
/*!
* @brief Set path to Certificate Authority (CA) bundle.
*
* @since 0.3.0
*/
virtual void set_cainfo(string_view path);
/*!
* @brief Sets the User-Agent.
*
* @since 0.3.0
*/
virtual void set_useragent(string_view useragent);
private:
CURL *_connection;
char _curl_buffer_error[CURL_ERROR_SIZE];
CURL *_connection{nullptr};
char _curl_buffer_error[CURL_ERROR_SIZE]{'\0'};
string _curl_buffer_headers;
string _curl_buffer_body;
bool _stream_cancelled;
bool _stream_cancelled{false};
/*!
* @brief Initializes curl and sets up connection.
*
* @since 0.5.2
*/
void init();
/*!
* @brief libcurl write callback function.
@ -238,7 +276,7 @@ private:
static inline size_t writer_body_wrapper(char *data, size_t sz,
size_t nmemb, void *f)
{
return static_cast<CURLWrapper*>(f)->writer_body(data, sz, nmemb);
return static_cast<CURLWrapper *>(f)->writer_body(data, sz, nmemb);
}
//! @copydoc writer_body
@ -248,7 +286,7 @@ private:
static inline size_t writer_header_wrapper(char *data, size_t sz,
size_t nmemb, void *f)
{
return static_cast<CURLWrapper*>(f)->writer_header(data, sz, nmemb);
return static_cast<CURLWrapper *>(f)->writer_header(data, sz, nmemb);
}
/*!
@ -259,15 +297,15 @@ private:
* @since 0.1.0
*/
int progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow);
curl_off_t ultotal, curl_off_t ulnow) const;
//! @copydoc writer_body_wrapper
static inline int progress_wrapper(void *f, void *clientp,
curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow)
{
return static_cast<CURLWrapper*>(f)->progress(clientp, dltotal, dlnow,
ultotal, ulnow);
return static_cast<CURLWrapper *>(f)->progress(clientp, dltotal, dlnow,
ultotal, ulnow);
}
/*!
@ -287,7 +325,8 @@ private:
*
* @since 0.1.0
*/
bool replace_parameter_in_uri(string &uri, const parameterpair &parameter);
static bool replace_parameter_in_uri(string &uri,
const parameterpair &parameter);
/*!
* @brief Add parameters to URI.
@ -297,7 +336,8 @@ private:
*
* @since 0.1.0
*/
void add_parameters_to_uri(string &uri, const parametermap &parameters);
static void add_parameters_to_uri(string &uri,
const parametermap &parameters);
/*!
* @brief Add `*curl_mimepart` to `*curl_mime`.
@ -306,10 +346,10 @@ private:
* @param data Data of the field. If it begins with <tt>`\@file:<tt>, the
* rest of the ergument is treated as a filename.
*
* @since 0.1.1
* @since 0.2.0
*/
void add_mime_part(curl_mime *mime,
string_view name, string_view data) const;
static void add_mime_part(curl_mime *mime, string_view name,
string_view data);
/*!
* @brief Convert parametermap to `*curl_mime`.
@ -331,4 +371,4 @@ private:
} // namespace mastodonpp
#endif // MASTODONPP_CURL_WRAPPER_HPP
#endif // MASTODONPP_CURL_WRAPPER_HPP

View File

@ -26,9 +26,9 @@
namespace mastodonpp
{
using std::uint16_t;
using std::exception;
using std::string;
using std::uint16_t;
/*!
* @brief Exception for libcurl errors.
@ -77,8 +77,7 @@ public:
*
* @since 0.1.0
*/
[[nodiscard]]
const char *what() const noexcept override;
[[nodiscard]] const char *what() const noexcept override;
private:
const string _message;
@ -87,4 +86,4 @@ private:
} // namespace mastodonpp
#endif // MASTODONPP_EXCEPTIONS_HPP
#endif // MASTODONPP_EXCEPTIONS_HPP

49
include/helpers.hpp Normal file
View File

@ -0,0 +1,49 @@
/* This file is part of mastodonpp.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MASTODONPP_HELPERS_HPP
#define MASTODONPP_HELPERS_HPP
#include <string>
#include <string_view>
//! @headerfile helpers.hpp mastodonpp/helpers.hpp
namespace mastodonpp
{
using std::string;
/*!
* @brief Replaces HTML entities with UTF-8 characters.
*
* Supports named and numbered entities, decimal and hexadecimal.
*
* Example:
* @code
* // Will output: 2€ = 2€ = 2€
* std::cout << mastodonpp::unescape_html("2&euro; = 2&#8364; = 2&#x20ac;");
* @endcode
*
* @param html The HTML to unescape.
*
* @since 0.4.0
*/
[[nodiscard]] string unescape_html(string html);
} // namespace mastodonpp
#endif // MASTODONPP_HELPERS_HPP

View File

@ -18,23 +18,29 @@
#define MASTODONPP_INSTANCE_HPP
#include "curl_wrapper.hpp"
#include "types.hpp"
#include <cstdint>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace mastodonpp
{
using std::uint64_t;
using std::string;
using std::string_view;
using std::move;
using std::uint64_t;
using std::vector;
/*!
* @brief Holds the access data of an instance.
*
* Instance%s are needed to initialize Connection%s. All properties you set
* here (with set_proxy(), set_useragent() and so on) are copied to every
* Connection you initialize afterwards.
*
* @since 0.1.0
*
* @headerfile instance.hpp mastodonpp/instance.hpp
@ -45,8 +51,6 @@ public:
/*!
* @brief Construct a new Instance object.
*
* Also queries `/api/v1/instance` for `max_toot_chars'.
*
* @param hostname The hostname of the instance.
* @param access_token Your access token.
*
@ -54,13 +58,47 @@ public:
*/
explicit Instance(string_view hostname, string_view access_token);
/*!
* @brief Copy constructor. A new CURLWrapper is constructed.
*
* @since 0.5.2
*/
Instance(const Instance &other);
//! Move constructor
Instance(Instance &&other) noexcept = delete;
//! Destructor
~Instance() noexcept override = default;
//! Copy assignment operator
Instance &operator=(const Instance &other) = delete;
//! Move assignment operator
Instance &operator=(Instance &&other) noexcept = delete;
/*!
* @brief Set the properties of the connection of the calling class up.
*
* Meant for internal use. This aligns the properties of the connection of
* the calling class with the properties of connection of this class.
*
* @param curlwrapper The CURLWrapper parent of the calling class.
*
* @since 0.3.0
*/
inline void copy_connection_properties(CURLWrapper &curlwrapper) const
{
curlwrapper.setup_connection_properties(_proxy, _access_token, _cainfo,
_useragent);
}
/*!
* @brief Returns the hostname.
*
* @since 0.1.0
*/
[[nodiscard]]
inline string_view get_hostname() const
[[nodiscard]] inline string_view get_hostname() const noexcept
{
return _hostname;
}
@ -72,8 +110,7 @@ public:
*
* @since 0.1.0
*/
[[nodiscard]]
inline string_view get_baseuri() const
[[nodiscard]] inline string_view get_baseuri() const noexcept
{
return _baseuri;
}
@ -83,8 +120,7 @@ public:
*
* @since 0.1.0
*/
[[nodiscard]]
inline string_view get_access_token() const
[[nodiscard]] inline string_view get_access_token() const noexcept
{
return _access_token;
}
@ -97,51 +133,192 @@ public:
*
* @since 0.1.0
*/
inline void set_access_token(string access_token)
inline void set_access_token(const string_view access_token)
{
_access_token = move(access_token);
_access_token = access_token;
CURLWrapper::set_access_token(access_token);
}
/*!
* @brief Returns the maximum number of characters per post.
*
* Queries `/api/v1/instance` for `max_toot_chars'. If the instance doesn't
* support it, the limit is assumed to be 500.
*
* After the first call, the value is saved internally. Subsequent calls
* return the saved value.
*
* @since 0.1.0
*/
[[nodiscard]]
uint64_t get_max_chars();
[[nodiscard]] uint64_t get_max_chars() noexcept;
/*! @copydoc CURLWrapper::set_proxy(string_view)
*
* Sets also the proxy for all Connection%s that are initialized with this
* Instance afterwards.
*/
void set_proxy(const string_view proxy)
void set_proxy(const string_view proxy) override
{
_proxy = proxy;
CURLWrapper::set_proxy(proxy);
}
/*!
* @brief Returns the proxy string that was previously set.
* @brief Returns the NodeInfo of the instance.
*
* Does not return the proxy if it was set from an environment variable.
* Attempts to download the [NodeInfo]
* (https://nodeinfo.diaspora.software/protocol.html) of the instance and
* returns it. Not every instance has it.
*
* @since 0.1.0
* @since 0.3.0
*/
[[nodiscard]]
string_view get_proxy() const
[[nodiscard]] answer_type get_nodeinfo();
/*!
* @brief Returns the allowed mime types for statuses.
*
* Extracts `metadata.postFormats` from NodeInfo. If none can be found,
* returns `{"text/plain"}`.
*
* After the first call, the value is saved internally. Subsequent calls
* return the saved value.
*
* @since 0.3.0
*/
vector<string> get_post_formats() noexcept;
/*!
* @brief Set path to Certificate Authority (CA) bundle.
*
* Sets also the CA info for all Connection%s that are initialized with
* this Instance afterwards.
*
* @since 0.3.0
*/
void set_cainfo(string_view path) override
{
return _proxy;
_cainfo = path;
CURLWrapper::set_cainfo(path);
}
/*!
* @brief Sets the User-Agent.
*
* Sets also the User-Agent for all Connection%s that are initialized with
* this Instance afterwards.
*
* @since 0.3.0
*/
void set_useragent(const string_view useragent) override
{
_useragent = useragent;
CURLWrapper::set_useragent(useragent);
}
/*!
* @brief Simplifies obtaining an OAuth 2.0 Bearer Access Token.
*
* * Create an Instance and initialize this class with it.
* * Call step_1() to get the URI your user has to visit.
* * Get the authorization code from your user.
* * Call step_2() with the code.
*
* Example:
* @code
* mastodonpp::Instance instance{"example.com", {}};
* mastodonpp::Instance::ObtainToken token{instance};
* auto answer{token.step1("Good program", "read:blocks read:mutes", "")};
* if (answer)
* {
* std::cout << "Please visit " << answer << "\nand paste the code: ";
* std::string code;
* std::cin >> code;
* answer = access_token{token.step2(code)};
* if (answer)
* {
* std::cout << "Success!\n";
* }
* }
* @endcode
*
* @since 0.3.0
*
* @headerfile instance.hpp mastodonpp/instance.hpp
*/
class ObtainToken : public CURLWrapper
{
public:
/*!
* @brief Constructor.
*
* @since 0.3.0
*/
explicit ObtainToken(Instance &instance)
: _instance{instance}
, _baseuri{instance.get_baseuri()}
{
_instance.copy_connection_properties(*this);
}
/*!
* @brief Creates an application via `/api/v1/apps`.
*
* The `body` of the returned @link answer_type answer @endlink
* contains only the URI, not the whole JSON response.
*
* Note that the required scopes may be different between Mastodon and
* other implementations, like Pleroma.
*
* @param client_name The name of your application.
* @param scopes Space separated list of scopes. Defaults to
* read if empty.
* @param website The URI to the homepage of your application. Can
* be an empty string.
*
* @return The URI your user has to visit.
*
* @since 0.3.0
*/
[[nodiscard]] answer_type step_1(string_view client_name,
string_view scopes,
string_view website);
/*!
* @brief Creates a token via `/oauth/token`.
*
* The `body` of the returned @link answer_type answer @endlink
* contains only the access token, not the whole JSON response.
*
* The access token will be set in the Instance you initialized
* this ObtainToken with.
*
* @param code The authorization code you got from the user.
*
* @return The access token.
*
* @since 0.3.0
*/
[[nodiscard]] answer_type step_2(string_view code);
private:
Instance &_instance;
const string _baseuri;
string _scopes;
string _client_id;
string _client_secret;
};
private:
const string _hostname;
const string _baseuri;
string _access_token;
uint64_t _max_chars;
string _proxy;
vector<string> _post_formats;
string _cainfo;
string _useragent;
};
} // namespace mastodonpp
#endif // MASTODONPP_INSTANCE_HPP
#endif // MASTODONPP_INSTANCE_HPP

View File

@ -17,15 +17,14 @@
#ifndef MASTODONPP_HPP
#define MASTODONPP_HPP
#include "answer.hpp"
#include "api.hpp"
#include "connection.hpp"
#include "exceptions.hpp"
#include "helpers.hpp"
#include "instance.hpp"
#include "types.hpp"
/*!
* @headerfile mastodonpp.hpp mastodonpp/mastodonpp.hpp
*
* @mainpage mastodonpp Reference
*
* @section using Using the library
@ -45,6 +44,9 @@
*
* Or compile your code with `g++ $(pkg-config --cflags --libs mastodonpp)`.
*
* Since we use C++17 features in the headers of this library, your program
* needs to be compiled as C++17 or higher too.
*
* @subsection example Example
*
* @code
@ -53,7 +55,7 @@
*
* int main()
* {
* mastodonpp::Instance instance{"example.com", ""};
* mastodonpp::Instance instance{"example.com", {}};
* std::cout << "Maximum characters per post: "
* << instance.get_max_chars() << std::endl;
*
@ -81,12 +83,10 @@
*
* @section thread_safety Thread safety
*
* The first time you construct an @link mastodonpp::Instance Instance @endlink
* or @link mastodonpp::Connection Connection @endlink, [curl_global_init(3)]
* (https://curl.haxx.se/libcurl/c/curl_global_init.html) is called. When the
* last @link mastodonpp::Instance Instance @endlink or @link
* mastodonpp::Connection Connection @endlink is destroyed,
* [curl_global_cleanup(3)]
* The first time you construct an @link mastodonpp::Instance Instance@endlink,
* [curl_global_init(3)](https://curl.haxx.se/libcurl/c/curl_global_init.html)
* is called. When the last @link mastodonpp::Instance Instance @endlink is
* destroyed, [curl_global_cleanup(3)]
* (https://curl.haxx.se/libcurl/c/curl_global_cleanup.html) is called. Both
* are not thread safe.
*
@ -105,14 +105,18 @@
* @example example05_update_notification_settings.cpp
* @example example06_update_name.cpp
* @example example07_delete_status.cpp
* @example example08_obtain_token.cpp
* @example example09_nlohmann_json.cpp
*/
/*!
* @brief C++ wrapper for the Mastodon %API.
*
* @since 0.1.0
*
* @headerfile mastodonpp.hpp mastodonpp/mastodonpp.hpp
*/
namespace mastodonpp
{} // namespace mastodonpp
#endif // MASTODONPP_HPP
#endif // MASTODONPP_HPP

View File

@ -14,28 +14,67 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MASTODONPP_ANSWER_HPP
#define MASTODONPP_ANSWER_HPP
// Types that are used in more than one file.
#ifndef MASTODONPP_TYPES_HPP
#define MASTODONPP_TYPES_HPP
#include <cstdint>
#include <map>
#include <ostream>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
namespace mastodonpp
{
using std::uint8_t;
using std::uint16_t;
using std::map;
using std::ostream;
using std::pair;
using std::string;
using std::string_view;
using std::uint16_t;
using std::uint8_t;
using std::variant;
using std::vector;
/*!
* @brief `std::map` of parameters for %API calls.
*
* Note that arrays always have to be specified as vectors, even if they have
* only 1 element. To send a file, use <tt>\@file:</tt> followed by the file
* name as value.
*
* Example:
* @code
* parametermap parameters
* {
* {"poll[expires_in]", "86400"},
* {"poll[options]", vector<string_view>{"Yes", "No", "Maybe"}},
* {"status", "How is the weather?"}
* };
* @endcode
*
* @since 0.1.0
*/
using parametermap = map<string_view,
variant<string_view, vector<string_view>>>;
/*!
* @brief A single parameter of a parametermap.
*
* @since 0.1.0
*/
using parameterpair = pair<string_view,
variant<string_view, vector<string_view>>>;
/*!
* @brief Return type for Request%s.
*
* @since 0.1.0
*
* @headerfile answer.hpp mastodonpp/answer.hpp
*
*/
struct answer_type
{
@ -46,6 +85,8 @@ struct answer_type
* [libcurl-errors(3)](https://curl.haxx.se/libcurl/c/libcurl-errors.html).
*
* @since 0.1.0
*
* @headerfile types.hpp mastodonpp/types.hpp
*/
uint8_t curl_error_code{0};
@ -97,8 +138,7 @@ struct answer_type
*
* @since 0.1.0
*/
friend std::ostream &operator <<(std::ostream &out,
const answer_type &answer);
friend ostream &operator<<(ostream &out, const answer_type &answer);
/*!
* @brief Returns the value of a header field.
@ -109,9 +149,45 @@ struct answer_type
*
* @since 0.1.0
*/
string_view get_header(string_view field) const;
[[nodiscard]] string_view get_header(string_view field) const;
/*!
* @brief Returns the parameters needed for the next entries.
*
* Parses the `Link` header.
*
* @since 0.3.0
*/
[[nodiscard]] inline parametermap next() const
{
return parse_pagination(true);
}
/*!
* @brief Returns the parameters needed for the previous entries.
*
*
* Parses the `Link` header.
*
* @since 0.3.0
*/
[[nodiscard]] inline parametermap prev() const
{
return parse_pagination(false);
}
private:
/*!
* @brief Returns the parameters needed for the next or previous entries.
*
*
* Parses the `Link` header.
*
* @since 0.3.0
*/
[[nodiscard]] parametermap parse_pagination(bool next) const;
};
} // namespace mastodonpp
#endif // MASTODONPP_ANSWER_HPP
#endif // MASTODONPP_TYPES_HPP

View File

@ -25,10 +25,9 @@ API::API(const endpoint_type &endpoint)
// TODO: look for a better way.
// NOLINTNEXTLINE(cert-err58-cpp)
const map<API::endpoint_type,string_view> API::_endpoint_map
{
const map<API::endpoint_type, string_view> API::_endpoint_map{
{v1::apps, "/api/v1/apps"},
{v1::apps_verify_credentials, "/api/v1/apps/verify/credentials"},
{v1::apps_verify_credentials, "/api/v1/apps/verify_credentials"},
{v1::accounts, "/api/v1/accounts"},
{v1::accounts_verify_credentials, "/api/v1/accounts/verify_credentials"},
@ -38,7 +37,7 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
{v1::accounts_id_followers, "/api/v1/accounts/<ID>/followers"},
{v1::accounts_id_following, "/api/v1/accounts/<ID>/following"},
{v1::accounts_id_lists, "/api/v1/accounts/<ID>/lists"},
{v1::accounts_id_identity_proofs, "/api/v1/accounts/<ID>/identity/proofs"},
{v1::accounts_id_identity_proofs, "/api/v1/accounts/<ID>/identity_proofs"},
{v1::accounts_id_follow, "/api/v1/accounts/<ID>/follow"},
{v1::accounts_id_unfollow, "/api/v1/accounts/<ID>/unfollow"},
{v1::accounts_id_block, "/api/v1/accounts/<ID>/block"},
@ -58,23 +57,23 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
{v1::blocks, "/api/v1/blocks"},
{v1::domain_blocks, "/api/v1/domain/blocks"},
{v1::domain_blocks, "/api/v1/domain_blocks"},
{v1::filters, "/api/v1/filters"},
{v1::filters_id, "/api/v1/filters/<ID>"},
{v1::reports, "/api/v1/reports"},
{v1::follow_requests, "/api/v1/follow/requests"},
{v1::follow_requests, "/api/v1/follow_requests"},
{v1::follow_requests_id_authorize,
"/api/v1/follow/requests/<ID>/authorize"},
{v1::follow_requests_id_reject, "/api/v1/follow/requests/<ID>/reject"},
"/api/v1/follow_requests/<ID>/authorize"},
{v1::follow_requests_id_reject, "/api/v1/follow_requests/<ID>/reject"},
{v1::endorsements, "/api/v1/endorsements"},
{v1::featured_tags, "/api/v1/featured/tags"},
{v1::featured_tags_id, "/api/v1/featured/tags/<ID>"},
{v1::featured_tags_suggestions, "/api/v1/featured/tags/suggestions"},
{v1::featured_tags_id, "/api/v1/featured_tags/<ID>"},
{v1::featured_tags_suggestions, "/api/v1/featured_tags/suggestions"},
{v1::preferences, "/api/v1/preferences"},
@ -84,8 +83,8 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
{v1::statuses, "/api/v1/statuses"},
{v1::statuses_id, "/api/v1/statuses/<ID>"},
{v1::statuses_id_context, "/api/v1/statuses/<ID>/context"},
{v1::statuses_id_reblogged_by, "/api/v1/statuses/<ID>/reblogged/by"},
{v1::statuses_id_favourited_by, "/api/v1/statuses/<ID>/favourited/by"},
{v1::statuses_id_reblogged_by, "/api/v1/statuses/<ID>/reblogged_by"},
{v1::statuses_id_favourited_by, "/api/v1/statuses/<ID>/favourited_by"},
{v1::statuses_id_favourite, "/api/v1/statuses/<ID>/favourite"},
{v1::statuses_id_unfavourite, "/api/v1/statuses/<ID>/unfavourite"},
{v1::statuses_id_reblog, "/api/v1/statuses/<ID>/reblog"},
@ -103,13 +102,13 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
{v1::polls_id, "/api/v1/polls/<ID>"},
{v1::polls_id_votes, "/api/v1/polls/<ID>/votes"},
{v1::scheduled_statuses, "/api/v1/scheduled/statuses"},
{v1::scheduled_statuses_id, "/api/v1/scheduled/statuses/<ID>"},
{v1::scheduled_statuses, "/api/v1/scheduled_statuses"},
{v1::scheduled_statuses_id, "/api/v1/scheduled_statuses/<ID>"},
{v1::timelines_public, "/api/v1/timelines/public"},
{v1::timelines_tag_hashtag, "/api/v1/timelines/tag/<HASHTAG>"},
{v1::timelines_home, "/api/v1/timelines/home"},
{v1::timelines_list_list_id, "/api/v1/timelines/list/list/<ID>"},
{v1::timelines_list_list_id, "/api/v1/timelines/list/<LIST_ID>"},
{v1::conversations, "/api/v1/conversations"},
{v1::conversations_id, "/api/v1/conversations/<ID>"},
@ -145,12 +144,12 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
{v1::directory, "/api/v1/directory"},
{v1::custom_emojis, "/api/v1/custom/emojis"},
{v1::custom_emojis, "/api/v1/custom_emojis"},
{v1::admin_accounts, "/api/v1/admin/accounts"},
{v1::admin_accounts_id, "/api/v1/admin/accounts/<ID>"},
{v1::admin_accounts_account_id_action,
"/api/v1/admin/accounts/account/<ID>/action"},
"/api/v1/admin/accounts/<ACCOUNT_ID>/action"},
{v1::admin_accounts_id_approve, "/api/v1/admin/accounts/<ID>/approve"},
{v1::admin_accounts_id_reject, "/api/v1/admin/accounts/<ID>/reject"},
{v1::admin_accounts_id_enable, "/api/v1/admin/accounts/<ID>/enable"},
@ -159,13 +158,12 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
{v1::admin_reports, "/api/v1/admin/reports"},
{v1::admin_reports_id, "/api/v1/admin/reports/<ID>"},
{v1::admin_reports_id_assign_to_self,
"/api/v1/admin/reports/<ID>/assign/to/self"},
"/api/v1/admin/reports/<ID>/assign_to_self"},
{v1::admin_reports_id_unassign, "/api/v1/admin/reports/<ID>/unassign"},
{v1::admin_reports_id_resolve, "/api/v1/admin/reports/<ID>/resolve"},
{v1::admin_reports_id_reopen, "/api/v1/admin/reports/<ID>/reopen"},
{v1::pleroma_notifications_read, " /api/v1/pleroma/notifications/read"},
{v1::pleroma_accounts_id_subscribe,
"/api/v1/pleroma/accounts/<ID>/subscribe"},
{v1::pleroma_accounts_id_unsubscribe,
@ -180,12 +178,19 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
"/api/v1/pleroma/accounts/update_background"},
{v1::pleroma_accounts_confirmation_resend,
"/api/v1/pleroma/accounts/confirmation_resend"},
{v1::pleroma_mascot, "/api/v1/pleroma/mascot"},
{v1::pleroma_conversations_id_statuses,
"/api/v1/pleroma/conversations/<ID>/statuses"},
{v1::pleroma_conversations_id, "/api/v1/pleroma/conversations/<ID>"},
{v1::pleroma_conversations_id_read,
"/api/v1/pleroma/conversations/<ID>/read"},
{v1::pleroma_accounts_id_scrobbles,
"/api/v1/pleroma/accounts/<ID>/scrobbles"},
{v1::pleroma_scrobble, "/api/v1/pleroma/scrobble"},
{v1::pleroma_statuses_id_reactions_emoji,
"/api/v1/pleroma/statuses/<ID>/reactions/<EMOJI>"},
{v1::pleroma_statuses_id_reactions,
"/api/v1/pleroma/statuses/<ID>/reactions"},
{v2::search, "/api/v2/search"},
@ -199,18 +204,25 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
{pleroma::admin_users, "/api/pleroma/admin/users"},
{pleroma::admin_users_follow, "/api/pleroma/admin/users/follow"},
{pleroma::admin_users_unfollow, "/api/pleroma/admin/users/unfollow"},
{pleroma::admin_users_nickname, "/api/pleroma/admin/users/<NICKNAME>"},
{pleroma::admin_users_nickname_toggle_activation,
"/api/pleroma/admin/users/<NICKNAME>/toggle_activation"},
{pleroma::admin_users_tag, "/api/pleroma/admin/users/tag"},
{pleroma::admin_users_nickname_permission_group,
"/api/pleroma/admin/users/<NICKNAME>/permission_group"},
{pleroma::admin_users_nickname_permission_group_permission_group,
"/api/pleroma/admin/users/<NICKNAME>/permission_group/<PERMISSION_GROUP>"},
{pleroma::admin_users_nickname_activation_status,
"/api/pleroma/admin/users/<NICKNAME>/activation_status"},
"/api/pleroma/admin/users/<NICKNAME>"
"/permission_group/<PERMISSION_GROUP>"},
{pleroma::admin_users_permission_group_permission_group,
"/api/pleroma/admin/users/permission_group/<PERMISSION_GROUP>"},
{pleroma::admin_users_activate, "/api/pleroma/admin/users/activate"},
{pleroma::admin_users_deactivate, "/api/pleroma/admin/users/deactivate"},
{pleroma::admin_users_nickname_or_id,
"/api/pleroma/admin/users/<NICKNAME_OR_ID>"},
{pleroma::admin_users_nickname_or_id_statuses,
"/api/pleroma/admin/users/<NICKNAME_OR_ID>/statuses"},
{pleroma::admin_instances_instance_statuses,
"/api/pleroma/admin/instances/<INSTANCE>/statuses"},
{pleroma::admin_statuses, "/api/pleroma/admin/statuses"},
{pleroma::admin_relay, "/api/pleroma/admin/relay"},
{pleroma::admin_users_invite_token,
"/api/pleroma/admin/users/invite_token"},
@ -221,27 +233,60 @@ const map<API::endpoint_type,string_view> API::_endpoint_map
"/api/pleroma/admin/users/email_invite"},
{pleroma::admin_users_nickname_password_reset,
"/api/pleroma/admin/users/<NICKNAME>/password_reset"},
{pleroma::admin_users_nickname_update_credentials,
"/api/pleroma/admin/users/<NICKNAME>/update_credentials"},
{pleroma::admin_users_force_password_reset,
"/api/pleroma/admin/users/force_password_reset"},
{pleroma::admin_reports, "/api/pleroma/admin/reports"},
{pleroma::admin_grouped_reports, "/api/pleroma/admin/grouped_reports"},
{pleroma::admin_reports_id, "/api/pleroma/admin/reports/<ID>"},
{pleroma::admin_reports_id_notes, "/api/pleroma/admin/reports/<ID>/notes"},
{pleroma::admin_reports_report_id_notes_id,
"/api/pleroma/admin/reports/<REPORT_ID>/notes/<ID>"},
{pleroma::admin_statuses_id, "/api/pleroma/admin/statuses/<ID>"},
{pleroma::admin_restart, "/api/pleroma/admin/restart"},
{pleroma::admin_config, "/api/pleroma/admin/config"},
{pleroma::admin_config_descriptions,
"/api/pleroma/admin/config/descriptions"},
{pleroma::admin_moderation_log, "/api/pleroma/admin/moderation_log"},
{pleroma::admin_reload_emoji, "/api/pleroma/admin/reload_emoji"},
{pleroma::admin_users_confirm_email,
"/api/pleroma/admin/users/confirm_email"},
{pleroma::admin_users_resend_confirm_email,
"/api/pleroma/admin/users/resend_confirm_email"},
{pleroma::admin_stats, "/api/pleroma/admin/stats"},
{pleroma::admin_users_nickname, "/api/pleroma/admin/users/<NICKNAME>"},
{pleroma::admin_users_nickname_activation_status,
"/api/pleroma/admin/users/<NICKNAME>/activation_status"},
{pleroma::admin_reports_id_respond,
"/api/pleroma/admin/reports/<ID>/respond"},
{pleroma::admin_statuses_id, "/api/pleroma/admin/statuses/<ID>"},
{pleroma::admin_config_migrate_to_db,
"/api/pleroma/admin/config/migrate_to_db"},
{pleroma::admin_config_migrate_from_db,
"/api/pleroma/admin/config/migrate_from_db"},
{pleroma::admin_config, "/api/pleroma/admin/config"},
{pleroma::emoji, "/api/pleroma/emoji"},
{pleroma::follow_import, "/api/pleroma/follow_import"},
{pleroma::captcha, "/api/pleroma/captcha,"},
{pleroma::delete_account, "/api/pleroma/delete_account"},
{pleroma::disable_account, "/api/pleroma/disable_account"},
{pleroma::account_register, "/api/pleroma/account/register"},
{pleroma::notification_settings, "/api/pleroma/notification_settings"},
{pleroma::healthcheck, "/api/pleroma/healthcheck"},
{pleroma::change_email, "/api/pleroma/change_email"},
{pleroma::emoji_packs, "/api/pleroma/emoji/packs"},
{pleroma::emoji_packs_name, "/api/pleroma/emoji/packs/<NAME>"},
{pleroma::emoji_packs_name_update_file,
"/api/pleroma/emoji/packs/<NAME>/update_file"},
{pleroma::emoji_packs_name_update_metadata,
"/api/pleroma/emoji/packs/<NAME>/update_metadata"},
{pleroma::emoji_packs_download_from,
"/api/pleroma/emoji/packs/download_from"},
{pleroma::emoji_packs_list_from, "/api/pleroma/emoji/packs/list_from"},
{pleroma::emoji_packs_name_download_shared,
"/api/pleroma/emoji/packs/<NAME>/download_shared"},
{pleroma::account_register, "/api/pleroma/account/register"},
};
} // namespace mastodonpp

View File

@ -21,28 +21,12 @@ namespace mastodonpp
using std::holds_alternative;
Connection::Connection(Instance &instance)
: _instance{instance}
, _baseuri{instance.get_baseuri()}
{
auto proxy{_instance.get_proxy()};
if (!proxy.empty())
{
CURLWrapper::set_proxy(proxy);
}
if (!_instance.get_access_token().empty())
{
CURLWrapper::set_access_token(_instance.get_access_token());
}
}
string Connection::endpoint_to_uri(const endpoint_variant &endpoint) const
{
if (holds_alternative<API::endpoint_type>(endpoint))
{
return string(_baseuri)
+= API{std::get<API::endpoint_type>(endpoint)}.to_string_view();
return string(_baseuri) += API{std::get<API::endpoint_type>(endpoint)}
.to_string_view();
}
return string(_baseuri) += std::get<string_view>(endpoint);
}
@ -50,56 +34,57 @@ string Connection::endpoint_to_uri(const endpoint_variant &endpoint) const
answer_type Connection::get(const endpoint_variant &endpoint,
const parametermap &parameters)
{
return make_request(http_method::GET,
endpoint_to_uri(endpoint), parameters);
return make_request(http_method::GET, endpoint_to_uri(endpoint),
parameters);
}
answer_type Connection::post(const endpoint_variant &endpoint,
const parametermap &parameters)
{
return make_request(http_method::POST,
endpoint_to_uri(endpoint), parameters);
return make_request(http_method::POST, endpoint_to_uri(endpoint),
parameters);
}
answer_type Connection::patch(const endpoint_variant &endpoint,
const parametermap &parameters)
{
return make_request(http_method::PATCH,
endpoint_to_uri(endpoint), parameters);
return make_request(http_method::PATCH, endpoint_to_uri(endpoint),
parameters);
}
answer_type Connection::put(const endpoint_variant &endpoint,
const parametermap &parameters)
{
return make_request(http_method::PUT,
endpoint_to_uri(endpoint), parameters);
return make_request(http_method::PUT, endpoint_to_uri(endpoint),
parameters);
}
answer_type Connection::del(const endpoint_variant &endpoint,
const parametermap &parameters)
{
return make_request(http_method::DELETE,
endpoint_to_uri(endpoint), parameters);
return make_request(http_method::DELETE, endpoint_to_uri(endpoint),
parameters);
}
string Connection::get_new_stream_contents()
{
buffer_mutex.lock();
_buffer_mutex.lock();
auto &buffer{get_buffer()};
auto buffer_copy{buffer};
string buffer_copy{buffer};
buffer.clear();
buffer_mutex.unlock();
_buffer_mutex.unlock();
return buffer_copy;
}
vector<event_type> Connection::get_new_events()
{
buffer_mutex.lock();
_buffer_mutex.lock();
auto &buffer{get_buffer()};
vector<event_type> events;
size_t pos{0};
while ((pos = buffer.find("event: ")) != string::npos)
constexpr string_view search_event{"event: "};
while ((pos = buffer.find(search_event)) != string::npos)
{
const auto endpos{buffer.find("\n\n", pos)};
if (endpos == string::npos)
@ -108,16 +93,17 @@ vector<event_type> Connection::get_new_events()
}
event_type event;
pos += 7; // Length of "event: ".
pos += search_event.size();
event.type = buffer.substr(pos, buffer.find('\n', pos) - pos);
pos = buffer.find("data: ") + 6;
constexpr string_view search_data{"data: "};
pos = buffer.find(search_data) + search_data.size();
event.data = buffer.substr(pos, endpos - pos);
events.push_back(event);
buffer.erase(0, endpos);
}
buffer_mutex.unlock();
_buffer_mutex.unlock();
return events;
}

View File

@ -15,6 +15,7 @@
*/
#include "curl_wrapper.hpp"
#include "exceptions.hpp"
#include "log.hpp"
#include "version.hpp"
@ -22,25 +23,26 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <cctype>
#include <cstdint>
namespace mastodonpp
{
using std::any_of;
using std::array; // NOLINT(misc-unused-using-decls)
using std::atomic;
using std::get;
using std::holds_alternative;
using std::any_of;
using std::array;
using std::atomic;
using std::uint8_t;
using std::toupper;
using std::transform;
using std::uint16_t;
using std::uint8_t;
// No one will ever need more than 65535 connections. 😉
static atomic<uint16_t> curlwrapper_instances{0};
CURLWrapper::CURLWrapper()
: _curl_buffer_error{}
, _stream_cancelled(false)
void CURLWrapper::init()
{
if (curlwrapper_instances == 0)
{
@ -52,6 +54,17 @@ CURLWrapper::CURLWrapper()
_connection = curl_easy_init();
setup_curl();
}
CURLWrapper::CURLWrapper()
{
init();
}
CURLWrapper::CURLWrapper(const CURLWrapper &)
{
init();
}
CURLWrapper::~CURLWrapper() noexcept
{
curl_easy_cleanup(_connection);
@ -64,16 +77,6 @@ CURLWrapper::~CURLWrapper() noexcept
}
}
void CURLWrapper::set_proxy(const string_view proxy)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
CURLcode code{curl_easy_setopt(_connection, CURLOPT_PROXY, proxy.data())};
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set proxy", _curl_buffer_error};
}
}
answer_type CURLWrapper::make_request(const http_method &method, string uri,
const parametermap &parameters)
{
@ -81,15 +84,14 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
_curl_buffer_headers.clear();
_curl_buffer_body.clear();
CURLcode code;
CURLcode code{CURLE_OK};
switch (method)
{
case http_method::GET:
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_HTTPGET, 1L);
add_parameters_to_uri(uri, parameters);
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
curl_easy_setopt(_connection, CURLOPT_HTTPGET, 1L);
break;
}
@ -98,13 +100,13 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
if (parameters.empty())
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_POST, 1L);
curl_easy_setopt(_connection, CURLOPT_POST, 1L);
}
else
{
curl_mime *mime{parameters_to_curl_mime(uri, parameters)};
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_MIMEPOST, mime);
curl_easy_setopt(_connection, CURLOPT_MIMEPOST, mime);
}
break;
@ -115,11 +117,15 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
{
curl_mime *mime{parameters_to_curl_mime(uri, parameters)};
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_MIMEPOST, mime);
curl_easy_setopt(_connection, CURLOPT_MIMEPOST, mime);
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_CUSTOMREQUEST, "PATCH");
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set URI", _curl_buffer_error};
}
break;
}
@ -129,11 +135,15 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
{
curl_mime *mime{parameters_to_curl_mime(uri, parameters)};
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_MIMEPOST, mime);
curl_easy_setopt(_connection, CURLOPT_MIMEPOST, mime);
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_CUSTOMREQUEST, "PUT");
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set URI", _curl_buffer_error};
}
break;
}
@ -143,20 +153,19 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
{
curl_mime *mime{parameters_to_curl_mime(uri, parameters)};
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_MIMEPOST, mime);
curl_easy_setopt(_connection, CURLOPT_MIMEPOST, mime);
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_CUSTOMREQUEST, "DELETE");
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set URI", _curl_buffer_error};
}
break;
}
}
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set HTTP method",
_curl_buffer_error};
}
debuglog << "Making request to: " << uri << '\n';
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
@ -171,7 +180,7 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
if (code == CURLE_OK
|| (code == CURLE_ABORTED_BY_CALLBACK && _stream_cancelled))
{
long http_status; // NOLINT(google-runtime-int)
long http_status{0}; // NOLINT(google-runtime-int)
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
curl_easy_getinfo(_connection, CURLINFO_RESPONSE_CODE, &http_status);
answer.http_status = static_cast<uint16_t>(http_status);
@ -191,6 +200,43 @@ answer_type CURLWrapper::make_request(const http_method &method, string uri,
return answer;
}
void CURLWrapper::setup_connection_properties(const string_view proxy,
const string_view access_token,
const string_view cainfo,
const string_view useragent)
{
if (!proxy.empty())
{
set_proxy(proxy);
}
if (!access_token.empty())
{
set_access_token(access_token);
}
if (!cainfo.empty())
{
set_cainfo(cainfo);
}
if (!useragent.empty())
{
set_useragent(useragent);
}
}
void CURLWrapper::set_proxy(const string_view proxy)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
CURLcode code{curl_easy_setopt(_connection, CURLOPT_PROXY, proxy.data())};
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set proxy", _curl_buffer_error};
}
debuglog << "Set proxy to: " << proxy << '\n';
}
void CURLWrapper::set_access_token(const string_view access_token)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg, hicpp-signed-bitwise)
@ -199,11 +245,11 @@ void CURLWrapper::set_access_token(const string_view access_token)
if (code != CURLE_OK)
{
throw CURLException{code, "Could not set authorization token.",
_curl_buffer_error};
_curl_buffer_error};
}
#if (LIBCURL_VERSION_NUM < 0x073d00) // libcurl < 7.61.0.
#define CURLAUTH_BEARER CURLAUTH_ANY
# define CURLAUTH_BEARER CURLAUTH_ANY
#endif
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg, hicpp-signed-bitwise)
@ -211,29 +257,52 @@ void CURLWrapper::set_access_token(const string_view access_token)
if (code != CURLE_OK)
{
throw CURLException{code, "Could not set authorization token.",
_curl_buffer_error};
_curl_buffer_error};
}
debuglog << "Set authorization token.\n";
}
void CURLWrapper::set_cainfo(const string_view path)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
CURLcode code{curl_easy_setopt(_connection, CURLOPT_CAINFO, path.data())};
if (code != CURLE_OK)
{
throw CURLException{code, "Could not set CA info.", _curl_buffer_error};
}
}
void CURLWrapper::set_useragent(const string_view useragent)
{
CURLcode code{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
curl_easy_setopt(_connection, CURLOPT_USERAGENT, useragent.data())};
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set User-Agent",
_curl_buffer_error};
}
debuglog << "Set User-Agent to: " << useragent << '\n';
}
size_t CURLWrapper::writer_body(char *data, size_t size, size_t nmemb)
{
if(data == nullptr)
if (data == nullptr)
{
return 0;
}
buffer_mutex.lock();
_buffer_mutex.lock();
_curl_buffer_body.append(data, size * nmemb);
buffer_mutex.unlock();
_buffer_mutex.unlock();
return size * nmemb;
}
size_t CURLWrapper::writer_header(char *data, size_t size, size_t nmemb)
{
if(data == nullptr)
if (data == nullptr)
{
return 0;
}
@ -243,8 +312,8 @@ size_t CURLWrapper::writer_header(char *data, size_t size, size_t nmemb)
return size * nmemb;
}
int CURLWrapper::progress(void *, curl_off_t , curl_off_t ,
curl_off_t , curl_off_t )
int CURLWrapper::progress(void *, curl_off_t, curl_off_t, curl_off_t,
curl_off_t) const
{
if (_stream_cancelled)
{
@ -281,18 +350,11 @@ void CURLWrapper::setup_curl()
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
curl_easy_setopt(_connection, CURLOPT_NOPROGRESS, 0L);
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
CURLcode code{curl_easy_setopt(_connection, CURLOPT_USERAGENT,
(string("mastodonpp/") += version).c_str())};
if (code != CURLE_OK)
{
throw CURLException{code, "Failed to set User-Agent",
_curl_buffer_error};
}
CURLWrapper::set_useragent((string("mastodonpp/") += version));
// The next 2 only fail if HTTP is not supported.
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
code = curl_easy_setopt(_connection, CURLOPT_FOLLOWLOCATION, 1L);
CURLcode code{curl_easy_setopt(_connection, CURLOPT_FOLLOWLOCATION, 1L)};
if (code != CURLE_OK)
{
throw CURLException{code, "HTTP is not supported.", _curl_buffer_error};
@ -304,15 +366,28 @@ void CURLWrapper::setup_curl()
bool CURLWrapper::replace_parameter_in_uri(string &uri,
const parameterpair &parameter)
{
static constexpr array replace
{
"id", "nickname", "nickname_or_id",
"hashtag", "permission_group"
};
static constexpr array replace{"id",
"nickname",
"nickname_or_id",
"account_id",
"list_id",
"hashtag",
"permission_group",
"instance",
"report_id",
"name",
"emoji"};
if (any_of(replace.begin(), replace.end(),
[&parameter](const auto &s) { return s == parameter.first; }))
{
const auto pos{uri.find('<')};
const string searchstring{[&parameter] {
string s{"<"};
s += parameter.first;
transform(s.begin(), s.end(), s.begin(),
[](const unsigned char c) { return toupper(c); });
return s;
}()};
const auto pos{uri.find(searchstring)};
if (pos != string::npos)
{
uri.replace(pos, parameter.first.size() + 2,
@ -329,6 +404,7 @@ bool CURLWrapper::replace_parameter_in_uri(string &uri,
void CURLWrapper::add_parameters_to_uri(string &uri,
const parametermap &parameters)
{
bool first{true};
// Replace <ID> with the value of parameter “id” and so on.
for (const auto &param : parameters)
{
@ -337,7 +413,6 @@ void CURLWrapper::add_parameters_to_uri(string &uri,
continue;
}
static bool first{true};
if (first)
{
uri += "?";
@ -365,8 +440,8 @@ void CURLWrapper::add_parameters_to_uri(string &uri,
}
}
void CURLWrapper::add_mime_part(curl_mime *mime,
string_view name, string_view data) const
void CURLWrapper::add_mime_part(curl_mime *mime, string_view name,
string_view data)
{
curl_mimepart *part{curl_mime_addpart(mime)};
if (part == nullptr)

View File

@ -21,8 +21,8 @@
namespace mastodonpp
{
using std::to_string;
using std::move;
using std::to_string;
CURLException::CURLException(const CURLcode &error, string message)
: error_code{error}

178
src/helpers.cpp Normal file
View File

@ -0,0 +1,178 @@
/* This file is part of mastodonpp.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "helpers.hpp"
#include <codecvt>
#include <locale>
#include <map>
#include <regex>
#include <stdexcept>
#include <string_view>
namespace mastodonpp
{
using std::codecvt_utf8;
using std::map;
using std::move;
using std::regex;
using std::regex_search;
using std::smatch;
using std::stoul;
using std::string_view;
using std::wstring_convert;
string unescape_html(string html)
{
string buffer{move(html)};
string output;
// Source: https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_
// entity_references#Character_entity_references_in_HTML
const map<string_view, char32_t>
names{{"exclamation", 0x0021}, {"quot", 0x0022}, {"percent", 0x0025},
{"amp", 0x0026}, {"apos", 0x0027}, {"add", 0x002B},
{"lt", 0x003C}, {"equal", 0x003D}, {"gt", 0x003E},
{"nbsp", 0x00A0}, {"iexcl", 0x00A1}, {"cent", 0x00A2},
{"pound", 0x00A3}, {"curren", 0x00A4}, {"yen", 0x00A5},
{"brvbar", 0x00A6}, {"sect", 0x00A7}, {"uml", 0x00A8},
{"copy", 0x00A9}, {"ordf", 0x00AA}, {"laquo", 0x00AB},
{"not", 0x00AC}, {"shy", 0x00AD}, {"reg", 0x00AE},
{"macr", 0x00AF}, {"deg", 0x00B0}, {"plusmn", 0x00B1},
{"sup2", 0x00B2}, {"sup3", 0x00B3}, {"acute", 0x00B4},
{"micro", 0x00B5}, {"para", 0x00B6}, {"middot", 0x00B7},
{"cedil", 0x00B8}, {"sup1", 0x00B9}, {"ordm", 0x00BA},
{"raquo", 0x00BB}, {"frac14", 0x00BC}, {"frac12", 0x00BD},
{"frac34", 0x00BE}, {"iquest", 0x00BF}, {"Agrave", 0x00C0},
{"Aacute", 0x00C1}, {"Acirc", 0x00C2}, {"Atilde", 0x00C3},
{"Auml", 0x00C4}, {"Aring", 0x00C5}, {"AElig", 0x00C6},
{"Ccedil", 0x00C7}, {"Egrave", 0x00C8}, {"Eacute", 0x00C9},
{"Ecirc", 0x00CA}, {"Euml", 0x00CB}, {"Igrave", 0x00CC},
{"Iacute", 0x00CD}, {"Icirc", 0x00CE}, {"Iuml", 0x00CF},
{"ETH", 0x00D0}, {"Ntilde", 0x00D1}, {"Ograve", 0x00D2},
{"Oacute", 0x00D3}, {"Ocirc", 0x00D4}, {"Otilde", 0x00D5},
{"Ouml", 0x00D6}, {"times", 0x00D7}, {"Oslash", 0x00D8},
{"Ugrave", 0x00D9}, {"Uacute", 0x00DA}, {"Ucirc", 0x00DB},
{"Uuml", 0x00DC}, {"Yacute", 0x00DD}, {"THORN", 0x00DE},
{"szlig", 0x00DF}, {"agrave", 0x00E0}, {"aacute", 0x00E1},
{"acirc", 0x00E2}, {"atilde", 0x00E3}, {"auml", 0x00E4},
{"aring", 0x00E5}, {"aelig", 0x00E6}, {"ccedil", 0x00E7},
{"egrave", 0x00E8}, {"eacute", 0x00E9}, {"ecirc", 0x00EA},
{"euml", 0x00EB}, {"igrave", 0x00EC}, {"iacute", 0x00ED},
{"icirc", 0x00EE}, {"iuml", 0x00EF}, {"eth", 0x00F0},
{"ntilde", 0x00F1}, {"ograve", 0x00F2}, {"oacute", 0x00F3},
{"ocirc", 0x00F4}, {"otilde", 0x00F5}, {"ouml", 0x00F6},
{"divide", 0x00F7}, {"oslash", 0x00F8}, {"ugrave", 0x00F9},
{"uacute", 0x00FA}, {"ucirc", 0x00FB}, {"uuml", 0x00FC},
{"yacute", 0x00FD}, {"thorn", 0x00FE}, {"yuml", 0x00FF},
{"OElig", 0x0152}, {"oelig", 0x0153}, {"Scaron", 0x0160},
{"scaron", 0x0161}, {"Yuml", 0x0178}, {"fnof", 0x0192},
{"circ", 0x02C6}, {"tilde", 0x02DC}, {"Alpha", 0x0391},
{"Beta", 0x0392}, {"Gamma", 0x0393}, {"Delta", 0x0394},
{"Epsilon", 0x0395}, {"Zeta", 0x0396}, {"Eta", 0x0397},
{"Theta", 0x0398}, {"Iota", 0x0399}, {"Kappa", 0x039A},
{"Lambda", 0x039B}, {"Mu", 0x039C}, {"Nu", 0x039D},
{"Xi", 0x039E}, {"Omicron", 0x039F}, {"Pi", 0x03A0},
{"Rho", 0x03A1}, {"Sigma", 0x03A3}, {"Tau", 0x03A4},
{"Upsilon", 0x03A5}, {"Phi", 0x03A6}, {"Chi", 0x03A7},
{"Psi", 0x03A8}, {"Omega", 0x03A9}, {"alpha", 0x03B1},
{"beta", 0x03B2}, {"gamma", 0x03B3}, {"delta", 0x03B4},
{"epsilon", 0x03B5}, {"zeta", 0x03B6}, {"eta", 0x03B7},
{"theta", 0x03B8}, {"iota", 0x03B9}, {"kappa", 0x03BA},
{"lambda", 0x03BB}, {"mu", 0x03BC}, {"nu", 0x03BD},
{"xi", 0x03BE}, {"omicron", 0x03BF}, {"pi", 0x03C0},
{"rho", 0x03C1}, {"sigmaf", 0x03C2}, {"sigma", 0x03C3},
{"tau", 0x03C4}, {"upsilon", 0x03C5}, {"phi", 0x03C6},
{"chi", 0x03C7}, {"psi", 0x03C8}, {"omega", 0x03C9},
{"thetasym", 0x03D1}, {"upsih", 0x03D2}, {"piv", 0x03D6},
{"ensp", 0x2002}, {"emsp", 0x2003}, {"thinsp", 0x2009},
{"zwnj", 0x200C}, {"zwj", 0x200D}, {"lrm", 0x200E},
{"rlm", 0x200F}, {"ndash", 0x2013}, {"mdash", 0x2014},
{"horbar", 0x2015}, {"lsquo", 0x2018}, {"rsquo", 0x2019},
{"sbquo", 0x201A}, {"ldquo", 0x201C}, {"rdquo", 0x201D},
{"bdquo", 0x201E}, {"dagger", 0x2020}, {"Dagger", 0x2021},
{"bull", 0x2022}, {"hellip", 0x2026}, {"permil", 0x2030},
{"prime", 0x2032}, {"Prime", 0x2033}, {"lsaquo", 0x2039},
{"rsaquo", 0x203A}, {"oline", 0x203E}, {"frasl", 0x2044},
{"euro", 0x20AC}, {"image", 0x2111}, {"weierp", 0x2118},
{"real", 0x211C}, {"trade", 0x2122}, {"alefsym", 0x2135},
{"larr", 0x2190}, {"uarr", 0x2191}, {"rarr", 0x2192},
{"darr", 0x2193}, {"harr", 0x2194}, {"crarr", 0x21B5},
{"lArr", 0x21D0}, {"uArr", 0x21D1}, {"rArr", 0x21D2},
{"dArr", 0x21D3}, {"hArr", 0x21D4}, {"forall", 0x2200},
{"part", 0x2202}, {"exist", 0x2203}, {"empty", 0x2205},
{"nabla", 0x2207}, {"isin", 0x2208}, {"notin", 0x2209},
{"ni", 0x220B}, {"prod", 0x220F}, {"sum", 0x2211},
{"minus", 0x2212}, {"lowast", 0x2217}, {"radic", 0x221A},
{"prop", 0x221D}, {"infin", 0x221E}, {"ang", 0x2220},
{"and", 0x2227}, {"or", 0x2228}, {"cap", 0x2229},
{"cup", 0x222A}, {"int", 0x222B}, {"there4", 0x2234},
{"sim", 0x223C}, {"cong", 0x2245}, {"asymp", 0x2248},
{"ne", 0x2260}, {"equiv", 0x2261}, {"le", 0x2264},
{"ge", 0x2265}, {"sub", 0x2282}, {"sup", 0x2283},
{"nsub", 0x2284}, {"sube", 0x2286}, {"supe", 0x2287},
{"oplus", 0x2295}, {"otimes", 0x2297}, {"perp", 0x22A5},
{"sdot", 0x22C5}, {"lceil", 0x2308}, {"rceil", 0x2309},
{"lfloor", 0x230A}, {"rfloor", 0x230B}, {"lang", 0x2329},
{"rang", 0x232A}, {"loz", 0x25CA}, {"spades", 0x2660},
{"clubs", 0x2663}, {"hearts", 0x2665}, {"diams", 0x2666}};
// Used to convert number to utf-8 char.
wstring_convert<codecvt_utf8<char32_t>, char32_t> u8c;
// Matches numbered entities between 1 and 8 digits, decimal or hexadecimal,
// or named entities.
const regex re_entity{"&(#(x)?([[:alnum:]]{1,8})"
"|[^;[:space:][:punct:]]+);"};
smatch match;
while (regex_search(buffer, match, re_entity))
{
output += match.prefix().str();
try
{
// clang-format off
const char32_t codepoint{[&match, &names]
{
// clang-format on
// If it doesn't start with a '#' it is a named entity.
if (match[1].str()[0] != '#')
{
return names.at(match[1].str());
}
// 'x' after '#' means the number is hexadecimal.
if (match[2].length() == 1)
{
return static_cast<char32_t>(
stoul(match[3].str(), nullptr, 16));
}
// '#' without 'x' means the number is decimal.
return static_cast<char32_t>(
stoul(match[3].str(), nullptr, 10));
}()};
output += u8c.to_bytes(codepoint);
}
catch (const std::out_of_range &) // Named entity could not be found.
{
output += match.str();
}
buffer = match.suffix().str();
}
output += buffer;
return output;
}
} // namespace mastodonpp

View File

@ -14,64 +14,245 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "answer.hpp"
#include "instance.hpp"
#include "log.hpp"
#include <algorithm>
#include <exception>
#include <regex>
namespace mastodonpp
{
using std::exception;
using std::regex;
using std::regex_search;
using std::smatch;
using std::sort;
using std::stoull;
Instance::Instance(const string_view hostname, const string_view access_token)
: _hostname{hostname}
, _baseuri{"https://" + _hostname}
, _access_token{access_token}
, _max_chars{0}
{}
{
set_access_token(access_token);
}
uint64_t Instance::get_max_chars()
Instance::Instance(const Instance &other)
: CURLWrapper{other}
, _hostname{other._hostname}
, _baseuri{other._baseuri}
, _access_token{other._access_token}
, _max_chars{other._max_chars}
, _proxy{other._proxy}
, _post_formats{other._post_formats}
, _cainfo{other._cainfo}
, _useragent{other._useragent}
{
CURLWrapper::setup_connection_properties(_proxy, _access_token, _cainfo,
_useragent);
}
uint64_t Instance::get_max_chars() noexcept
{
constexpr uint64_t default_max_chars{500};
if (_max_chars == 0)
if (_max_chars != 0)
{
try
return _max_chars;
}
try
{
debuglog << "Querying " << _hostname << " for max_toot_chars…\n";
const auto answer{
make_request(http_method::GET, _baseuri + "/api/v1/instance", {})};
if (!answer)
{
debuglog << "Querying " << _hostname << " for max_toot_chars…\n";
const auto answer{make_request(http_method::GET,
_baseuri + "/api/v1/instance", {})};
if (!answer)
debuglog << "Could not get instance info.\n";
return default_max_chars;
}
// clang-format off
_max_chars = [&answer]
{
// clang-format on
const regex re_chars{R"("max_toot_chars"\s*:\s*([^"]+))"};
smatch match;
if (regex_search(answer.body, match, re_chars))
{
debuglog << "Could not get instance info.\n";
return default_max_chars;
return static_cast<uint64_t>(stoull(match[1].str()));
}
_max_chars = [&answer]
{
auto &body{answer.body};
size_t pos_start{body.find("max_toot_chars")};
if (pos_start == string::npos)
{
debuglog << "max_toot_chars not found.\n";
return default_max_chars;
}
pos_start = body.find(':', pos_start) + 1;
const size_t pos_end{body.find(',', pos_start)};
const auto max_toot_chars{body.substr(pos_start,
pos_end - pos_start)};
return static_cast<uint64_t>(stoull(max_toot_chars));
}();
debuglog << "Set _max_chars to: " << _max_chars << '\n';
}
catch (const std::exception &e)
{
debuglog << "Unexpected exception: " << e.what() << '\n';
}
debuglog << "max_toot_chars not found.\n";
return default_max_chars;
}();
debuglog << "Set _max_chars to: " << _max_chars << '\n';
}
catch (const exception &e)
{
debuglog << "Unexpected exception: " << e.what() << '\n';
return default_max_chars;
}
return _max_chars;
}
answer_type Instance::get_nodeinfo()
{
auto answer{
make_request(http_method::GET, _baseuri + "/.well-known/nodeinfo", {})};
if (!answer)
{
debuglog << "NodeInfo not found.\n";
return answer;
}
vector<string> hrefs;
const regex re_href{R"("href"\s*:\s*"([^"]+)\")"};
smatch match;
string body = answer.body;
while (regex_search(body, match, re_href))
{
hrefs.push_back(match[1].str());
debuglog << "Found href: " << hrefs.back() << '\n';
body = match.suffix();
}
sort(hrefs.begin(), hrefs.end()); // We assume they are sortable strings.
debuglog << "Selecting href: " << hrefs.back() << '\n';
return make_request(http_method::GET, hrefs.back(), {});
}
vector<string> Instance::get_post_formats() noexcept
{
constexpr auto default_value{"text/plain"};
if (!_post_formats.empty())
{
return _post_formats;
}
try
{
debuglog << "Querying " << _hostname << " for postFormats…\n";
const auto answer{get_nodeinfo()};
if (!answer)
{
debuglog << "Couldn't get NodeInfo.\n";
_post_formats = {default_value};
return _post_formats;
}
const regex re_allformats(R"("postFormats"\s*:\s*\[([^\]]+)\])");
smatch match;
if (!regex_search(answer.body, match, re_allformats))
{
debuglog << "Couldn't find metadata.postFormats.\n";
_post_formats = {default_value};
return _post_formats;
}
string allformats{match[1].str()};
debuglog << "Found postFormats: " << allformats << '\n';
const regex re_format(R"(\s*"([^"]+)\"\s*,?)");
while (regex_search(allformats, match, re_format))
{
_post_formats.push_back(match[1].str());
allformats = match.suffix();
debuglog << "Found postFormat: " << _post_formats.back() << '\n';
}
}
catch (const std::exception &e)
{
debuglog << "Unexpected exception: " << e.what() << '\n';
return {default_value};
}
return _post_formats;
}
answer_type Instance::ObtainToken::step_1(const string_view client_name,
const string_view scopes,
const string_view website)
{
parametermap parameters{{"client_name", client_name},
{"redirect_uris", "urn:ietf:wg:oauth:2.0:oob"}};
if (!scopes.empty())
{
_scopes = scopes;
parameters.insert({"scopes", scopes});
}
if (!website.empty())
{
parameters.insert({"website", website});
}
auto answer{
make_request(http_method::POST, _baseuri + "/api/v1/apps", parameters)};
if (answer)
{
const regex re_id{R"("client_id"\s*:\s*"([^"]+)\")"};
const regex re_secret{R"("client_secret"\s*:\s*"([^"]+)\")"};
smatch match;
if (regex_search(answer.body, match, re_id))
{
_client_id = match[1].str();
}
if (regex_search(answer.body, match, re_secret))
{
_client_secret = match[1].str();
}
string uri{_baseuri + "/oauth/authorize?scope=" + escape_url(scopes)
+ "&response_type=code"
"&redirect_uri="
+ escape_url("urn:ietf:wg:oauth:2.0:oob")
+ "&client_id=" + _client_id};
if (!website.empty())
{
uri += "&website=" + escape_url(website);
}
answer.body = uri;
debuglog << "Built URI.";
}
return answer;
}
answer_type Instance::ObtainToken::step_2(const string_view code)
{
parametermap parameters{{"client_id", _client_id},
{"client_secret", _client_secret},
{"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
{"code", code},
{"grant_type", "authorization_code"}};
if (!_scopes.empty())
{
parameters.insert({"scope", _scopes});
}
auto answer{
make_request(http_method::POST, _baseuri + "/oauth/token", parameters)};
if (answer)
{
const regex re_token{R"("access_token"\s*:\s*"([^"]+)\")"};
smatch match;
if (regex_search(answer.body, match, re_token))
{
answer.body = match[1].str();
debuglog << "Got access token.\n";
_instance.set_access_token(answer.body);
}
}
return answer;
}
} // namespace mastodonpp

View File

@ -23,13 +23,13 @@
namespace mastodonpp
{
using std::cerr;
using std::cerr; // NOLINT(misc-unused-using-decls)
using std::string_view;
//! @private
constexpr auto shorten_filename(const string_view &filename)
{
for (const string_view &dir : {"/src/", "/include/"})
for (const string_view dir : {"/src/", "/include/"})
{
const auto pos{filename.rfind(dir)};
if (pos != string_view::npos)
@ -40,15 +40,18 @@ constexpr auto shorten_filename(const string_view &filename)
return filename;
}
#define commonlog cerr << '[' << shorten_filename(__FILE__) \
<< ':' << __LINE__ << ']'
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define commonlog \
cerr << '[' << shorten_filename(__FILE__) << ':' << __LINE__ << ']'
#ifndef NDEBUG
#define debuglog commonlog << " DEBUG: "
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
# define debuglog commonlog << " DEBUG: "
#else
#define debuglog false && cerr
# define debuglog false && cerr
#endif
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define errorlog commonlog << " ERROR: "
} // namespace mastodonpp
#endif // MASTODONPP_LOG_HPP
#endif // MASTODONPP_LOG_HPP

View File

@ -1,5 +1,5 @@
/* This file is part of mastodonpp.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
* Copyright © 2020, 2021 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -14,7 +14,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "answer.hpp"
#include "types.hpp"
#include "log.hpp"
#include <algorithm>
#include <cctype>
@ -35,7 +37,7 @@ answer_type::operator string_view() const
return body;
}
std::ostream &operator <<(std::ostream &out, const answer_type &answer)
std::ostream &operator<<(std::ostream &out, const answer_type &answer)
{
out << answer.body;
return out;
@ -44,10 +46,11 @@ std::ostream &operator <<(std::ostream &out, const answer_type &answer)
string_view answer_type::get_header(const string_view field) const
{
const string_view searchstring{string(field) += ':'};
auto it{search(headers.begin(), headers.end(),
searchstring.begin(), searchstring.end(),
[](unsigned char a, unsigned char b)
// clang-format off
auto it{search(headers.begin(), headers.end(), searchstring.begin(),
searchstring.end(), [](unsigned char a, unsigned char b)
{ return tolower(a) == tolower(b); })};
// clang-format on
if (it != headers.end())
{
@ -60,4 +63,40 @@ string_view answer_type::get_header(const string_view field) const
return {};
}
parametermap answer_type::parse_pagination(const bool next) const
{
const string_view link{get_header("Link")};
if (link.empty())
{
return {};
}
const string_view direction{next ? R"(rel="next")" : R"(rel="prev")"};
auto endpos{link.find(direction)};
endpos = link.rfind('>', endpos);
auto startpos{link.rfind('?', endpos) + 1};
const string_view paramstr{link.substr(startpos, endpos - startpos)};
debuglog << "Found parameters in Link header: " << paramstr << '\n';
startpos = 0;
parametermap parameters;
while ((endpos = paramstr.find('=', startpos)) != string_view::npos)
{
parameterpair param;
param.first = paramstr.substr(startpos, endpos - startpos);
startpos = endpos + 1;
endpos = paramstr.find('&', startpos);
param.second = paramstr.substr(startpos, endpos - startpos);
parameters.insert(param);
if (endpos == string_view::npos)
{
break;
}
startpos = endpos + 1;
}
return parameters;
}
} // namespace mastodonpp

View File

@ -3,11 +3,16 @@ include(CTest)
file(GLOB sources_tests test_*.cpp)
find_package(Catch2 CONFIG)
if(Catch2_FOUND) # Catch 2.x
if(Catch2_FOUND) # Catch 2.x / 3.x
include(Catch)
add_executable(all_tests main.cpp ${sources_tests})
target_link_libraries(all_tests
PRIVATE Catch2::Catch2 ${PROJECT_NAME})
if(TARGET Catch2::Catch2WithMain) # Catch 3.x
target_link_libraries(all_tests
PRIVATE Catch2::Catch2WithMain ${PROJECT_NAME})
else() # Catch 2.x
target_link_libraries(all_tests
PRIVATE Catch2::Catch2 ${PROJECT_NAME})
endif()
target_include_directories(all_tests PRIVATE "/usr/include/catch2")
catch_discover_tests(all_tests EXTRA_ARGS "${EXTRA_TEST_ARGS}")
else() # Catch 1.x

View File

@ -1,5 +1,5 @@
/* This file is part of mastodonpp.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
* Copyright © 2020, 2022 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -16,4 +16,9 @@
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
// catch 3 does not have catch.hpp anymore
#if __has_include(<catch.hpp>)
# include <catch.hpp>
#else
# include <catch_all.hpp>
#endif

View File

@ -1,5 +1,5 @@
/* This file is part of mastodonpp.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
* Copyright © 2020, 2022 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -14,22 +14,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "instance.hpp"
#include "connection.hpp"
#include "instance.hpp"
#include <catch.hpp>
// catch 3 does not have catch.hpp anymore
#if __has_include(<catch.hpp>)
# include <catch.hpp>
#else
# include <catch_all.hpp>
#endif
#include <exception>
namespace mastodonpp
{
SCENARIO ("mastodonpp::Connection.")
SCENARIO("mastodonpp::Connection.")
{
bool exception = false;
WHEN ("Connection is instantiated.")
WHEN("Connection is instantiated.")
{
try
{
@ -41,7 +45,7 @@ SCENARIO ("mastodonpp::Connection.")
exception = true;
}
THEN ("No exception is thrown")
THEN("No exception is thrown")
{
REQUIRE_FALSE(exception);
}

View File

@ -0,0 +1,59 @@
/* This file is part of mastodonpp.
* Copyright © 2020, 2022 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "helpers.hpp"
// catch 3 does not have catch.hpp anymore
#if __has_include(<catch.hpp>)
# include <catch.hpp>
#else
# include <catch_all.hpp>
#endif
#include <exception>
#include <string>
namespace mastodonpp
{
using std::string;
SCENARIO("mastodonpp::html_unescape()")
{
bool exception = false;
WHEN("html_unescape() is called.")
{
string result;
try
{
result = unescape_html("2&euro; = 2&#8364; = 2&#x20ac;");
}
catch (const std::exception &e)
{
exception = true;
}
THEN("No exception is thrown")
AND_THEN("Result is as expected.")
{
REQUIRE_FALSE(exception);
REQUIRE(result == "2€ = 2€ = 2€");
}
}
}
} // namespace mastodonpp

View File

@ -1,5 +1,5 @@
/* This file is part of mastodonpp.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
* Copyright © 2020, 2022 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@ -16,7 +16,12 @@
#include "instance.hpp"
#include <catch.hpp>
// catch 3 does not have catch.hpp anymore
#if __has_include(<catch.hpp>)
# include <catch.hpp>
#else
# include <catch_all.hpp>
#endif
#include <exception>
#include <string>
@ -26,11 +31,11 @@ namespace mastodonpp
using std::string;
SCENARIO ("mastopp::Instance")
SCENARIO("mastopp::Instance")
{
bool exception = false;
WHEN ("Instance is instantiated.")
WHEN("Instance is instantiated.")
{
try
{
@ -41,13 +46,13 @@ SCENARIO ("mastopp::Instance")
exception = true;
}
THEN ("No exception is thrown")
THEN("No exception is thrown")
{
REQUIRE_FALSE(exception);
}
}
WHEN ("Variables are set.")
WHEN("Variables are set.")
{
constexpr auto hostname{"likeable.space"};
constexpr auto proxy{"socks4a://[::1]:9050"};
@ -64,14 +69,12 @@ SCENARIO ("mastopp::Instance")
exception = true;
}
THEN ("No exception is thrown")
AND_THEN ("get_proxy() returns the set value.")
AND_THEN ("get_access_token() returns the set value.")
AND_THEN ("get_hostname() returns the set value.")
AND_THEN ("get_baseuri() returns the expected value.")
THEN("No exception is thrown")
AND_THEN("get_access_token() returns the set value.")
AND_THEN("get_hostname() returns the set value.")
AND_THEN("get_baseuri() returns the expected value.")
{
REQUIRE_FALSE(exception);
REQUIRE(instance.get_proxy() == proxy);
REQUIRE(instance.get_access_token() == access_token);
REQUIRE(instance.get_hostname() == hostname);
REQUIRE(instance.get_baseuri() == (string("https://") += hostname));