app.d 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /*
  2. * pixiv_down - CLI-based downloading tool for https://www.pixiv.net.
  3. * Copyright (C) 2023, 2024 Mio
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, version 3 of the License.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. */
  17. module app.app;
  18. import std.datetime.systime : SysTime;
  19. import pd.configuration;
  20. import std.experimental.logger;
  21. import app.pd_version : VCS_TAG;
  22. // dup (logger.d); should be moved to config file.
  23. enum PIXIV_DOWN_VERSION_STRING = "2024.10.0";
  24. version(PD_USE_GAMUT)
  25. {
  26. pragma(msg, "NOTE: pixiv_down doesn't make proper use of gamut yet. GIFs won't download successfully.");
  27. }
  28. /*
  29. * --------------------------------------------------------------------------
  30. * Handles
  31. * --------------------------------------------------------------------------
  32. */
  33. ///
  34. /// Handle any "help" commands.
  35. ///
  36. /// This only runs when "help" is used as a command by itself, or when it
  37. /// is succeeded by a different command (thus displaying the help message
  38. /// for that command).
  39. ///
  40. int helpHandle(string[] args, in Config)
  41. {
  42. import app.cmds;
  43. if (args.length == 1) {
  44. displayDefaultHelp(true);
  45. return 0;
  46. }
  47. switch(args[1]) {
  48. case "artist":
  49. displayArtistHelp();
  50. break;
  51. case "artwork":
  52. displayArtworkHelp();
  53. break;
  54. case "bookmarked":
  55. displayBookmarkedHelp();
  56. break;
  57. case "compact":
  58. displayCompactHelp();
  59. break;
  60. case "daily":
  61. displayDailyHelp();
  62. break;
  63. case "following":
  64. displayFollowingHelp();
  65. break;
  66. case "novel":
  67. displayNovelHelp();
  68. break;
  69. case "prune":
  70. displayPruneHelp();
  71. break;
  72. default:
  73. displayDefaultHelp();
  74. return 2;
  75. }
  76. return 0;
  77. }
  78. /*
  79. * --------------------------------------------------------------------------
  80. * Help
  81. * --------------------------------------------------------------------------
  82. */
  83. ///
  84. /// Print the default help information.
  85. ///
  86. /// Params:
  87. /// programName = The name of the executable being run
  88. /// showMore = Show examples and links to pixiv_down's websites.
  89. ///
  90. void displayDefaultHelp(bool showMore = false)
  91. {
  92. import std.stdio : stderr;
  93. stderr.writefln(
  94. "pixiv_down - A tool for downloading from pixiv.net [%s (%s)]\n" ~
  95. "\nUsage:\tpixiv_down <command> [options]\n" ~
  96. "\npixiv_down is a tool that allows you to download various content\n" ~
  97. "from the pixiv website. You can download illustrations, manga, and\n" ~
  98. "novels.\n" ~
  99. "\nMain Commands:\n" ~
  100. "\tartist \tDownload content from a specified artist.\n" ~
  101. "\tartwork \tDownload specific artworks via IDs.\n" ~
  102. "\tbookmarked\tDownload all bookmarked content.\n" ~
  103. "\tcompact \tCompact all account directories in to one.\n" ~
  104. "\tdaily \tDownload all followed users' content between dates.\n" ~
  105. "\tfollowing \tDownload all followed users' content by account.\n" ~
  106. "\tnovel \tDownload specific novels via IDs.\n" ~
  107. "\tprune \tRemove unfollowed and/or non-existing accounts.\n" ~
  108. "\nSide Commands:\n" ~
  109. "\thelp \tDisplay this help information and exit.\n" ~
  110. "\treset \tReset your PHPSESSID.\n" ~
  111. "\tversion \tDisplay the current version of pixiv_down.",
  112. PIXIV_DOWN_VERSION_STRING, VCS_TAG);
  113. if (showMore) {
  114. stderr.writeln(
  115. "\nExamples:\n" ~
  116. "\n Download a single illustration:\n" ~
  117. " pixiv_down artwork 108985926\n" ~
  118. "\n Download all content from a single user:\n" ~
  119. " pixiv_down artist 10109777\n" ~
  120. "\nYou can find more information about pixiv_down at\n" ~
  121. " <https://yume-neru.neocities.org/p/pixiv_down.html>.\n" ~
  122. "\nIf you have any issues or feedback, please see the project's page" ~
  123. " at codeberg\n" ~ " <https://codeberg.org/supercell/pixiv_down>.");
  124. } else {
  125. stderr.writeln("\n" ~
  126. "For more information (including examples) use the ``help'' command.");
  127. }
  128. }
  129. /*
  130. * --------------------------------------------------------------------------
  131. * Util
  132. * --------------------------------------------------------------------------
  133. */
  134. version(PD_USE_MAGICK) {
  135. ///
  136. /// Print GraphicsMagick version information to the pixiv_down log file.
  137. ///
  138. void logLibGMVersion()
  139. {
  140. import std.string : fromStringz;
  141. import pd.imaging.magick : GetMagickVersion;
  142. infof("%s", fromStringz(GetMagickVersion(null)));
  143. }
  144. } // version(PD_USE_MAGICK)
  145. /// Returns the two character locale set in the environment.
  146. string getLocale()
  147. {
  148. import core.stdc.locale;
  149. import std.string : fromStringz;
  150. setlocale(LC_ALL, "");
  151. char* currentLocale = setlocale(LC_MESSAGES, null);
  152. if (null is currentLocale) {
  153. return "en";
  154. }
  155. char[] arr = fromStringz(currentLocale);
  156. if ("C" == arr || "POSIX" == arr) {
  157. return "en";
  158. }
  159. return arr[0..2].dup;
  160. }
  161. /*
  162. * --------------------------------------------------------------------------
  163. * Main
  164. * --------------------------------------------------------------------------
  165. */
  166. // Don't compile in |main| when building unittests.
  167. version(unittest) {} else:
  168. int main(string[] args)
  169. {
  170. import app.cmds;
  171. import pd.pixiv : fetchCSRFToken;
  172. import std.stdio : stderr, writeln, writefln;
  173. import std.file : exists, remove;
  174. import app.logger: initializeLogger;
  175. auto subcmds = [
  176. "artist": &artistHandle,
  177. "artwork": &artworkHandle,
  178. "bookmarked": &bookmarkedHandle,
  179. "compact": &compactHandle,
  180. "daily": &dailyHandle,
  181. "following": &followingHandle,
  182. "novel": &novelHandle,
  183. "prune": &pruneHandle,
  184. "help": &helpHandle,
  185. ];
  186. if (args.length < 2) {
  187. displayDefaultHelp(false);
  188. return 2;
  189. }
  190. Config cliConfigOverrides;
  191. string configFilePathOverride;
  192. size_t subCommandIndex = 1;
  193. foreach(idx, arg; args[1..$]) {
  194. if (arg in subcmds) {
  195. subCommandIndex = idx + 1;
  196. break;
  197. }
  198. switch(arg) {
  199. case "-h":
  200. case "--help":
  201. displayDefaultHelp(true);
  202. return 0;
  203. case "-v":
  204. case "--version":
  205. case "version":
  206. writefln("pixiv_down %s (%s)", PIXIV_DOWN_VERSION_STRING, VCS_TAG);
  207. return 0;
  208. case "--output-directory":
  209. if (idx + 2 >= args.length) {
  210. stderr.writefln("error: invalid argument: %s (no output directory provided)", arg);
  211. return 1;
  212. }
  213. cliConfigOverrides.outputDirectory = args[idx + 2];
  214. subCommandIndex += 2;
  215. break;
  216. case "--config-file":
  217. if (idx + 2 >= args.length) {
  218. stderr.writefln("error: invalid argument: %s (no path provided for config file", arg);
  219. return 1;
  220. }
  221. configFilePathOverride = args[idx + 2];
  222. subCommandIndex += 2;
  223. break;
  224. case "reset":
  225. resetSessionID();
  226. return 0;
  227. default:
  228. break;
  229. }
  230. import std.string : indexOf, startsWith;
  231. import app.util : split;
  232. if (arg.startsWith("--") && arg.indexOf("=") != -1) {
  233. string[] argAndValue = arg.split('=', 2);
  234. if (argAndValue[0] == "--config-file") {
  235. configFilePathOverride = argAndValue[1];
  236. } else if (argAndValue[0] == "--output-directory") {
  237. cliConfigOverrides.outputDirectory = argAndValue[1];
  238. }
  239. }
  240. }
  241. if (subCommandIndex >= args.length) {
  242. stderr.writeln("error: no command provided.");
  243. displayDefaultHelp(false);
  244. return 2;
  245. }
  246. auto func = (args[subCommandIndex] in subcmds);
  247. if (func is null) {
  248. stderr.writefln("%s: '%s' is not a valid pixiv_down command. See '%s help'.", args[0],
  249. args[subCommandIndex], args[0]);
  250. // TODO: Suggest similar commands (if possible).
  251. // e.g. pixiv_down user <id> -> Did you mean artist? [y/N]
  252. return 2;
  253. }
  254. initializeLogger();
  255. version(PD_USE_MAGICK)
  256. {
  257. // TODO: remove from app.d, should be in plugin.
  258. import pd.imaging.magick;
  259. loadMagick(args[0]);
  260. scope(exit) destroyMagick();
  261. logLibGMVersion();
  262. }
  263. Config config = configFilePathOverride == "" ? loadConfig() : loadConfig(configFilePathOverride);
  264. if (cliConfigOverrides.outputDirectory != "") {
  265. infof("Provided config outputDirectory override: %s", cliConfigOverrides.outputDirectory);
  266. config.outputDirectory = cliConfigOverrides.outputDirectory;
  267. }
  268. infof("config outputDirectory: %s", config.outputDirectory);
  269. config.locale = getLocale();
  270. infof("Current locale = %s", config.locale);
  271. return (*func)(args[subCommandIndex..$], config);
  272. }