|
@@ -28,13 +28,18 @@ public void displayBookmarkedHelp()
|
|
|
"\nThis command allows you to download all of your bookmarked works.\n" ~
|
|
|
"A list containing the works that are no longer available (because\n" ~
|
|
|
"they've been removed or made private) can be found in a file called\n" ~
|
|
|
- "'pixiv_down-missing.txt' after running the `bookmarked' command." ~
|
|
|
+ "'pixiv_down-missing.txt' after running the `bookmarked' command.\n" ~
|
|
|
"\nOptions:\n" ~
|
|
|
- " -h, --help \tDisplay this help message and exit.\n" ~
|
|
|
- " -s, --skip OFFSET\tSkip downloading the first OFFSET creators.\n" ~
|
|
|
- " --novels \tDownload bookmarked novels instead of artworks.\n" ~
|
|
|
- " --private \tDownload your privately bookmarked works instead.\n" ~
|
|
|
- " --remove-invalid \tRemove bookmarks for works that are no longer available.\n");
|
|
|
+ " -h, --help \tDisplay this help message and exit.\n" ~
|
|
|
+ " -s, --skip OFFSET \tSkip downloading the first OFFSET creators.\n" ~
|
|
|
+ " --novels \tDownload bookmarked novels instead of artworks.\n" ~
|
|
|
+ " --private \tDownload your privately bookmarked works instead.\n" ~
|
|
|
+ " --remove-invalid \tRemove bookmarks for works that are no longer available.\n" ~
|
|
|
+ " --remove-from-file FILE\tRemove bookmarks for works listed in FILE.\n" ~
|
|
|
+ "\nThe --remove-invalid and --remove-from-file options DO NOT remove any\n" ~
|
|
|
+ "files from your system, they only remove the \"bookmarked\" status on\n" ~
|
|
|
+ "pixiv. The FILE for --remove-from-file is expected to be the generated\n" ~
|
|
|
+ "pixiv_down-missing.txt.");
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -49,6 +54,7 @@ public void displayBookmarkedHelp()
|
|
|
*/
|
|
|
public int bookmarkedHandle(string[] args, in Config config)
|
|
|
{
|
|
|
+ import std.file : exists;
|
|
|
import std.getopt : getopt, GetOptException, GetOptOption = config;
|
|
|
|
|
|
Options options;
|
|
@@ -58,7 +64,8 @@ public int bookmarkedHandle(string[] args, in Config config)
|
|
|
"private|p", &options.privateRequested,
|
|
|
"skip|s", &options.offset,
|
|
|
"remove-invalid", &options.removeInvalid,
|
|
|
- "novels", &options.novelsRequested);
|
|
|
+ "novels", &options.novelsRequested,
|
|
|
+ "remove-from-file", &options.removalFilePath);
|
|
|
|
|
|
if (helpInformation.helpWanted) {
|
|
|
displayBookmarkedHelp();
|
|
@@ -70,6 +77,11 @@ public int bookmarkedHandle(string[] args, in Config config)
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
+ if ((options.removalFilePath != string.init) && (exists(options.removalFilePath))) {
|
|
|
+ removeBookmarksFromFile(options, config);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
fetchAndDownloadBookmarks(options, config);
|
|
|
return 0;
|
|
|
}
|
|
@@ -88,14 +100,17 @@ struct Options
|
|
|
bool removeInvalid;
|
|
|
bool novelsRequested;
|
|
|
long offset;
|
|
|
+ string removalFilePath;
|
|
|
}
|
|
|
|
|
|
void fetchAndDownloadBookmarks(in Options options, in Config config)
|
|
|
{
|
|
|
- long processedIDs = options.offset;
|
|
|
long totalIDs;
|
|
|
+ long processedIDs = options.offset;
|
|
|
long numberOfMissingIDs = 0;
|
|
|
|
|
|
+ const csrfToken = fetchCSRFToken(config.sessionid);
|
|
|
+
|
|
|
do {
|
|
|
trace("fetching user bookmarks...");
|
|
|
Bookmarks bookmarks = fetchUserBookmarks(
|
|
@@ -106,12 +121,12 @@ void fetchAndDownloadBookmarks(in Options options, in Config config)
|
|
|
/* Break early incase someone has manually removed bookmarks
|
|
|
* while we've been downloading. */
|
|
|
if (totalIDs <= processedIDs) {
|
|
|
- warningf("totalIDs (%d) has changed to be less than processedIDs (%d)", totalIDs,
|
|
|
+ warningf("totalIDs (≈%d) has changed to be less than processedIDs (%d)", totalIDs,
|
|
|
processedIDs);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- string[] missingIDs = downloadBookmarks(bookmarks, options, config);
|
|
|
+ string[] missingIDs = downloadBookmarks(bookmarks, csrfToken, options, config);
|
|
|
|
|
|
numberOfMissingIDs += missingIDs.length;
|
|
|
if (missingIDs.length > 0) {
|
|
@@ -140,21 +155,24 @@ void fetchAndDownloadBookmarks(in Options options, in Config config)
|
|
|
writeln("Finished downloading bookmarked works.");
|
|
|
}
|
|
|
|
|
|
-/// Returns number of bookmarks processed.
|
|
|
-string[] downloadBookmarks(Bookmarks bookmarks, in Options options, in Config config)
|
|
|
+/// Returns an array containing all Missing IDs.
|
|
|
+string[] downloadBookmarks(Bookmarks bookmarks, in string csrfToken, in Options options, in Config config)
|
|
|
{
|
|
|
+ import std.format : format;
|
|
|
+
|
|
|
string[] missingIDs;
|
|
|
const type = options.novelsRequested ? "novels" : "illusts";
|
|
|
|
|
|
foreach(bookmark; bookmarks.works) {
|
|
|
if (bookmark.isMasked) {
|
|
|
warningf("masked bookmark ID:%s reason:%s", bookmark.id, bookmark.maskReason);
|
|
|
- missingIDs ~= bookmark.id;
|
|
|
+ missingIDs ~= format("%s\t%s\t%s", bookmark.id, bookmark.bookmarkData.id, type);
|
|
|
if (options.removeInvalid) {
|
|
|
- bool success = postBookmarksDelete(bookmark.bookmarkData.id, type, config);
|
|
|
+ const success = postBookmarksDelete(bookmark.bookmarkData.id, csrfToken, type, config);
|
|
|
if (success) {
|
|
|
info("successfully removed masked bookmark");
|
|
|
}
|
|
|
+ sleep(1, 3, false);
|
|
|
}
|
|
|
continue;
|
|
|
}
|
|
@@ -166,7 +184,7 @@ string[] downloadBookmarks(Bookmarks bookmarks, in Options options, in Config co
|
|
|
ArtworkInfo artworkInfo = fetchArtworkInfo(bookmark.id, config);
|
|
|
downloadArtwork(artworkInfo, config);
|
|
|
}
|
|
|
- sleep(2, 7);
|
|
|
+ sleep(4, 9);
|
|
|
Term.goUpAndClearLine(Yes.useStderr);
|
|
|
}
|
|
|
|
|
@@ -201,3 +219,58 @@ void writeMissingIDs(string[] ids, bool removeInvalid)
|
|
|
idFile.writeln(id);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+void removeBookmarksFromFile(in Options options, in Config config)
|
|
|
+{
|
|
|
+ import std.algorithm.searching : any;
|
|
|
+ import std.string : split, stripRight;
|
|
|
+ import pixiv : postBookmarksDelete;
|
|
|
+
|
|
|
+ const csrfToken = fetchCSRFToken(config.sessionid);
|
|
|
+
|
|
|
+ bool encounteredErrors = false;
|
|
|
+ auto missingIDs = File(options.removalFilePath, "r");
|
|
|
+ foreach(string line; missingIDs.byLineCopy(No.keepTerminator)) {
|
|
|
+ // Skip empty lines and comment lines
|
|
|
+ if (line.length == 0 || line[0] == '#') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // <work_id>\t<bookmark_id>\t<work_type>
|
|
|
+ const segments = line.split("\t");
|
|
|
+ if (segments.length != 3) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const workID = segments[0];
|
|
|
+ const bookmarkID = segments[1];
|
|
|
+ const workType = segments[2];
|
|
|
+ if (any!"a < '0' || a > '9'"(workID)) {
|
|
|
+ stderr.writefln("Failed to remove bookmark: Invalid work ID: ", workID);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (any!"a < '0' || a > '9'"(bookmarkID)) {
|
|
|
+ stderr.writefln("Failed to remove bookmark: Invalid bookmark ID: ", bookmarkID);
|
|
|
+ errorf("Invalid bookmark ID: %s (workID = %s, workType = %s)", bookmarkID, workID,
|
|
|
+ workType);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (workType != "novels" && workType != "illusts") {
|
|
|
+ stderr.writefln("Failed to remove bookmark: Invalid type: ", workType);
|
|
|
+ errorf("Invalid bookmark type: %s (workID = %s, bookmarkID = %s)", workType, workID,
|
|
|
+ bookmarkID);
|
|
|
+ }
|
|
|
+
|
|
|
+ const success = postBookmarksDelete(bookmarkID, csrfToken, workType, config);
|
|
|
+ if (!success) {
|
|
|
+ encounteredErrors = true;
|
|
|
+ stderr.writefln("Failed to remove bookmark for ID %s", workID);
|
|
|
+ errorf("Failed to unbookmark bookmarkID %s (workID = %s, workType = %s)", bookmarkID,
|
|
|
+ workID, workType);
|
|
|
+ }
|
|
|
+ sleep(2, 7);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (encounteredErrors) {
|
|
|
+ stderr.writeln("There were some errors removing bookmarks.");
|
|
|
+ }
|
|
|
+}
|