diff --git a/package.json b/package.json
index c1fcff7126182f14b2d86903bd10392a3bf30322..10ddea2c1ee45fffecd0effb04eafa6745bde6b3 100644
--- a/package.json
+++ b/package.json
@@ -208,6 +208,7 @@
 		"seedrandom": "3.0.5",
 		"sharp": "0.29.1",
 		"speakeasy": "2.0.0",
+		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
 		"style-loader": "3.3.0",
 		"summaly": "2.4.1",
diff --git a/src/models/repositories/signin.ts b/src/models/repositories/signin.ts
index 9942d2d9626c844af6899eba5902024527effb60..f375f9b5c056d24c8c0bfc35a3d7ea9cd6ce9b66 100644
--- a/src/models/repositories/signin.ts
+++ b/src/models/repositories/signin.ts
@@ -4,7 +4,7 @@ import { Signin } from '@/models/entities/signin';
 @EntityRepository(Signin)
 export class SigninRepository extends Repository<Signin> {
 	public async pack(
-		src: any,
+		src: Signin,
 	) {
 		return src;
 	}
diff --git a/src/server/api/common/read-messaging-message.ts b/src/server/api/common/read-messaging-message.ts
index 1dce76d2a99ec125252a9c2c793b30a65a0bf24a..33f41b277028b2aa45f51cf5513777b13abea20f 100644
--- a/src/server/api/common/read-messaging-message.ts
+++ b/src/server/api/common/read-messaging-message.ts
@@ -77,7 +77,7 @@ export async function readGroupMessagingMessage(
 		id: In(messageIds)
 	});
 
-	const reads = [];
+	const reads: MessagingMessage['id'][] = [];
 
 	for (const message of messages) {
 		if (message.userId === userId) continue;
diff --git a/src/server/api/endpoints/antennas/update.ts b/src/server/api/endpoints/antennas/update.ts
index ff13e89bcc62837deda65645502a6ee551eb1097..d69b4feee6578ef4af692f1caf0a01f78b746bee 100644
--- a/src/server/api/endpoints/antennas/update.ts
+++ b/src/server/api/endpoints/antennas/update.ts
@@ -137,7 +137,7 @@ export default define(meta, async (ps, user) => {
 		notify: ps.notify,
 	});
 
-	publishInternalEvent('antennaUpdated', Antennas.findOneOrFail(antenna.id));
+	publishInternalEvent('antennaUpdated', await Antennas.findOneOrFail(antenna.id));
 
 	return await Antennas.pack(antenna.id);
 });
diff --git a/src/server/api/stream/channels/antenna.ts b/src/server/api/stream/channels/antenna.ts
index bf9c53c4530887f7412765fc811ca20a5e45b436..3cbdfebb436bf709442cf642271703b49410f789 100644
--- a/src/server/api/stream/channels/antenna.ts
+++ b/src/server/api/stream/channels/antenna.ts
@@ -3,6 +3,7 @@ import Channel from '../channel';
 import { Notes } from '@/models/index';
 import { isMutedUserRelated } from '@/misc/is-muted-user-related';
 import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
