自動生成したが何も書き込まなかった振り返りページを削除するUserScript
動機
何もせず、ただ空のページが自動生成される状況は気分が悪い
書いていなかった時期のページは、実態に合わせてを消しておきたい
削除判断
created === updatedのページを、何も書き込んでいないページと判断する
14:52:00 間違えてcreated !== updatedのページを消してしまった
復元する
15:06:32 復元完了
これを止めないと、削除しても再生成されてしまう
コードを書き換えて、startを外部から設定できるようにした
実行
deleted 220 pages
code:ts
import { main } from "./main.ts";
await main(
"takker",
);
code:main.ts
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="dom" />
/// <reference lib="dom.iterable" />
import { getCodeBlock } from "jsr:@cosense/std@0.29/rest";
import { useStatusBar } from "jsr:@cosense/std@0.29/browser/dom";
import { scrapbox } from "jsr:@cosense/types@0.10/userscript";
import {
connect,
disconnect,
patch,
ScrapboxSocket,
} from "jsr:@cosense/std@0.29/browser/websocket";
import { addDays } from "npm:date-fns@4/addDays";
import { eachDayOfInterval } from "npm:date-fns@4/eachDayOfInterval";
import { eachWeekOfInterval } from "npm:date-fns@4/eachWeekOfInterval";
import { isSameDay } from "npm:date-fns@4/isSameDay";
import { isErr, unwrapErr, unwrapOk } from "npm:option-t@51/plain_result";
import { delay } from "jsr:@std/async@1/delay";
/**
* Delete review pages.
*/
export const main = async (
project: string,
): Promise<void> => {
if (scrapbox.Project.name !== project) return;
// scrapbox.Project.pagesが生成されるまで待つ
// 生成したpagesはcacheしておく
let pages = scrapbox.Project.pages;
await new Promise<void>((resolve) => {
const timer = setInterval(() => {
if (pages.length === 0) {
pages = scrapbox.Project.pages;
return;
}
clearInterval(timer);
resolve();
}, 2000);
});
const start = new Date(2023, 1, 3);
const now = new Date();
const interval = { start, end: addDays(now, 1) };
const { render, dispose } = useStatusBar();
let socket: ScrapboxSocket | undefined;
try {
// テンプレートを取得
const dailyTemplateText = await fetchTemplate(dailyTemplate);
const weeklyTemplateText = await fetchTemplate(weeklyTemplate);
/* 生成する振り返りページの日付リスト */
const dates = eachDayOfInterval(interval).filter((date) => {
const title = template(date, dailyTemplateText)0; const page = pages.find((page) => page.title === title);
return page?.exists;
});
/* 生成する1週間の振り返りページの日付リスト */
const weeklyDates = eachWeekOfInterval(interval).filter((date) => {
const title = template(date, weeklyTemplateText)0; const page = pages.find((page) => page.title === title);
return page?.exists;
});
if (dates.length === 0 && weeklyDates.length === 0) return;
let total = dates.length + weeklyDates.length;
let counter = 0;
render(
{ type: "spinner" },
{ type: "text", text: delete ${counter}/${total} review pages... },
);
const result = await connect();
if (isErr(result)) throw unwrapErr(result);
socket = unwrapOk(result);
for (
const lines of [
...dates.map((date) => template(date, dailyTemplateText)),
...weeklyDates.map((date) => template(date, weeklyTemplateText)),
]
) {
await patch(project, lines0, (_, { title, created, updated, persistent, lines }) => { if (!persistent
|| created !== updated
|| lines.some((line) => updated !== line.created || updated !== line.updated)
) {
console.info(Skip ${title});
total--;
return;
}
counter++;
console.info(Delete ${title});
return [];
}, { socket });
render(
{ type: "spinner" },
{ type: "text", text: delete ${counter}/${total} review pages... ${lines[0]} },
);
}
render(
{ type: "check-circle" },
{
type: "text",
text: deleted ${counter} review pages.,
},
);
} catch (e: unknown) {
render(
{ type: "exclamation-triangle" },
{
type: "text",
text: e instanceof Error
? ${e.name} ${e.message}
: Unknown error! (see developper console),
},
);
console.error(e);
} finally {
if (socket) await disconnect(socket);
await delay(1000);
dispose();
}
};
const result = await getCodeBlock(path0, path1, path2); if (isErr(result)) {
const error = new Error();
error.name = unwrapErr(result).name;
error.message = `${unwrapErr(result).message} at fetching /${path0}/${ throw error;
}
const template = unwrapOk(result).split("\n");
if (template.length === 0) {
throw new Error(template "/${path[0]}/${path[1]}/${path[2]}" is empty!);
}
return template;
};
一旦削除したのを復活させる
code:restore.ts
import { pooledMap } from "jsr:@std/async@1/pool";
import { importPages } from "jsr:@cosense/std@0.29/rest";
import type { ImportedPage } from "jsr:@cosense/types@0.10/rest";
import { isErr, unwrapErr, unwrapOk } from "npm:option-t@51/plain_result";
const res = await fetch("/api/stream/takker");
const { events } = (await res.json()) as { events: {
pageId: string;
data?: { titleLc?: string; };
}[]; };
const pages: ImportedPage[] = await Array.fromAsync(pooledMap(
3,
events.flatMap(({ data, pageId }) => {
if (!data?.titleLc) return [];
if (!/\d{4}-\d{2}-\d{2}_振り返り/.test(data.titleLc)) return [];
}),
async (pageId) => {
const res = await fetch(/api/deleted-pages/takker/${pageId});
return (await res.json()) as ImportedPage;
},
));
const result = await importPages("takker", { pages }, { csrf: (globalThis as any)._csrf });
if (isErr(result)) {
console.error(unwrapErr(result));
} else {
console.info(unwrapOk(result));
}