configuration.d 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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 pd.configuration;
  18. private enum ProjectQualifier = null;
  19. private enum ProjectOrg = "YumeNeru Software";
  20. private enum ProjectName = "pixiv_down";
  21. private enum PHPSessionIDFileName = ".phpsessid";
  22. /// pixiv_down configuration.
  23. ///
  24. /// Use `configuration.loadConfig` to load the correct values.
  25. struct Config
  26. {
  27. /// The directory where content is downloaded to.
  28. string outputDirectory;
  29. /// The PHPSESSID of the current account.
  30. string sessionid;
  31. /// Cross Site Request Forgery Token (unused)
  32. string csrfToken;
  33. /// Two character locale used for pixiv requests
  34. string locale;
  35. /// Configuration for the `bookmarked` command
  36. BookmarkedConfig bookmarked;
  37. }
  38. private struct BookmarkedConfig
  39. {
  40. /// Whether invalid works should automatically be removed
  41. /// from the account's bookmarks.
  42. bool alwaysRemoveInvalid;
  43. }
  44. public Config loadConfig(string path)
  45. {
  46. import mlib.configparser: ConfigParser;
  47. import mlib.directories: getUserDirectories;
  48. import std.exception: ErrnoException;
  49. import std.experimental.logger: errorf, tracef;
  50. const userDirectories = getUserDirectories();
  51. tracef("Loading configuration file at %s", path);
  52. try {
  53. auto parser = new ConfigParser();
  54. parser.read(path);
  55. const outputDirectory = parser.get("output", "directory",
  56. parser.get("output", "base_directory",
  57. userDirectories.pictureDir));
  58. const alwaysRemoveInvalid = parser.getBool("bookmarked", "always_remove_invalid", false);
  59. return Config(outputDirectory, fetchSessionID(), "", "",
  60. BookmarkedConfig(alwaysRemoveInvalid));
  61. } catch(ErrnoException e) {
  62. errorf("Failed to load configuration file: %s", e.msg);
  63. // Fallback to default values
  64. return Config(userDirectories.pictureDir, fetchSessionID());
  65. }
  66. }
  67. /// Load the configuration for pixiv_down.
  68. public Config loadConfig()
  69. {
  70. import mlib.directories: getProjectDirectories;
  71. import std.file: exists;
  72. import std.path: buildPath;
  73. const projectDirs = getProjectDirectories(ProjectQualifier, ProjectOrg, ProjectName);
  74. const newConfigPath = buildPath(projectDirs.configDir, "pixiv_down.conf");
  75. const oldConfigPath = buildPath(projectDirs.configDir, "settings.conf");
  76. /* I'd prefer to use the new config path over the old one when possible,
  77. * but if that's the only one present then use it. */
  78. const path = exists(newConfigPath) ? newConfigPath : (exists(oldConfigPath) ? oldConfigPath : newConfigPath);
  79. return loadConfig(path);
  80. }
  81. /// Prompt to reset the PHPSESSID.
  82. ///
  83. /// This will store the value in the appropriate location so it
  84. /// can be read in the future.
  85. public void resetSessionID() @safe
  86. {
  87. import mlib.directories: getProjectDirectories;
  88. import std.file: exists, mkdirRecurse;
  89. import std.path: buildPath;
  90. const projectDirs = getProjectDirectories(ProjectQualifier, ProjectOrg, ProjectName);
  91. const id = promptForSessionID();
  92. if (false == exists(projectDirs.dataDir)) {
  93. mkdirRecurse(projectDirs.dataDir);
  94. }
  95. storeSessionID(id, buildPath(projectDirs.dataDir, PHPSessionIDFileName));
  96. }
  97. private string promptForSessionID() @trusted
  98. {
  99. import std.stdio : readln, stderr, write;
  100. import std.string : empty, strip;
  101. import core.stdc.stdlib : exit;
  102. stderr.writeln("========== NOTICE ==========");
  103. stderr.writeln("No PHPSESSID found.\n");
  104. stderr.writeln("Please see the below webpage for instructions to find your PHPSESSID");
  105. stderr.writeln("https://codeberg.org/supercell/pixiv_down/src/branch/trunk/HowTo-PHPSESSID.txt");
  106. stderr.writeln("============================");
  107. write("\nPlease enter your PHPSESSID (leave blank to exit): ");
  108. const sessionid = readln.strip;
  109. if (empty(sessionid))
  110. {
  111. exit(0);
  112. }
  113. return sessionid;
  114. }
  115. pragma(inline, true)
  116. private void storeSessionID(const(char)[] sessionid, const(char)[] path) @safe
  117. {
  118. import std.stdio : File;
  119. File(path, "w+").writeln(sessionid);
  120. }
  121. private string fetchSessionID() /* @safe */
  122. {
  123. import std.file : exists, mkdirRecurse;
  124. import std.path : buildPath;
  125. import std.process : environment;
  126. import std.stdio : File;
  127. import std.string : empty, strip;
  128. import mlib.directories : getProjectDirectories;
  129. // Environment variable takes precedence over stored value
  130. const environmentValue = environment.get("PIXIV_DOWN_SESSID");
  131. if (environmentValue !is null) {
  132. return environmentValue;
  133. }
  134. const projectDirs = getProjectDirectories(ProjectQualifier, ProjectOrg, ProjectName);
  135. const sessidPath = buildPath(projectDirs.dataDir, PHPSessionIDFileName);
  136. if (false == exists(sessidPath)) {
  137. mkdirRecurse(projectDirs.dataDir);
  138. const newID = promptForSessionID();
  139. storeSessionID(newID, sessidPath);
  140. return newID;
  141. }
  142. const sessionid = File(sessidPath).readln.strip;
  143. if (empty(sessionid)) {
  144. const newID = promptForSessionID();
  145. storeSessionID(newID, sessidPath);
  146. return newID;
  147. }
  148. return sessionid;
  149. }