207 lines
6.8 KiB
JavaScript
207 lines
6.8 KiB
JavaScript
import { JSZip } from "https://deno.land/x/jszip@0.11.0/mod.ts";
|
|
|
|
export async function fetch(url, options = {}, nothrow) {
|
|
console.log("fetch", url);
|
|
options.headers ||= {};
|
|
options.headers["User-Agent"] ||= "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36";
|
|
for (var i = 3; i; i--) {
|
|
try {
|
|
var res = await globalThis.fetch(url, options);
|
|
break;
|
|
} catch (error) {
|
|
console.error(error.stack);
|
|
if (i <= 1) throw error;
|
|
}
|
|
console.log("retry");
|
|
}
|
|
if (!nothrow && !res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
return res;
|
|
}
|
|
|
|
export var credentials = JSON.parse(Deno.readTextFileSync("credentials.json"));
|
|
var {client_id, client_secret, username, password, pixiv_cookie, access_token, pleroma_user_id} = credentials;
|
|
|
|
export var known_ids = {};
|
|
try {
|
|
known_ids = Object.fromEntries(Deno.readTextFileSync("known_ids.csv").trim().split("\n").map(line => line.split(",")));
|
|
} catch (e) {}
|
|
|
|
|
|
if (!access_token) {
|
|
let form = new FormData();
|
|
form.append("client_id", client_id);
|
|
form.append("client_secret", client_secret);
|
|
form.append("grant_type", "password");
|
|
form.append("username", username);
|
|
form.append("password", password);
|
|
let data = await fetch("https://pleroma.lamp.wtf/oauth/token", {
|
|
method: "POST",
|
|
body: form
|
|
}).then(res => res.json());
|
|
console.log ("logged in", data);
|
|
credentials.access_token = access_token = data.access_token;
|
|
Deno.writeTextFileSync("credentials.json", JSON.stringify(credentials, null, '\t'));
|
|
}
|
|
|
|
export async function getAllStatuses() {
|
|
var allStatuses = [];
|
|
await (async function getPage(max_id) {
|
|
var url = `https://pleroma.lamp.wtf/api/v1/accounts/${pleroma_user_id}/statuses?limit=40`;
|
|
if (max_id) url += `&max_id=${max_id}`;
|
|
var statuses = await fetch(url, {headers: {"Authorization": `Bearer ${access_token}`}}).then(res => res.json());
|
|
if (!statuses.length) return;
|
|
allStatuses.push(...statuses);
|
|
var last_id = statuses.at(-1).id;
|
|
await getPage(last_id);
|
|
})();
|
|
return allStatuses;
|
|
}
|
|
|
|
async function uploadFile({data, name}) {
|
|
var form = new FormData();
|
|
form.append("file", data, name);
|
|
var res = await fetch("https://pleroma.lamp.wtf/api/v1/media", {
|
|
method: "POST",
|
|
body: form,
|
|
headers: {"Authorization": `Bearer ${access_token}`}
|
|
});
|
|
var json = await res.json();
|
|
console.log("uploaded file", res.status, json.url);
|
|
return json;
|
|
}
|
|
|
|
async function postStatus({status, visibility = "unlisted", content_type = "text/plain", media_ids = [], sensitive, files, edit}) {
|
|
if (files) {
|
|
media_ids = (await Promise.all(files.map(file => uploadFile(file)))).map(d => d.id);
|
|
}
|
|
var form = new FormData();
|
|
form.append("status", status);
|
|
form.append("visibility", visibility);
|
|
form.append("source", "bot");
|
|
form.append("content_type", content_type);
|
|
for (let media_id of media_ids) {
|
|
form.append("media_ids[]", media_id);
|
|
}
|
|
if (sensitive) form.append("sensitive", "true");
|
|
var res = await fetch("https://pleroma.lamp.wtf/api/v1/statuses" + (edit ? `/${edit}` : ''), {
|
|
method: edit ? "PUT" : "POST",
|
|
body: form,
|
|
headers: {"Authorization": `Bearer ${access_token}`}
|
|
});
|
|
var json = await res.json();
|
|
console.log(edit ? "edited" : "posted", res.status, json.uri || json);
|
|
return json;
|
|
}
|
|
|
|
async function downloadPixivIllust(illust_id) {
|
|
var url = `https://www.pixiv.net/ajax/illust/${illust_id}?lang=en`;
|
|
var res = await fetch(url, {headers: {"Cookie": pixiv_cookie}}, true);
|
|
if (!res.ok) {
|
|
console.error(res.status);
|
|
var res = await fetch(url);
|
|
}
|
|
var json = await res.json();
|
|
if (json.error) throw new Error(json.message);
|
|
var illust = json.body;
|
|
if (illust.illustType == 2) {
|
|
//ugoira
|
|
let res = await fetch(`https://www.pixiv.net/ajax/illust/${illust_id}/ugoira_meta`, {headers: {"Cookie": pixiv_cookie}});
|
|
let json = await res.json();
|
|
if (json.error) throw new Error(json.message);
|
|
let zip = await fetch(json.body.originalSrc, {headers: {"Referer": "https://www.pixiv.net/"}}).then(res => res.arrayBuffer());
|
|
let {data, width, height} = await ugoira2webp(zip, json.body.frames);
|
|
return {illust, images: [{
|
|
name: json.body.originalSrc.split('/').pop() + '.webp',
|
|
data: new Blob([data], {type: "image/webp"}),
|
|
type: "image/webp",
|
|
width, height
|
|
}]};
|
|
}
|
|
try {
|
|
let res = await fetch(`https://www.pixiv.net/ajax/illust/${illust_id}/pages`, {headers: {"Cookie": pixiv_cookie}});
|
|
let json = await res.json();
|
|
var images = json.body.map(x => ({
|
|
url: x.urls.original,
|
|
width: x.width,
|
|
height: x.height
|
|
}));
|
|
} catch (error) {
|
|
console.error(error.stack);
|
|
if (!illust.urls.original) {
|
|
console.error("missing original urls", illust.urls);
|
|
throw error;
|
|
}
|
|
var images = [];
|
|
for (let i = 0; i < illust.pageCount; i++) {
|
|
images.push({
|
|
url: illust.urls.original.replace('p0', 'p'+i)
|
|
});
|
|
}
|
|
}
|
|
for (let image of images) {
|
|
image.name = image.url.split('/').pop();
|
|
image.data = await fetch(image.url, {headers: {"Referer": "https://www.pixiv.net/"}}).then(res => res.blob());
|
|
image.type = "image/" + image.url.split('.').pop();
|
|
}
|
|
return {illust, images};
|
|
}
|
|
|
|
var decoder = new TextDecoder();
|
|
async function ugoira2webp(zip, frames) {
|
|
console.log("convert ugoira");
|
|
var tmpdir = Deno.makeTempDirSync();
|
|
var z = new JSZip();
|
|
await z.loadAsync(zip);
|
|
var webpmux_args = [];
|
|
var width, height;
|
|
for (let {file, delay} of frames) {
|
|
let file_data = await z.file(file).async("uint8array");
|
|
Deno.writeFileSync(tmpdir + "/" + file, file_data);
|
|
let output = new Deno.Command("cwebp", {
|
|
args: [file, "-o", `${file}.webp`],
|
|
cwd: tmpdir
|
|
}).outputSync();
|
|
let text = decoder.decode(output.stderr);
|
|
console.debug(text);
|
|
let match = text.match(/Dimension: (\d+) x (\d+)/);
|
|
if (match) {
|
|
width ||= Number(match[1]);
|
|
height ||= Number(match[2]);
|
|
} else console.warn("missing dimension match");
|
|
webpmux_args.push("-frame", `${file}.webp`, `+${delay}`);
|
|
}
|
|
new Deno.Command("webpmux", {
|
|
args: [...webpmux_args, "-o", "output.webp"],
|
|
cwd: tmpdir
|
|
}).outputSync();
|
|
return {
|
|
data: Deno.readFileSync(tmpdir + "/output.webp"),
|
|
width, height
|
|
}
|
|
}
|
|
|
|
|
|
export async function pixivToPleroma(illust_id, status_id) {
|
|
try {
|
|
var {illust, images} = await downloadPixivIllust(illust_id);
|
|
var {url} = await postStatus({
|
|
status: `https://www.pixiv.net/en/artworks/${illust_id}`
|
|
+ ((images.length > 4) ? `\n⚠ There are ${images.length} attachments` : ''),
|
|
files: images,
|
|
visibility: "unlisted",
|
|
sensitive: Boolean(illust.xRestrict),
|
|
edit: status_id
|
|
});
|
|
Deno.writeTextFileSync("known_ids.csv", `${illust_id},${url}\n`, {append: true});
|
|
|
|
var {postPixivIllustToMatrix} = await import("./matrix.js"); //circular dependency -.-
|
|
await postPixivIllustToMatrix(illust, images, url);
|
|
|
|
} catch (error) {
|
|
console.error(error.stack);
|
|
postStatus({
|
|
status: `https://www.pixiv.net/en/artworks/${illust_id}\n#error:\n${error.stack}`,
|
|
edit: status_id
|
|
});
|
|
}
|
|
} |