LETUSのページから授業情報ページを作成するbookmarklet
実装
LETUSから該当するCLASSの情報を取得する
CSPがガバなら行けるけど
08:32:37 CORS設定してなかった……
だめだったらLETUS上にあるCLASS情報ページをfetchする
scrapingする必要あり
実装したいこと
ついでにLETUS上のcontentsも取得する
どういう形式で取得すればいい?
正直ここは講義ごとに中身がばらばらだから、あんまり統一的に処理できなそう
しかもちょくちょく更新される
compile buttonで取得したコードtextを、\`javascript:(async()=>{${encodeURIComponent(text)}})()\`で変換する これしないと剰余%をパーセントエンコーディングと誤認識してしまう
code:bookmarklet.ts
import { scrap } from "./script.ts";
await scrap("takker");
code:sh
code:script.ts
import { getSyllabus } from "./syllabus.ts";
import { getCourseCode, getLetusEditon } from "./letus.ts";
import { encodeTitleURI } from "../scrapbox-userscript-std/dom.ts";
import { lightFormat } from "../date-fns/lightFormat.ts";
export const scrap = async (project: string) => {
const pageTitle = window.prompt("page title");
if (!pageTitle) return;
const courseCode = getCourseCode(document);
const year = getLetusEditon(document);
const {
title,
englishTitle,
instructors,
semester,
hours,
credits,
descriptions,
objectives,
outcomes,
prerequisites,
preparationAndReview,
evaluation,
references,
plan,
} = await getSyllabus(courseCode, year);
const letusURL = location.href;
const body = [
"table:basic information",
Course title\t${format(title)},
` Instructor\t${
instructors.map((instructor) => [${instructor.replace(" ", "")}])
}`,
Schedule\t${semester} ${hours.join(" ")},
Course credits\t${credits},
Course code\t${courseCode},
[LETUS ${letusURL}],
[syllabus https://tus-class-api.vercel.app/v1/${year}/${courseCode}],
"",
"Descriptions",
...descriptions.map((text) => ${format(text)}),
"",
"Objectives",
...objectives.map((text) => ${format(text)}),
"",
"Outcomes",
...outcomes.map((text) => ${format(text)}),
"",
"Course notes prerequisites",
...prerequisites.map((text) => ${format(text)}),
"",
"Preparation and review",
...preparationAndReview.map((text) => ${format(text)}),
"",
"Evaluation",
...evaluation.map((text) => ${format(text)}),
"",
"Materials",
...references.map((text) => ${format(text)}),
"",
"Plan",
...plan.map((text) => ${format(text)}),
"",
#${lightFormat(new Date(), "yyyy-MM-dd HH:mm:ss")},
].join("\n");
window.open(
encodeURIComponent(body)
}`,
"_self",
);
};
const format = (text: string): string =>
text
.replace(/\s+$/, "") // 末尾の余計な空白を消す
.replace(/^(\s*)・/, "$1 ") // ・を箇条書きに変える
.replace(
(s) => String.fromCharCode(s.charCodeAt(0) - 0xFEE0),
) // 全角英数を半角に直す
.replace(/\s?\[/g, "[") // リンク記法をescapeする
.replace(/\s?\[/g, "[")
.replace(/(\d+).\s*/g, "$1. ") // 番号付き箇条書きにする
.replaceAll(".", "。") // 句読点を変換する
.replaceAll(",", "、");
LETUSのページから情報を取得する
code:letus.ts
// WIP
/** LETUSの授業ページの各項目を取得する */
//export const getTopics = (doc: Document): HTMLLiElement[] | undefined
/** LETUSの年度を取得する */
export const getLetusEditon = (doc: Document): number => {
const div = document.querySelector("#page-footer div:not(class)");
const year = div?.textContent?.match?.(/\d+-(\d+)/)?.1; // year===0はありえないので無視していい
if (!year) throw SyntaxError("could not detect LETUS edition");
return parseInt(year);
};
/** cource pageから授業コードを取得する */
export const getCourseCode = (doc: Document): number => {
const title = doc.title;
const courseCode = title.trim().match(/\((\d+)\)$/)?.1; if (!courseCode) throw SyntaxError("could not get course Id");
return parseInt(courseCode);
};
code:syllabus.ts
/// <reference no-default-lib="ture" />
/// <reference lib="esnext" />
/// <reference lib="dom" />
/** APIから帰ってくるSyllabus
*
* 使わないpropertiesは省略してある
*/
export interface Syllabus {
title: string;
englishTitle: string;
instructors :string[];
semester: string;
hours: string[];
credits: string;
descriptions: string[];
objectives: string[];
outcomes: string[];
prerequisites: string[];
preparationAndReview: string[];
evaluation: string[];
references: string[];
plan: string[];
}
export const getSyllabus = async (courseCode: number, year: number): Promise<Syllabus> => {
const res = await fetch(
https://tus-class-api.vercel.app/v1/syllabuses/${year}/${courseCode}?format=true,
);
if (!res.ok) throw Error(await res.text());
return await res.json();
};