You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

512 lines
21 KiB

  1. /*
  2. * This program is free software: you can redistribute it and/or modify
  3. it under the terms of the GNU General Public License as published by
  4. the Free Software Foundation, either version 3 of the License, or
  5. (at your option) any later version.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. GNU General Public License for more details.
  10. You should have received a copy of the GNU General Public License
  11. along with this program. If not, see <https://www.gnu.org/licenses/>.
  12. *
  13. * [email protected] 2019
  14. */
  15. #include "crunchyrollapi.h"
  16. CrunchyrollAPI::CrunchyrollAPI()
  17. {
  18. }
  19. void CrunchyrollAPI::setLocale(QString _locale){
  20. locale = _locale;
  21. }
  22. QString CrunchyrollAPI::getLocale() const
  23. {
  24. return locale;
  25. }
  26. void CrunchyrollAPI::setSessionID(QString _session){
  27. session = _session;
  28. }
  29. QString CrunchyrollAPI::getSessionID() const
  30. {
  31. return session;
  32. }
  33. QString CrunchyrollAPI::getRandomDeviceID() const
  34. {
  35. const QString possibleCharacters("abcdef0123456789");
  36. const int randomStringLength = 12;
  37. QString randomString;
  38. for(int i=0; i<randomStringLength; ++i)
  39. {
  40. int index = qrand() % possibleCharacters.length();
  41. QChar nextChar = possibleCharacters.at(index);
  42. randomString.append(nextChar);
  43. }
  44. return randomString;
  45. }
  46. QPair<session_t,api_status_t> CrunchyrollAPI::newSession(bool USproxy){
  47. session_t sess;
  48. api_status_t stat;
  49. stat.failed = false;
  50. stat.api_error = false;
  51. qDebug("Requesting session id...");
  52. QString url = "";
  53. if(USproxy)
  54. url = "https://api1.cr-unblocker.com/getsession.php?version=1.1";
  55. else
  56. url = "https://api.crunchyroll.com/start_session.1.json";
  57. NetworkRequest request;
  58. request.setRequestMethod(NetworkRequest::Post);
  59. request.addRequestAttribute("device_id", QVariant(getRandomDeviceID()));
  60. request.addRequestAttribute("device_type", "com.crunchyroll.crunchyroid");
  61. request.addRequestAttribute("access_token", "WveH9VkPLrXvuNm");
  62. const QByteArray postStatus = request.loadSync(url);
  63. if(request.lastError() != ""){
  64. qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
  65. stat.failed = true;
  66. stat.description = request.lastError();
  67. return QPair<session_t,api_status_t>(sess,stat);
  68. }
  69. QJsonDocument doc = QJsonDocument::fromJson(postStatus);
  70. api_error_t e = checkAPIresponse(doc);
  71. if(e.error){
  72. qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
  73. stat.failed = true;
  74. stat.api_error = true;
  75. stat.description = e.message + " (" + e.code + ")";
  76. return QPair<session_t,api_status_t>(sess,stat);
  77. }
  78. QString country = doc.object().value("data").toObject().value("country_code").toString();
  79. session = doc.object().value("data").toObject().value("session_id").toString();
  80. sess.id = session;
  81. sess.country = country;
  82. qDebug(FBLUE("Session ID received (%s)"),country.toUtf8().constData());
  83. return QPair<session_t,api_status_t>(sess,stat);
  84. }
  85. QPair<user_t,api_status_t> CrunchyrollAPI::doProxyLogin(QString mail,QString pass){
  86. user_t user;
  87. api_status_t stat;
  88. stat.failed = false;
  89. stat.api_error = false;
  90. qDebug("Fetching auth token for proxy login...");
  91. NetworkRequest request_l;
  92. request_l.setRequestMethod(NetworkRequest::Post);
  93. request_l.addRequestAttribute("session_id", QVariant(session).toString());
  94. request_l.addRequestAttribute("account", QVariant(mail).toString());
  95. request_l.addRequestAttribute("password", QVariant(pass).toString());
  96. const QByteArray postStatus_l = request_l.loadSync("https://api.crunchyroll.com/login.2.json");
  97. if(request_l.lastError() != ""){
  98. qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request_l.lastError() << RST;
  99. stat.failed = true;
  100. stat.description = request_l.lastError();
  101. return QPair<user_t,api_status_t>(user,stat);
  102. }
  103. QJsonDocument doc_l = QJsonDocument::fromJson(postStatus_l);
  104. api_error_t e = checkAPIresponse(doc_l);
  105. if(e.error){
  106. qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
  107. stat.failed = true;
  108. stat.api_error = true;
  109. stat.description = e.message + " (" + e.code + ")";
  110. return QPair<user_t,api_status_t>(user,stat);
  111. }
  112. QJsonObject datapart_l = doc_l.object().value("data").toObject();
  113. qDebug(FBLUE("Auth token received"));
  114. qDebug("Logging in via proxy...");
  115. QString url = "https://api2.cr-unblocker.com/start_session?version=1.1&auth="
  116. + datapart_l.value("auth").toString() + "&user_id=" + datapart_l.value("user").toObject().value("user_id").toString();
  117. NetworkRequest request;
  118. request.setRequestMethod(NetworkRequest::Get);
  119. const QByteArray getStatus = request.loadSync(url);
  120. if(request.lastError() != ""){
  121. qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
  122. stat.failed = true;
  123. stat.description = request.lastError();
  124. return QPair<user_t,api_status_t>(user,stat);
  125. }
  126. QJsonDocument doc = QJsonDocument::fromJson(getStatus);
  127. api_error_t e2 = checkAPIresponse(doc);
  128. if(e2.error){
  129. qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
  130. stat.failed = true;
  131. stat.api_error = true;
  132. stat.description = e2.message + " (" + e2.code + ")";
  133. return QPair<user_t,api_status_t>(user,stat);
  134. }
  135. QJsonObject datapart = doc.object().value("data").toObject();
  136. QString username = datapart.value("user").toObject().value("username").toString();
  137. QString access = datapart.value("user").toObject().value("access_type").toString();
  138. QString expires = datapart.value("expires").toString();
  139. session = datapart.value("session_id").toString();
  140. user.session_id = session;
  141. user.username = username;
  142. user.premium = (access.toLower()=="premium");
  143. qDebug(FBLUE("Logged in as %s (%s)"),username.toUtf8().constData(),access.toUtf8().constData());
  144. return QPair<user_t,api_status_t>(user,stat);
  145. }
  146. QPair<user_t,api_status_t> CrunchyrollAPI::doLogin(QString mail,QString pass){
  147. user_t user;
  148. api_status_t stat;
  149. stat.failed = false;
  150. stat.api_error = false;
  151. qDebug("Logging in...");
  152. NetworkRequest request_l;
  153. request_l.setCookieJar(new CustomCookieJar());
  154. request_l.setRequestMethod(NetworkRequest::Post);
  155. request_l.addRequestAttribute("session_id", QVariant(session).toString());
  156. request_l.addRequestAttribute("account", QVariant(mail).toString());
  157. request_l.addRequestAttribute("password", QVariant(pass).toString());
  158. const QByteArray postStatus_l = request_l.loadSync("https://api.crunchyroll.com/login.2.json");
  159. if(request_l.lastError() != ""){
  160. qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request_l.lastError() << RST;
  161. stat.failed = true;
  162. stat.description = request_l.lastError();
  163. return QPair<user_t,api_status_t>(user,stat);
  164. }
  165. QJsonDocument doc_l = QJsonDocument::fromJson(postStatus_l);
  166. api_error_t e = checkAPIresponse(doc_l);
  167. if(e.error){
  168. qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
  169. stat.failed = true;
  170. stat.api_error = true;
  171. stat.description = e.message + " (" + e.code + ")";
  172. return QPair<user_t,api_status_t>(user,stat);
  173. }
  174. QJsonObject datapart = doc_l.object().value("data").toObject();
  175. QString username = datapart.value("user").toObject().value("username").toString();
  176. QString access = datapart.value("user").toObject().value("access_type").toString();
  177. QString expires = datapart.value("expires").toString();
  178. user.username = username;
  179. user.premium = (access.toLower()=="premium");
  180. qDebug(FBLUE("Logged in as %s (%s)"),username.toUtf8().constData(),access.toUtf8().constData());
  181. CustomCookieJar* cookies = request_l.getCookieJar();
  182. for(auto cookie:cookies->getCookies()){
  183. if(cookie.name()=="session_id"){
  184. session = cookie.value();
  185. user.session_id = session;
  186. return QPair<user_t,api_status_t>(user,stat);
  187. }
  188. }
  189. qWarning(FRED("Login failed. No session id returned"));
  190. stat.failed = true;
  191. stat.description = "No session id returned by the server";
  192. return QPair<user_t,api_status_t>(user,stat);
  193. }
  194. QPair<QVector<query_t>,api_status_t> CrunchyrollAPI::searchMedia(QString query){
  195. api_status_t stat;
  196. stat.failed = false;
  197. stat.api_error = false;
  198. QVector<query_t> response;
  199. qDebug("Searching for '%s'",query.toUtf8().constData());
  200. NetworkRequest request;
  201. request.setRequestMethod(NetworkRequest::Post);
  202. request.addRequestAttribute("session_id", session);
  203. request.addRequestAttribute("q", query);
  204. request.addRequestAttribute("limit", "50");
  205. request.addRequestAttribute("offset", "0");
  206. request.addRequestAttribute("media_types", "anime|drama");
  207. const QByteArray postStatus = request.loadSync("https://api.crunchyroll.com/autocomplete.0.json");
  208. if(request.lastError() != ""){
  209. qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
  210. stat.failed = true;
  211. stat.description = request.lastError();
  212. return QPair<QVector<query_t>,api_status_t>(response,stat);
  213. }
  214. QJsonDocument doc = QJsonDocument::fromJson(postStatus);
  215. api_error_t e = checkAPIresponse(doc);
  216. if(e.error){
  217. qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
  218. stat.failed = true;
  219. stat.api_error = true;
  220. stat.description = e.message + " (" + e.code + ")";
  221. return QPair<QVector<query_t>,api_status_t>(response,stat);
  222. }
  223. QJsonArray results = doc.object().value("data").toArray();
  224. for (auto result:results) {
  225. query_t query;
  226. QJsonObject obj = result.toObject();
  227. query.name = obj.value("name").toString();
  228. query.series_id = obj.value("series_id").toString();
  229. query.thumbnail = QUrl(obj.value("portrait_image").toObject().value("thumb_url").toString());
  230. query.description = obj.value("description").toString();
  231. response.push_back(query);
  232. }
  233. if(response.count()==0)
  234. qDebug(FRED("No results"));
  235. else if(response.count()==1)
  236. qDebug(FBLUE("1 result found (%s)"),response.first().name.toUtf8().constData());
  237. else
  238. qDebug(FBLUE("%d results found"), response.count());
  239. return QPair<QVector<query_t>,api_status_t>(response,stat);
  240. }
  241. QPair<QVector<collection_t>,api_status_t> CrunchyrollAPI::getCollections(int seriesid){
  242. api_status_t stat;
  243. stat.failed = false;
  244. stat.api_error = false;
  245. QVector<collection_t> response;
  246. QString fields = "collections.collection_id,collections.series_id,collections.name,\
  247. collections.series_name,collections.description,collections.media_type,\
  248. collections.season,collections.complete,collections.landscape_image,\
  249. collections.portrait_image,collections.availability_notes,collections.media_count,\
  250. collections.premium_only,collections.created,collections.mature";
  251. NetworkRequest request;
  252. request.setRequestMethod(NetworkRequest::Post);
  253. request.addRequestAttribute("session_id", QVariant(session).toString());
  254. request.addRequestAttribute("series_id", QVariant(seriesid).toString());
  255. request.addRequestAttribute("sort", "asc");
  256. request.addRequestAttribute("locale", QVariant(locale).toString());
  257. request.addRequestAttribute("fields", fields);
  258. const QByteArray postStatus = request.loadSync("https://api.crunchyroll.com/list_collections.0.json");
  259. if(request.lastError() != ""){
  260. qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
  261. stat.failed = true;
  262. stat.description = request.lastError();
  263. return QPair<QVector<collection_t>,api_status_t>(response,stat);
  264. }
  265. QJsonDocument doc = QJsonDocument::fromJson(postStatus);
  266. api_error_t e = checkAPIresponse(doc);
  267. if(e.error){
  268. qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
  269. stat.failed = true;
  270. stat.api_error = true;
  271. stat.description = e.message + " (" + e.code + ")";
  272. return QPair<QVector<collection_t>,api_status_t>(response,stat);
  273. }
  274. QJsonArray results = doc.object().value("data").toArray();
  275. for (auto result:results) {
  276. collection_t c;
  277. QJsonObject obj = result.toObject();
  278. c.name = obj.value("name").toString();
  279. c.collection_id = obj.value("collection_id").toString();
  280. c.season = obj.value("season").toString();
  281. c.description = obj.value("description").toString();
  282. response.push_back(c);
  283. }
  284. if(response.count()==0)
  285. qDebug(FRED("No collections"));
  286. else
  287. qDebug(FBLUE("%d collection(s) found"), response.count());
  288. return QPair<QVector<collection_t>,api_status_t>(response,stat);
  289. }
  290. QPair<QVector<meta_episode_t>,api_status_t> CrunchyrollAPI::getEpisodes(int SeriesOrCollectionID, bool useSeriesID){
  291. QVector<meta_episode_t> response;
  292. api_status_t stat;
  293. stat.failed = false;
  294. stat.api_error = false;
  295. NetworkRequest request;
  296. request.setRequestMethod(NetworkRequest::Post);
  297. request.addRequestAttribute("session_id", QVariant(session).toString());
  298. if(useSeriesID)request.addRequestAttribute("series_id", QVariant(SeriesOrCollectionID));
  299. else request.addRequestAttribute("collection_id", QVariant(SeriesOrCollectionID));
  300. request.addRequestAttribute("sort", "asc");
  301. request.addRequestAttribute("locale", QVariant(locale).toString());
  302. request.addRequestAttribute("offset", "0");
  303. request.addRequestAttribute("limit", "5000");
  304. const QByteArray postStatus = request.loadSync("https://api.crunchyroll.com/list_media.0.json");
  305. if(request.lastError() != ""){
  306. qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
  307. stat.failed = true;
  308. stat.description = request.lastError();
  309. return QPair<QVector<meta_episode_t>,api_status_t>(response,stat);
  310. }
  311. QJsonDocument doc = QJsonDocument::fromJson(postStatus);
  312. api_error_t e = checkAPIresponse(doc);
  313. if(e.error){
  314. qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
  315. stat.failed = true;
  316. stat.api_error = true;
  317. stat.description = e.message + " (" + e.code + ")";
  318. return QPair<QVector<meta_episode_t>,api_status_t>(response,stat);
  319. }
  320. QJsonArray results = doc.object().value("data").toArray();
  321. for (auto result:results) {
  322. meta_episode_t c;
  323. QJsonObject obj = result.toObject();
  324. c.name = obj.value("name").toString();
  325. c.number = obj.value("episode_number").toString().toInt();
  326. c.media_id = obj.value("media_id").toString();
  327. c.description = obj.value("description").toString();
  328. c.available = obj.value("available").toBool();
  329. c.free_available = obj.value("free_available").toBool();
  330. c.thumbnail = QUrl(obj.value("screenshot_image").toObject().value("thumb_url").toString());
  331. response.push_back(c);
  332. }
  333. if(response.count()==0)
  334. qDebug(FRED("No episodes found"));
  335. else
  336. qDebug(FBLUE("%d episode(s) found"), response.count());
  337. return QPair<QVector<meta_episode_t>,api_status_t>(response,stat);
  338. }
  339. QPair<episode_t,api_status_t> CrunchyrollAPI::getEpisode(int mediaID){
  340. api_status_t stat;
  341. stat.failed = false;
  342. stat.api_error = false;
  343. episode_t c;
  344. NetworkRequest request;
  345. request.setRequestMethod(NetworkRequest::Post);
  346. request.addRequestAttribute("session_id", QVariant(session).toString());
  347. request.addRequestAttribute("media_id", QVariant(mediaID).toString());
  348. request.addRequestAttribute("locale", QVariant(locale).toString());
  349. request.addRequestAttribute("fields", "media.collection_id,media.collection_name,\
  350. media.stream_data,media.available,media.episode_number,media.duration,\
  351. media.series_name,media.premium_only,media.name,media.screenshot_image");
  352. const QByteArray postStatus = request.loadSync("https://api.crunchyroll.com/info.0.json");
  353. if(request.lastError() != ""){
  354. qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
  355. stat.failed = true;
  356. stat.description = request.lastError();
  357. return QPair<episode_t,api_status_t>(c,stat);
  358. }
  359. QJsonDocument doc = QJsonDocument::fromJson(postStatus);
  360. api_error_t e = checkAPIresponse(doc);
  361. if(e.error){
  362. qDebug().nospace().noquote() << KRED << BOLD("An api error occurred: ") << KRED << e.message << " (" << e.code << ")" RST;
  363. stat.failed = true;
  364. stat.api_error = true;
  365. stat.description = e.message + " (" + e.code + ")";
  366. return QPair<episode_t,api_status_t>(c,stat);
  367. }
  368. QJsonObject obj = doc.object().value("data").toObject();
  369. c.name = obj.value("name").toString();
  370. c.number = obj.value("episode_number").toString().toInt();
  371. c.seriesname = obj.value("series_name").toString();
  372. c.description = obj.value("description").toString();
  373. c.collectionname = obj.value("collection_name").toString();
  374. c.audio_lang = obj.value("audio_lang").toString();
  375. c.subtitle_lang = obj.value("hardsub_lang").toString();
  376. c.available = obj.value("available").toBool();
  377. c.duration = obj.value("duration").toInt();
  378. c.premium_only = obj.value("premium_only").toBool();
  379. c.thumbnail = QUrl(obj.value("screenshot_image").toObject().value("thumb_url").toString());
  380. QVector<stream_t> stream_container;
  381. QJsonArray streams = obj.value("stream_data").toObject().value("streams").toArray();
  382. for(auto stream:streams){
  383. stream_t st;
  384. QJsonObject s = stream.toObject();
  385. st.quality = s.value("quality").toString();
  386. st.url = QUrl(s.value("url").toString());
  387. stream_container.push_back(st);
  388. }
  389. c.streams = stream_container;
  390. return QPair<episode_t,api_status_t>(c,stat);
  391. }
  392. QPair<QString,api_status_t> CrunchyrollAPI::downloadM3U(episode_t episode){
  393. api_status_t stat;
  394. stat.failed = false;
  395. stat.api_error = false;
  396. for(stream_t stream:episode.streams){
  397. QString quality = stream.quality;
  398. if(quality.toLower() == "adaptive"){
  399. qDebug() << stream.url;//<<<<<<<<<<<<<<<<<<< REMOVE THIS
  400. NetworkRequest request;
  401. request.setRequestMethod(NetworkRequest::Get);
  402. const QByteArray postStatus = request.loadSync(stream.url);
  403. if(request.lastError() != ""){
  404. qDebug().nospace().noquote() << KRED << BOLD("An error occurred: ") << KRED << request.lastError() << RST;
  405. stat.failed = true;
  406. stat.description = request.lastError();
  407. return QPair<QString,api_status_t>("",stat);
  408. }
  409. return QPair<QString,api_status_t>(QString::fromUtf8(postStatus),stat);
  410. }
  411. else continue;
  412. }
  413. qDebug(FRED("No adaptive stream available"));
  414. return QPair<QString,api_status_t>("",stat);
  415. }
  416. void CrunchyrollAPI::printEpisodeDetails(episode_t episode){
  417. std::cout << KBOLD << "#" << episode.number << " - " << episode.name.toUtf8().constData()
  418. << RST << " (" << episode.collectionname.toUtf8().constData() << ")" << std::endl;
  419. if(episode.available)
  420. std::cout << "Available: " << KGREEN << "true " << RST;
  421. else
  422. std::cout << "Available: " << KRED << "false " << RST;
  423. if(episode.premium_only)
  424. std::cout << "Premium only: " << KRED << "true" << RST << std::endl;
  425. else
  426. std::cout << "Premium only: " << KGREEN << "false" << RST << std::endl;
  427. if(episode.streams.count() < 1)
  428. std::cout << KBOLD << KRED << "Either this episode is premium or not available in the selected locale ("
  429. << locale.toUtf8().constData() << ")" << RST << std::endl;
  430. }
  431. api_error_t CrunchyrollAPI::checkAPIresponse(QJsonDocument doc){
  432. QJsonObject obj = doc.object();
  433. api_error_t e;
  434. e.error = obj.value("error").toBool();
  435. e.code = obj.value("code").toString();
  436. if(obj.keys().contains("message")) e.message = obj.value("message").toString();
  437. else e.message = "";
  438. return e;
  439. }