mirror of
https://github.com/ChronosX88/wind-vue.git
synced 2024-12-04 16:22:23 +00:00
Start implementation of NNTP client
This commit is contained in:
parent
ea3defeeb0
commit
bad5f439b6
13
package-lock.json
generated
13
package-lock.json
generated
@ -9,7 +9,8 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"pinia": "^2.0.13",
|
||||
"vue": "^3.2.33"
|
||||
"vue": "^3.2.33",
|
||||
"websocket-ts": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.1.0",
|
||||
@ -2821,6 +2822,11 @@
|
||||
"typescript": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/websocket-ts": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/websocket-ts/-/websocket-ts-1.1.1.tgz",
|
||||
"integrity": "sha512-rm+S60J74Ckw5iizzgID12ju+OfaHAa6dhXhULIOrXkl0e05RzxfY42/vMStpz5jWL3iz9mkyjPcFUY1IgI0fw=="
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@ -4765,6 +4771,11 @@
|
||||
"@volar/vue-typescript": "0.34.12"
|
||||
}
|
||||
},
|
||||
"websocket-ts": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/websocket-ts/-/websocket-ts-1.1.1.tgz",
|
||||
"integrity": "sha512-rm+S60J74Ckw5iizzgID12ju+OfaHAa6dhXhULIOrXkl0e05RzxfY42/vMStpz5jWL3iz9mkyjPcFUY1IgI0fw=="
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
@ -10,7 +10,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"pinia": "^2.0.13",
|
||||
"vue": "^3.2.33"
|
||||
"vue": "^3.2.33",
|
||||
"websocket-ts": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.1.0",
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import App from "./App.vue";
|
||||
import { NNTPClient } from "./nntp";
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(createPinia());
|
||||
|
||||
app.mount("#app");
|
||||
|
||||
const nntp = new NNTPClient("wss://nntp.antiope.link");
|
||||
|
109
src/nntp.ts
Normal file
109
src/nntp.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { Websocket, WebsocketBuilder } from "websocket-ts";
|
||||
import type { GroupInfo } from "./nntp/group_info";
|
||||
import type {
|
||||
CommandRequest,
|
||||
CommandResponse,
|
||||
NNTPCommand,
|
||||
} from "./nntp/nntp_command";
|
||||
import { Completer } from "./utils/completer";
|
||||
import { Queue } from "./utils/queue";
|
||||
|
||||
export class NNTPClient {
|
||||
private commandQueue = new Queue<NNTPCommand>();
|
||||
private tempBuffer: Array<string> = [];
|
||||
private ws: Websocket;
|
||||
|
||||
constructor(url: string) {
|
||||
this.ws = new WebsocketBuilder(url)
|
||||
.onMessage((ins, evt) => {
|
||||
if ((evt.data as string).startsWith("201")) {
|
||||
console.debug("skipping welcome message");
|
||||
}
|
||||
let data = evt.data as string;
|
||||
|
||||
let responseLines = data.split("\r\n");
|
||||
|
||||
if (
|
||||
(responseLines.length > 1 || this.tempBuffer.length != 0) &&
|
||||
responseLines[responseLines.length - 1] != "."
|
||||
) {
|
||||
// if it's multiline response and it doesn't contain dot in the end
|
||||
// then looks like we need to wait for next message to concatenate with current msg
|
||||
this.tempBuffer.push(data);
|
||||
}
|
||||
|
||||
if (this.tempBuffer.length != 0) {
|
||||
this.tempBuffer.push(data);
|
||||
data = this.tempBuffer.join();
|
||||
responseLines = data.split("\r\n");
|
||||
responseLines.pop();
|
||||
this.tempBuffer = [];
|
||||
}
|
||||
|
||||
const command = this.commandQueue.dequeue();
|
||||
const respCode = parseInt(responseLines[0].split(" ")[0]);
|
||||
command?.response.complete({
|
||||
responseCode: respCode,
|
||||
lines: responseLines,
|
||||
} as CommandResponse);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private async sendCommand(
|
||||
command: string,
|
||||
args: string[]
|
||||
): Promise<CommandResponse> {
|
||||
const cmd = {
|
||||
request: { command: command, args: args } as CommandRequest,
|
||||
response: new Completer<CommandResponse>(),
|
||||
} as NNTPCommand;
|
||||
|
||||
this.commandQueue.enqueue(cmd);
|
||||
if (args.length > 0) {
|
||||
this.ws.send(`${command} ${args.join(" ")}\r\n`);
|
||||
} else {
|
||||
this.ws.send(`${command}`);
|
||||
}
|
||||
|
||||
const result = await cmd.response.promise;
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getNewsGroupList(): Promise<GroupInfo[]> {
|
||||
const l: GroupInfo[] = [];
|
||||
const groupMap: Record<string, Partial<GroupInfo>> = {};
|
||||
|
||||
await this.sendCommand("LIST", ["NEWSGROUPS"]).then((value) => {
|
||||
value.lines.shift();
|
||||
value.lines.pop();
|
||||
value.lines.forEach((elem) => {
|
||||
const firstSpace = elem.indexOf(" ");
|
||||
const name = elem.substring(0, firstSpace);
|
||||
groupMap[name] = { description: elem.substring(firstSpace + 1) };
|
||||
});
|
||||
});
|
||||
|
||||
await this.sendCommand("LIST", ["ACTIVE"]).then((value) => {
|
||||
value.lines.shift();
|
||||
value.lines.pop();
|
||||
value.lines.forEach((elem) => {
|
||||
const splitted = elem.split(" ");
|
||||
const [name, high, low] = splitted;
|
||||
groupMap[name].highWater = Number(high);
|
||||
groupMap[name].lowWater = Number(low);
|
||||
});
|
||||
});
|
||||
|
||||
Object.keys(groupMap).forEach((key) => {
|
||||
l.push({
|
||||
name: groupMap[key].name!,
|
||||
description: groupMap[key].description!,
|
||||
lowWater: groupMap[key].lowWater!,
|
||||
highWater: groupMap[key].highWater!,
|
||||
});
|
||||
});
|
||||
|
||||
return l;
|
||||
}
|
||||
}
|
6
src/nntp/group_info.ts
Normal file
6
src/nntp/group_info.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface GroupInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
lowWater: number;
|
||||
highWater: number;
|
||||
}
|
16
src/nntp/nntp_command.ts
Normal file
16
src/nntp/nntp_command.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { Completer } from "@/utils/completer";
|
||||
|
||||
export interface NNTPCommand {
|
||||
request: CommandRequest;
|
||||
response: Completer<CommandResponse>;
|
||||
}
|
||||
|
||||
export interface CommandRequest {
|
||||
command: string;
|
||||
args: Array<string>;
|
||||
}
|
||||
|
||||
export interface CommandResponse {
|
||||
responseCode: number;
|
||||
lines: Array<string>;
|
||||
}
|
12
src/utils/completer.ts
Normal file
12
src/utils/completer.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export class Completer<T> {
|
||||
public readonly promise: Promise<T>;
|
||||
public complete!: (value: (PromiseLike<T> | T)) => void;
|
||||
private reject!: (reason?: any) => void;
|
||||
|
||||
public constructor() {
|
||||
this.promise = new Promise<T>((resolve, reject) => {
|
||||
this.complete = resolve;
|
||||
this.reject = reject;
|
||||
})
|
||||
}
|
||||
}
|
24
src/utils/queue.ts
Normal file
24
src/utils/queue.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export interface IQueue<T> {
|
||||
enqueue(item: T): void;
|
||||
dequeue(): T | undefined;
|
||||
size(): number;
|
||||
}
|
||||
|
||||
export class Queue<T> implements IQueue<T> {
|
||||
private storage: T[] = [];
|
||||
|
||||
constructor(private capacity: number = Infinity) {}
|
||||
|
||||
enqueue(item: T): void {
|
||||
if (this.size() === this.capacity) {
|
||||
throw Error("Queue has reached max capacity, you cannot add more items");
|
||||
}
|
||||
this.storage.push(item);
|
||||
}
|
||||
dequeue(): T | undefined {
|
||||
return this.storage.shift();
|
||||
}
|
||||
size(): number {
|
||||
return this.storage.length;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user