following.d 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /*
  2. * pixiv_down - CLI-based downloading tool for https://www.pixiv.net.
  3. * Copyright (C) 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.cmds.following;
  18. import pd.configuration;
  19. import std.experimental.logger;
  20. public int followingHandle(string[] args, in Config config)
  21. {
  22. import std.getopt : getopt, GetOptException;
  23. import std.stdio : stdout, stderr;
  24. import mlib.term;
  25. import pd.pixiv;
  26. /* following (--private | --public) */
  27. if (args.length < 2) {
  28. stderr.writeln("pixiv_down: Must provide one of --public or --private.");
  29. stderr.writeln("Run 'pixiv_down following --help' for more information.");
  30. return 1;
  31. }
  32. bool fromPrivate = false;
  33. bool fromPublic = false;
  34. int skip;
  35. try {
  36. auto helpInformation = getopt(args,
  37. "private", &fromPrivate,
  38. "public", &fromPublic,
  39. "skip|s", &skip);
  40. if (helpInformation.helpWanted) {
  41. displayFollowingHelp();
  42. return 0;
  43. }
  44. } catch (GetOptException e) {
  45. stderr.writefln("pixiv_down following: %s", e.msg);
  46. stderr.writefln("Run 'pixiv_down help following' for more information.");
  47. }
  48. if (fromPublic && fromPrivate) {
  49. stderr.writefln("pixiv_down: Cannot download both publicly and privately followed accounts.");
  50. displayFollowingHelp();
  51. return 1;
  52. }
  53. if (!fromPublic && !fromPrivate) {
  54. stderr.writeln("pixiv_down: Must provide one of --public or --private.");
  55. stderr.writeln("Run 'pixiv_down following --help' for more information.");
  56. return 1;
  57. }
  58. const visibility = fromPrivate ? "privately" : "publicly";
  59. infof("Downloading %s followed accounts", visibility);
  60. stdout.writefln("Fetching %s followed accounts...", visibility);
  61. string[] userIds = fetchFollowingIDs(fromPrivate, skip, config);
  62. const total = userIds.length;
  63. infof("following: received total of %d accounts", total);
  64. Term.goUpAndClearLine(1);
  65. stdout.writefln("Fetched %s followed accounts.", visibility);
  66. foreach(index, id; userIds) {
  67. import app.cmds.artist;
  68. artistHandle(["artist", id], config);
  69. }
  70. return 0;
  71. }
  72. public void displayFollowingHelp()
  73. {
  74. import std.stdio : stderr;
  75. stderr.writefln(
  76. "pixiv_down following - Bulk download all creators you follow.\n" ~
  77. "\nUsage:\tpixiv_down following [--public|--private] [options]\n" ~
  78. "\nDownload all content from every creator that you follow, starting\n" ~
  79. "from your most recently followed creator down to your least recently\n" ~
  80. "followed creator.\n" ~
  81. "\nYou must specify whether to download your publicaly or privately\n" ~
  82. "followed creators by using the --public and --private options. They\n" ~
  83. "cannot be combined.\n" ~
  84. "\nOptions:\n" ~
  85. " -h, --help \tPrint this help message and exit.\n" ~
  86. " -s, --skip NUMBER\tSkip downloading the first NUMBER creators.\n" ~
  87. " --private \tDownload from privately followed creators.\n" ~
  88. " --public \tDownload from publicaly followed creators.\n" ~
  89. "\nExamples:\n" ~
  90. "\n Download all publicaly followed accounts:\n" ~
  91. " pixiv_down following --public\n" ~
  92. "\n Download all privately followed accounts, skipping the first 2:\n" ~
  93. " pixiv_down following --private --skip 2\n");
  94. }
  95. private:
  96. string[] fetchFollowingIDs(bool private_, in int skip, in Config config)
  97. {
  98. import std.array : array;
  99. import std.algorithm.iteration : map;
  100. import mlib.term;
  101. import pd.pixiv;
  102. import app.util : sleep;
  103. User[] users;
  104. long total;
  105. long offset = skip;
  106. do {
  107. User[] page = fetchFollowing(private_, offset, total, config);
  108. tracef("following page %s", page);
  109. users ~= page;
  110. offset += page.length;
  111. reportProgress(total - skip, offset - skip);
  112. sleep(1, 2, false);
  113. } while (offset < total);
  114. Term.clearCurrentLine();
  115. return cast(string[])users.map!(u => u.id).array;
  116. }
  117. void reportProgress(long total, long current)
  118. {
  119. import std.format: format;
  120. import std.stdio;
  121. import mlib.term;
  122. Term.clearCurrentLine();
  123. const ratioCompleted = cast(double)current / total;
  124. const prefix = format!"%d ["(current);
  125. const suffix = format!"] %3.0f%% "(ratioCompleted * 100);
  126. const barLength = Term.getColumnCount() - prefix.length - suffix.length;
  127. stdout.write(prefix);
  128. foreach(i; 0..barLength) {
  129. stdout.write(i < (ratioCompleted * barLength) ? '#' : ' ');
  130. }
  131. stdout.write(suffix);
  132. stdout.flush();
  133. }