diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts
index 9643c45d1a0bade81c2b7584ae5851e2de4a92dd..78ca8b43ba2a4163fd9d7885a8b3e7860ae02c41 100644
--- a/packages/backend/test/e2e/fetch-resource.ts
+++ b/packages/backend/test/e2e/fetch-resource.ts
@@ -1,7 +1,8 @@
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { startServer, signup, post, api, simpleGet } from '../utils.js';
+import { startServer, channel, clip, cookie, galleryPost, signup, page, play, post, simpleGet, uploadFile } from '../utils.js';
+import type { SimpleGetResponse } from '../utils.js';
 import type { INestApplicationContext } from '@nestjs/common';
 
 // Request Accept
@@ -15,189 +16,446 @@ const AP = 'application/activity+json; charset=utf-8';
 const HTML = 'text/html; charset=utf-8';
 const JSON_UTF8 = 'application/json; charset=utf-8';
 
-describe('Fetch resource', () => {
+describe('Webリソース', () => {
 	let app: INestApplicationContext;
 
 	let alice: any;
+	let aliceUploadedFile: any;
 	let alicesPost: any;
+	let alicePage: any;
+	let alicePlay: any;
+	let aliceClip: any;
+	let aliceGalleryPost: any;
+	let aliceChannel: any;
+
+	type Request = { 
+		path: string, 
+		accept?: string,
+		cookie?: string,
+	};
+	const ok = async (param: Request & {
+		type?: string,
+	}):Promise<SimpleGetResponse> => {
+		const { path, accept, cookie, type } = param;
+		const res = await simpleGet(path, accept, cookie);
+		assert.strictEqual(res.status, 200);
+		assert.strictEqual(res.type, type ?? HTML);
+		return res;
+	};
+
+	const notOk = async (param: Request & {
+		status?: number,
+		code?: string,
+	}): Promise<SimpleGetResponse> => {	
+		const { path, accept, cookie, status, code } = param;
+		const res = await simpleGet(path, accept, cookie);
+		assert.notStrictEqual(res.status, 200);
+		if (status != null) {
+			assert.strictEqual(res.status, status);
+		}
+		if (code != null) {
+			assert.strictEqual(res.body.error.code, code);
+		}
+		return res;
+	};
+	
+	const notFound = async (param: Request): Promise<SimpleGetResponse> => {	
+		return await notOk({
+			...param,
+			status: 404,
+		});
+	};
+
+	const metaTag = (res: SimpleGetResponse, key: string, superkey = 'name'): string => {
+		return res.body.window.document.querySelector('meta[' + superkey + '="' + key + '"]')?.content;
+	};
 
 	beforeAll(async () => {
 		app = await startServer();
 		alice = await signup({ username: 'alice' });
+		aliceUploadedFile = await uploadFile(alice);
 		alicesPost = await post(alice, {
 			text: 'test',
 		});
+		alicePage = await page(alice, {});
+		alicePlay = await play(alice, {});
+		aliceClip = await clip(alice, {});
+		aliceGalleryPost = await galleryPost(alice, {
+			fileIds: [aliceUploadedFile.body.id],
+		});
+		aliceChannel = await channel(alice, {});
 	}, 1000 * 60 * 2);
 
 	afterAll(async () => {
 		await app.close();
 	});
 
-	describe('Common', () => {
-		test('meta', async () => {
-			const res = await api('/meta', {
-			});
+	describe.each([
+		{ path: '/', type: HTML },
+		{ path: '/docs/ja-JP/about', type: HTML }, // "指定されたURLに該当するページはありませんでした。"
+		// fastify-static gives charset=UTF-8 instead of utf-8 and that's okay
+		{ path: '/api-doc', type: 'text/html; charset=UTF-8' }, 
+		{ path: '/api.json', type: JSON_UTF8 }, 
+		{ path: '/api-console', type: HTML }, 
+		{ path: '/_info_card_', type: HTML }, 
+		{ path: '/bios', type: HTML }, 
+		{ path: '/cli', type: HTML }, 
+		{ path: '/flush', type: HTML }, 
+		{ path: '/robots.txt', type: 'text/plain; charset=UTF-8' },
+		{ path: '/favicon.ico', type: 'image/vnd.microsoft.icon' }, 
+		{ path: '/opensearch.xml', type: 'application/opensearchdescription+xml' },
+		{ path: '/apple-touch-icon.png', type: 'image/png' }, 
+		{ path: '/twemoji/2764.svg', type: 'image/svg+xml' }, 
+		{ path: '/twemoji/2764-fe0f-200d-1f525.svg', type: 'image/svg+xml' }, 
+		{ path: '/twemoji-badge/2764.png', type: 'image/png' }, 
+		{ path: '/twemoji-badge/2764-fe0f-200d-1f525.png', type: 'image/png' },
+		{ path: '/fluent-emoji/2764.png', type: 'image/png' }, 
+		{ path: '/fluent-emoji/2764-fe0f-200d-1f525.png', type: 'image/png' }, 
+	])('$path', (p) => {
+		test('がGETできる。', async () => await ok({ ...p }));
+
+		// 注意: Webページが200で取得できても、実際のHTMLが正しく表示できるとは限らない
+		//      例えば、 /@xxx/pages/yyy に存在しないIDを渡した場合、HTTPレスポンスではエラーを区別できない
+		//      こういったアサーションはフロントエンドE2EやAPI Endpointのテストで担保する。
+	});
 
-			assert.strictEqual(res.status, 200);
-		});
+	describe.each([
+		{ path: '/twemoji/2764.png' }, 
+		{ path: '/twemoji/2764-fe0f-200d-1f525.png' }, 
+		{ path: '/twemoji-badge/2764.svg' }, 
+		{ path: '/twemoji-badge/2764-fe0f-200d-1f525.svg' },
+		{ path: '/fluent-emoji/2764.svg' }, 
+		{ path: '/fluent-emoji/2764-fe0f-200d-1f525.svg' }, 
+	])('$path', ({ path }) => {
+		test('はGETできない。', async () => await notFound({ path }));
+	});
 
-		test('GET root', async () => {
-			const res = await simpleGet('/');
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
-		});
+	describe.each([
+		{ ext: 'rss', type: 'application/rss+xml; charset=utf-8' }, 
+		{ ext: 'atom', type: 'application/atom+xml; charset=utf-8' }, 
+		{ ext: 'json', type: 'application/json; charset=utf-8' }, 
+	])('/@:username.$ext', ({ ext, type }) => {
+		const path = (username: string): string => `/@${username}.${ext}`;
+
+		test('がGETできる。', async () => await ok({ 
+			path: path(alice.username),
+			type,
+		}));
+
+		test('は存在しないユーザーはGETできない。', async () => await notOk({ 
+			path: path('nonexisting'),
+			status: 404, 
+		}));
+	});
 
-		test('GET docs', async () => {
-			const res = await simpleGet('/docs/ja-JP/about');
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
-		});
+	describe.each([{ path: '/api/foo' }])('$path', ({ path }) => {
+		test('はGETできない。', async () => await notOk({ 
+			path,
+			status: 404, 
+			code: 'UNKNOWN_API_ENDPOINT',
+		}));
+	});
 
-		test('GET api-doc', async () => {
-			const res = await simpleGet('/api-doc');
-			assert.strictEqual(res.status, 200);
-			// fastify-static gives charset=UTF-8 instead of utf-8 and that's okay
-			assert.strictEqual(res.type?.toLowerCase(), HTML);
-		});
+	describe.each([{ path: '/queue' }])('$path', ({ path }) => {
+		test('はadminでなければGETできない。', async () => await notOk({ 
+			path,
+			status: 500, // FIXME? 403ではない。
+		}));
+		
+		test('はadminならGETできる。', async () => await ok({ 
+			path,
+			cookie: cookie(alice),
+		}));	
+	});
 
-		test('GET api.json', async () => {
-			const res = await simpleGet('/api.json');
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, JSON_UTF8);
-		});
+	describe.each([{ path: '/streaming' }])('$path', ({ path }) => {
+		test('はGETできない。', async () => await notOk({ 
+			path,
+			status: 503, 
+		}));
+	});
 
-		test('GET api/foo (存在しない)', async () => {
-			const res = await simpleGet('/api/foo');
-			assert.strictEqual(res.status, 404);
-			assert.strictEqual(res.body.error.code, 'UNKNOWN_API_ENDPOINT');
+	describe('/@:username', () => {
+		const path = (username: string): string => `/@${username}`;
+
+		describe.each([
+			{ accept: PREFER_HTML },
+			{ accept: UNSPECIFIED },
+		])('(Acceptヘッダ: $accept)', ({ accept }) => {
+			test('はHTMLとしてGETできる。', async () => {
+				const res = await ok({ 
+					path: path(alice.username), 
+					accept, 
+					type: HTML,
+				});
+				assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
+				assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
+				
+				// TODO ogタグの検証
+				// TODO profile.noCrawleの検証
+				// TODO twitter:creatorの検証
+				// TODO <link rel="me" ...>の検証
+			});
+			test('はHTMLとしてGETできる。(存在しないIDでも。)', async () => await ok({ 
+				path: path('xxxxxxxxxx'), 
+				type: HTML,
+			}));
 		});
 
-		test('GET api-console (client page)', async () => {
-			const res = await simpleGet('/api-console');
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
+		describe.each([
+			{ accept: ONLY_AP },
+			{ accept: PREFER_AP },
+		])('(Acceptヘッダ: $accept)', ({ accept }) => {
+			test('はActivityPubとしてGETできる。', async () => {
+				const res = await ok({ 
+					path: path(alice.username), 
+					accept, 
+					type: AP,
+				});
+				assert.strictEqual(res.body.type, 'Person');
+			});
+
+			test('は存在しないIDのときActivityPubとしてGETできない。', async () => await notFound({ 
+				path: path('xxxxxxxxxx'), 
+				accept,
+			}));
 		});
+	});
 
-		test('GET favicon.ico', async () => {
-			const res = await simpleGet('/favicon.ico');
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, 'image/vnd.microsoft.icon');
+	describe.each([ 
+		// 実際のハンドルはフロントエンド(index.vue)で行われる
+		{ sub: 'home' },
+		{ sub: 'notes' },
+		{ sub: 'activity' },
+		{ sub: 'achievements' },
+		{ sub: 'reactions' },
+		{ sub: 'clips' },
+		{ sub: 'pages' },
+		{ sub: 'gallery' },
+	])('/@:username/$sub', ({ sub }) => {
+		const path = (username: string): string => `/@${username}/${sub}`;
+
+		test('はHTMLとしてGETできる。', async () => {
+			const res = await ok({ 
+				path: path(alice.username), 
+			});
+			assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
+			assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
 		});
+	});
+	
+	describe('/@:user/pages/:page', () => {
+		const path = (username: string, pagename: string): string => `/@${username}/pages/${pagename}`;
 
-		test('GET apple-touch-icon.png', async () => {
-			const res = await simpleGet('/apple-touch-icon.png');
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, 'image/png');
+		test('はHTMLとしてGETできる。', async () => {
+			const res = await ok({ 
+				path: path(alice.username, alicePage.name), 
+			});
+			assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
+			assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
+			assert.strictEqual(metaTag(res, 'misskey:page-id'), alicePage.id);
+			
+			// TODO ogタグの検証
+			// TODO profile.noCrawleの検証
+			// TODO twitter:creatorの検証
 		});
+		
+		test('はGETできる。(存在しないIDでも。)', async () => await ok({ 
+			path: path(alice.username, 'xxxxxxxxxx'), 
+		}));
+	});
 
-		test('GET twemoji svg', async () => {
-			const res = await simpleGet('/twemoji/2764.svg');
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, 'image/svg+xml');
+	describe('/users/:id', () => {
+		const path = (id: string): string => `/users/${id}`;
+
+		describe.each([
+			{ accept: PREFER_HTML },
+			{ accept: UNSPECIFIED },
+		])('(Acceptヘッダ: $accept)', ({ accept }) => {
+			test('は/@:usernameにリダイレクトする', async () => {
+				const res = await simpleGet(path(alice.id), accept);
+				assert.strictEqual(res.status, 302);
+				assert.strictEqual(res.location, `/@${alice.username}`);
+			});
+
+			test('は存在しないユーザーはGETできない。', async () => await notFound({ 
+				path: path('xxxxxxxx'),
+			}));
 		});
 
-		test('GET twemoji svg with hyphen', async () => {
-			const res = await simpleGet('/twemoji/2764-fe0f-200d-1f525.svg');
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, 'image/svg+xml');
+		describe.each([
+			{ accept: ONLY_AP },
+			{ accept: PREFER_AP },
+		])('(Acceptヘッダ: $accept)', ({ accept }) => {
+			test('はActivityPubとしてGETできる。', async () => {
+				const res = await ok({ 
+					path: path(alice.id), 
+					accept, 
+					type: AP,
+				});
+				assert.strictEqual(res.body.type, 'Person');
+			});
+
+			test('は存在しないIDのときActivityPubとしてGETできない。', async () => await notOk({ 
+				path: path('xxxxxxxx'),
+				accept,
+				status: 404,
+			}));
 		});
 	});
+	
+	describe('/users/inbox', () => {
+		test('がGETできる。(POST専用だけど4xx/5xxにならずHTMLが返ってくる)', async () => await ok({ 
+			path: '/inbox',
+		}));
 
-	describe('/@:username', () => {
-		test('Only AP => AP', async () => {
-			const res = await simpleGet(`/@${alice.username}`, ONLY_AP);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, AP);
-		});
+		// test.todo('POSTできる?');
+	});
 
-		test('Prefer AP => AP', async () => {
-			const res = await simpleGet(`/@${alice.username}`, PREFER_AP);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, AP);
-		});
+	describe('/users/:id/inbox', () => {
+		const path = (id: string): string => `/users/${id}/inbox`;
 
-		test('Prefer HTML => HTML', async () => {
-			const res = await simpleGet(`/@${alice.username}`, PREFER_HTML);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
-		});
+		test('がGETできる。(POST専用だけど4xx/5xxにならずHTMLが返ってくる)', async () => await ok({ 
+			path: path(alice.id),
+		}));
 
-		test('Unspecified => HTML', async () => {
-			const res = await simpleGet(`/@${alice.username}`, UNSPECIFIED);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
-		});
+		// test.todo('POSTできる?');
 	});
 
-	describe('/users/:id', () => {
-		test('Only AP => AP', async () => {
-			const res = await simpleGet(`/users/${alice.id}`, ONLY_AP);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, AP);
-		});
+	describe('/users/:id/outbox', () => {
+		const path = (id: string): string => `/users/${id}/outbox`;
 
-		test('Prefer AP => AP', async () => {
-			const res = await simpleGet(`/users/${alice.id}`, PREFER_AP);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, AP);
+		test('がGETできる。', async () => {
+			const res = await ok({ 
+				path: path(alice.id), 
+				type: AP,
+			});
+			assert.strictEqual(res.body.type, 'OrderedCollection');
 		});
+	});
+	
+	describe('/notes/:id', () => {
+		const path = (noteId: string): string => `/notes/${noteId}`;
+
+		describe.each([
+			{ accept: PREFER_HTML },
+			{ accept: UNSPECIFIED },
+		])('(Acceptヘッダ: $accept)', ({ accept }) => {
+			test('はHTMLとしてGETできる。', async () => {
+				const res = await ok({ 
+					path: path(alicesPost.id), 
+					accept, 
+					type: HTML,
+				});
+				assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
+				assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
+				assert.strictEqual(metaTag(res, 'misskey:note-id'), alicesPost.id);	
+				
+				// TODO ogタグの検証
+				// TODO profile.noCrawleの検証
+				// TODO twitter:creatorの検証
+			});
 
-		test('Prefer HTML => Redirect to /@:username', async () => {
-			const res = await simpleGet(`/users/${alice.id}`, PREFER_HTML);
-			assert.strictEqual(res.status, 302);
-			assert.strictEqual(res.location, `/@${alice.username}`);
+			test('はHTMLとしてGETできる。(存在しないIDでも。)', async () => await ok({ 
+				path: path('xxxxxxxxxx'), 
+			}));
 		});
 
-		test('Undecided => HTML', async () => {
-			const res = await simpleGet(`/users/${alice.id}`, UNSPECIFIED);
-			assert.strictEqual(res.status, 302);
-			assert.strictEqual(res.location, `/@${alice.username}`);
+		describe.each([
+			{ accept: ONLY_AP },
+			{ accept: PREFER_AP },
+		])('(Acceptヘッダ: $accept)', ({ accept }) => {
+			test('はActivityPubとしてGETできる。', async () => {
+				const res = await ok({ 
+					path: path(alicesPost.id), 
+					accept,
+					type: AP,
+				});
+				assert.strictEqual(res.body.type, 'Note');
+			});
+
+			test('は存在しないIDのときActivityPubとしてGETできない。', async () => await notFound({ 
+				path: path('xxxxxxxxxx'), 
+				accept,
+			}));
 		});
 	});
+	
+	describe('/play/:id', () => {
+		const path = (playid: string): string => `/play/${playid}`;
 
-	describe('/notes/:id', () => {
-		test('Only AP => AP', async () => {
-			const res = await simpleGet(`/notes/${alicesPost.id}`, ONLY_AP);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, AP);
+		test('がGETできる。', async () => {
+			const res = await ok({ 
+				path: path(alicePlay.id), 
+			});
+			assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
+			assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
+			assert.strictEqual(metaTag(res, 'misskey:flash-id'), alicePlay.id);
+			
+			// TODO ogタグの検証
+			// TODO profile.noCrawleの検証
+			// TODO twitter:creatorの検証
 		});
 
-		test('Prefer AP => AP', async () => {
-			const res = await simpleGet(`/notes/${alicesPost.id}`, PREFER_AP);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, AP);
-		});
+		test('がGETできる。(存在しないIDでも。)', async () => await ok({ 
+			path: path('xxxxxxxxxx'), 
+		}));
+	});
+	
+	describe('/clips/:clip', () => {
+		const path = (clip: string): string => `/clips/${clip}`;
 
-		test('Prefer HTML => HTML', async () => {
-			const res = await simpleGet(`/notes/${alicesPost.id}`, PREFER_HTML);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
-		});
+		test('がGETできる。', async () => {
+			const res = await ok({ 
+				path: path(aliceClip.id), 
+			});
+			assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
+			assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
+			assert.strictEqual(metaTag(res, 'misskey:clip-id'), aliceClip.id);
 
-		test('Unspecified => HTML', async () => {
-			const res = await simpleGet(`/notes/${alicesPost.id}`, UNSPECIFIED);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, HTML);
+			// TODO ogタグの検証
+			// TODO profile.noCrawleの検証
 		});
+		
+		test('がGETできる。(存在しないIDでも。)', async () => await ok({ 
+			path: path('xxxxxxxxxx'), 
+		}));
 	});
 
-	describe('Feeds', () => {
-		test('RSS', async () => {
-			const res = await simpleGet(`/@${alice.username}.rss`, UNSPECIFIED);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, 'application/rss+xml; charset=utf-8');
-		});
+	describe('/gallery/:post', () => {
+		const path = (post: string): string => `/gallery/${post}`;
+
+		test('がGETできる。', async () => {
+			const res = await ok({ 
+				path: path(aliceGalleryPost.id), 
+			});
+			assert.strictEqual(metaTag(res, 'misskey:user-username'), alice.username);
+			assert.strictEqual(metaTag(res, 'misskey:user-id'), alice.id);
 
-		test('ATOM', async () => {
-			const res = await simpleGet(`/@${alice.username}.atom`, UNSPECIFIED);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, 'application/atom+xml; charset=utf-8');
+			// FIXME: misskey:gallery-post-idみたいなmetaタグの設定がない
+			// TODO profile.noCrawleの検証
+			// TODO twitter:creatorの検証
 		});
+		
+		test('がGETできる。(存在しないIDでも。)', async () => await ok({ 
+			path: path('xxxxxxxxxx'), 
+		}));
+	});
+	
+	describe('/channels/:channel', () => {
+		const path = (channel: string): string => `/channels/${channel}`;
+
+		test('はGETできる。', async () => {
+			const res = await ok({
+				path: path(aliceChannel.id), 
+			});
 
-		test('JSON', async () => {
-			const res = await simpleGet(`/@${alice.username}.json`, UNSPECIFIED);
-			assert.strictEqual(res.status, 200);
-			assert.strictEqual(res.type, 'application/json; charset=utf-8');
+			// FIXME: misskey関連のmetaタグの設定がない
+			// TODO ogタグの検証
 		});
+		
+		test('がGETできる。(存在しないIDでも。)', async () => await ok({ 
+			path: path('xxxxxxxxxx'), 
+		}));
 	});
 });
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index d1a5d6d9497e40c048a15ff9b82a970291b50c5c..4d52c2f06284e4c2031bc78c3dde20c5d96990c3 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -3,6 +3,7 @@ import { isAbsolute, basename } from 'node:path';
 import WebSocket from 'ws';
 import fetch, { Blob, File, RequestInit } from 'node-fetch';
 import { DataSource } from 'typeorm';
+import { JSDOM } from 'jsdom';
 import { entities } from '../src/postgres.js';
 import { loadConfig } from '../src/config.js';
 import type * as misskey from 'misskey-js';
@@ -12,6 +13,10 @@ export { server as startServer } from '@/boot/common.js';
 const config = loadConfig();
 export const port = config.port;
 
+export const cookie = (me: any): string => {
+	return `token=${me.token};`;
+};
+
 export const api = async (endpoint: string, params: any, me?: any) => {
 	const normalized = endpoint.replace(/^\//, '');
 	return await request(`api/${normalized}`, params, me);
@@ -71,6 +76,71 @@ export const react = async (user: any, note: any, reaction: string): Promise<any
 	}, user);
 };
 
+export const page = async (user: any, page: any = {}): Promise<any> => {
+	const res = await api('pages/create', {
+		alignCenter: false,
+		content: [
+			{
+				id: '2be9a64b-5ada-43a3-85f3-ec3429551ded',
+				text: 'Hello World!',
+				type: 'text',
+			},
+		],
+		eyeCatchingImageId: null,
+		font: 'sans-serif',
+		hideTitleWhenPinned: false,
+		name: '1678594845072',
+		script: '',
+		summary: null,
+		title: '',
+		variables: [],
+		...page,
+	}, user);
+	return res.body;
+};
+
+export const play = async (user: any, play: any = {}): Promise<any> => {
+	const res = await api('flash/create', {
+		permissions: [],
+		script: 'test',
+		summary: '',
+		title: 'test',
+		...play,
+	}, user);
+	return res.body;
+};
+
+export const clip = async (user: any, clip: any = {}): Promise<any> => {
+	const res = await api('clips/create', {
+		description: null,
+		isPublic: true,
+		name: 'test',
+		...clip,
+	}, user);
+	return res.body;
+};
+
+export const galleryPost = async (user: any, channel: any = {}): Promise<any> => {
+	const res = await api('gallery/posts/create', {
+		description: null,
+		fileIds: [],
+		isSensitive: false,
+		title: 'test',
+		...channel,
+	}, user);
+	return res.body;
+};
+
+export const channel = async (user: any, channel: any = {}): Promise<any> => {
+	const res = await api('channels/create', {
+		bannerId: null,
+		description: null,
+		name: 'test',
+		...channel,
+	}, user);
+	return res.body;
+};
+
 interface UploadOptions {
 	/** Optional, absolute path or relative from ./resources/ */
 	path?: string | URL;
@@ -196,10 +266,17 @@ export const waitFire = async (user: any, channel: string, trgr: () => any, cond
 	});
 };
 
-export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status: number, body: any, type: string | null, location: string | null }> => {
+export type SimpleGetResponse = { 
+	status: number, 
+	body: any | JSDOM | null, 
+	type: string | null, 
+	location: string | null 
+};
+export const simpleGet = async (path: string, accept = '*/*', cookie: any = undefined): Promise<SimpleGetResponse> => {
 	const res = await relativeFetch(path, {
 		headers: {
 			Accept: accept,
+			Cookie: cookie,
 		},
 		redirect: 'manual',
 	});
@@ -208,10 +285,14 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status:
 		'application/json; charset=utf-8',
 		'application/activity+json; charset=utf-8',
 	];
+	const htmlTypes = [
+		'text/html; charset=utf-8',
+	];
 
-	const body = jsonTypes.includes(res.headers.get('content-type') ?? '')
-		? await res.json()
-		: null;
+	const body = 
+		jsonTypes.includes(res.headers.get('content-type') ?? '')	? await res.json() : 
+		htmlTypes.includes(res.headers.get('content-type') ?? '')	? new JSDOM(await res.text()) : 
+		null;
 
 	return {
 		status: res.status,