+import { StreamMessages } from '../types';
 
 export default class extends Channel {
 	public readonly chName = 'antenna';
@@ -19,11 +20,9 @@ export default class extends Channel {
 	}
 
 	@autobind
-	private async onEvent(data: any) {
-		const { type, body } = data;
-
-		if (type === 'note') {
-			const note = await Notes.pack(body.id, this.user, { detail: true });
+	private async onEvent(data: StreamMessages['antenna']['payload']) {
+		if (data.type === 'note') {
+			const note = await Notes.pack(data.body.id, this.user, { detail: true });
 
 			// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 			if (isMutedUserRelated(note, this.muting)) return;
@@ -34,7 +33,7 @@ export default class extends Channel {
 
 			this.send('note', note);
 		} else {
-			this.send(type, body);
+			this.send(data.type, data.body);
 		}
 	}
 
diff --git a/src/server/api/stream/channels/channel.ts b/src/server/api/stream/channels/channel.ts
index 72ddbf93b4b3e513774622687ef395674dcd3025..bf7942f522e8b48272afe67f09d5e445867b399f 100644
--- a/src/server/api/stream/channels/channel.ts
+++ b/src/server/api/stream/channels/channel.ts
@@ -4,6 +4,7 @@ import { Notes, Users } from '@/models/index';
 import { isMutedUserRelated } from '@/misc/is-muted-user-related';
 import { isBlockerUserRelated } from '@/misc/is-blocker-user-related';
 import { User } from '@/models/entities/user';
+import { StreamMessages } from '../types';
 import { Packed } from '@/misc/schema';
 
 export default class extends Channel {
@@ -52,7 +53,7 @@ export default class extends Channel {
 	}
 
 	@autobind
-	private onEvent(data: any) {
+	private onEvent(data: StreamMessages['channel']['payload']) {
 		if (data.type === 'typing') {
 			const id = data.body;
 			const begin = this.typers[id] == null;
diff --git a/src/server/api/stream/channels/main.ts b/src/server/api/stream/channels/main.ts
index b99cb931da3ee74f7de384c39d7d979762c464c1..131ac3047230f35ec99fea4a38fd8dd51b05a315 100644
--- a/src/server/api/stream/channels/main.ts
+++ b/src/server/api/stream/channels/main.ts
@@ -11,35 +11,33 @@ export default class extends Channel {
 	public async init(params: any) {
 		// Subscribe main stream channel
 		this.subscriber.on(`mainStream:${this.user!.id}`, async data => {
-			const { type } = data;
-			let { body } = data;
-
-			switch (type) {
+			switch (data.type) {
 				case 'notification': {
-					if (this.muting.has(body.userId)) return;
-					if (body.note && body.note.isHidden) {
-						const note = await Notes.pack(body.note.id, this.user, {
+					if (data.body.userId && this.muting.has(data.body.userId)) return;
+
+					if (data.body.note && data.body.note.isHidden) {
+						const note = await Notes.pack(data.body.note.id, this.user, {
 							detail: true
 						});
 						this.connection.cacheNote(note);
-						body.note = note;
+						data.body.note = note;
 					}
 					break;
 				}
 				case 'mention': {
-					if (this.muting.has(body.userId)) return;
-					if (body.isHidden) {
-						const note = await Notes.pack(body.id, this.user, {
+					if (this.muting.has(data.body.userId)) return;
+					if (data.body.isHidden) {
+						const note = await Notes.pack(data.body.id, this.user, {
 							detail: true
 						});
 						this.connection.cacheNote(note);
-						body = note;
+						data.body = note;
 					}
 					break;
 				}
 			}
 
-			this.send(type, body);
+			this.send(data.type, data.body);
 		});
 	}
 }
diff --git a/src/server/api/stream/channels/messaging.ts b/src/server/api/stream/channels/messaging.ts
index 015b0a765031705066c5474f3725cca2e5c7f586..c049e880b9373567f0b5f7bf0e3be084dd59278d 100644
--- a/src/server/api/stream/channels/messaging.ts
+++ b/src/server/api/stream/channels/messaging.ts
@@ -3,6 +3,8 @@ import { readUserMessagingMessage, readGroupMessagingMessage, deliverReadActivit
 import Channel from '../channel';
 import { UserGroupJoinings, Users, MessagingMessages } from '@/models/index';
 import { User, ILocalUser, IRemoteUser } from '@/models/entities/user';
+import { UserGroup } from '@/models/entities/user-group';
+import { StreamMessages } from '../types';
 
 export default class extends Channel {
 	public readonly chName = 'messaging';
@@ -12,7 +14,7 @@ export default class extends Channel {
 	private otherpartyId: string | null;
 	private otherparty: User | null;
 	private groupId: string | null;
-	private subCh: string;
+	private subCh: `messagingStream:${User['id']}-${User['id']}` | `messagingStream:${UserGroup['id']}`;
 	private typers: Record<User['id'], Date> = {};
 	private emitTypersIntervalId: ReturnType<typeof setInterval>;
 
@@ -45,7 +47,7 @@ export default class extends Channel {
 	}
 
 	@autobind
-	private onEvent(data: any) {
+	private onEvent(data: StreamMessages['messaging']['payload'] | StreamMessages['groupMessaging']['payload']) {
 		if (data.type === 'typing') {
 			const id = data.body;
 			const begin = this.typers[id] == null;
diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts
index ccd555e149ef92499ea901cef719b62ad2d2b45c..da4ea5ec99d48b2faad58a7df23b29b0555dfb93 100644
--- a/src/server/api/stream/index.ts
+++ b/src/server/api/stream/index.ts
@@ -14,6 +14,7 @@ import { AccessToken } from '@/models/entities/access-token';
 import { UserProfile } from '@/models/entities/user-profile';
 import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '@/services/stream';
 import { UserGroup } from '@/models/entities/user-group';
+import { StreamEventEmitter, StreamMessages } from './types';
 import { Packed } from '@/misc/schema';
 
 /**
@@ -28,7 +29,7 @@ export default class Connection {
 	public followingChannels: Set<ChannelModel['id']> = new Set();
 	public token?: AccessToken;
 	private wsConnection: websocket.connection;
-	public subscriber: EventEmitter;
+	public subscriber: StreamEventEmitter;
 	private channels: Channel[] = [];
 	private subscribingNotes: any = {};
 	private cachedNotes: Packed<'Note'>[] = [];
@@ -46,8 +47,8 @@ export default class Connection {
 
 		this.wsConnection.on('message', this.onWsConnectionMessage);
 
-		this.subscriber.on('broadcast', async ({ type, body }) => {
-			this.onBroadcastMessage(type, body);
+		this.subscriber.on('broadcast', data => {
+			this.onBroadcastMessage(data);
 		});
 
 		if (this.user) {
@@ -57,43 +58,41 @@ export default class Connection {
 			this.updateFollowingChannels();
 			this.updateUserProfile();
 
-			this.subscriber.on(`user:${this.user.id}`, ({ type, body }) => {
-				this.onUserEvent(type, body);
-			});
+			this.subscriber.on(`user:${this.user.id}`, this.onUserEvent);
 		}
 	}
 
 	@autobind
-	private onUserEvent(type: string, body: any) {
-		switch (type) {
+	private onUserEvent(data: StreamMessages['user']['payload']) { // { type, body }と展開するとそれぞれ型が分離してしまう
+		switch (data.type) {
 			case 'follow':
-				this.following.add(body.id);
+				this.following.add(data.body.id);
 				break;
 
 			case 'unfollow':
-				this.following.delete(body.id);
+				this.following.delete(data.body.id);
 				break;
 
 			case 'mute':
-				this.muting.add(body.id);
+				this.muting.add(data.body.id);
 				break;
 
 			case 'unmute':
-				this.muting.delete(body.id);
+				this.muting.delete(data.body.id);
 				break;
 
 			// TODO: block events
 
 			case 'followChannel':
-				this.followingChannels.add(body.id);
+				this.followingChannels.add(data.body.id);
 				break;
 
 			case 'unfollowChannel':
-				this.followingChannels.delete(body.id);
+				this.followingChannels.delete(data.body.id);
 				break;
 
 			case 'updateUserProfile':
-				this.userProfile = body;
+				this.userProfile = data.body;
 				break;
 
 			case 'terminate':
@@ -145,8 +144,8 @@ export default class Connection {
 	}
 
 	@autobind
-	private onBroadcastMessage(type: string, body: any) {
-		this.sendMessageToWs(type, body);
+	private onBroadcastMessage(data: StreamMessages['broadcast']['payload']) {
+		this.sendMessageToWs(data.type, data.body);
 	}
 
 	@autobind
@@ -249,7 +248,7 @@ export default class Connection {
 	}
 
 	@autobind
-	private async onNoteStreamMessage(data: any) {
+	private async onNoteStreamMessage(data: StreamMessages['note']['payload']) {
 		this.sendMessageToWs('noteUpdated', {
 			id: data.body.id,
 			type: data.type,
diff --git a/src/server/api/stream/types.ts b/src/server/api/stream/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..70eb5c5ce5c365e3970c5b192b7a0706a1389eb0
--- /dev/null
+++ b/src/server/api/stream/types.ts
@@ -0,0 +1,299 @@
+import { EventEmitter } from 'events';
+import Emitter from 'strict-event-emitter-types';
+import { Channel } from '@/models/entities/channel';
+import { User } from '@/models/entities/user';
+import { UserProfile } from '@/models/entities/user-profile';
+import { Note } from '@/models/entities/note';
+import { Antenna } from '@/models/entities/antenna';
+import { DriveFile } from '@/models/entities/drive-file';
+import { DriveFolder } from '@/models/entities/drive-folder';
+import { Emoji } from '@/models/entities/emoji';
+import { UserList } from '@/models/entities/user-list';
+import { MessagingMessage } from '@/models/entities/messaging-message';
+import { UserGroup } from '@/models/entities/user-group';
+import { ReversiGame } from '@/models/entities/games/reversi/game';
+import { AbuseUserReport } from '@/models/entities/abuse-user-report';
+import { Signin } from '@/models/entities/signin';
+import { Page } from '@/models/entities/page';
+import { Packed } from '@/misc/schema';
+
+//#region Stream type-body definitions
+export interface InternalStreamTypes {
+	antennaCreated: Antenna;
+	antennaDeleted: Antenna;
+	antennaUpdated: Antenna;
+}
+
+export interface BroadcastTypes {
+	emojiAdded: {
+		emoji: Packed<'Emoji'>;
+	};
+}
+
+export interface UserStreamTypes {
+	terminate: {};
+	followChannel: Channel;
+	unfollowChannel: Channel;
+	updateUserProfile: UserProfile;
+	mute: User;
+	unmute: User;
+	follow: Packed<'User'>;
+	unfollow: Packed<'User'>;
+	userAdded: Packed<'User'>;
+}
+
+export interface MainStreamTypes {
+	notification: Packed<'Notification'>;
+	mention: Packed<'Note'>;
+	reply: Packed<'Note'>;
+	renote: Packed<'Note'>;
+	follow: Packed<'User'>;
+	followed: Packed<'User'>;
+	unfollow: Packed<'User'>;
+	meUpdated: Packed<'User'>;
+	pageEvent: {
+		pageId: Page['id'];
+		event: string;
+		var: any;
+		userId: User['id'];
+		user: Packed<'User'>;
+	};
+	urlUploadFinished: {
+		marker?: string | null;
+		file: Packed<'DriveFile'>;
+	};
+	readAllNotifications: undefined;
+	unreadNotification: Packed<'Notification'>;
+	unreadMention: Note['id'];
+	readAllUnreadMentions: undefined;
+	unreadSpecifiedNote: Note['id'];
+	readAllUnreadSpecifiedNotes: undefined;
+	readAllMessagingMessages: undefined;
+	messagingMessage: Packed<'MessagingMessage'>;
+	unreadMessagingMessage: Packed<'MessagingMessage'>;
+	readAllAntennas: undefined;
+	unreadAntenna: Antenna;
+	readAllAnnouncements: undefined;
+	readAllChannels: undefined;
+	unreadChannel: Note['id'];
+	myTokenRegenerated: undefined;
+	reversiNoInvites: undefined;
+	reversiInvited: Packed<'ReversiMatching'>;
+	signin: Signin;
+	registryUpdated: {
+		scope?: string[];
+		key: string;
+		value: any | null;
+	};
+	driveFileCreated: Packed<'DriveFile'>;
+	readAntenna: Antenna;
+}
+
+export interface DriveStreamTypes {
+	fileCreated: Packed<'DriveFile'>;
+	fileDeleted: DriveFile['id'];
+	fileUpdated: Packed<'DriveFile'>;
+	folderCreated: Packed<'DriveFolder'>;
+	folderDeleted: DriveFolder['id'];
+	folderUpdated: Packed<'DriveFolder'>;
+}
+
+export interface NoteStreamTypes {
+	pollVoted: {
+		choice: number;
+		userId: User['id'];
+	};
+	deleted: {
+		deletedAt: Date;
+	};
+	reacted: {
+		reaction: string;
+		emoji?: Emoji;
+		userId: User['id'];
+	};
+	unreacted: {
+		reaction: string;
+		userId: User['id'];
+	};
+}
+type NoteStreamEventTypes = {
+	[key in keyof NoteStreamTypes]: {
+		id: Note['id'];
+		body: NoteStreamTypes[key];
+	};
+};
+
+export interface ChannelStreamTypes {
+	typing: User['id'];
+}
+
+export interface UserListStreamTypes {
+	userAdded: Packed<'User'>;
+	userRemoved: Packed<'User'>;
+}
+
+export interface AntennaStreamTypes {
+	note: Note;
+}
+
+export interface MessagingStreamTypes {
+	read: MessagingMessage['id'][];
+	typing: User['id'];
+	message: Packed<'MessagingMessage'>;
+	deleted: MessagingMessage['id'];
+}
+
+export interface GroupMessagingStreamTypes {
+	read: {
+		ids: MessagingMessage['id'][];
+		userId: User['id'];
+	};
+	typing: User['id'];
+	message: Packed<'MessagingMessage'>;
+	deleted: MessagingMessage['id'];
+}
+
+export interface MessagingIndexStreamTypes {
+	read: MessagingMessage['id'][];
+	message: Packed<'MessagingMessage'>;
+}
+
+export interface ReversiStreamTypes {
+	matched: Packed<'ReversiGame'>;
+	invited: Packed<'ReversiMatching'>;
+}
+
+export interface ReversiGameStreamTypes {
+	started: Packed<'ReversiGame'>;
+	ended: {
+		winnerId?: User['id'] | null,
+		game: Packed<'ReversiGame'>;
+	};
+	updateSettings: {
+		key: string;
+		value: FIXME;
+	};
+	initForm: {
+		userId: User['id'];
+		form: FIXME;
+	};
+	updateForm: {
+		userId: User['id'];
+		id: string;
+		value: FIXME;
+	};
+	message: {
+		userId: User['id'];
+		message: FIXME;
+	};
+	changeAccepts: {
+		user1: boolean;
+		user2: boolean;
+	};
+	set: {
+		at: Date;
+		color: boolean;
+		pos: number;
+		next: boolean;
+	};
+	watching: User['id'];
+}
+
+export interface AdminStreamTypes {
+	newAbuseUserReport: {
+		id: AbuseUserReport['id'];
+		targetUserId: User['id'],
+		reporterId: User['id'],
+		comment: string;
+	};
+}
+//#endregion
+
+// 辞書(interface or type)から{ type, body }ユニオンを定義
+// https://stackoverflow.com/questions/49311989/can-i-infer-the-type-of-a-value-using-extends-keyof-type
+// VS Codeの展開を防止するためにEvents型を定義
+type Events<T extends object> = { [K in keyof T]: { type: K; body: T[K]; } };
+type EventUnionFromDictionary<
+	T extends object,
+	U = Events<T>
+> = U[keyof U];
+
+// name/messages(spec) pairs dictionary
+export type StreamMessages = {
+	internal: {
+		name: 'internal';
+		payload: EventUnionFromDictionary<InternalStreamTypes>;
+	};
+	broadcast: {
+		name: 'broadcast';
+		payload: EventUnionFromDictionary<BroadcastTypes>;
+	};
+	user: {
+		name: `user:${User['id']}`;
+		payload: EventUnionFromDictionary<UserStreamTypes>;
+	};
+	main: {
+		name: `mainStream:${User['id']}`;
+		payload: EventUnionFromDictionary<MainStreamTypes>;
+	};
+	drive: {
+		name: `driveStream:${User['id']}`;
+		payload: EventUnionFromDictionary<DriveStreamTypes>;
+	};
+	note: {
+		name: `noteStream:${Note['id']}`;
+		payload: EventUnionFromDictionary<NoteStreamEventTypes>;
+	};
+	channel: {
+		name: `channelStream:${Channel['id']}`;
+		payload: EventUnionFromDictionary<ChannelStreamTypes>;
+	};
+	userList: {
+		name: `userListStream:${UserList['id']}`;
+		payload: EventUnionFromDictionary<UserListStreamTypes>;
+	};
+	antenna: {
+		name: `antennaStream:${Antenna['id']}`;
+		payload: EventUnionFromDictionary<AntennaStreamTypes>;
+	};
+	messaging: {
+		name: `messagingStream:${User['id']}-${User['id']}`;
+		payload: EventUnionFromDictionary<MessagingStreamTypes>;
+	};
+	groupMessaging: {
+		name: `messagingStream:${UserGroup['id']}`;
+		payload: EventUnionFromDictionary<GroupMessagingStreamTypes>;
+	};
+	messagingIndex: {
+		name: `messagingIndexStream:${User['id']}`;
+		payload: EventUnionFromDictionary<MessagingIndexStreamTypes>;
+	};
+	reversi: {
+		name: `reversiStream:${User['id']}`;
+		payload: EventUnionFromDictionary<ReversiStreamTypes>;
+	};
+	reversiGame: {
+		name: `reversiGameStream:${ReversiGame['id']}`;
+		payload: EventUnionFromDictionary<ReversiGameStreamTypes>;
+	};
+	admin: {
+		name: `adminStream:${User['id']}`;
+		payload: EventUnionFromDictionary<AdminStreamTypes>;
+	};
+	notes: {
+		name: 'notesStream';
+		payload: Packed<'Note'>;
+	};
+};
+
+// API event definitions
+// ストリームごとのEmitterの辞書を用意
+type EventEmitterDictionary = { [x in keyof StreamMessages]: Emitter<EventEmitter, { [y in StreamMessages[x]['name']]: (e: StreamMessages[x]['payload']) => void }> };
+// 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
+type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
+// Emitter辞書から共用体型を作り、UnionToIntersectionで交差型にする
+export type StreamEventEmitter = UnionToIntersection<EventEmitterDictionary[keyof StreamMessages]>;
+// { [y in name]: (e: spec) => void }をまとめてその交差型をEmitterにかけるとts(2590)にひっかかる
+
+// provide stream channels union
+export type StreamChannels = StreamMessages[keyof StreamMessages]['name'];
diff --git a/src/services/stream.ts b/src/services/stream.ts
index 4db1a77395b36c0cd2d429db6f1dbb062de321ea..2c308a1b54dd4cec59139b9af30f703d16c68fa6 100644
--- a/src/services/stream.ts
+++ b/src/services/stream.ts
@@ -7,9 +7,28 @@ import { UserGroup } from '@/models/entities/user-group';
 import config from '@/config/index';
 import { Antenna } from '@/models/entities/antenna';
 import { Channel } from '@/models/entities/channel';
+import {
+	StreamChannels,
+	AdminStreamTypes,
+	AntennaStreamTypes,
+	BroadcastTypes,
+	ChannelStreamTypes,
+	DriveStreamTypes,
+	GroupMessagingStreamTypes,
+	InternalStreamTypes,
+	MainStreamTypes,
+	MessagingIndexStreamTypes,
+	MessagingStreamTypes,
+	NoteStreamTypes,
+	ReversiGameStreamTypes,
+	ReversiStreamTypes,
+	UserListStreamTypes,
+	UserStreamTypes
+} from '@/server/api/stream/types';
+import { Packed } from '@/misc/schema';
 
 class Publisher {
-	private publish = (channel: string, type: string | null, value?: any): void => {
+	private publish = (channel: StreamChannels, type: string | null, value?: any): void => {
 		const message = type == null ? value : value == null ?
 			{ type: type, body: null } :
 			{ type: type, body: value };
@@ -20,70 +39,70 @@ class Publisher {
 		}));
 	}
 
-	public publishInternalEvent = (type: string, value?: any): void => {
+	public publishInternalEvent = <K extends keyof InternalStreamTypes>(type: K, value?: InternalStreamTypes[K]): void => {
 		this.publish('internal', type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishUserEvent = (userId: User['id'], type: string, value?: any): void => {
+	public publishUserEvent = <K extends keyof UserStreamTypes>(userId: User['id'], type: K, value?: UserStreamTypes[K]): void => {
 		this.publish(`user:${userId}`, type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishBroadcastStream = (type: string, value?: any): void => {
+	public publishBroadcastStream = <K extends keyof BroadcastTypes>(type: K, value?: BroadcastTypes[K]): void => {
 		this.publish('broadcast', type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishMainStream = (userId: User['id'], type: string, value?: any): void => {
+	public publishMainStream = <K extends keyof MainStreamTypes>(userId: User['id'], type: K, value?: MainStreamTypes[K]): void => {
 		this.publish(`mainStream:${userId}`, type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishDriveStream = (userId: User['id'], type: string, value?: any): void => {
+	public publishDriveStream = <K extends keyof DriveStreamTypes>(userId: User['id'], type: K, value?: DriveStreamTypes[K]): void => {
 		this.publish(`driveStream:${userId}`, type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishNoteStream = (noteId: Note['id'], type: string, value: any): void => {
+	public publishNoteStream = <K extends keyof NoteStreamTypes>(noteId: Note['id'], type: K, value?: NoteStreamTypes[K]): void => {
 		this.publish(`noteStream:${noteId}`, type, {
 			id: noteId,
 			body: value
 		});
 	}
 
-	public publishChannelStream = (channelId: Channel['id'], type: string, value?: any): void => {
+	public publishChannelStream = <K extends keyof ChannelStreamTypes>(channelId: Channel['id'], type: K, value?: ChannelStreamTypes[K]): void => {
 		this.publish(`channelStream:${channelId}`, type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishUserListStream = (listId: UserList['id'], type: string, value?: any): void => {
+	public publishUserListStream = <K extends keyof UserListStreamTypes>(listId: UserList['id'], type: K, value?: UserListStreamTypes[K]): void => {
 		this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishAntennaStream = (antennaId: Antenna['id'], type: string, value?: any): void => {
+	public publishAntennaStream = <K extends keyof AntennaStreamTypes>(antennaId: Antenna['id'], type: K, value?: AntennaStreamTypes[K]): void => {
 		this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishMessagingStream = (userId: User['id'], otherpartyId: User['id'], type: string, value?: any): void => {
+	public publishMessagingStream = <K extends keyof MessagingStreamTypes>(userId: User['id'], otherpartyId: User['id'], type: K, value?: MessagingStreamTypes[K]): void => {
 		this.publish(`messagingStream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishGroupMessagingStream = (groupId: UserGroup['id'], type: string, value?: any): void => {
+	public publishGroupMessagingStream = <K extends keyof GroupMessagingStreamTypes>(groupId: UserGroup['id'], type: K, value?: GroupMessagingStreamTypes[K]): void => {
 		this.publish(`messagingStream:${groupId}`, type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishMessagingIndexStream = (userId: User['id'], type: string, value?: any): void => {
+	public publishMessagingIndexStream = <K extends keyof MessagingIndexStreamTypes>(userId: User['id'], type: K, value?: MessagingIndexStreamTypes[K]): void => {
 		this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishReversiStream = (userId: User['id'], type: string, value?: any): void => {
+	public publishReversiStream = <K extends keyof ReversiStreamTypes>(userId: User['id'], type: K, value?: ReversiStreamTypes[K]): void => {
 		this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishReversiGameStream = (gameId: ReversiGame['id'], type: string, value?: any): void => {
+	public publishReversiGameStream = <K extends keyof ReversiGameStreamTypes>(gameId: ReversiGame['id'], type: K, value?: ReversiGameStreamTypes[K]): void => {
 		this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value);
 	}
 
-	public publishNotesStream = (note: any): void => {
+	public publishNotesStream = (note: Packed<'Note'>): void => {
 		this.publish('notesStream', null, note);
 	}
 
-	public publishAdminStream = (userId: User['id'], type: string, value?: any): void => {
+	public publishAdminStream = <K extends keyof AdminStreamTypes>(userId: User['id'], type: K, value?: AdminStreamTypes[K]): void => {
 		this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value);
 	}
 }
diff --git a/yarn.lock b/yarn.lock
index cd76b59af60fca2ada2c5f5c0acf84ba7e019bc1..e2140e185a45d44a00d8fd2cc439e9eb6fbc6b61 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10288,6 +10288,11 @@ streamsearch@0.1.2:
   resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
   integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
 
+strict-event-emitter-types@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz#05e15549cb4da1694478a53543e4e2f4abcf277f"
+  integrity sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==
+
 strict-uri-encode@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"