diff --git a/packages/backend/src/misc/post.ts b/packages/backend/src/misc/post.ts
new file mode 100644
index 0000000000..e14aa3444f
--- /dev/null
+++ b/packages/backend/src/misc/post.ts
@@ -0,0 +1,14 @@
+export type Post = {
+ text: string | null;
+ cw: string | null;
+ localOnly: boolean;
+ createdAt: Date;
+};
+
+export function parse(acct: any): Post {
+ return { text: acct.text, cw: acct.cw, localOnly: acct.localOnly, createdAt: new Date(acct.createdAt) };
+}
+
+export function toJson(acct: Post): string {
+ return { text: acct.text, cw: acct.cw, localOnly: acct.localOnly }.toString();
+}
diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts
index c387efe927..d5b42d637f 100644
--- a/packages/backend/src/queue/index.ts
+++ b/packages/backend/src/queue/index.ts
@@ -314,6 +314,23 @@ export function createImportFollowingJob(
);
}
+export function createImportPostsJob(
+ user: ThinUser,
+ fileId: DriveFile["id"],
+) {
+ return dbQueue.add(
+ "importPosts",
+ {
+ user: user,
+ fileId: fileId,
+ },
+ {
+ removeOnComplete: true,
+ removeOnFail: true,
+ },
+ );
+}
+
export function createImportMutingJob(user: ThinUser, fileId: DriveFile["id"]) {
return dbQueue.add(
"importMuting",
diff --git a/packages/backend/src/queue/processors/db/index.ts b/packages/backend/src/queue/processors/db/index.ts
index 90173053fb..22b55a3683 100644
--- a/packages/backend/src/queue/processors/db/index.ts
+++ b/packages/backend/src/queue/processors/db/index.ts
@@ -11,6 +11,7 @@ import { importFollowing } from "./import-following.js";
import { importUserLists } from "./import-user-lists.js";
import { deleteAccount } from "./delete-account.js";
import { importMuting } from "./import-muting.js";
+import { importPosts } from "./import-posts.js";
import { importBlocking } from "./import-blocking.js";
import { importCustomEmojis } from "./import-custom-emojis.js";
@@ -26,6 +27,7 @@ const jobs = {
importMuting,
importBlocking,
importUserLists,
+ importPosts,
importCustomEmojis,
deleteAccount,
} as Record<
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index ba0e721b9e..920f871995 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -182,6 +182,7 @@ import * as ep___i_exportBlocking from "./endpoints/i/export-blocking.js";
import * as ep___i_exportFollowing from "./endpoints/i/export-following.js";
import * as ep___i_exportMute from "./endpoints/i/export-mute.js";
import * as ep___i_exportNotes from "./endpoints/i/export-notes.js";
+import * as ep___i_importPosts from "./endpoints/i/import-posts.js";
import * as ep___i_exportUserLists from "./endpoints/i/export-user-lists.js";
import * as ep___i_favorites from "./endpoints/i/favorites.js";
import * as ep___i_gallery_likes from "./endpoints/i/gallery/likes.js";
@@ -527,6 +528,7 @@ const eps = [
["i/export-following", ep___i_exportFollowing],
["i/export-mute", ep___i_exportMute],
["i/export-notes", ep___i_exportNotes],
+ ["i/import-posts", ep___i_importPosts],
["i/export-user-lists", ep___i_exportUserLists],
["i/favorites", ep___i_favorites],
["i/gallery/likes", ep___i_gallery_likes],
diff --git a/packages/backend/src/server/api/endpoints/i/import-posts.ts b/packages/backend/src/server/api/endpoints/i/import-posts.ts
new file mode 100644
index 0000000000..254e37d8a4
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/import-posts.ts
@@ -0,0 +1,43 @@
+import define from "../../define.js";
+import { createImportPostsJob } from "@/queue/index.js";
+import { ApiError } from "../../error.js";
+import { DriveFiles } from "@/models/index.js";
+import { DAY } from "@/const.js";
+
+export const meta = {
+ secure: true,
+ requireCredential: true,
+ limit: {
+ duration: DAY,
+ max: 1,
+ },
+ errors: {
+ noSuchFile: {
+ message: "No such file.",
+ code: "NO_SUCH_FILE",
+ id: "e674141e-bd2a-ba85-e616-aefb187c9c2a",
+ },
+
+ emptyFile: {
+ message: "That file is empty.",
+ code: "EMPTY_FILE",
+ id: "d2f12af1-e7b4-feac-86a3-519548f2728e",
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: "object",
+ properties: {
+ fileId: { type: "string", format: "misskey:id" },
+ },
+ required: ["fileId"],
+} as const;
+
+export default define(meta, paramDef, async (ps, user) => {
+ const file = await DriveFiles.findOneBy({ id: ps.fileId });
+
+ if (file == null) throw new ApiError(meta.errors.noSuchFile);
+ if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
+ createImportPostsJob(user, file.id);
+});
diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue
index 64c3cb4f98..4822ccff06 100644
--- a/packages/client/src/pages/settings/import-export.vue
+++ b/packages/client/src/pages/settings/import-export.vue
@@ -7,6 +7,11 @@
{{ i18n.ts.export }}
+
+ {{ i18n.ts.import }}
+
+ {{ i18n.ts.import }}
+
{{ i18n.ts._exportOrImport.followingList }}
@@ -108,6 +113,11 @@ const exportNotes = () => {
os.api('i/export-notes', {}).then(onExportSuccess).catch(onError);
};
+const importPosts = async (ev) => {
+ const file = await selectFile(ev.currentTarget ?? ev.target);
+ os.api('i/import-posts', { fileId: file.id }).then(onImportSuccess).catch(onError);
+};
+
const exportFollowing = () => {
os.api('i/export-following', {
excludeMuting: excludeMutingUsers.value,