-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #85 from xpadev-net/refactor/input-parser
[改善] 入力のパース処理を改善
- Loading branch information
Showing
11 changed files
with
364 additions
and
310 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 []; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_; | ||
}; |
Oops, something went wrong.