BBS2chProxyBoardManager.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. #include "BBS2chProxyBoardManager.h"
  2. #include <vector>
  3. #include <time.h>
  4. #include <string.h>
  5. #include <stdlib.h>
  6. #include <curl/curl.h>
  7. #include "parson/parson.h"
  8. #include "BBS2chProxyURL.h"
  9. #include "stringEncodingConverter.h"
  10. #ifdef _WIN32
  11. #define localtime_r(a, b) localtime_s(b, a)
  12. #endif
  13. extern char *proxy_server;
  14. extern long proxy_port;
  15. extern long proxy_type;
  16. extern long timeout;
  17. extern char *user_agent;
  18. extern int force_ipv4;
  19. extern CURLSH *curl_share;
  20. extern void log_printf(int level, const char *format ...);
  21. static size_t write_callback_download(char *buffer, size_t size, size_t nitems, void *userdata)
  22. {
  23. std::vector<char> *data = static_cast<std::vector<char> *>(userdata);
  24. size_t downloaded = size*nitems;
  25. data->insert(data->end(), buffer, buffer+downloaded);
  26. return downloaded;
  27. }
  28. static JSON_Value *getJSONFromURL(const std::string &url)
  29. {
  30. std::vector<char> data;
  31. JSON_Value *json = NULL;
  32. CURL *curl = curl_easy_init();
  33. if (curl_share) curl_easy_setopt(curl, CURLOPT_SHARE, curl_share);
  34. curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
  35. curl_easy_setopt(curl, CURLOPT_ENCODING, "");
  36. curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
  37. curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
  38. curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
  39. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
  40. curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
  41. curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback_download);
  42. curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
  43. if (user_agent) {
  44. curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
  45. }
  46. if (proxy_server) {
  47. curl_easy_setopt(curl, CURLOPT_PROXY, proxy_server);
  48. curl_easy_setopt(curl, CURLOPT_PROXYPORT, proxy_port);
  49. curl_easy_setopt(curl, CURLOPT_PROXYTYPE, proxy_type);
  50. }
  51. if (force_ipv4) curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  52. CURLcode res = curl_easy_perform(curl);
  53. if (res == CURLE_OK) {
  54. long statusCode;
  55. curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode);
  56. if (statusCode == 200) {
  57. data.push_back('\0');
  58. json = json_parse_string(&data.front());
  59. }
  60. else log_printf(0, "Cannot download json from %s: server returned %ld\n", url.c_str(), statusCode);
  61. }
  62. else log_printf(0, "curl error while downloading %s: %s\n", url.c_str(), curl_easy_strerror(res));
  63. curl_easy_cleanup(curl);
  64. return json;
  65. }
  66. static JSON_Value *mixBoardJSON(JSON_Value *base, JSON_Value *additional, std::string suffix, bool talkTo5ch)
  67. {
  68. JSON_Value *mixed = json_value_init_object();
  69. JSON_Object *rootBase, *rootAdditional, *rootMixed;
  70. JSON_Array *categoriesBase, *categoriesAdditional, *categoriesMixed;
  71. std::map<std::string, size_t> categoriesBaseToIndex;
  72. time_t modifiedBase = 0, modifiedAdditional = 0;
  73. if (!base || !additional) goto end;
  74. if (json_type(base) != JSONObject || json_type(additional) != JSONObject ) {
  75. goto end;
  76. }
  77. rootBase = json_object(base);
  78. rootAdditional = json_object(additional);
  79. rootMixed = json_object(mixed);
  80. json_object_set_value(rootMixed, "menu_list", json_value_init_array());
  81. categoriesBase = json_object_get_array(rootBase, "menu_list");
  82. categoriesMixed = json_object_get_array(rootMixed, "menu_list");
  83. categoriesAdditional = json_object_get_array(rootAdditional, "menu_list");
  84. if (!categoriesBase || !categoriesAdditional) {
  85. goto end;
  86. }
  87. modifiedBase = json_object_get_number(rootBase, "last_modify");
  88. modifiedAdditional = json_object_get_number(rootAdditional, "last_modify");
  89. if (modifiedBase < modifiedAdditional) modifiedBase = modifiedAdditional;
  90. for (size_t i=0, length=json_array_get_count(categoriesBase); i<length; i++) {
  91. JSON_Value *value = json_array_get_value(categoriesBase, i);
  92. if (!value || json_type(value) != JSONObject) continue;
  93. JSON_Object *category = json_object(value);
  94. std::string categoryName = json_object_get_string(category, "category_name");
  95. categoriesBaseToIndex[categoryName] = json_array_get_count(categoriesMixed);
  96. json_array_append_value(categoriesMixed, json_value_deep_copy(value));
  97. }
  98. for (size_t i=0, length=json_array_get_count(categoriesAdditional); i<length; i++) {
  99. JSON_Value *value = json_array_get_value(categoriesAdditional, i);
  100. if (!value || json_type(value) != JSONObject) continue;
  101. JSON_Object *category = json_object(json_value_deep_copy(value));;
  102. JSON_Array *baseBoards = NULL;
  103. std::string categoryName = json_object_get_string(category, "category_name");
  104. std::map<std::string, size_t>::iterator it = categoriesBaseToIndex.find(categoryName);
  105. size_t categoryNum;
  106. bool isPink = false;
  107. if (categoryName == "BBSPINK") isPink = true;
  108. if (it != categoriesBaseToIndex.end()) {
  109. JSON_Object *baseCategory = json_array_get_object(categoriesMixed, it->second);
  110. categoryNum = it->second + 1;
  111. baseBoards = json_object_get_array(baseCategory, "category_content");
  112. } else {
  113. categoryNum = json_array_get_count(categoriesMixed)+1;
  114. json_object_set_number(category, "category_number", categoryNum);
  115. json_array_append_value(categoriesMixed, json_object_get_wrapping_value(category));
  116. if (!isPink) {
  117. categoryName += suffix;
  118. json_object_set_string(category, "category_name", categoryName.c_str());
  119. }
  120. }
  121. JSON_Array *boards = json_object_get_array(category, "category_content");
  122. if (boards) {
  123. for (size_t j=0, length=json_array_get_count(boards); j<length; j++) {
  124. JSON_Object *board = json_array_get_object(boards, j);
  125. if (!board) continue;
  126. json_object_set_number(board, "category", categoryNum);
  127. std::string directory = json_object_get_string(board, "directory_name");
  128. json_object_set_string(board, "category_name", categoryName.c_str());
  129. if (directory != "NONE") {
  130. std::string name = json_object_get_string(board, "board_name");
  131. if (!isPink) {
  132. name += suffix;
  133. json_object_set_string(board, "board_name", name.c_str());
  134. }
  135. if (talkTo5ch) {
  136. std::string modDirectory = "5channel_";
  137. modDirectory += directory;
  138. std::string url = "https://classic.talk-platform.com/";
  139. url += modDirectory;
  140. json_object_set_string(board, "directory_name", modDirectory.c_str());
  141. json_object_set_string(board, "url", url.c_str());
  142. }
  143. }
  144. if (baseBoards) {
  145. json_object_set_number(board, "category_order", json_array_get_count(baseBoards)+1);
  146. json_array_append_value(baseBoards, json_value_deep_copy(json_object_get_wrapping_value(board)));
  147. }
  148. }
  149. }
  150. if (baseBoards) {
  151. JSON_Object *baseCategory = json_array_get_object(categoriesMixed, it->second);
  152. json_object_set_number(baseCategory, "category_total", json_array_get_count(baseBoards));
  153. json_value_free(json_object_get_wrapping_value(category));
  154. }
  155. }
  156. end:
  157. struct tm lastModified_tm = {};
  158. char dateStr[256] = "";
  159. localtime_r(&modifiedBase, &lastModified_tm);
  160. strftime(dateStr, 256, "%Y/%m/%d(%a) %T", &lastModified_tm);
  161. json_object_set_string(rootMixed, "last_modify_string", dateStr);
  162. json_object_set_number(rootMixed, "last_modify", modifiedBase);
  163. json_object_set_string(rootMixed, "description", "");
  164. return mixed;
  165. }
  166. static JSON_Value *getBoardJSONFor5ch(bool shouldUseHttp, bool shouldUse2ch, bool shouldIncludeTalk)
  167. {
  168. JSON_Value *json5ch = getJSONFromURL("http://menu.5ch.net/bbsmenu.json");
  169. JSON_Value *jsonTalk = NULL;
  170. JSON_Value *jsonMixed = NULL;
  171. JSON_Object *root;
  172. JSON_Array *categories;
  173. if (!json5ch || json_type(json5ch) != JSONObject) goto end;
  174. if (shouldIncludeTalk) {
  175. jsonTalk = getJSONFromURL("http://classic.talk-platform.com/bbsmenu.json");
  176. jsonMixed = mixBoardJSON(json5ch, jsonTalk, " (Talk)", false);
  177. if (!jsonMixed || json_type(jsonMixed) != JSONObject) goto end;
  178. }
  179. root = shouldIncludeTalk ? json_object(jsonMixed) : json_object(json5ch);
  180. categories = json_object_get_array(root, "menu_list");
  181. if (!categories) goto end;
  182. for (size_t i=0, length=json_array_get_count(categories); i<length; i++) {
  183. JSON_Object *category = json_array_get_object(categories, i);
  184. if (!category) continue;
  185. const char *categoryName = json_object_get_string(category, "category_name");
  186. if (!categoryName) continue;
  187. JSON_Array *boards = json_object_get_array(category, "category_content");
  188. if (boards) {
  189. for (size_t j=0, length=json_array_get_count(boards); j<length; j++) {
  190. JSON_Object *board = json_array_get_object(boards, j);
  191. if (!board) continue;
  192. const char *url = json_object_get_string(board, "url");
  193. if (!url) continue;
  194. if (shouldUseHttp || shouldUse2ch) {
  195. BBS2chProxyURL modUrl(url);
  196. if (shouldUseHttp) modUrl.setScheme("http");
  197. if (shouldUse2ch) modUrl.replaceHost("5ch.net", "2ch.net");
  198. json_object_set_string(board, "url", modUrl.absoluteString().c_str());
  199. }
  200. }
  201. }
  202. }
  203. end:
  204. if (jsonMixed && json5ch) json_value_free(json5ch);
  205. if (jsonTalk) json_value_free(jsonTalk);
  206. return jsonMixed ? jsonMixed : json5ch;
  207. }
  208. void BBS2chProxyBoardManager::updateMap() {
  209. JSON_Value *json = getJSONFromURL("http://menu.5ch.net/bbsmenu.json");
  210. JSON_Object *root;
  211. JSON_Array *categories;
  212. if (!json || json_type(json) != JSONObject) goto end;
  213. root = json_object(json);
  214. categories = json_object_get_array(root, "menu_list");
  215. if (!categories) {
  216. goto end;
  217. }
  218. for (size_t i=0, length=json_array_get_count(categories); i<length; i++) {
  219. JSON_Object *category = json_array_get_object(categories, i);
  220. if (!category) continue;
  221. JSON_Array *boards = json_object_get_array(category, "category_content");
  222. if (!boards) continue;
  223. for (size_t j=0, length=json_array_get_count(boards); j<length; j++) {
  224. JSON_Object *board = json_array_get_object(boards, j);
  225. if (!board) continue;
  226. const char *name = json_object_get_string(board, "directory_name");
  227. const char *url = json_object_get_string(board, "url");
  228. if (name && url && strcmp(name, "NONE")) {
  229. if (_boardToServer.find(name) != _boardToServer.end()) {
  230. continue;
  231. }
  232. _boardToServer[name] = BBS2chProxyURL(url).getHost();
  233. }
  234. }
  235. }
  236. _lastUpdated = time(NULL);
  237. end:
  238. if (json) json_value_free(json);
  239. }
  240. std::string BBS2chProxyBoardManager::getBoardJSONForTalkAndFake5ch()
  241. {
  242. JSON_Value *jsonTalk = getJSONFromURL("http://classic.talk-platform.com/bbsmenu.json");
  243. JSON_Value *json5ch = getJSONFromURL("http://menu.5ch.net/bbsmenu.json");
  244. JSON_Value *jsonMixed = NULL;
  245. JSON_Object *rootMixed;
  246. time_t now = time(NULL);
  247. struct tm now_tm = {};
  248. char nowStr[256];
  249. localtime_r(&now, &now_tm);
  250. strftime(nowStr, 256, "%Y/%m/%d(%a) %T", &now_tm);
  251. if (!jsonTalk || !json5ch) goto end;
  252. if (json_type(jsonTalk) != JSONObject || json_type(json5ch) != JSONObject ) {
  253. goto end;
  254. }
  255. jsonMixed = mixBoardJSON(jsonTalk, json5ch, " (5ch)", true);
  256. rootMixed = json_object(jsonMixed);
  257. json_object_set_string(rootMixed, "last_modify_string", nowStr);
  258. json_object_set_number(rootMixed, "last_modify", now);
  259. json_object_set_string(rootMixed, "description", "");
  260. end:
  261. std::string out;
  262. if (jsonMixed) {
  263. char *jsonStr = json_serialize_to_string(jsonMixed);
  264. out = jsonStr;
  265. json_free_serialized_string(jsonStr);
  266. json_value_free(jsonMixed);
  267. }
  268. if (jsonTalk) json_value_free(jsonTalk);
  269. if (json5ch) json_value_free(json5ch);
  270. return out;
  271. }
  272. std::string BBS2chProxyBoardManager::getBoardHTML(bool shouldUseHttp, bool shouldUse2ch, bool shouldIncludeTalk)
  273. {
  274. std::string outHtml = "<HTML>\n<HEAD>\n<META http-equiv=\"Content-Type\" content=\"text/html; charset=Shift_JIS\">\n<TITLE>BBSMENU for 5ch.net</TITLE>\n<BASE TARGET=\"_right\">\n</HEAD>\n<BODY TEXT=\"#CC3300\" BGCOLOR=\"#FFFFFF\" link=\"#0000FF\" alink=\"#ff0000\" vlink=\"#660099\">\n<font size=2>\n\n";
  275. JSON_Value *json = getBoardJSONFor5ch(shouldUseHttp, shouldUse2ch, shouldIncludeTalk);
  276. JSON_Object *root;
  277. JSON_Array *categories;
  278. time_t lastModified;
  279. struct tm lastModified_tm = {};
  280. char dateStr[256] = "";
  281. if (!json || json_type(json) != JSONObject) goto end;
  282. root = json_object(json);
  283. categories = json_object_get_array(root, "menu_list");
  284. if (!categories) goto end;
  285. lastModified = json_object_get_number(root, "last_modify");
  286. if (lastModified) {
  287. localtime_r(&lastModified, &lastModified_tm);
  288. strftime(dateStr, 256, "%Y/%m/%d", &lastModified_tm);
  289. }
  290. for (size_t i=0, length=json_array_get_count(categories); i<length; i++) {
  291. JSON_Object *category = json_array_get_object(categories, i);
  292. if (!category) continue;
  293. const char *categoryName = json_object_get_string(category, "category_name");
  294. if (!categoryName) continue;
  295. char *categoryNameSJIS = convertUTF8ToShiftJIS(categoryName, strlen(categoryName));
  296. outHtml += "<BR><BR><B>";
  297. outHtml += categoryNameSJIS;
  298. outHtml += "</B><BR>\n";
  299. free(categoryNameSJIS);
  300. JSON_Array *boards = json_object_get_array(category, "category_content");
  301. if (boards) {
  302. bool added = false;
  303. for (size_t j=0, length=json_array_get_count(boards); j<length; j++) {
  304. JSON_Object *board = json_array_get_object(boards, j);
  305. if (!board) continue;
  306. const char *boardName = json_object_get_string(board, "board_name");
  307. const char *url = json_object_get_string(board, "url");
  308. if (!boardName || !url) continue;
  309. char *boardNameSJIS = convertUTF8ToShiftJIS(boardName, strlen(boardName));
  310. if (!boardNameSJIS) continue;
  311. if (added) outHtml += "<br>\n";
  312. outHtml += "<A HREF=";
  313. outHtml += url;
  314. if (outHtml[outHtml.size()-1] != '/') outHtml += '/';
  315. outHtml += ">";
  316. outHtml += boardNameSJIS;
  317. outHtml += "</A>";
  318. added = true;
  319. free(boardNameSJIS);
  320. }
  321. if (added) outHtml += "\n\n";
  322. }
  323. }
  324. end:
  325. if (json) json_value_free(json);
  326. outHtml += "<BR><BR>\n\x8d\x58\x90\x56\x93\xfa ";
  327. outHtml += dateStr;
  328. outHtml += "\n\n</font></BODY></HTML>";
  329. return outHtml;
  330. }
  331. std::string BBS2chProxyBoardManager::getBoardJSON(bool shouldUseHttp, bool shouldUse2ch, bool shouldIncludeTalk)
  332. {
  333. std::string out;
  334. JSON_Value *json = getBoardJSONFor5ch(shouldUseHttp, shouldUse2ch, shouldIncludeTalk);
  335. if (json) {
  336. char *jsonStr = json_serialize_to_string(json);
  337. out = jsonStr;
  338. json_free_serialized_string(jsonStr);
  339. json_value_free(json);
  340. }
  341. return out;
  342. }
  343. std::string BBS2chProxyBoardManager::getServerForBoard(const std::string &name)
  344. {
  345. std::string url;
  346. pthread_mutex_lock(&_mutex);
  347. if (_lastUpdated < time(NULL) - 86400) updateMap();
  348. std::map<std::string, std::string>::iterator it = _boardToServer.find(name);
  349. if (it != _boardToServer.end()) url = it->second;
  350. pthread_mutex_unlock(&_mutex);
  351. return url;
  352. }