summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2025-11-13 12:20:39 -0500
committerStar Rauchenberger <fefferburbia@gmail.com>2025-11-13 12:20:39 -0500
commit55912eb0293ab8c539acbc97d85ee9cc73346091 (patch)
tree5fc90cbf7fda6d2fedc0c55cf33e5372f58db167
parentc728c44d896425bbe39f0ce3803e028d119bd63c (diff)
downloadsap-55912eb0293ab8c539acbc97d85ee9cc73346091.tar.gz
sap-55912eb0293ab8c539acbc97d85ee9cc73346091.tar.bz2
sap-55912eb0293ab8c539acbc97d85ee9cc73346091.zip
Bot now posts to Tumblr
-rw-r--r--.gitmodules6
-rw-r--r--CMakeLists.txt25
-rw-r--r--director.cpp2
-rw-r--r--main.cpp1
-rw-r--r--sap.cpp106
-rw-r--r--sap.h10
m---------vendor/liboauthcpp0
m---------vendor/mastodonpp0
m---------vendor/rawr-ebooks0
9 files changed, 92 insertions, 58 deletions
diff --git a/.gitmodules b/.gitmodules index 8187520..a534e8e 100644 --- a/.gitmodules +++ b/.gitmodules
@@ -1,6 +1,6 @@
1[submodule "vendor/rawr-ebooks"] 1[submodule "vendor/rawr-ebooks"]
2 path = vendor/rawr-ebooks 2 path = vendor/rawr-ebooks
3 url = https://git.fourisland.com/rawr-ebooks 3 url = https://git.fourisland.com/rawr-ebooks
4[submodule "vendor/mastodonpp"] 4[submodule "vendor/liboauthcpp"]
5 path = vendor/mastodonpp 5 path = vendor/liboauthcpp
6 url = https://schlomp.space/tastytea/mastodonpp.git 6 url = https://github.com/sirikata/liboauthcpp.git
diff --git a/CMakeLists.txt b/CMakeLists.txt index 13fb6d7..aa7e4b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -1,28 +1,36 @@
1cmake_minimum_required (VERSION 3.1) 1cmake_minimum_required (VERSION 3.27)
2project (sap) 2project (sap)
3 3
4set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") 4set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
5 5
6find_package(FFMPEG REQUIRED) 6IF (NOT FFMPEG_FOUND)
7 find_package(FFMPEG)
8ENDIF()
7 9
8find_package(PkgConfig) 10find_package(PkgConfig)
9pkg_check_modules(yaml-cpp yaml-cpp REQUIRED) 11pkg_check_modules(yaml-cpp yaml-cpp REQUIRED)
10pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED) 12pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED)
11 13
14set(RAWR_ONLY_LIBRARY TRUE)
12add_subdirectory(vendor/rawr-ebooks EXCLUDE_FROM_ALL) 15add_subdirectory(vendor/rawr-ebooks EXCLUDE_FROM_ALL)
13add_subdirectory(vendor/mastodonpp) 16
17set(LIBOAUTHCPP_BUILD_DEMOS FALSE)
18add_subdirectory(vendor/liboauthcpp/build EXCLUDE_FROM_ALL)
19include_directories(vendor/liboauthcpp/include)
14 20
15include_directories( 21include_directories(
16 ${FFMPEG_INCLUDE_DIRS} 22 ${FFMPEG_INCLUDE_DIRS}
17 vendor/rawr-ebooks 23 vendor/rawr-ebooks
18 vendor/mastodonpp/include 24 vendor/liboauthcpp/include
25 ${CURL_INCLUDE_DIRS}
19 vendor/json 26 vendor/json
20 ${yaml-cpp_INCLUDE_DIRS} 27 ${yaml-cpp_INCLUDE_DIRS}
21 ${GraphicsMagick_INCLUDE_DIRS}) 28 ${GraphicsMagick_INCLUDE_DIRS})
22 29
23link_directories( 30link_directories(
24 ${yaml-cpp_LIBRARY_DIRS} 31 ${yaml-cpp_LIBRARY_DIRS}
25 ${GraphicsMagick_LIBRARY_DIRS}) 32 ${GraphicsMagick_LIBRARY_DIRS}
33 ${FFMPEG_LIBRARY_DIRS})
26 34
27add_executable(sap sap.cpp director.cpp designer.cpp main.cpp) 35add_executable(sap sap.cpp director.cpp designer.cpp main.cpp)
28set_property(TARGET sap PROPERTY CXX_STANDARD 17) 36set_property(TARGET sap PROPERTY CXX_STANDARD 17)
@@ -30,7 +38,10 @@ set_property(TARGET sap PROPERTY CXX_STANDARD_REQUIRED ON)
30 38
31target_link_libraries(sap 39target_link_libraries(sap
32 rawr 40 rawr
33 mastodonpp 41 curl
34 ${FFMPEG_LIBRARIES} 42 oauthcpp
43 avcodec
44 swscale
45 avformat
35 ${GraphicsMagick_LIBRARIES} 46 ${GraphicsMagick_LIBRARIES}
36 ${yaml-cpp_LIBRARIES}) 47 ${yaml-cpp_LIBRARIES})
diff --git a/director.cpp b/director.cpp index 24335da..a38377e 100644 --- a/director.cpp +++ b/director.cpp
@@ -51,7 +51,7 @@ namespace ffmpeg {
51 format& fmt, 51 format& fmt,
52 AVStream* st) 52 AVStream* st)
53 { 53 {
54 AVCodec* dec = avcodec_find_decoder(st->codecpar->codec_id); 54 const AVCodec* dec = avcodec_find_decoder(st->codecpar->codec_id);
55 if (!dec) 55 if (!dec)
56 { 56 {
57 throw ffmpeg_error("Failed to find codec"); 57 throw ffmpeg_error("Failed to find codec");
diff --git a/main.cpp b/main.cpp index 6ac8ee9..125bab3 100644 --- a/main.cpp +++ b/main.cpp
@@ -10,7 +10,6 @@ extern "C" {
10int main(int argc, char** argv) 10int main(int argc, char** argv)
11{ 11{
12 Magick::InitializeMagick(nullptr); 12 Magick::InitializeMagick(nullptr);
13 av_register_all();
14 13
15 std::random_device randomDevice; 14 std::random_device randomDevice;
16 std::mt19937 rng(randomDevice()); 15 std::mt19937 rng(randomDevice());
diff --git a/sap.cpp b/sap.cpp index 093e1fd..8805d70 100644 --- a/sap.cpp +++ b/sap.cpp
@@ -5,6 +5,7 @@
5#include <fstream> 5#include <fstream>
6#include <iostream> 6#include <iostream>
7#include <json.hpp> 7#include <json.hpp>
8#include <curl/curl.h>
8 9
9/* - random frames from Spongebob (using ffmpeg) 10/* - random frames from Spongebob (using ffmpeg)
10 * with strange text overlaid, possibly rawr'd from 11 * with strange text overlaid, possibly rawr'd from
@@ -21,12 +22,12 @@ sap::sap(
21 // Load the config file. 22 // Load the config file.
22 YAML::Node config = YAML::LoadFile(configFile); 23 YAML::Node config = YAML::LoadFile(configFile);
23 tempfile_ = config["tempfile"].as<std::string>(); 24 tempfile_ = config["tempfile"].as<std::string>();
25 blog_name_ = config["blog_name"].as<std::string>();
24 26
25 // Set up the Mastodon client. 27 // Set up the Tumblr client.
26 instance_ = std::make_unique<mastodonpp::Instance>( 28 tumblr_consumer_ = std::make_unique<OAuth::Consumer>(config["consumer_key"].as<std::string>(), config["consumer_secret"].as<std::string>());
27 config["mastodon_instance"].as<std::string>(), 29 tumblr_token_ = std::make_unique<OAuth::Token>(config["access_token"].as<std::string>(), config["access_token_secret"].as<std::string>());
28 config["mastodon_token"].as<std::string>()); 30 tumblr_client_ = std::make_unique<OAuth::Client>(tumblr_consumer_.get(), tumblr_token_.get());
29 connection_ = std::make_unique<mastodonpp::Connection>(*instance_);
30 31
31 // Set up the text generator. 32 // Set up the text generator.
32 for (const YAML::Node& corpusname : config["corpuses"]) 33 for (const YAML::Node& corpusname : config["corpuses"])
@@ -72,22 +73,22 @@ void sap::run() const
72 73
73 // Generate the text. 74 // Generate the text.
74 std::uniform_int_distribution<size_t> lenDist(5, 19); 75 std::uniform_int_distribution<size_t> lenDist(5, 19);
75 std::string action = kgramstats_.randomSentence(lenDist(rng_)); 76 std::string action = kgramstats_.randomSentence(lenDist(rng_), rng_);
76 77
77 // Lay the text on the video frame. 78 // Lay the text on the video frame.
78 Magick::Image textimage = 79 Magick::Image textimage =
79 layout_->generate(image.columns(), image.rows(), action, rng_); 80 layout_->generate(image.columns(), image.rows(), action, rng_);
80 image.composite(textimage, 0, 0, Magick::OverCompositeOp); 81 image.composite(textimage, 0, 0, Magick::OverCompositeOp);
81 82
82 // Send the tweet. 83 // Post to tumblr.
83 std::cout << "Sending tweet..." << std::endl; 84 std::cout << "Posting to Tumblr..." << std::endl;
84 85
85 sendTweet(std::move(image), action); 86 postToTumblr(action, std::move(image));
86 87
87 std::cout << "Tweeted!" << std::endl; 88 std::cout << std::endl << "Posted!" << std::endl;
88 89
89 // Wait. 90 // Wait.
90 std::this_thread::sleep_for(std::chrono::hours(12)); 91 std::this_thread::sleep_for(std::chrono::hours(11));
91 } catch (const Magick::ErrorImage& ex) 92 } catch (const Magick::ErrorImage& ex)
92 { 93 {
93 std::cout << "Image error: " << ex.what() << std::endl; 94 std::cout << "Image error: " << ex.what() << std::endl;
@@ -97,42 +98,63 @@ void sap::run() const
97 } 98 }
98} 99}
99 100
100void sap::sendTweet(Magick::Image image, const std::string& text) const 101void sap::postToTumblr(const std::string& post_text, Magick::Image image) const
101{ 102{
102 image.magick("jpeg"); 103 image.magick("jpeg");
103 image.write(tempfile_); 104 image.write(tempfile_);
104 105
105 auto answer{connection_->post(mastodonpp::API::v1::media, 106 nlohmann::json jsonMedia;
106 {{"file", std::string("@file:") + tempfile_}, {"description", text}})}; 107 jsonMedia["type"] = "image/jpeg";
107 if (!answer) 108 jsonMedia["identifier"] = "some-identifier";
108 { 109 jsonMedia["width"] = image.columns();
109 if (answer.curl_error_code == 0) 110 jsonMedia["height"] = image.rows();
110 { 111 jsonMedia["alt_text"] = post_text;
111 std::cout << "HTTP status: " << answer.http_status << std::endl; 112 jsonMedia["caption"] = post_text;
112 }
113 else
114 {
115 std::cout << "libcurl error " << std::to_string(answer.curl_error_code)
116 << ": " << answer.error_message << std::endl;
117 }
118 return;
119 }
120 113
121 nlohmann::json response_json = nlohmann::json::parse(answer.body); 114 nlohmann::json contentBlock;
122 answer = connection_->post(mastodonpp::API::v1::statuses, 115 contentBlock["type"] = "image";
123 {{"status", ""}, {"media_ids", 116 contentBlock["media"] = jsonMedia;
124 std::vector<std::string_view>{response_json["id"].get<std::string>()}}});
125 117
126 if (!answer) 118 nlohmann::json postBody;
127 { 119 postBody["content"].push_back(contentBlock);
128 if (answer.curl_error_code == 0) 120 postBody["tags"] = "spongebob,generated";
129 { 121
130 std::cout << "HTTP status: " << answer.http_status << std::endl; 122 std::string postText = postBody.dump();
131 } 123 std::cout << postText << std::endl;
132 else 124
133 { 125 Magick::Blob blob;
134 std::cout << "libcurl error " << std::to_string(answer.curl_error_code) 126 image.write(&blob);
135 << ": " << answer.error_message << std::endl; 127
136 } 128 std::string url = "https://api.tumblr.com/v2/blog/" + blog_name_ + "/posts";
129
130 std::string oauthHeader = tumblr_client_->getFormattedHttpHeader(OAuth::Http::Post, url, "");
131
132 struct curl_slist* header_list = NULL;
133 header_list = curl_slist_append(header_list, oauthHeader.c_str());
134
135 CURL* easy = curl_easy_init();
136 curl_mime* mime = curl_mime_init(easy);
137 curl_mimepart* json_part = curl_mime_addpart(mime);
138 curl_mime_data(json_part, postText.c_str(), CURL_ZERO_TERMINATED);
139 curl_mime_name(json_part, "json");
140 curl_mime_type(json_part, "application/json");
141
142 curl_mimepart* image_part = curl_mime_addpart(mime);
143 curl_mime_data(image_part, reinterpret_cast<const char*>(blob.data()), blob.length());
144 curl_mime_name(image_part, "some-identifier");
145 curl_mime_type(image_part, "image/jpeg");
146 curl_mime_filename(image_part, "image.jpg");
147
148 curl_easy_setopt(easy, CURLOPT_MIMEPOST, mime);
149 curl_easy_setopt(easy, CURLOPT_HTTPHEADER, header_list);
150 curl_easy_setopt(easy, CURLOPT_URL, url.c_str());
151
152 CURLcode error = curl_easy_perform(easy);
153 if (error != CURLE_OK) {
154 std::cout << curl_easy_strerror(error) << std::endl;
137 } 155 }
156
157 curl_easy_cleanup(easy);
158 curl_slist_free_all(header_list);
159 curl_mime_free(mime);
138} 160}
diff --git a/sap.h b/sap.h index 44ece23..c7f5909 100644 --- a/sap.h +++ b/sap.h
@@ -5,7 +5,7 @@
5#include <string> 5#include <string>
6#include <memory> 6#include <memory>
7#include <Magick++.h> 7#include <Magick++.h>
8#include <mastodonpp.hpp> 8#include <liboauthcpp/liboauthcpp.h>
9#include <rawr.h> 9#include <rawr.h>
10#include "designer.h" 10#include "designer.h"
11#include "director.h" 11#include "director.h"
@@ -21,15 +21,17 @@ public:
21 21
22private: 22private:
23 23
24 void sendTweet(Magick::Image image, const std::string& text) const; 24 void postToTumblr(const std::string& text, Magick::Image image) const;
25 25
26 std::mt19937& rng_; 26 std::mt19937& rng_;
27 std::unique_ptr<mastodonpp::Instance> instance_; 27 std::unique_ptr<OAuth::Consumer> tumblr_consumer_;
28 std::unique_ptr<mastodonpp::Connection> connection_; 28 std::unique_ptr<OAuth::Token> tumblr_token_;
29 std::unique_ptr<OAuth::Client> tumblr_client_;
29 rawr kgramstats_; 30 rawr kgramstats_;
30 std::unique_ptr<designer> layout_; 31 std::unique_ptr<designer> layout_;
31 std::unique_ptr<director> director_; 32 std::unique_ptr<director> director_;
32 std::string tempfile_; 33 std::string tempfile_;
34 std::string blog_name_;
33 35
34}; 36};
35 37
diff --git a/vendor/liboauthcpp b/vendor/liboauthcpp new file mode 160000
Subproject 2893f1bf42e1dfd5acf0dd849519cf27ac9c739
diff --git a/vendor/mastodonpp b/vendor/mastodonpp deleted file mode 160000
Subproject c48f1dc3d0566cef2baf96df7b3a7c55490a3e9
diff --git a/vendor/rawr-ebooks b/vendor/rawr-ebooks
Subproject 247ee4de24eab5ecd030542724db9f69aaa1ed1 Subproject f4fe2a79e3ac9590c83886d208561b9f6ee4ebb