Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[改善] 入力のパース処理を改善 #85

Merged
merged 1 commit into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/@types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from "./format.legacy";
export * from "./format.owner";
export * from "./format.v1";
export * from "./IComment";
export * from "./input-parser";
export * from "./IPlugins";
export * from "./options";
export * from "./renderer";
Expand Down
6 changes: 6 additions & 0 deletions src/@types/input-parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { FormattedComment } from "@/@types/format.formatted";

export interface InputParser {
key: string[];
parse: (input: unknown) => FormattedComment[];
}
8 changes: 8 additions & 0 deletions src/input/empty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { FormattedComment, InputParser } from "@/@types";

export const EmptyParser: InputParser = {
key: ["empty"],
parse: (): FormattedComment[] => {
return [];
},
};
11 changes: 11 additions & 0 deletions src/input/formatted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { array, parse } from "valibot";

import type { FormattedComment, InputParser } from "@/@types";
import { ZFormattedComment } from "@/@types";

export const FormattedParser: InputParser = {
key: ["formatted", "niconicome"],
parse: (input: unknown): FormattedComment[] => {
return parse(array(ZFormattedComment), input);
},
};
19 changes: 19 additions & 0 deletions src/input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { InputParser } from "@/@types";

import { EmptyParser } from "./empty";
import { FormattedParser } from "./formatted";
import { LegacyParser } from "./legacy";
import { LegacyOwnerParser } from "./legacyOwner";
import { OwnerParser } from "./owner";
import { V1Parser } from "./v1";
import { XmlDocumentParser } from "./xmlDocument";

export const parsers: InputParser[] = [
EmptyParser,
FormattedParser,
LegacyParser,
LegacyOwnerParser,
OwnerParser,
V1Parser,
XmlDocumentParser,
];
61 changes: 61 additions & 0 deletions src/input/legacy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { array, parse, safeParse } from "valibot";

import type { InputParser } from "@/@types";
import {
type FormattedComment,
type RawApiResponse,
ZApiChat,
ZRawApiResponse,
} from "@/@types";

export const LegacyParser: InputParser = {
key: ["legacy"],
parse: (input) => {
return fromLegacy(parse(array(ZRawApiResponse), input));
},
};

/**
* ニコニコ公式のlegacy apiから帰ってきたデータ処理する
* @param data legacy apiから帰ってきたデータ
* @returns 変換後のデータ
*/
const fromLegacy = (data: RawApiResponse[]): FormattedComment[] => {
const data_: FormattedComment[] = [],
userList: string[] = [];
for (const _val of data) {
const val = safeParse(ZApiChat, _val.chat);
if (!val.success) continue;
const value = val.output;
if (value.deleted !== 1) {
const tmpParam: FormattedComment = {
id: value.no,
vpos: value.vpos,
content: value.content || "",
date: value.date,
date_usec: value.date_usec || 0,
owner: !value.user_id,
premium: value.premium === 1,
mail: [],
user_id: -1,
layer: -1,
is_my_post: false,
};
if (value.mail) {
tmpParam.mail = value.mail.split(/\s+/g);
}
if (value.content.startsWith("/") && !value.user_id) {
tmpParam.mail.push("invisible");
}
const isUserExist = userList.indexOf(value.user_id);
if (isUserExist === -1) {
tmpParam.user_id = userList.length;
userList.push(value.user_id);
} else {
tmpParam.user_id = isUserExist;
}
data_.push(tmpParam);
}
}
return data_;
};
55 changes: 55 additions & 0 deletions src/input/legacyOwner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { InputParser } from "@/@types";
import { type FormattedComment } from "@/@types";
import { InvalidFormatError } from "@/errors";
import typeGuard from "@/typeGuard";

export const LegacyOwnerParser: InputParser = {
key: ["legacyOwner"],
parse: (input) => {
if (!typeGuard.legacyOwner.comments(input)) throw new InvalidFormatError();
return fromLegacyOwner(input);
},
};

/**
* 旧プレイヤーの投稿者コメントのエディターのデータを処理する
* @param data 旧投米のテキストデータ
* @returns 変換後のデータ
*/
const fromLegacyOwner = (data: string): FormattedComment[] => {
const data_: FormattedComment[] = [],
comments = data.split("\n");
for (let i = 0, n = comments.length; i < n; i++) {
const value = comments[i];
if (!value) continue;
const commentData = value.split(":");
if (commentData.length < 3) {
continue;
} else if (commentData.length > 3) {
for (let j = 3, n = commentData.length; j < n; j++) {
commentData[2] += `:${commentData[j]}`;
}
}
const tmpParam: FormattedComment = {
id: i,
vpos: Number(commentData[0]) * 100,
content: commentData[2] ?? "",
date: i,
date_usec: 0,
owner: true,
premium: true,
mail: [],
user_id: -1,
layer: -1,
is_my_post: false,
};
if (commentData[1]) {
tmpParam.mail = commentData[1].split(/[\s+]/g);
}
if (tmpParam.content.startsWith("/")) {
tmpParam.mail.push("invisible");
}
data_.push(tmpParam);
}
return data_;
};
82 changes: 82 additions & 0 deletions src/input/owner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { array, parse } from "valibot";

import type { InputParser } from "@/@types";
import {
type FormattedComment,
type OwnerComment,
ZOwnerComment,
} from "@/@types";

export const OwnerParser: InputParser = {
key: ["owner"],
parse: (input) => {
return fromOwner(parse(array(ZOwnerComment), input));
},
};

/**
* 投稿者コメントのエディターのデータを処理する
* @param data 投米のデータ
* @returns 変換後のデータ
*/
const fromOwner = (data: OwnerComment[]): FormattedComment[] => {
const data_: FormattedComment[] = [];
for (let i = 0, n = data.length; i < n; i++) {
const value = data[i];
if (!value) continue;
const tmpParam: FormattedComment = {
id: i,
vpos: time2vpos(value.time),
content: value.comment,
date: i,
date_usec: 0,
owner: true,
premium: true,
mail: [],
user_id: -1,
layer: -1,
is_my_post: false,
};
if (value.command) {
tmpParam.mail = value.command.split(/\s+/g);
}
if (tmpParam.content.startsWith("/")) {
tmpParam.mail.push("invisible");
}
data_.push(tmpParam);
}
return data_;
};

/**
* 投稿者コメントのエディターは秒数の入力フォーマットに割りと色々対応しているのでvposに変換
* @param input 分:秒.秒・分:秒・秒.秒・秒
* @returns vpos
*/
const time2vpos = (input: string): number => {
const time = RegExp(
/^(?:(\d+):(\d+)\.(\d+)|(\d+):(\d+)|(\d+)\.(\d+)|(\d+))$/,
).exec(input);
if (time) {
if (
time[1] !== undefined &&
time[2] !== undefined &&
time[3] !== undefined
) {
return (
(Number(time[1]) * 60 + Number(time[2])) * 100 +
Number(time[3]) / Math.pow(10, time[3].length - 2)
);
} else if (time[4] !== undefined && time[5] !== undefined) {
return (Number(time[4]) * 60 + Number(time[5])) * 100;
} else if (time[6] !== undefined && time[7] !== undefined) {
return (
Number(time[6]) * 100 +
Number(time[7]) / Math.pow(10, time[7].length - 2)
);
} else if (time[8] !== undefined) {
return Number(time[8]) * 100;
}
}
return 0;
};
60 changes: 60 additions & 0 deletions src/input/v1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { array, parse } from "valibot";

import type { InputParser } from "@/@types";
import { type FormattedComment, type V1Thread, ZV1Thread } from "@/@types";

export const V1Parser: InputParser = {
key: ["v1"],
parse: (input: unknown) => {
return fromV1(parse(array(ZV1Thread), input));
},
};

/**
* ニコニコ公式のv1 apiから帰ってきたデータ処理する
* data内threadsのデータを渡されることを想定
* @param data v1 apiから帰ってきたデータ
* @returns 変換後のデータ
*/
const fromV1 = (data: V1Thread[]): FormattedComment[] => {
const data_: FormattedComment[] = [],
userList: string[] = [];
for (const item of data) {
const val = item.comments,
forkName = item.fork;
for (const value of val) {
const tmpParam: FormattedComment = {
id: value.no,
vpos: Math.floor(value.vposMs / 10),
content: value.body,
date: date2time(value.postedAt),
date_usec: 0,
owner: forkName === "owner",
premium: value.isPremium,
mail: value.commands,
user_id: -1,
layer: -1,
is_my_post: value.isMyPost,
};
if (tmpParam.content.startsWith("/") && tmpParam.owner) {
tmpParam.mail.push("invisible");
}
const isUserExist = userList.indexOf(value.userId);
if (isUserExist === -1) {
tmpParam.user_id = userList.length;
userList.push(value.userId);
} else {
tmpParam.user_id = isUserExist;
}
data_.push(tmpParam);
}
}
return data_;
};

/**
* v1 apiのpostedAtはISO 8601のtimestampなのでDate関数を使ってunix timestampに変換
* @param date ISO 8601 timestamp
* @returns unix timestamp
*/
const date2time = (date: string): number => Math.floor(Date.parse(date) / 1000);
54 changes: 54 additions & 0 deletions src/input/xmlDocument.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { FormattedComment, InputParser } from "@/@types";
import { InvalidFormatError } from "@/errors";
import typeGuard from "@/typeGuard";

export const XmlDocumentParser: InputParser = {
key: ["formatted", "niconicome"],
parse: (input: unknown): FormattedComment[] => {
if (!typeGuard.xmlDocument(input)) throw new InvalidFormatError();
return parseXMLDocument(input);
},
};

/**
* niconicome等が吐き出すxml形式のコメントデータを処理する
* @param data 吐き出されたxmlをDOMParserでparseFromStringしたもの
* @returns 変換後のデータ
*/
const parseXMLDocument = (data: XMLDocument): FormattedComment[] => {
const data_: FormattedComment[] = [],
userList: string[] = [];
let index = Array.from(data.documentElement.children).length;
for (const item of Array.from(data.documentElement.children)) {
if (item.nodeName !== "chat") continue;
const tmpParam: FormattedComment = {
id: Number(item.getAttribute("no")) || index++,
vpos: Number(item.getAttribute("vpos")),
content: item.textContent ?? "",
date: Number(item.getAttribute("date")) || 0,
date_usec: Number(item.getAttribute("date_usec")) || 0,
owner: !item.getAttribute("user_id"),
premium: item.getAttribute("premium") === "1",
mail: [],
user_id: -1,
layer: -1,
is_my_post: false,
};
if (item.getAttribute("mail")) {
tmpParam.mail = item.getAttribute("mail")?.split(/\s+/g) ?? [];
}
if (tmpParam.content.startsWith("/") && tmpParam.owner) {
tmpParam.mail.push("invisible");
}
const userId = item.getAttribute("user_id") ?? "";
const isUserExist = userList.indexOf(userId);
if (isUserExist === -1) {
tmpParam.user_id = userList.length;
userList.push(userId);
} else {
tmpParam.user_id = isUserExist;
}
data_.push(tmpParam);
}
return data_;
};
Loading
Loading