Skip to content

Instantly share code, notes, and snippets.

@eai04191
Last active July 18, 2023 19:54
Show Gist options
  • Save eai04191/454f1b78b1346df16b4b4e21a93b3fa2 to your computer and use it in GitHub Desktop.
Save eai04191/454f1b78b1346df16b4b4e21a93b3fa2 to your computer and use it in GitHub Desktop.
/**
* pixivのブックマークにタグつけるやつ
*
* 全てのブックマークを取得して、ブックマークタグがない作品に、現状の作品のタグを付けれるだけ付けます
* ブックマークページのコンソールに貼り付けて動かします
*
* このスクリプトはpixivのAPIを使用しています。
* このスクリプトを使用したことにより使用者になんらかの不都合が発生しても、開発者は一切の責任を負いません。
* 自己責任で使用してください。
*
* また、悪意のあるスクリプトの実行は重篤なセキュリティリスクを招きます。
* 悪意のあるスクリプトの実行を防ぐため、このスクリプトが何をしているのかわからない場合は絶対に使用しないでください。
* 念の為そのままでは動かせないように細工がしてあります。使用する場合は編集してください。
*
* Copyright (c) 2022 Eai
* Released under the MIT license
* https://opensource.org/licenses/mit-license.php
*/
"use strict";
/**
* ブックマーク取得時、1回のリクエストで指定できるlimit値の最大
*/
const LIMIT_MAX = 100;
/**
* ブックマークに指定できるタグの最大数
*/
const BOOKMARK_TAG_MAX = 10;
/**
* リクエストのインターバル (ms)
*/
const REQUEST_INTERVAL = 500;
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function splitByNumber(array, number) {
const result = [];
while (array.length > 0) {
result.push(array.splice(0, number));
}
return result;
}
/**
* スクリプトを実行できるページにいるか確認する
* @returns {boolean} 実行できるページならtrue
*/
function openedSupportURL() {
const URL = document.URL;
if (!URL.startsWith("https://www.pixiv.net/users/")) {
return false;
}
if (!URL.includes("/bookmarks/artworks")) {
return false;
}
return true;
}
/**
* csrfトークンを取得する
*/
async function fetchCsrfToken() {
return (
await (
await fetch(
"https://www.pixiv.net/bookmark_add.php?type=illust&illust_id=95087894"
)
).text()
).match(/token = "([a-zA-Z0-9]{32})"/)[1];
}
/**
* すべてのブックマークを取得する
*/
async function fetchAllBookmarks() {
/**
* @typedef {Object} BookmarkData
* @property {number} id
* @property {boolean} private
*/
/**
* @typedef {Object} Bookmark
* @property {number} id
* @property {string[]} tags
* @property {BookmarkData} bookmarkData
* @property {string[]} bookmarkTags
*/
/**
* @type {Set<Bookmark>}
*/
const bookmarks = new Set();
for (const rest of ["show", "hide"]) {
let index = 0;
while (true) {
const offset = index * LIMIT_MAX;
const limit = LIMIT_MAX;
console.log(
`${
rest === "show" ? "公開" : "非公開"
}ブックマークを取得中... offset: ${offset}`
);
const response = await fetch(
`https://www.pixiv.net/ajax/user/${userID}/illusts/bookmarks?tag=&offset=${offset}&limit=${limit}&rest=${rest}&lang=ja`
);
const json = await response.json();
json.body.works.forEach((work) => {
// json.bookmarkTags: keyがbookmarkIdでvalueがstring[]のobject
const tags =
json.body.bookmarkTags?.[work.bookmarkData.id] ?? [];
bookmarks.add({
...work,
bookmarkTags: tags,
});
});
if (json.body.works.length !== limit) break;
index++;
await sleep(REQUEST_INTERVAL);
}
console.log(
`${rest === "show" ? "公開" : "非公開"}ブックマーク取得完了`
);
await sleep(REQUEST_INTERVAL);
}
return bookmarks;
}
/**
* ブックマークのタグを変更する
* @param {string} bookmarkIds 変更するbookmarkId 名前に反してidは1つしか指定できない
* @param {string[]} newTags 新しいタグの配列
* @param {string[]} oldTags 古いタグの配列
*/
async function changeTags(bookmarkIds, newTags, oldTags) {
// oldTagsとnewTagsを比較してaddTagsとremoveTagsを作る
const addTags = newTags.filter((tag) => !oldTags.includes(tag));
const removeTags = oldTags.filter((tag) => !newTags.includes(tag));
if (addTags.length === 0 && removeTags.length === 0) {
console.log(`タグは変更されませんでした`);
return;
}
const fetchParam = {
method: "POST",
headers: {
accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
"x-csrf-token": csrfToken,
},
};
if (addTags.length > 0) {
const response = await fetch(
`https://www.pixiv.net/ajax/illusts/bookmarks/add_tags`,
{
...fetchParam,
body: JSON.stringify({
bookmarkIds: [bookmarkIds],
tags: addTags,
}),
}
);
const json = await response.json();
if (json.error === false) {
console.log(
`${bookmarkIds}にタグを追加しました: ${addTags.join(", ")}`
);
}
}
if (removeTags.length > 0) {
const response = await fetch(
`https://www.pixiv.net/ajax/illusts/bookmarks/remove_tags`,
{
...fetchParam,
body: JSON.stringify({
bookmarkIds: [bookmarkIds],
tags: removeTags,
}),
}
);
const json = await response.json();
if (json.error === false) {
console.log(
`${bookmarkIds}からタグを削除しました: ${removeTags.join(", ")}`
);
}
}
}
throw new Error("使用する前に説明を読んでください。");
if (!openedSupportURL()) {
throw new Error("このスクリプトはブックマークページで実行してください");
}
const csrfToken = await fetchCsrfToken();
// console.log(`csrfToken:`, csrfToken);
const userID = dataLayer[0].user_id;
// console.log(`userID:`, userID);
console.log(`すべてのブックマークを取得します`);
const bookmarks = await fetchAllBookmarks();
console.log("すべてのブックマークを取得しました", bookmarks);
const noTagWorks = [...bookmarks]
.filter((work) => work.bookmarkTags.length === 0)
// 非公開作品は除外
.filter((work) => work.isMasked === false);
console.log(`うちタグがない作品`, noTagWorks);
console.log(`タグがない作品に、現状の作品のタグを付けます`);
if (noTagWorks.length === 0) {
console.log(`タグがない作品はありませんでした`);
} else {
for (const work of noTagWorks) {
// おそらく常に空配列
const oldTags = work.bookmarkTags;
const newTags = [...oldTags, ...work.tags].slice(0, BOOKMARK_TAG_MAX);
await changeTags(work.bookmarkData.id, newTags, oldTags);
await sleep(REQUEST_INTERVAL);
}
}
console.log(`----`);
console.log(`終了`);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment