aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/Fourchan.cpp
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2020-10-11 21:35:37 +0200
committerdec05eba <dec05eba@protonmail.com>2020-10-13 13:13:01 +0200
commit77ed51898157d99112be7550471ec06e32344c9e (patch)
tree0645274d0f13b4fa6940d4054f74a070611a8ef0 /src/plugins/Fourchan.cpp
parentda89ec98fb34757f0c46dc8cb2dd87ae78d317ce (diff)
Refactor plugin into seperate pages
TODO: Readd 4chan login page, manganelo creators page, autocomplete
Diffstat (limited to 'src/plugins/Fourchan.cpp')
-rw-r--r--src/plugins/Fourchan.cpp649
1 files changed, 279 insertions, 370 deletions
diff --git a/src/plugins/Fourchan.cpp b/src/plugins/Fourchan.cpp
index 1cecc2b..1d3681a 100644
--- a/src/plugins/Fourchan.cpp
+++ b/src/plugins/Fourchan.cpp
@@ -1,6 +1,7 @@
#include "../../plugins/Fourchan.hpp"
#include "../../include/DataView.hpp"
#include "../../include/Storage.hpp"
+#include "../../include/StringUtils.hpp"
#include <json/reader.h>
#include <string.h>
#include <tidy.h>
@@ -11,40 +12,9 @@
static const std::string fourchan_url = "https://a.4cdn.org/";
static const std::string fourchan_image_url = "https://i.4cdn.org/";
-namespace QuickMedia {
- Fourchan::Fourchan(const std::string &resources_root) : ImageBoard("4chan"), resources_root(resources_root) {
- thread_list_update_thread = std::thread([this]() {
- BodyItems new_thread_list_items;
- while(running) {
- new_thread_list_items.clear();
- auto start_time = std::chrono::steady_clock::now();
-
- std::string board_url = get_board_url();
- if(!board_url.empty()) {
- PluginResult plugin_result = get_threads_internal(board_url, new_thread_list_items);
- if(plugin_result == PluginResult::OK)
- set_board_thread_list(std::move(new_thread_list_items));
- }
-
- auto time_passed = std::chrono::steady_clock::now() - start_time;
- if(time_passed < std::chrono::seconds(15)) {
- auto time_to_sleep = std::chrono::seconds(15) - time_passed;
- std::unique_lock<std::mutex> lock(thread_list_cache_mutex);
- thread_list_update_cv.wait_for(lock, time_to_sleep);
- }
- }
- });
- }
-
- Fourchan::~Fourchan() {
- running = false;
- {
- std::unique_lock<std::mutex> lock(thread_list_cache_mutex);
- thread_list_update_cv.notify_one();
- }
- thread_list_update_thread.join();
- }
+static const char *SERVICE_NAME = "4chan";
+namespace QuickMedia {
// Returns empty string on failure to read cookie
static std::string get_pass_id_from_cookies_file(const Path &cookies_filepath) {
std::string file_content;
@@ -63,53 +33,6 @@ namespace QuickMedia {
return strip(file_content.substr(pass_id_index, line_end - pass_id_index));
}
- PluginResult Fourchan::get_front_page(BodyItems &result_items) {
- if(pass_id.empty()) {
- Path cookies_filepath;
- if(get_cookies_filepath(cookies_filepath, name) != 0) {
- fprintf(stderr, "Failed to get 4chan cookies filepath\n");
- } else {
- pass_id = get_pass_id_from_cookies_file(cookies_filepath);
- }
- }
-
- std::string server_response;
- if(file_get_content(resources_root + "boards.json", server_response) != 0) {
- fprintf(stderr, "failed to read boards.json\n");
- return PluginResult::ERR;
- }
-
- Json::Value json_root;
- Json::CharReaderBuilder json_builder;
- std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
- std::string json_errors;
- if(!json_reader->parse(server_response.data(), server_response.data() + server_response.size(), &json_root, &json_errors)) {
- fprintf(stderr, "4chan front page json error: %s\n", json_errors.c_str());
- return PluginResult::ERR;
- }
-
- if(!json_root.isObject())
- return PluginResult::ERR;
-
- const Json::Value &boards = json_root["boards"];
- if(boards.isArray()) {
- for(const Json::Value &board : boards) {
- const Json::Value &board_id = board["board"]; // /g/, /a/, /b/ etc
- const Json::Value &board_title = board["title"];
- const Json::Value &board_description = board["meta_description"];
- if(board_id.isString() && board_title.isString() && board_description.isString()) {
- std::string board_description_str = board_description.asString();
- html_unescape_sequences(board_description_str);
- auto body_item = BodyItem::create("/" + board_id.asString() + "/ " + board_title.asString());
- body_item->url = board_id.asString();
- result_items.emplace_back(std::move(body_item));
- }
- }
- }
-
- return PluginResult::OK;
- }
-
struct CommentPiece {
enum class Type {
TEXT,
@@ -210,270 +133,74 @@ namespace QuickMedia {
tidyRelease(doc);
}
- PluginResult Fourchan::get_threads_internal(const std::string &url, BodyItems &result_items) {
+ PluginResult FourchanBoardsPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
Json::Value json_root;
DownloadResult result = download_json(json_root, fourchan_url + url + "/catalog.json", {}, true);
if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
- if(json_root.isArray()) {
- for(const Json::Value &page_data : json_root) {
- if(!page_data.isObject())
- continue;
-
- const Json::Value &threads = page_data["threads"];
- if(!threads.isArray())
- continue;
-
- for(const Json::Value &thread : threads) {
- if(!thread.isObject())
- continue;
-
- const Json::Value &sub = thread["sub"];
- const char *sub_begin = "";
- const char *sub_end = sub_begin;
- sub.getString(&sub_begin, &sub_end);
-
- const Json::Value &com = thread["com"];
- const char *comment_begin = "";
- const char *comment_end = comment_begin;
- com.getString(&comment_begin, &comment_end);
-
- const Json::Value &thread_num = thread["no"];
- if(!thread_num.isNumeric())
- continue;
-
- std::string title_text;
- extract_comment_pieces(sub_begin, sub_end - sub_begin,
- [&title_text](const CommentPiece &cp) {
- switch(cp.type) {
- case CommentPiece::Type::TEXT:
- title_text.append(cp.text.data, cp.text.size);
- break;
- case CommentPiece::Type::QUOTE:
- title_text += '>';
- title_text.append(cp.text.data, cp.text.size);
- //comment_text += '\n';
- break;
- case CommentPiece::Type::QUOTELINK: {
- title_text.append(cp.text.data, cp.text.size);
- break;
- }
- case CommentPiece::Type::LINE_CONTINUE: {
- if(!title_text.empty() && title_text.back() == '\n') {
- title_text.pop_back();
- }
- break;
- }
- }
- }
- );
- if(!title_text.empty() && title_text.back() == '\n')
- title_text.back() = ' ';
- html_unescape_sequences(title_text);
-
- std::string comment_text;
- extract_comment_pieces(comment_begin, comment_end - comment_begin,
- [&comment_text](const CommentPiece &cp) {
- switch(cp.type) {
- case CommentPiece::Type::TEXT:
- comment_text.append(cp.text.data, cp.text.size);
- break;
- case CommentPiece::Type::QUOTE:
- comment_text += '>';
- comment_text.append(cp.text.data, cp.text.size);
- //comment_text += '\n';
- break;
- case CommentPiece::Type::QUOTELINK: {
- comment_text.append(cp.text.data, cp.text.size);
- break;
- }
- case CommentPiece::Type::LINE_CONTINUE: {
- if(!comment_text.empty() && comment_text.back() == '\n') {
- comment_text.pop_back();
- }
- break;
- }
- }
- }
- );
- html_unescape_sequences(comment_text);
- // TODO: Do the same when wrapping is implemented
- // TODO: Remove this
- int num_lines = 0;
- for(size_t i = 0; i < comment_text.size(); ++i) {
- if(comment_text[i] == '\n') {
- ++num_lines;
- if(num_lines == 6) {
- comment_text = comment_text.substr(0, i) + " (...)";
- break;
- }
- }
- }
- auto body_item = BodyItem::create(std::move(comment_text));
- body_item->set_author(std::move(title_text));
- body_item->url = std::to_string(thread_num.asInt64());
-
- const Json::Value &ext = thread["ext"];
- const Json::Value &tim = thread["tim"];
- if(tim.isNumeric() && ext.isString()) {
- std::string ext_str = ext.asString();
- if(ext_str == ".png" || ext_str == ".jpg" || ext_str == ".jpeg" || ext_str == ".webm" || ext_str == ".mp4" || ext_str == ".gif") {
- } else {
- fprintf(stderr, "TODO: Support file extension: %s\n", ext_str.c_str());
- }
- // "s" means small, that's the url 4chan uses for thumbnails.
- // thumbnails always has .jpg extension even if they are gifs or webm.
- body_item->thumbnail_url = fourchan_image_url + url + "/" + std::to_string(tim.asInt64()) + "s.jpg";
- }
-
- result_items.emplace_back(std::move(body_item));
- }
- }
- }
-
- return PluginResult::OK;
- }
-
- void Fourchan::set_board_url(const std::string &new_url) {
- {
- std::lock_guard<std::mutex> lock(board_url_mutex);
- if(current_board_url == new_url)
- return;
- current_board_url = new_url;
- }
-
- std::lock_guard<std::mutex> thread_list_lock(thread_list_cache_mutex);
- thread_list_update_cv.notify_one();
- thread_list_cached = false;
- }
-
- std::string Fourchan::get_board_url() {
- std::lock_guard<std::mutex> lock(board_url_mutex);
- return current_board_url;
- }
-
- void Fourchan::set_board_thread_list(BodyItems body_items) {
- {
- std::lock_guard<std::mutex> lock(board_list_mutex);
- cached_thread_list_items.clear();
- for(auto &body_item : body_items) {
- cached_thread_list_items.push_back(std::move(body_item));
- }
- }
-
- std::unique_lock<std::mutex> thread_list_cache_lock(thread_list_cache_mutex);
- if(!thread_list_cached) {
- thread_list_cached = true;
- thread_list_cached_cv.notify_one();
- }
- }
-
- BodyItems Fourchan::get_board_thread_list() {
- std::lock_guard<std::mutex> lock(board_list_mutex);
- BodyItems body_items;
- for(auto &cached_body_item : cached_thread_list_items) {
- body_items.push_back(std::make_shared<BodyItem>(*cached_body_item));
- }
- return body_items;
- }
-
- PluginResult Fourchan::get_threads(const std::string &url, BodyItems &result_items) {
- set_board_url(url);
-
- std::unique_lock<std::mutex> lock(thread_list_cache_mutex);
- if(!thread_list_cached) {
- if(thread_list_cached_cv.wait_for(lock, std::chrono::seconds(10)) == std::cv_status::timeout)
- return PluginResult::NET_ERR;
- }
-
- result_items = get_board_thread_list();
- return PluginResult::OK;
- }
-
- // TODO: Merge with get_threads_internal
- PluginResult Fourchan::get_thread_comments(const std::string &list_url, const std::string &url, BodyItems &result_items) {
- cached_media_urls.clear();
-
- Json::Value json_root;
- DownloadResult result = download_json(json_root, fourchan_url + list_url + "/thread/" + url + ".json", {}, true);
- if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
-
- if(!json_root.isObject())
+ if(!json_root.isArray())
return PluginResult::ERR;
- std::unordered_map<int64_t, size_t> comment_by_postno;
+ BodyItems result_items;
- const Json::Value &posts = json_root["posts"];
- if(posts.isArray()) {
- for(const Json::Value &post : posts) {
- if(!post.isObject())
- continue;
+ for(const Json::Value &page_data : json_root) {
+ if(!page_data.isObject())
+ continue;
- const Json::Value &post_num = post["no"];
- if(!post_num.isNumeric())
- continue;
-
- int64_t post_num_int = post_num.asInt64();
- comment_by_postno[post_num_int] = result_items.size();
- result_items.push_back(BodyItem::create(""));
- result_items.back()->post_number = std::to_string(post_num_int);
- }
- }
+ const Json::Value &threads = page_data["threads"];
+ if(!threads.isArray())
+ continue;
- size_t body_item_index = 0;
- if(posts.isArray()) {
- for(const Json::Value &post : posts) {
- if(!post.isObject())
+ for(const Json::Value &thread : threads) {
+ if(!thread.isObject())
continue;
- const Json::Value &sub = post["sub"];
+ const Json::Value &sub = thread["sub"];
const char *sub_begin = "";
const char *sub_end = sub_begin;
sub.getString(&sub_begin, &sub_end);
- const Json::Value &com = post["com"];
+ const Json::Value &com = thread["com"];
const char *comment_begin = "";
const char *comment_end = comment_begin;
com.getString(&comment_begin, &comment_end);
- const Json::Value &post_num = post["no"];
- if(!post_num.isNumeric())
+ const Json::Value &thread_num = thread["no"];
+ if(!thread_num.isNumeric())
continue;
- const Json::Value &author = post["name"];
- std::string author_str = "Anonymous";
- if(author.isString())
- author_str = author.asString();
-
- std::string comment_text;
+ std::string title_text;
extract_comment_pieces(sub_begin, sub_end - sub_begin,
- [&comment_text](const CommentPiece &cp) {
+ [&title_text](const CommentPiece &cp) {
switch(cp.type) {
case CommentPiece::Type::TEXT:
- comment_text.append(cp.text.data, cp.text.size);
+ title_text.append(cp.text.data, cp.text.size);
break;
case CommentPiece::Type::QUOTE:
- comment_text += '>';
- comment_text.append(cp.text.data, cp.text.size);
+ title_text += '>';
+ title_text.append(cp.text.data, cp.text.size);
//comment_text += '\n';
break;
case CommentPiece::Type::QUOTELINK: {
- comment_text.append(cp.text.data, cp.text.size);
+ title_text.append(cp.text.data, cp.text.size);
break;
}
case CommentPiece::Type::LINE_CONTINUE: {
- if(!comment_text.empty() && comment_text.back() == '\n') {
- comment_text.pop_back();
+ if(!title_text.empty() && title_text.back() == '\n') {
+ title_text.pop_back();
}
break;
}
}
}
);
- if(!comment_text.empty())
- comment_text += '\n';
+ if(!title_text.empty() && title_text.back() == '\n')
+ title_text.back() = ' ';
+ html_unescape_sequences(title_text);
+
+ std::string comment_text;
extract_comment_pieces(comment_begin, comment_end - comment_begin,
- [&comment_text, &comment_by_postno, &result_items, body_item_index](const CommentPiece &cp) {
+ [&comment_text](const CommentPiece &cp) {
switch(cp.type) {
case CommentPiece::Type::TEXT:
comment_text.append(cp.text.data, cp.text.size);
@@ -485,13 +212,6 @@ namespace QuickMedia {
break;
case CommentPiece::Type::QUOTELINK: {
comment_text.append(cp.text.data, cp.text.size);
- auto it = comment_by_postno.find(cp.quote_postnumber);
- if(it == comment_by_postno.end()) {
- // TODO: Link this quote to a 4chan archive that still has the quoted comment (if available)
- comment_text += "(dead)";
- } else {
- result_items[it->second]->replies.push_back(body_item_index);
- }
break;
}
case CommentPiece::Type::LINE_CONTINUE: {
@@ -503,15 +223,25 @@ namespace QuickMedia {
}
}
);
- if(!comment_text.empty() && comment_text.back() == '\n')
- comment_text.back() = ' ';
html_unescape_sequences(comment_text);
- BodyItem *body_item = result_items[body_item_index].get();
- body_item->set_title(std::move(comment_text));
- body_item->set_author(std::move(author_str));
+ // TODO: Do the same when wrapping is implemented
+ // TODO: Remove this
+ int num_lines = 0;
+ for(size_t i = 0; i < comment_text.size(); ++i) {
+ if(comment_text[i] == '\n') {
+ ++num_lines;
+ if(num_lines == 6) {
+ comment_text = comment_text.substr(0, i) + " (...)";
+ break;
+ }
+ }
+ }
+ auto body_item = BodyItem::create(std::move(comment_text));
+ body_item->set_author(std::move(title_text));
+ body_item->url = std::to_string(thread_num.asInt64());
- const Json::Value &ext = post["ext"];
- const Json::Value &tim = post["tim"];
+ const Json::Value &ext = thread["ext"];
+ const Json::Value &tim = thread["tim"];
if(tim.isNumeric() && ext.isString()) {
std::string ext_str = ext.asString();
if(ext_str == ".png" || ext_str == ".jpg" || ext_str == ".jpeg" || ext_str == ".webm" || ext_str == ".mp4" || ext_str == ".gif") {
@@ -520,76 +250,210 @@ namespace QuickMedia {
}
// "s" means small, that's the url 4chan uses for thumbnails.
// thumbnails always has .jpg extension even if they are gifs or webm.
- std::string tim_str = std::to_string(tim.asInt64());
- body_item->thumbnail_url = fourchan_image_url + list_url + "/" + tim_str + "s.jpg";
- body_item->attached_content_url = fourchan_image_url + list_url + "/" + tim_str + ext_str;
- cached_media_urls.push_back(body_item->attached_content_url);
+ body_item->thumbnail_url = fourchan_image_url + url + "/" + std::to_string(tim.asInt64()) + "s.jpg";
}
- ++body_item_index;
+ result_items.push_back(std::move(body_item));
}
}
+ auto body = create_body();
+ body->items = std::move(result_items);
+ body->draw_thumbnails = true;
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<FourchanThreadListPage>(program, title, url), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
return PluginResult::OK;
}
- PostResult Fourchan::post_comment(const std::string &board, const std::string &thread, const std::string &captcha_id, const std::string &comment) {
- std::string url = "https://sys.4chan.org/" + board + "/post";
+ void FourchanBoardsPage::get_boards(BodyItems &result_items) {
+ std::string server_response;
+ if(file_get_content(resources_root + "boards.json", server_response) != 0) {
+ fprintf(stderr, "failed to read boards.json\n");
+ return;
+ }
- std::vector<CommandArg> additional_args = {
- CommandArg{"-H", "Referer: https://boards.4chan.org/"},
- CommandArg{"-H", "Origin: https://boards.4chan.org"},
- CommandArg{"-F", "resto=" + thread},
- CommandArg{"-F", "com=" + comment},
- CommandArg{"-F", "mode=regist"}
- };
+ Json::Value json_root;
+ Json::CharReaderBuilder json_builder;
+ std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader());
+ std::string json_errors;
+ if(!json_reader->parse(server_response.data(), server_response.data() + server_response.size(), &json_root, &json_errors)) {
+ fprintf(stderr, "4chan front page json error: %s\n", json_errors.c_str());
+ return;
+ }
- if(pass_id.empty()) {
- additional_args.push_back(CommandArg{"-F", "g-recaptcha-response=" + captcha_id});
- } else {
- Path cookies_filepath;
- if(get_cookies_filepath(cookies_filepath, name) != 0) {
- fprintf(stderr, "Failed to get 4chan cookies filepath\n");
- return PostResult::ERR;
- } else {
- additional_args.push_back(CommandArg{"-c", cookies_filepath.data});
- additional_args.push_back(CommandArg{"-b", cookies_filepath.data});
+ if(!json_root.isObject())
+ return;
+
+ const Json::Value &boards = json_root["boards"];
+ if(!boards.isArray())
+ return;
+
+ for(const Json::Value &board : boards) {
+ const Json::Value &board_id = board["board"]; // /g/, /a/, /b/ etc
+ const Json::Value &board_title = board["title"];
+ const Json::Value &board_description = board["meta_description"];
+ if(board_id.isString() && board_title.isString() && board_description.isString()) {
+ std::string board_description_str = board_description.asString();
+ html_unescape_sequences(board_description_str);
+ auto body_item = BodyItem::create("/" + board_id.asString() + "/ " + board_title.asString());
+ body_item->url = board_id.asString();
+ result_items.push_back(std::move(body_item));
}
}
-
- std::string response;
- if(download_to_string(url, response, additional_args, use_tor, true) != DownloadResult::OK)
- return PostResult::ERR;
-
- if(response.find("successful") != std::string::npos)
- return PostResult::OK;
- if(response.find("banned") != std::string::npos)
- return PostResult::BANNED;
- if(response.find("try again") != std::string::npos || response.find("No valid captcha") != std::string::npos)
- return PostResult::TRY_AGAIN;
- return PostResult::ERR;
}
- BodyItems Fourchan::get_related_media(const std::string &url) {
- BodyItems body_items;
- auto it = std::find(cached_media_urls.begin(), cached_media_urls.end(), url);
- if(it == cached_media_urls.end())
- return body_items;
-
- ++it;
- for(; it != cached_media_urls.end(); ++it) {
- auto body_item = BodyItem::create("");
- body_item->url = *it;
- body_items.push_back(std::move(body_item));
+ // TODO: Merge with get_threads_internal
+ PluginResult FourchanThreadListPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
+ (void)title;
+ cached_media_urls.clear();
+
+ Json::Value json_root;
+ DownloadResult result = download_json(json_root, fourchan_url + board_id + "/thread/" + url + ".json", {}, true);
+ if(result != DownloadResult::OK) return download_result_to_plugin_result(result);
+
+ if(!json_root.isObject())
+ return PluginResult::ERR;
+
+ BodyItems result_items;
+ std::unordered_map<int64_t, size_t> comment_by_postno;
+
+ const Json::Value &posts = json_root["posts"];
+ if(!posts.isArray())
+ return PluginResult::OK;
+
+ for(const Json::Value &post : posts) {
+ if(!post.isObject())
+ continue;
+
+ const Json::Value &post_num = post["no"];
+ if(!post_num.isNumeric())
+ continue;
+
+ int64_t post_num_int = post_num.asInt64();
+ comment_by_postno[post_num_int] = result_items.size();
+ result_items.push_back(BodyItem::create(""));
+ result_items.back()->post_number = std::to_string(post_num_int);
}
- return body_items;
+
+ size_t body_item_index = 0;
+ for(const Json::Value &post : posts) {
+ if(!post.isObject())
+ continue;
+
+ const Json::Value &sub = post["sub"];
+ const char *sub_begin = "";
+ const char *sub_end = sub_begin;
+ sub.getString(&sub_begin, &sub_end);
+
+ const Json::Value &com = post["com"];
+ const char *comment_begin = "";
+ const char *comment_end = comment_begin;
+ com.getString(&comment_begin, &comment_end);
+
+ const Json::Value &post_num = post["no"];
+ if(!post_num.isNumeric())
+ continue;
+
+ const Json::Value &author = post["name"];
+ std::string author_str = "Anonymous";
+ if(author.isString())
+ author_str = author.asString();
+
+ std::string comment_text;
+ extract_comment_pieces(sub_begin, sub_end - sub_begin,
+ [&comment_text](const CommentPiece &cp) {
+ switch(cp.type) {
+ case CommentPiece::Type::TEXT:
+ comment_text.append(cp.text.data, cp.text.size);
+ break;
+ case CommentPiece::Type::QUOTE:
+ comment_text += '>';
+ comment_text.append(cp.text.data, cp.text.size);
+ //comment_text += '\n';
+ break;
+ case CommentPiece::Type::QUOTELINK: {
+ comment_text.append(cp.text.data, cp.text.size);
+ break;
+ }
+ case CommentPiece::Type::LINE_CONTINUE: {
+ if(!comment_text.empty() && comment_text.back() == '\n') {
+ comment_text.pop_back();
+ }
+ break;
+ }
+ }
+ }
+ );
+ if(!comment_text.empty())
+ comment_text += '\n';
+ extract_comment_pieces(comment_begin, comment_end - comment_begin,
+ [&comment_text, &comment_by_postno, &result_items, body_item_index](const CommentPiece &cp) {
+ switch(cp.type) {
+ case CommentPiece::Type::TEXT:
+ comment_text.append(cp.text.data, cp.text.size);
+ break;
+ case CommentPiece::Type::QUOTE:
+ comment_text += '>';
+ comment_text.append(cp.text.data, cp.text.size);
+ //comment_text += '\n';
+ break;
+ case CommentPiece::Type::QUOTELINK: {
+ comment_text.append(cp.text.data, cp.text.size);
+ auto it = comment_by_postno.find(cp.quote_postnumber);
+ if(it == comment_by_postno.end()) {
+ // TODO: Link this quote to a 4chan archive that still has the quoted comment (if available)
+ comment_text += "(dead)";
+ } else {
+ result_items[it->second]->replies.push_back(body_item_index);
+ }
+ break;
+ }
+ case CommentPiece::Type::LINE_CONTINUE: {
+ if(!comment_text.empty() && comment_text.back() == '\n') {
+ comment_text.pop_back();
+ }
+ break;
+ }
+ }
+ }
+ );
+ if(!comment_text.empty() && comment_text.back() == '\n')
+ comment_text.back() = ' ';
+ html_unescape_sequences(comment_text);
+ BodyItem *body_item = result_items[body_item_index].get();
+ body_item->set_title(std::move(comment_text));
+ body_item->set_author(std::move(author_str));
+
+ const Json::Value &ext = post["ext"];
+ const Json::Value &tim = post["tim"];
+ if(tim.isNumeric() && ext.isString()) {
+ std::string ext_str = ext.asString();
+ if(ext_str == ".png" || ext_str == ".jpg" || ext_str == ".jpeg" || ext_str == ".webm" || ext_str == ".mp4" || ext_str == ".gif") {
+ } else {
+ fprintf(stderr, "TODO: Support file extension: %s\n", ext_str.c_str());
+ }
+ // "s" means small, that's the url 4chan uses for thumbnails.
+ // thumbnails always has .jpg extension even if they are gifs or webm.
+ std::string tim_str = std::to_string(tim.asInt64());
+ body_item->thumbnail_url = fourchan_image_url + board_id + "/" + tim_str + "s.jpg";
+ body_item->attached_content_url = fourchan_image_url + board_id + "/" + tim_str + ext_str;
+ cached_media_urls.push_back(body_item->attached_content_url);
+ }
+
+ ++body_item_index;
+ }
+
+ auto body = create_body();
+ body->items = std::move(result_items);
+ body->draw_thumbnails = true;
+ result_tabs.push_back(Tab{std::move(body), std::make_unique<FourchanThreadPage>(program, board_id, url, std::move(cached_media_urls)), nullptr});
+ return PluginResult::OK;
}
- PluginResult Fourchan::login(const std::string &token, const std::string &pin, std::string &response_msg) {
+ PluginResult FourchanThreadPage::login(const std::string &token, const std::string &pin, std::string &response_msg) {
response_msg.clear();
Path cookies_filepath;
- if(get_cookies_filepath(cookies_filepath, name) != 0) {
+ if(get_cookies_filepath(cookies_filepath, SERVICE_NAME) != 0) {
fprintf(stderr, "Failed to get 4chan cookies filepath\n");
return PluginResult::ERR;
}
@@ -626,7 +490,52 @@ namespace QuickMedia {
}
}
- const std::string& Fourchan::get_pass_id() const {
+ PostResult FourchanThreadPage::post_comment(const std::string &captcha_id, const std::string &comment) {
+ std::string url = "https://sys.4chan.org/" + board_id + "/post";
+
+ std::vector<CommandArg> additional_args = {
+ CommandArg{"-H", "Referer: https://boards.4chan.org/"},
+ CommandArg{"-H", "Origin: https://boards.4chan.org"},
+ CommandArg{"-F", "resto=" + thread_id},
+ CommandArg{"-F", "com=" + comment},
+ CommandArg{"-F", "mode=regist"}
+ };
+
+ if(pass_id.empty()) {
+ additional_args.push_back(CommandArg{"-F", "g-recaptcha-response=" + captcha_id});
+ } else {
+ Path cookies_filepath;
+ if(get_cookies_filepath(cookies_filepath, SERVICE_NAME) != 0) {
+ fprintf(stderr, "Failed to get 4chan cookies filepath\n");
+ return PostResult::ERR;
+ } else {
+ additional_args.push_back(CommandArg{"-c", cookies_filepath.data});
+ additional_args.push_back(CommandArg{"-b", cookies_filepath.data});
+ }
+ }
+
+ std::string response;
+ if(download_to_string(url, response, additional_args, is_tor_enabled(), true) != DownloadResult::OK)
+ return PostResult::ERR;
+
+ if(response.find("successful") != std::string::npos)
+ return PostResult::OK;
+ if(response.find("banned") != std::string::npos)
+ return PostResult::BANNED;
+ if(response.find("try again") != std::string::npos || response.find("No valid captcha") != std::string::npos)
+ return PostResult::TRY_AGAIN;
+ return PostResult::ERR;
+ }
+
+ const std::string& FourchanThreadPage::get_pass_id() {
+ if(pass_id.empty()) {
+ Path cookies_filepath;
+ if(get_cookies_filepath(cookies_filepath, SERVICE_NAME) != 0) {
+ fprintf(stderr, "Failed to get 4chan cookies filepath\n");
+ } else {
+ pass_id = get_pass_id_from_cookies_file(cookies_filepath);
+ }
+ }
return pass_id;
}
} \ No newline at end of file