Compare commits
4 Commits
032ed74968
...
02e92ae911
Author | SHA1 | Date |
---|---|---|
Valentin Anger | 02e92ae911 | |
Valentin Anger | 22ada6b6ae | |
Valentin Anger | bbd265b7b9 | |
Valentin Anger | 4052dcd524 |
|
@ -0,0 +1,104 @@
|
|||
// Gemini v0.11.0 client
|
||||
|
||||
import {
|
||||
BufReader,
|
||||
readLines,
|
||||
} from "https://deno.land/std@0.74.0/io/bufio.ts";
|
||||
|
||||
const content_buffer_size = 512;
|
||||
const gemini_port = "1965";
|
||||
|
||||
interface Response {
|
||||
status: string;
|
||||
meta: string;
|
||||
content?: Uint8Array;
|
||||
}
|
||||
|
||||
async function fetchGemini(
|
||||
url: URL,
|
||||
redirect_count: number = 5,
|
||||
): Promise<Response> {
|
||||
if (url.protocol != "gemini:") {
|
||||
throw new Error("Unsupported protocol");
|
||||
}
|
||||
|
||||
const conn = await Deno.connectTls({
|
||||
hostname: url.hostname,
|
||||
port: parseInt(url.port != "" ? url.port : gemini_port),
|
||||
});
|
||||
|
||||
try {
|
||||
const request = new TextEncoder().encode(url.href + "\r\n");
|
||||
if ((await conn.write(request)) != request.length) {
|
||||
throw new Error("Write failure");
|
||||
}
|
||||
|
||||
const reader = new BufReader(conn);
|
||||
const header = await reader.readString("\n");
|
||||
|
||||
if (header == null) {
|
||||
throw new Error("Unexpected end of stream");
|
||||
}
|
||||
|
||||
const [_, status, meta] = header.match(/(\d+)\s+(.*)/) ?? ["", "", ""];
|
||||
|
||||
if (
|
||||
status == undefined ||
|
||||
status.length != 2 ||
|
||||
(meta != undefined && meta.length > 1024)
|
||||
) {
|
||||
throw new Error("Received malformed response");
|
||||
}
|
||||
|
||||
switch (status[0]) {
|
||||
case "2":
|
||||
break;
|
||||
case "3":
|
||||
if (redirect_count == 0) {
|
||||
throw new Error("Too many redirects");
|
||||
}
|
||||
|
||||
return fetchGemini(new URL(meta, url), redirect_count - 1);
|
||||
default:
|
||||
return {
|
||||
status,
|
||||
meta,
|
||||
};
|
||||
}
|
||||
|
||||
// Read remaining stream
|
||||
|
||||
let total = new Uint8Array(0);
|
||||
let current = new Uint8Array(content_buffer_size);
|
||||
for (
|
||||
let count = await reader.read(current);
|
||||
count != null;
|
||||
count = await reader.read(current)
|
||||
) {
|
||||
let new_total = new Uint8Array(total.length + count);
|
||||
new_total.set(total, 0);
|
||||
new_total.set(current.slice(0, count), total.length);
|
||||
total = new_total;
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
meta,
|
||||
content: total,
|
||||
};
|
||||
} finally {
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (Deno.args.length != 1) {
|
||||
console.error("Invalid number of arguments");
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
const res = await fetchGemini(new URL(Deno.args[0]));
|
||||
|
||||
console.log("Status " + res.status + " " + res.meta);
|
||||
if (res.content != undefined) {
|
||||
console.log(new TextDecoder().decode(res.content));
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
function traverse(obj: unknown, path: string = "") {
|
||||
switch (typeof obj) {
|
||||
case "object":
|
||||
if (obj !== null) {
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
traverse(v, Array.isArray(obj) ? `${path}[${k}]` : `${path}."${k}"`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.log(`${path}: ${obj}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
if (Deno.args.length !== 0) {
|
||||
console.error("Usage: jpath");
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
const filename = Deno.args[0];
|
||||
|
||||
const json_text = new TextDecoder().decode(Deno.readAllSync(Deno.stdin));
|
||||
const json = JSON.parse(json_text);
|
||||
|
||||
traverse(json);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
function sortJSON<T>(obj: T): T {
|
||||
if (typeof obj !== "object" || Array.isArray(obj) || obj === null) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj as Record<string, unknown>).sort((a, b) =>
|
||||
a[0].localeCompare(b[0])
|
||||
).map(([k, v]) => [k, sortJSON(v)]),
|
||||
) as unknown as T;
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
if (Deno.args.length !== 1) {
|
||||
console.error("Usage: jsort <file>");
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
const filename = Deno.args[0];
|
||||
|
||||
const json_text = Deno.readTextFileSync(filename);
|
||||
const json = JSON.parse(json_text);
|
||||
const sorted = sortJSON(json);
|
||||
const sorted_json_text = JSON.stringify(sorted);
|
||||
|
||||
Deno.writeTextFileSync(filename, sorted_json_text);
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
async function* cleanWatch(dir: string) {
|
||||
let lastPath = null;
|
||||
let lastKind = null;
|
||||
for await (
|
||||
const { kind, paths } of Deno.watchFs(dir, {
|
||||
recursive: true,
|
||||
})
|
||||
) {
|
||||
for (const path of paths) {
|
||||
if (lastPath != path || lastKind != kind) {
|
||||
lastPath = path;
|
||||
lastKind = kind;
|
||||
yield { kind, path };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const color: Record<string, string> = {
|
||||
"any": "color: grey",
|
||||
"create": "color: lightgreen",
|
||||
"modify": "color: yellow",
|
||||
"remove": "color: red",
|
||||
"access": "color: cyan",
|
||||
};
|
||||
|
||||
const prefix: Record<string, string> = {
|
||||
"any": "?",
|
||||
"create": "+",
|
||||
"modify": "~",
|
||||
"remove": "-",
|
||||
"access": ".",
|
||||
};
|
||||
|
||||
let lastTime = new Date(0);
|
||||
for await (
|
||||
const event of cleanWatch(Deno.args[0])
|
||||
) {
|
||||
let time = new Date();
|
||||
if (time.valueOf() - lastTime.valueOf() > 5000) console.log(time);
|
||||
lastTime = time;
|
||||
|
||||
console.log(`%c${prefix[event.kind]} ${event.path}`, color[event.kind]);
|
||||
}
|
||||
|
||||
export {};
|
|
@ -0,0 +1,60 @@
|
|||
const search_url = "https://www.youtube.com/results?pbj=1&search_query=";
|
||||
|
||||
const headers = {
|
||||
"X-YouTube-Client-Name": "1",
|
||||
"X-YouTube-Client-Version": "2.20200319.09.00",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
};
|
||||
|
||||
async function* mediaIterator(search) {
|
||||
const result = await fetch(search_url + search, {
|
||||
headers,
|
||||
}).then((c) => c.json());
|
||||
|
||||
const videos = result?.[1]?.response?.contents?.twoColumnSearchResultsRenderer
|
||||
?.primaryContents?.sectionListRenderer?.contents?.[0]?.itemSectionRenderer
|
||||
?.contents;
|
||||
|
||||
if (videos == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const content of videos) {
|
||||
if (content?.videoRenderer != undefined) {
|
||||
const video = content.videoRenderer;
|
||||
if (video.videoId == undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield {
|
||||
title: video.title?.accessibility?.accessibilityData?.label,
|
||||
id: video.videoId,
|
||||
};
|
||||
} else if (content?.playlistRenderer != undefined) {
|
||||
const playl = content.playlistRenderer;
|
||||
if (playl.playlistId == undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield {
|
||||
title: playl.title?.simpleText,
|
||||
id: playl.playlistId,
|
||||
count: playl.videoCount,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Deno.args.length == 0) {
|
||||
console.error("Expected search keywords");
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
for await (const media of mediaIterator(Deno.args.join("+"))) {
|
||||
console.log(
|
||||
media.title +
|
||||
(media.count != undefined ? " <" + media.count + " Videos>" : "") +
|
||||
":",
|
||||
);
|
||||
console.log("ytdl://" + media.id);
|
||||
}
|
|
@ -56,10 +56,13 @@ interface Card {
|
|||
|
||||
type Hand = Card[];
|
||||
|
||||
function cardString(card: Card, colored_wild = false): string {
|
||||
function cardString(
|
||||
card: Card,
|
||||
opts: { colored_wild?: boolean; no_background?: boolean } = {},
|
||||
): string {
|
||||
return `${irc.bold()}${
|
||||
irc.color(
|
||||
!colored_wild && wild_cards.includes(card.value)
|
||||
!opts.colored_wild && wild_cards.includes(card.value)
|
||||
? irc.Color.Default
|
||||
: card.color === "Blue"
|
||||
? irc.Color.Blue
|
||||
|
@ -68,7 +71,7 @@ function cardString(card: Card, colored_wild = false): string {
|
|||
: card.color === "Red"
|
||||
? irc.Color.Red
|
||||
: irc.Color.Yellow,
|
||||
irc.Color.Black,
|
||||
opts.no_background ? undefined : irc.Color.Black,
|
||||
)
|
||||
}${card.value}${irc.reset()}`;
|
||||
}
|
||||
|
@ -167,7 +170,9 @@ class Game {
|
|||
await msg.replyNotice(
|
||||
`It's ${player}'s (${
|
||||
this.#hands.get(player)!.length
|
||||
}) turn. Current card: ${cardString(this.#current_card, true)}` +
|
||||
}) turn. Current card: ${
|
||||
cardString(this.#current_card, { colored_wild: true })
|
||||
}` +
|
||||
(this.#draw_count > 0 ? ` Draw count: ${this.#draw_count}` : ""),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue