Skip to content

Commit

Permalink
Merge pull request #85 from xpadev-net/refactor/input-parser
Browse files Browse the repository at this point in the history
[改善] 入力のパース処理を改善
  • Loading branch information
xpadev-net authored Jan 4, 2024
2 parents 92329f0 + 03f9e98 commit e7e9a0b
Show file tree
Hide file tree
Showing 11 changed files with 364 additions and 310 deletions.
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

0 comments on commit e7e9a0b

Please sign in to comment.