#include "../../plugins/Fourchan.hpp" #include #include #include "../../include/DataView.hpp" #include #include // API documentation: https://github.com/4chan/4chan-API static const std::string fourchan_url = "https://a.4cdn.org/"; static const std::string fourchan_image_url = "https://i.4cdn.org/"; // Legacy recaptcha command: curl 'https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc' -H 'Referer: https://boards.4channel.org/' -H 'Cookie: CONSENT=YES' /* Answering recaptcha: curl 'https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0' -H 'Referer: https://www.google.com/recaptcha/api/fallback?k=6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc' -H 'Content-Type: application/x-www-form-urlencoded' --data 'c=03AOLTBLQ66PjSi9s8S-R1vUS2Jgm-Z_ghEejvvjAaeF3FoR9MiM0zHhCxuertrCo7MAcFUEqcIg4l2WJzVtrJhJVLkncF12OzCaeIvbm46hgDZDZjLD89-LMn1Zs0TP37P-Hd4cuRG8nHuEBXc2ZBD8CVX-6HAs9VBgSmsgQeKF1PWm1tAMBccJhlh4rAOkpjzaEXMMGOe17N0XViwDYZxLGhe4H8IAG2KNB1fb4rz4YKJTPbL30_FvHw7zkdFtojjWiqVW0yCN6N192dhfd9oKz2r9pGRrR6N4AkkX-L0DsBD4yNK3QRsQn3dB1fs3JRZPAh1yqUqTQYhOaqdggyc1EwL8FZHouGRkHTOcCmLQjyv6zuhi6CJbg&response=1&response=4&response=5&response=7' */ /* Response: reCAPTCHA challenge
Copy this code and paste it in the empty box below
This code is valid for 2 minutes
*/ /* Posting message: curl 'https://sys.4chan.org/bant/post' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0' -H 'Referer: https://boards.4chan.org/' -H 'Content-Type: multipart/form-data; boundary=---------------------------119561554312148213571335532670' -H 'Origin: https://boards.4chan.org' -H 'Cookie: __cfduid=d4bd4932e46bc3272fae4ce7a4e2aac511546800687; 4chan_pass=_SsBuZaATt3dIqfVEWlpemhU5XLQ6i9RC' --data-binary $'-----------------------------119561554312148213571335532670\r\nContent-Disposition: form-data; name="resto"\r\n\r\n8640736\r\n-----------------------------119561554312148213571335532670\r\nContent-Disposition: form-data; name="com"\r\n\r\n>>8640771\r\nShe looks finnish\r\n-----------------------------119561554312148213571335532670\r\nContent-Disposition: form-data; name="mode"\r\n\r\nregist\r\n-----------------------------119561554312148213571335532670\r\nContent-Disposition: form-data; name="pwd"\r\n\r\n_SsBuZaATt3dIqfVEWlpemhU5XLQ6i9RC\r\n-----------------------------119561554312148213571335532670\r\nContent-Disposition: form-data; name="g-recaptcha-response"\r\n\r\n03AOLTBLS5lshp5aPj5pG6xdVMQ0pHuHxAtJoCEYuPLNKYlsRWNCPQegjB9zgL-vwdGMzjcT-L9iW4bnQ5W3TqUWHOVqtsfnx9GipLUL9o2XbC6r9zy-EEiPde7l6J0WcZbr9nh_MGcUpKl6RGaZoYB3WwXaDq74N5hkmEAbqM_CBtbAVVlQyPmemI2HhO2J6K0yFVKBrBingtIZ6-oXBXZ4jC4rT0PeOuVaH_gf_EBjTpb55ueaPmTbeLGkBxD4-wL1qA8F8h0D8c\r\n-----------------------------119561554312148213571335532670--\r\n' */ /* Response if banned: /g/ - Technology - 4chan
[a / b / c / d / e / f / g / gif / h / hr / k / m / o / p / r / s / t / u / v / vg / vr / w / wg] [i / ic] [r9k / s4s / vip / qa] [cm / hm / lgbt / y] [3 / aco / adv / an / asp / bant / biz / cgl / ck / co / diy / fa / fit / gd / hc / his / int / jp / lit / mlp / mu / n / news / out / po / pol / qst / sci / soc / sp / tg / toy / trv / tv / vp / wsg / wsr / x] [Settings] [Search] [Mobile] [Home]
/g/ - Technology

Error: You are banned.

[Return]



All trademarks and copyrights on this page are owned by their respective parties. Images uploaded are the responsibility of the Poster. Comments are owned by the Poster.
*/ /* Banned page: 4chan - Banned
4chan

You are banned! ;_;

Banned You have been banned from /g/ for posting >>73505519, a violation of Rule 1:

Off-topic; all images and discussion should pertain to technology and related topics.

Your ban was filed on November 9th, 2019 and expires on November 10th, 2019 at 22:10 ET, which is 23 hours and 14 minutes from now.

According to our server, your IP is: YOURIP. The name you were posting with was Anonymous.

Because of the short length of your ban, you may not appeal it. Please check back when your ban has expired.

*/ namespace QuickMedia { PluginResult Fourchan::get_front_page(BodyItems &result_items) { std::string server_response; if(download_to_string(fourchan_url + "boards.json", server_response) != DownloadResult::OK) return PluginResult::NET_ERR; Json::Value json_root; Json::CharReaderBuilder json_builder; std::unique_ptr 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; } 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 = std::make_unique("/" + board_id.asString() + "/ " + board_title.asString()); body_item->url = board_id.asString(); result_items.emplace_back(std::move(body_item)); } } } return PluginResult::OK; } SearchResult Fourchan::search(const std::string &url, BodyItems &result_items) { return SearchResult::OK; } SuggestionResult Fourchan::update_search_suggestions(const std::string &text, BodyItems &result_items) { return SuggestionResult::OK; } static bool string_ends_with(const std::string &str, const std::string &ends_with_str) { size_t ends_len = ends_with_str.size(); return ends_len == 0 || (str.size() >= ends_len && memcmp(&str[str.size() - ends_len], ends_with_str.data(), ends_len) == 0); } struct CommentPiece { enum class Type { TEXT, QUOTE, // > QUOTELINK, // >>POSTNO, LINE_CONTINUE }; DataView text; // Set when type is TEXT, QUOTE or QUOTELINK int64_t quote_postnumber; // Set when type is QUOTELINK Type type; }; static TidyAttr get_attribute_by_name(TidyNode node, const char *name) { for(TidyAttr attr = tidyAttrFirst(node); attr; attr = tidyAttrNext(attr)) { const char *attr_name = tidyAttrName(attr); if(attr_name && strcmp(name, attr_name) == 0) return attr; } return nullptr; } static const char* get_attribute_value(TidyNode node, const char *name) { TidyAttr attr = get_attribute_by_name(node, name); if(attr) return tidyAttrValue(attr); return nullptr; } using CommentPieceCallback = std::function; static void extract_comment_pieces(TidyDoc doc, TidyNode node, CommentPieceCallback callback) { for(TidyNode child = tidyGetChild(node); child; child = tidyGetNext(child)) { const char *node_name = tidyNodeGetName(child); if(node_name && strcmp(node_name, "wbr") == 0) { CommentPiece comment_piece; comment_piece.type = CommentPiece::Type::LINE_CONTINUE; comment_piece.text = { (char*)"", 0 }; callback(comment_piece); continue; } TidyNodeType node_type = tidyNodeGetType(child); if(node_type == TidyNode_Start && node_name) { TidyNode text_node = tidyGetChild(child); //fprintf(stderr, "Child node name: %s, child text type: %d\n", node_name, tidyNodeGetType(text_node)); if(tidyNodeGetType(text_node) == TidyNode_Text) { TidyBuffer tidy_buffer; tidyBufInit(&tidy_buffer); if(tidyNodeGetText(doc, text_node, &tidy_buffer)) { CommentPiece comment_piece; comment_piece.type = CommentPiece::Type::TEXT; comment_piece.text = { (char*)tidy_buffer.bp, tidy_buffer.size }; if(strcmp(node_name, "span") == 0) { const char *span_class = get_attribute_value(child, "class"); //fprintf(stderr, "span class: %s\n", span_class); if(span_class && strcmp(span_class, "quote") == 0) comment_piece.type = CommentPiece::Type::QUOTE; } else if(strcmp(node_name, "a") == 0) { const char *a_class = get_attribute_value(child, "class"); const char *a_href = get_attribute_value(child, "href"); //fprintf(stderr, "a class: %s, href: %s\n", a_class, a_href); if(a_class && a_href && strcmp(a_class, "quotelink") == 0 && strncmp(a_href, "#p", 2) == 0) { comment_piece.type = CommentPiece::Type::QUOTELINK; comment_piece.quote_postnumber = strtoll(a_href + 2, nullptr, 10); } } callback(comment_piece); } tidyBufFree(&tidy_buffer); } } else if(node_type == TidyNode_Text) { TidyBuffer tidy_buffer; tidyBufInit(&tidy_buffer); if(tidyNodeGetText(doc, child, &tidy_buffer)) { CommentPiece comment_piece; comment_piece.type = CommentPiece::Type::TEXT; comment_piece.text = { (char*)tidy_buffer.bp, tidy_buffer.size }; callback(comment_piece); } tidyBufFree(&tidy_buffer); } } } static void extract_comment_pieces(const char *html_source, size_t size, CommentPieceCallback callback) { TidyDoc doc = tidyCreate(); for(int i = 0; i < N_TIDY_OPTIONS; ++i) tidyOptSetBool(doc, (TidyOptionId)i, no); if(tidyParseString(doc, html_source) < 0) { CommentPiece comment_piece; comment_piece.type = CommentPiece::Type::TEXT; // Warning: Cast from const char* to char* ... comment_piece.text = { (char*)html_source, size }; callback(comment_piece); } else { extract_comment_pieces(doc, tidyGetBody(doc), std::move(callback)); } tidyRelease(doc); } PluginResult Fourchan::get_threads(const std::string &url, BodyItems &result_items) { std::string server_response; if(download_to_string(fourchan_url + url + "/catalog.json", server_response) != DownloadResult::OK) return PluginResult::NET_ERR; Json::Value json_root; Json::CharReaderBuilder json_builder; std::unique_ptr 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 catalog json error: %s\n", json_errors.c_str()); return PluginResult::ERR; } 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 &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 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 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 = std::make_unique(std::move(comment_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") { } 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; } PluginResult Fourchan::get_thread_comments(const std::string &list_url, const std::string &url, BodyItems &result_items) { std::string server_response; if(download_to_string(fourchan_url + list_url + "/thread/" + url + ".json", server_response) != DownloadResult::OK) return PluginResult::NET_ERR; Json::Value json_root; Json::CharReaderBuilder json_builder; std::unique_ptr 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 thread json error: %s\n", json_errors.c_str()); return PluginResult::ERR; } std::unordered_map comment_by_postno; const Json::Value &posts = json_root["posts"]; if(posts.isArray()) { 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(std::make_unique("")); result_items.back()->post_number = std::to_string(post_num_int); } } size_t body_item_index = 0; if(posts.isArray()) { for(const Json::Value &post : posts) { if(!post.isObject()) continue; 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(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; } } } ); html_unescape_sequences(comment_text); BodyItem *body_item = result_items[body_item_index].get(); body_item->set_title(std::move(comment_text)); body_item->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") { } 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 + list_url + "/" + std::to_string(tim.asInt64()) + "s.jpg"; } ++body_item_index; } } return PluginResult::OK; } }