diff --git a/src/entities.ts b/src/entities.ts
index 64124f45b538ab176cb58a2918a0848c05f413b3..3e7d5839441f71c4b59968ae6f69d1608a7589ad 100644
--- a/src/entities.ts
+++ b/src/entities.ts
@@ -1,5 +1,7 @@
 export type ID = string;
 
+type TODO = Record<string, any>;
+
 export type User = {
 	id: ID;
 	username: string;
@@ -14,6 +16,17 @@ export type User = {
 	}[];
 };
 
+export type MeDetailed = User & {
+	avatarId: DriveFile['id'];
+	bannerId: DriveFile['id'];
+	autoAcceptFollowed: boolean;
+	noCrawle: boolean;
+	isExplorable: boolean;
+	hideOnlineStatus: boolean;
+	mutedWords: string[][];
+	[other: string]: any;
+};
+
 export type DriveFile = {
 	id: ID;
 	createdAt: string;
@@ -59,6 +72,74 @@ export type Note = {
 	}[];
 };
 
+export type Notification = {
+	id: ID;
+	createdAt: string;
+	isRead: boolean;
+} & ({
+	type: 'reaction';
+	reaction: string;
+	user: User;
+	userId: User['id'];
+	note: Note;
+} | {
+	type: 'reply';
+	user: User;
+	userId: User['id'];
+	note: Note;
+} | {
+	type: 'renote';
+	user: User;
+	userId: User['id'];
+	note: Note;
+} | {
+	type: 'quote';
+	user: User;
+	userId: User['id'];
+	note: Note;
+} | {
+	type: 'mention';
+	user: User;
+	userId: User['id'];
+	note: Note;
+} | {
+	type: 'pollVote';
+	user: User;
+	userId: User['id'];
+	note: Note;
+} | {
+	type: 'follow';
+	user: User;
+	userId: User['id'];
+} | {
+	type: 'followRequestAccepted';
+	user: User;
+	userId: User['id'];
+} | {
+	type: 'receiveFollowRequest';
+	user: User;
+	userId: User['id'];
+} | {
+	type: 'groupInvited'; // TODO
+} | {
+	type: 'app';
+	body: string;
+	icon: string;
+});
+
+export type MessagingMessage = {
+	id: ID;
+	createdAt: string;
+	file: DriveFile | null;
+	fileId: DriveFile['id'] | null;
+	isRead: boolean;
+	reads: User['id'][];
+	text: string | null;
+	user: User;
+	userId: User['id'];
+	groupId: string; // TODO
+};
+
 export type InstanceMetadata = {
 	emojis: {
 		category: string;
@@ -119,5 +200,13 @@ export type Page = {
 	isLiked?: boolean;
 };
 
+export type PageEvent = {
+	pageId: Page['id'];
+	event: string;
+	var: any;
+	userId: User['id'];
+	user: User;
+};
+
 export type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+updatedAt' | '-updatedAt';
 export type OriginType = 'combined' | 'local' | 'remote';
diff --git a/src/streaming.ts b/src/streaming.ts
index a30ef54971f0a7760fd848df5a3890b278097c01..effa1f11bd74e064f2b5f912b1ddda0de24d8c74 100644
--- a/src/streaming.ts
+++ b/src/streaming.ts
@@ -3,6 +3,7 @@ import { EventEmitter } from 'eventemitter3';
 import ReconnectingWebsocket from 'reconnecting-websocket';
 import { stringify } from 'querystring';
 import { markRaw } from '@vue/reactivity';
+import { MeDetailed, MessagingMessage, Note, Notification, PageEvent, User } from './entities';
 
 function urlQuery(obj: {}): string {
 	return stringify(Object.entries(obj)
@@ -10,10 +11,84 @@ function urlQuery(obj: {}): string {
 		.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>));
 }
 
+type FIXME = any;
+
+type ChannelDef = {
+	main: {
+		events: {
+			notification: (payload: Notification) => void;
+			mention: (payload: Note) => void;
+			reply: (payload: Note) => void;
+			renote: (payload: Note) => void;
+			follow: (payload: User) => void; // 自分が他人をフォローしたとき
+			followed: (payload: User) => void; // 他人が自分をフォローしたとき
+			unfollow: (payload: User) => void; // 自分が他人をフォロー解除したとき
+			meUpdated: (payload: MeDetailed) => void;
+			pageEvent: (payload: PageEvent) => void;
+		};
+	};
+	homeTimeline: {
+		events: {
+			note: (payload: Note) => void;
+		};
+	};
+	localTimeline: {
+		events: {
+			note: (payload: Note) => void;
+		};
+	};
+	hybridTimeline: {
+		events: {
+			note: (payload: Note) => void;
+		};
+	};
+	globalTimeline: {
+		events: {
+			note: (payload: Note) => void;
+		};
+	};
+	messaging: {
+		events: {
+			message: (payload: MessagingMessage) => void;
+			deleted: (payload: MessagingMessage['id']) => void;
+			read: (payload: MessagingMessage['id'][]) => void;
+			typing: (payload: User['id']) => void;
+		};
+	};
+};
+
+type NoteUpdatedEvent = {
+	id: Note['id'];
+	type: 'reacted';
+	body: {
+		reaction: string;
+		userId: User['id'];
+	};
+} | {
+	id: Note['id'];
+	type: 'deleted';
+	body: {
+		deletedAt: string;
+	};
+} | {
+	id: Note['id'];
+	type: 'pollVoted';
+	body: {
+		choice: number;
+		userId: User['id'];
+	};
+};
+
+type StreamEvents = {
+	_connected_: void;
+	_disconnected_: void;
+	noteUpdated: (payload: NoteUpdatedEvent) => void;
+};
+
 /**
  * Misskey stream connection
  */
-export default class Stream extends EventEmitter {
+export default class Stream extends EventEmitter<StreamEvents> {
 	private stream: ReconnectingWebsocket;
 	public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing';
 	private sharedConnectionPools: Pool[] = [];
@@ -38,7 +113,7 @@ export default class Stream extends EventEmitter {
 	}
 
 	@autobind
-	public useSharedConnection(channel: string, name?: string): SharedConnection {
+	public useSharedConnection<C extends keyof ChannelDef>(channel: C, name?: string): SharedConnection<ChannelDef[C]['events']> {
 		let pool = this.sharedConnectionPools.find(p => p.channel === channel);
 
 		if (pool == null) {
@@ -62,7 +137,7 @@ export default class Stream extends EventEmitter {
 	}
 
 	@autobind
-	public connectToChannel(channel: string, params?: any): NonSharedConnection {
+	public connectToChannel<C extends keyof ChannelDef>(channel: C, params?: any): NonSharedConnection<ChannelDef[C]['events']> {
 		const connection = markRaw(new NonSharedConnection(this, channel, params));
 		this.nonSharedConnections.push(connection);
 		return connection;
@@ -227,7 +302,7 @@ class Pool {
 	}
 }
 
-abstract class Connection extends EventEmitter {
+abstract class Connection<Events extends Record<string, any> = any> extends EventEmitter<Events> {
 	public channel: string;
 	protected stream: Stream;
 	public abstract id: string;
@@ -261,7 +336,7 @@ abstract class Connection extends EventEmitter {
 	public abstract dispose(): void;
 }
 
-class SharedConnection extends Connection {
+class SharedConnection<Events = any> extends Connection<Events> {
 	private pool: Pool;
 
 	public get id(): string {
@@ -288,7 +363,7 @@ class SharedConnection extends Connection {
 	}
 }
 
-class NonSharedConnection extends Connection {
+class NonSharedConnection<Events = any> extends Connection<Events> {
 	public id: string;
 	protected params: any;
 
diff --git a/test/streaming.ts b/test/streaming.ts
index 65ab449b6973145b41798cbcb89fdb0ecdaaf5f5..bfa872ce946744a2bc2f3270b21560f88fee9488 100644
--- a/test/streaming.ts
+++ b/test/streaming.ts
@@ -7,7 +7,7 @@ describe('Streaming', () => {
 		const stream = new Stream('https://misskey.test', { token: 'TOKEN' });
 		const mainChannelReceived: any[] = [];
 		const main = stream.useSharedConnection('main');
-		main.on('foo', payload => {
+		main.on('meUpdated', payload => {
 			mainChannelReceived.push(payload);
 		});
 		await server.connected;
@@ -21,15 +21,15 @@ describe('Streaming', () => {
 			type: 'channel',
 			body: {
 				id: mainChannelId,
-				type: 'foo',
+				type: 'meUpdated',
 				body: {
-					bar: 'buzz'
+					id: 'foo'
 				}
 			}
 		}));
 
 		expect(mainChannelReceived[0]).toEqual({
-			bar: 'buzz'
+			id: 'foo'
 		});
 	});
 });