Skip to content

Commit

Permalink
Merge pull request #121 from xpadev-net/develop
Browse files Browse the repository at this point in the history
release: v0.2.28
  • Loading branch information
xpadev-net authored Dec 17, 2024
2 parents 6c91789 + 26b42cf commit b5c5d43
Show file tree
Hide file tree
Showing 25 changed files with 1,161 additions and 1,874 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as fs from "node:fs";
import type { FormattedComment, V1Thread } from "@xpadev-net/niconicomments";
import type { FormattedComment } from "@xpadev-net/niconicomments";
import NiconiComments from "@xpadev-net/niconicomments";
import { Builder } from "@xpadev-net/xml2js";

Expand All @@ -11,14 +11,20 @@ import type {
import type { CommentQueue } from "@/@types/queue";
import type { V1Raw } from "@/@types/types";

import { sendMessageToController } from "../../controller-window";
import { sleep } from "../../utils";
import { sendMessageToController } from "../../../controller-window";
import { sleep } from "../../../utils";
import {
type BaseData,
downloadCustomComment,
stopDownloadCustomComment,
} from "./custom-comment-downloader";
import { convertV3ToFormatted } from "./utils";

let interrupt = false;

const downloadComment = async (
queue: CommentQueue,
updateProgress: (total: number, progress: number) => void,
updateProgress: (total: number, progress: number, message?: string) => void,
): Promise<void> => {
try {
const formattedComments =
Expand All @@ -33,9 +39,12 @@ const downloadComment = async (
queue.metadata,
updateProgress,
);
console.log("comment downloaded");
const xml = convertToXml(formattedComments);
console.log("comment converted");
fs.writeFileSync(queue.path, xml, "utf-8");
} catch (e) {
console.error(e);
sendMessageToController({
type: "message",
title: "コメントのダウンロードに失敗しました",
Expand Down Expand Up @@ -107,11 +116,11 @@ const downloadV3V1SimpleComment = async (
const downloadV3V1CustomComment = async (
option: TCommentOptionCustom,
metadata: V3MetadataComment,
updateProgress: (total: number, progress: number) => void,
updateProgress: (total: number, progress: number, message?: string) => void,
): Promise<FormattedComment[]> => {
interrupt = false;
const userList: string[] = [];
const comments: FormattedComment[] = [];
const comments: FormattedComment[][] = [];
const start = Math.floor(new Date(option.start).getTime() / 1000);
const threadTotal = option.threads.filter((thread) => thread.enable).length;
const total =
Expand All @@ -121,9 +130,9 @@ const downloadV3V1CustomComment = async (
let threadId = 0;
for (const thread of option.threads) {
if (!thread.enable) continue;
const threadComments: FormattedComment[] = [];
let when = Math.floor(new Date(option.start).getTime() / 1000);
const baseData = {
if (interrupt) break;
const when = Math.floor(new Date(option.start).getTime() / 1000);
const baseData: BaseData = {
threadKey: metadata.nvComment.threadKey,
params: {
language: metadata.nvComment.params.language,
Expand All @@ -135,109 +144,29 @@ const downloadV3V1CustomComment = async (
],
},
};
while (
(option.end.type === "date" &&
when > Math.floor(new Date(option.end.date).getTime() / 1000)) ||
(option.end.type === "count" && threadComments.length < option.end.count)
) {
if (interrupt) return comments;
await sleep(1000);
const req = await fetch(`${metadata.nvComment.server}/v1/threads`, {
method: "POST",
headers: {
"content-type": "text/plain;charset=UTF-8",
"x-client-os-type": "others",
"x-frontend-id": "6",
"x-frontend-version": "0",
},
body: JSON.stringify({
...baseData,
additionals: {
res_from: -1000,
when: when,
},
}),
});
const res = (await req.json()) as unknown;
const thread = (res as V1Raw)?.data?.threads[0];
if (!NiconiComments.typeGuard.v1.thread(thread))
throw new Error("failed to get comments");
const oldestCommentDate = new Date(
Math.min(
...thread.comments.map((comment) => {
return new Date(comment.postedAt).getTime();
}),
),
);
when = Math.floor(oldestCommentDate.getTime() / 1000);
threadComments.push(...convertV3ToFormatted([thread], userList));
if (option.end.type === "date") {
updateProgress(total * threadTotal, total * threadId + start - when);
} else {
updateProgress(
option.end.count * threadTotal,
option.end.count * threadId + threadComments.length,
);
}
if (
thread.comments.length < 5 ||
threadComments[threadComments.length - 1]?.id < 5
)
break;
}
comments.push(...threadComments);
comments.push(
await downloadCustomComment(
option,
metadata,
updateProgress,
baseData,
when,
total,
threadTotal,
threadId,
start,
userList,
),
);
threadId++;
await sleep(1000);
}
return comments;
};

const convertV3ToFormatted = (
input: V1Thread[],
userList: string[],
): FormattedComment[] => {
const comments = [];
for (const thread of input) {
const forkName = thread.fork;
for (const comment of thread.comments) {
const tmpParam: FormattedComment = {
id: comment.no,
vpos: Math.floor(comment.vposMs / 10),
content: comment.body,
date: date2time(comment.postedAt),
date_usec: 0,
owner: forkName === "owner",
premium: comment.isPremium,
mail: comment.commands,
user_id: -1,
layer: -1,
is_my_post: false,
};
if (tmpParam.content.startsWith("/") && tmpParam.owner) {
tmpParam.mail.push("invisible");
}
const isUserExist = userList.indexOf(comment.userId);
if (isUserExist === -1) {
tmpParam.user_id = userList.length;
userList.push(comment.userId);
} else {
tmpParam.user_id = isUserExist;
}
comments.push(tmpParam);
}
}
return comments;
return comments.flat(1);
};

const interruptCommentDownload = (): void => {
interrupt = true;
stopDownloadCustomComment();
};

/**
* v1 apiのpostedAtはISO 8601のtimestampなのでDate関数を使ってunix timestampに変換
* @param date {string} ISO 8601 timestamp
* @return {number} unix timestamp
*/
const date2time = (date: string): number =>
Math.floor(new Date(date).getTime() / 1000);

export { downloadComment, interruptCommentDownload };
137 changes: 137 additions & 0 deletions electron/lib/niconico/comments/custom-comment-downloader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import type {
TCommentOptionCustom,
V3MetadataComment,
} from "@/@types/niconico";
import type { V1Raw } from "@/@types/types";
import NiconiComments, {
type FormattedComment,
} from "@xpadev-net/niconicomments";
import { convertV3ToFormatted } from "./utils";

export type BaseData = {
threadKey: string;
params: {
language: string;
targets: {
id: string;
fork: string;
}[];
};
};

let baseData: BaseData;
let when: number;
let option: TCommentOptionCustom;
let metadata: V3MetadataComment;
let progress: (total: number, progress: number, message?: string) => void;
let interrupt: boolean;
let userList: string[];
let threadComments: FormattedComment[];
let resolve: (comments: FormattedComment[]) => void;
let reject: (error: Error) => void;
let total: number;
let threadTotal: number;
let threadId: number;
let start: number;
let end: number;

export const downloadCustomComment = (
_option: TCommentOptionCustom,
_metadata: V3MetadataComment,
_progress: (total: number, progress: number, message?: string) => void,
_baseData: BaseData,
_when: number,
_total: number,
_threadTotal: number,
_threadId: number,
_start: number,
_userList: string[],
): Promise<FormattedComment[]> => {
interrupt = false;
userList = _userList;
threadComments = [];
baseData = _baseData;
when = _when;
progress = _progress;
option = _option;
metadata = _metadata;
total = _total;
threadTotal = _threadTotal;
threadId = _threadId;
start = _start;
end =
_option.end.type === "date"
? new Date(_option.end.date).getTime() / 1000
: 0;

return new Promise<FormattedComment[]>((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
void download().catch(reject);
});
};

export const stopDownloadCustomComment = () => {
interrupt = true;
};

const download = async () => {
if (interrupt) {
resolve(threadComments);
return;
}

const req = await fetch(`${metadata.nvComment.server}/v1/threads`, {
method: "POST",
headers: {
"content-type": "text/plain;charset=UTF-8",
"x-client-os-type": "others",
"x-frontend-id": "6",
"x-frontend-version": "0",
},
body: JSON.stringify({
...baseData,
additionals: {
res_from: -1000,
when: when,
},
}),
});
const res = (await req.json()) as unknown;
const thread = (res as V1Raw)?.data?.threads[0];
if (!NiconiComments.typeGuard.v1.thread(thread))
throw new Error("failed to get comments");
const oldestCommentDate = new Date(
Math.min(
...thread.comments.map((comment) => {
return new Date(comment.postedAt).getTime();
}),
),
);
when = Math.floor(oldestCommentDate.getTime() / 1000);
threadComments.push(...convertV3ToFormatted([thread], userList));
if (option.end.type === "date") {
const endDate = new Date(end * 1000);
progress(
total * threadTotal,
total * threadId + ((start - when) / (start - end)) * total,
`${thread.fork}${threadComments.length} まで取得しました (${oldestCommentDate.getFullYear()}/${oldestCommentDate.getMonth() + 1}/${oldestCommentDate.getDate()} ${oldestCommentDate.getHours()}:${oldestCommentDate.getMinutes()}:${oldestCommentDate.getSeconds()} / ${endDate.getFullYear()}/${endDate.getMonth() + 1}/${endDate.getDate()} ${endDate.getHours()}:${endDate.getMinutes()}:${endDate.getSeconds()})`,
);
} else {
progress(
option.end.count * threadTotal,
option.end.count * threadId + threadComments.length,
`${thread.fork}${threadComments.length} まで取得しました (${threadComments.length} / ${option.end.count})`,
);
}
if (
thread.comments.length < 5 ||
threadComments[threadComments.length - 1]?.id < 5 ||
(option.end.type === "date" && when < end) ||
(option.end.type === "count" && threadComments.length >= option.end.count)
) {
resolve(threadComments);
return;
}
setTimeout(() => download().catch(reject), 1000);
};
1 change: 1 addition & 0 deletions electron/lib/niconico/comments/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./comments";
46 changes: 46 additions & 0 deletions electron/lib/niconico/comments/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { FormattedComment, V1Thread } from "@xpadev-net/niconicomments";

export const convertV3ToFormatted = (
input: V1Thread[],
userList: string[],
): FormattedComment[] => {
const comments = [];
for (const thread of input) {
const forkName = thread.fork;
for (const comment of thread.comments) {
const tmpParam: FormattedComment = {
id: comment.no,
vpos: Math.floor(comment.vposMs / 10),
content: comment.body,
date: date2time(comment.postedAt),
date_usec: 0,
owner: forkName === "owner",
premium: comment.isPremium,
mail: comment.commands,
user_id: -1,
layer: -1,
is_my_post: false,
};
if (tmpParam.content.startsWith("/") && tmpParam.owner) {
tmpParam.mail.push("invisible");
}
const userIndex = userList.indexOf(comment.userId);
if (userIndex === -1) {
tmpParam.user_id = userList.length;
userList.push(comment.userId);
} else {
tmpParam.user_id = userIndex;
}
comments.push(tmpParam);
}
}
return comments;
};

/**
* v1 apiのpostedAtはISO 8601のtimestampなのでDate関数を使ってunix timestampに変換
* @param date {string} ISO 8601 timestamp
* @return {number} unix timestamp
*/
const date2time = (date: string): number =>
Math.floor(new Date(date).getTime() / 1000);
Loading

0 comments on commit b5c5d43

Please sign in to comment.