add: Megalodon, initial mastodon api
This commit is contained in:
		
							parent
							
								
									240d76a987
								
							
						
					
					
						commit
						2375d043d1
					
				
					 103 changed files with 9492 additions and 82 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -58,6 +58,9 @@ ormconfig.json
 | 
			
		|||
temp
 | 
			
		||||
/packages/frontend/src/**/*.stories.ts
 | 
			
		||||
 | 
			
		||||
# Sharkey
 | 
			
		||||
/packages/megalodon/lib
 | 
			
		||||
 | 
			
		||||
# blender backups
 | 
			
		||||
*.blend1
 | 
			
		||||
*.blend2
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ COPY --link ["packages/backend/package.json", "./packages/backend/"]
 | 
			
		|||
COPY --link ["packages/frontend/package.json", "./packages/frontend/"]
 | 
			
		||||
COPY --link ["packages/sw/package.json", "./packages/sw/"]
 | 
			
		||||
COPY --link ["packages/misskey-js/package.json", "./packages/misskey-js/"]
 | 
			
		||||
COPY --link ["packages/megalodon/package.json", "./packages/megalodon/"]
 | 
			
		||||
 | 
			
		||||
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
 | 
			
		||||
	pnpm i --frozen-lockfile --aggregate-output
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -99,6 +99,7 @@
 | 
			
		|||
		"date-fns": "2.30.0",
 | 
			
		||||
		"deep-email-validator": "0.1.21",
 | 
			
		||||
		"fastify": "4.23.2",
 | 
			
		||||
		"fastify-multer": "^2.0.3",
 | 
			
		||||
		"feed": "4.2.2",
 | 
			
		||||
		"file-type": "18.5.0",
 | 
			
		||||
		"fluent-ffmpeg": "2.1.2",
 | 
			
		||||
| 
						 | 
				
			
			@ -116,6 +117,7 @@
 | 
			
		|||
		"json5": "2.2.3",
 | 
			
		||||
		"jsonld": "8.3.1",
 | 
			
		||||
		"jsrsasign": "10.8.6",
 | 
			
		||||
		"megalodon": "workspace:*",
 | 
			
		||||
		"meilisearch": "0.34.2",
 | 
			
		||||
		"mfm-js": "0.23.3",
 | 
			
		||||
		"microformats-parser": "1.5.2",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,7 @@ import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js';
 | 
			
		|||
import { ServerStatsChannelService } from './api/stream/channels/server-stats.js';
 | 
			
		||||
import { UserListChannelService } from './api/stream/channels/user-list.js';
 | 
			
		||||
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
 | 
			
		||||
import { MastodonApiServerService } from './api/mastodon/MastodonApiServerService.js';
 | 
			
		||||
import { ClientLoggerService } from './web/ClientLoggerService.js';
 | 
			
		||||
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
 | 
			
		||||
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
 | 
			
		||||
| 
						 | 
				
			
			@ -84,6 +85,7 @@ import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
 | 
			
		|||
		ServerStatsChannelService,
 | 
			
		||||
		UserListChannelService,
 | 
			
		||||
		OpenApiServerService,
 | 
			
		||||
		MastodonApiServerService,
 | 
			
		||||
		OAuth2ProviderService,
 | 
			
		||||
	],
 | 
			
		||||
	exports: [
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,6 +30,7 @@ import { WellKnownServerService } from './WellKnownServerService.js';
 | 
			
		|||
import { FileServerService } from './FileServerService.js';
 | 
			
		||||
import { ClientServerService } from './web/ClientServerService.js';
 | 
			
		||||
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
 | 
			
		||||
import { MastodonApiServerService } from './api/mastodon/MastodonApiServerService.js';
 | 
			
		||||
import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
 | 
			
		||||
 | 
			
		||||
const _dirname = fileURLToPath(new URL('.', import.meta.url));
 | 
			
		||||
| 
						 | 
				
			
			@ -56,6 +57,7 @@ export class ServerService implements OnApplicationShutdown {
 | 
			
		|||
		private userEntityService: UserEntityService,
 | 
			
		||||
		private apiServerService: ApiServerService,
 | 
			
		||||
		private openApiServerService: OpenApiServerService,
 | 
			
		||||
		private mastodonApiServerService: MastodonApiServerService,
 | 
			
		||||
		private streamingApiServerService: StreamingApiServerService,
 | 
			
		||||
		private activityPubServerService: ActivityPubServerService,
 | 
			
		||||
		private wellKnownServerService: WellKnownServerService,
 | 
			
		||||
| 
						 | 
				
			
			@ -95,6 +97,7 @@ export class ServerService implements OnApplicationShutdown {
 | 
			
		|||
 | 
			
		||||
		fastify.register(this.apiServerService.createServer, { prefix: '/api' });
 | 
			
		||||
		fastify.register(this.openApiServerService.createServer);
 | 
			
		||||
		fastify.register(this.mastodonApiServerService.createServer, { prefix: '/api' });
 | 
			
		||||
		fastify.register(this.fileServerService.createServer);
 | 
			
		||||
		fastify.register(this.activityPubServerService.createServer);
 | 
			
		||||
		fastify.register(this.nodeinfoServerService.createServer);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,192 @@
 | 
			
		|||
import { fileURLToPath } from 'node:url';
 | 
			
		||||
import { Inject, Injectable } from '@nestjs/common';
 | 
			
		||||
import type { UsersRepository } from '@/models/_.js';
 | 
			
		||||
import { DI } from '@/di-symbols.js';
 | 
			
		||||
import { bindThis } from '@/decorators.js';
 | 
			
		||||
import megalodon, { MegalodonInterface } from "megalodon";
 | 
			
		||||
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
 | 
			
		||||
import { convertId, IdConvertType as IdType, convertAccount, convertAnnouncement, convertFilter, convertAttachment } from './converters.js';
 | 
			
		||||
import { IsNull } from 'typeorm';
 | 
			
		||||
import type { Config } from '@/config.js';
 | 
			
		||||
import { getInstance } from './endpoints/meta.js';
 | 
			
		||||
import { MetaService } from '@/core/MetaService.js';
 | 
			
		||||
import multer from 'fastify-multer';
 | 
			
		||||
 | 
			
		||||
const staticAssets = fileURLToPath(new URL('../../../../assets/', import.meta.url));
 | 
			
		||||
 | 
			
		||||
export function getClient(BASE_URL: string, authorization: string | undefined): MegalodonInterface {
 | 
			
		||||
	const accessTokenArr = authorization?.split(" ") ?? [null];
 | 
			
		||||
	const accessToken = accessTokenArr[accessTokenArr.length - 1];
 | 
			
		||||
	const generator = (megalodon as any).default;
 | 
			
		||||
	const client = generator(BASE_URL, accessToken) as MegalodonInterface;
 | 
			
		||||
	return client;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class MastodonApiServerService {
 | 
			
		||||
	constructor(
 | 
			
		||||
		@Inject(DI.usersRepository)
 | 
			
		||||
		private usersRepository: UsersRepository,
 | 
			
		||||
        @Inject(DI.config)
 | 
			
		||||
		private config: Config,
 | 
			
		||||
        private metaService: MetaService,
 | 
			
		||||
	) { }
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
 | 
			
		||||
        const upload = multer({
 | 
			
		||||
            storage: multer.diskStorage({}),
 | 
			
		||||
            limits: {
 | 
			
		||||
                fileSize: this.config.maxFileSize || 262144000,
 | 
			
		||||
                files: 1,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        fastify.register(multer.contentParser);
 | 
			
		||||
 | 
			
		||||
        fastify.get("/v1/custom_emojis", async (_request, reply) => {
 | 
			
		||||
            const BASE_URL = `${_request.protocol}://${_request.hostname}`;
 | 
			
		||||
            const accessTokens = _request.headers.authorization;
 | 
			
		||||
            const client = getClient(BASE_URL, accessTokens);
 | 
			
		||||
            try {
 | 
			
		||||
                const data = await client.getInstanceCustomEmojis();
 | 
			
		||||
                reply.send(data.data);
 | 
			
		||||
            } catch (e: any) {
 | 
			
		||||
                console.error(e);
 | 
			
		||||
                reply.code(401).send(e.response.data);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    
 | 
			
		||||
        fastify.get("/v1/instance", async (_request, reply) => {
 | 
			
		||||
            const BASE_URL = `${_request.protocol}://${_request.hostname}`;
 | 
			
		||||
            const accessTokens = _request.headers.authorization;
 | 
			
		||||
            const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
 | 
			
		||||
            // displayed without being logged in
 | 
			
		||||
            try {
 | 
			
		||||
                const data = await client.getInstance();
 | 
			
		||||
                const admin = await this.usersRepository.findOne({
 | 
			
		||||
                    where: {
 | 
			
		||||
                        host: IsNull(),
 | 
			
		||||
                        isRoot: true,
 | 
			
		||||
                        isDeleted: false,
 | 
			
		||||
                        isSuspended: false,
 | 
			
		||||
                    },
 | 
			
		||||
                    order: { id: "ASC" },
 | 
			
		||||
                });
 | 
			
		||||
                const contact = admin == null ? null : convertAccount((await client.getAccount(admin.id)).data);
 | 
			
		||||
                reply.send(await getInstance(data.data, contact, this.config, await this.metaService.fetch()));
 | 
			
		||||
            } catch (e: any) {
 | 
			
		||||
                console.error(e);
 | 
			
		||||
                reply.code(401).send(e.response.data);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    
 | 
			
		||||
        fastify.get("/v1/announcements", async (_request, reply) => {
 | 
			
		||||
            const BASE_URL = `${_request.protocol}://${_request.hostname}`;
 | 
			
		||||
            const accessTokens = _request.headers.authorization;
 | 
			
		||||
            const client = getClient(BASE_URL, accessTokens);
 | 
			
		||||
            try {
 | 
			
		||||
                const data = await client.getInstanceAnnouncements();
 | 
			
		||||
                reply.send(data.data.map((announcement) => convertAnnouncement(announcement)));
 | 
			
		||||
            } catch (e: any) {
 | 
			
		||||
                console.error(e);
 | 
			
		||||
                reply.code(401).send(e.response.data);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    
 | 
			
		||||
        fastify.post<{ Body: { id: string } }>("/v1/announcements/:id/dismiss", async (_request, reply) => {
 | 
			
		||||
                const BASE_URL = `${_request.protocol}://${_request.hostname}`;
 | 
			
		||||
                const accessTokens = _request.headers.authorization;
 | 
			
		||||
                const client = getClient(BASE_URL, accessTokens);
 | 
			
		||||
                try {
 | 
			
		||||
                    const data = await client.dismissInstanceAnnouncement(
 | 
			
		||||
                        convertId(_request.body['id'], IdType.SharkeyId)
 | 
			
		||||
                    );
 | 
			
		||||
                    reply.send(data.data);
 | 
			
		||||
                } catch (e: any) {
 | 
			
		||||
                    console.error(e);
 | 
			
		||||
                    reply.code(401).send(e.response.data);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        fastify.post("/v1/media", { preHandler: upload.single('file') }, async (_request, reply) => {
 | 
			
		||||
            const BASE_URL = `${_request.protocol}://${_request.hostname}`;
 | 
			
		||||
            const accessTokens = _request.headers.authorization;
 | 
			
		||||
            const client = getClient(BASE_URL, accessTokens);
 | 
			
		||||
            try {
 | 
			
		||||
                const multipartData = await _request.file;
 | 
			
		||||
                if (!multipartData) {
 | 
			
		||||
                    reply.code(401).send({ error: "No image" });
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                const data = await client.uploadMedia(multipartData);
 | 
			
		||||
                reply.send(convertAttachment(data.data as Entity.Attachment));
 | 
			
		||||
            } catch (e: any) {
 | 
			
		||||
                console.error(e);
 | 
			
		||||
                reply.code(401).send(e.response.data);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        fastify.post("/v2/media", { preHandler: upload.single('file') }, async (_request, reply) => {
 | 
			
		||||
            const BASE_URL = `${_request.protocol}://${_request.hostname}`;
 | 
			
		||||
            const accessTokens = _request.headers.authorization;
 | 
			
		||||
            const client = getClient(BASE_URL, accessTokens);
 | 
			
		||||
            try {
 | 
			
		||||
                const multipartData = await _request.file;
 | 
			
		||||
                if (!multipartData) {
 | 
			
		||||
                    reply.code(401).send({ error: "No image" });
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                const data = await client.uploadMedia(multipartData, _request.body!);
 | 
			
		||||
                reply.send(convertAttachment(data.data as Entity.Attachment));
 | 
			
		||||
            } catch (e: any) {
 | 
			
		||||
                console.error(e);
 | 
			
		||||
                reply.code(401).send(e.response.data);
 | 
			
		||||
            }
 | 
			
		||||
        });        
 | 
			
		||||
    
 | 
			
		||||
        fastify.get("/v1/filters", async (_request, reply) => {
 | 
			
		||||
            const BASE_URL = `${_request.protocol}://${_request.hostname}`;
 | 
			
		||||
            const accessTokens = _request.headers.authorization;
 | 
			
		||||
            const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
 | 
			
		||||
            // displayed without being logged in
 | 
			
		||||
            try {
 | 
			
		||||
                const data = await client.getFilters();
 | 
			
		||||
                reply.send(data.data.map((filter) => convertFilter(filter)));
 | 
			
		||||
            } catch (e: any) {
 | 
			
		||||
                console.error(e);
 | 
			
		||||
                reply.code(401).send(e.response.data);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    
 | 
			
		||||
        fastify.get("/v1/trends", async (_request, reply) => {
 | 
			
		||||
            const BASE_URL = `${_request.protocol}://${_request.hostname}`;
 | 
			
		||||
            const accessTokens = _request.headers.authorization;
 | 
			
		||||
            const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
 | 
			
		||||
            // displayed without being logged in
 | 
			
		||||
            try {
 | 
			
		||||
                const data = await client.getInstanceTrends();
 | 
			
		||||
                reply.send(data.data);
 | 
			
		||||
            } catch (e: any) {
 | 
			
		||||
                console.error(e);
 | 
			
		||||
                reply.code(401).send(e.response.data);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    
 | 
			
		||||
        fastify.get("/v1/preferences", async (_request, reply) => {
 | 
			
		||||
            const BASE_URL = `${_request.protocol}://${_request.hostname}`;
 | 
			
		||||
            const accessTokens = _request.headers.authorization;
 | 
			
		||||
            const client = getClient(BASE_URL, accessTokens); // we are using this here, because in private mode some info isnt
 | 
			
		||||
            // displayed without being logged in
 | 
			
		||||
            try {
 | 
			
		||||
                const data = await client.getPreferences();
 | 
			
		||||
                reply.send(data.data);
 | 
			
		||||
            } catch (e: any) {
 | 
			
		||||
                console.error(e);
 | 
			
		||||
                reply.code(401).send(e.response.data);
 | 
			
		||||
            }
 | 
			
		||||
        });    
 | 
			
		||||
		done();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										136
									
								
								packages/backend/src/server/api/mastodon/converters.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								packages/backend/src/server/api/mastodon/converters.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,136 @@
 | 
			
		|||
import { Entity } from "megalodon";
 | 
			
		||||
 | 
			
		||||
const CHAR_COLLECTION: string = "0123456789abcdefghijklmnopqrstuvwxyz";
 | 
			
		||||
 | 
			
		||||
export enum IdConvertType {
 | 
			
		||||
    MastodonId,
 | 
			
		||||
    SharkeyId,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function convertId(in_id: string, id_convert_type: IdConvertType): string {
 | 
			
		||||
    switch (id_convert_type) {
 | 
			
		||||
        case IdConvertType.MastodonId:
 | 
			
		||||
          let out: bigint = BigInt(0);
 | 
			
		||||
          const lowerCaseId = in_id.toLowerCase();
 | 
			
		||||
          for (let i = 0; i < lowerCaseId.length; i++) {
 | 
			
		||||
            const charValue = numFromChar(lowerCaseId.charAt(i));
 | 
			
		||||
            out += BigInt(charValue) * BigInt(36) ** BigInt(i);
 | 
			
		||||
          }
 | 
			
		||||
          return out.toString();
 | 
			
		||||
    
 | 
			
		||||
        case IdConvertType.SharkeyId:
 | 
			
		||||
          let input: bigint = BigInt(in_id);
 | 
			
		||||
          let outStr = '';
 | 
			
		||||
          while (input > BigInt(0)) {
 | 
			
		||||
            const remainder = Number(input % BigInt(36));
 | 
			
		||||
            outStr = charFromNum(remainder) + outStr;
 | 
			
		||||
            input /= BigInt(36);
 | 
			
		||||
          }
 | 
			
		||||
          return outStr;
 | 
			
		||||
    
 | 
			
		||||
        default:
 | 
			
		||||
          throw new Error('Invalid ID conversion type');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function numFromChar(character: string): number {
 | 
			
		||||
    for (let i = 0; i < CHAR_COLLECTION.length; i++) {
 | 
			
		||||
      if (CHAR_COLLECTION.charAt(i) === character) {
 | 
			
		||||
        return i;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    throw new Error('Invalid character in parsed base36 id');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function charFromNum(number: number): string {
 | 
			
		||||
    if (number >= 0 && number < CHAR_COLLECTION.length) {
 | 
			
		||||
      return CHAR_COLLECTION.charAt(number);
 | 
			
		||||
    } else {
 | 
			
		||||
      throw new Error('Invalid number for base-36 encoding');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function simpleConvert(data: any) {
 | 
			
		||||
	// copy the object to bypass weird pass by reference bugs
 | 
			
		||||
	const result = Object.assign({}, data);
 | 
			
		||||
	result.id = convertId(data.id, IdConvertType.MastodonId);
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function convertAccount(account: Entity.Account) {
 | 
			
		||||
	return simpleConvert(account);
 | 
			
		||||
}
 | 
			
		||||
export function convertAnnouncement(announcement: Entity.Announcement) {
 | 
			
		||||
	return simpleConvert(announcement);
 | 
			
		||||
}
 | 
			
		||||
export function convertAttachment(attachment: Entity.Attachment) {
 | 
			
		||||
	return simpleConvert(attachment);
 | 
			
		||||
}
 | 
			
		||||
export function convertFilter(filter: Entity.Filter) {
 | 
			
		||||
	return simpleConvert(filter);
 | 
			
		||||
}
 | 
			
		||||
export function convertList(list: Entity.List) {
 | 
			
		||||
	return simpleConvert(list);
 | 
			
		||||
}
 | 
			
		||||
export function convertFeaturedTag(tag: Entity.FeaturedTag) {
 | 
			
		||||
	return simpleConvert(tag);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function convertNotification(notification: Entity.Notification) {
 | 
			
		||||
	notification.account = convertAccount(notification.account);
 | 
			
		||||
	notification.id = convertId(notification.id, IdConvertType.MastodonId);
 | 
			
		||||
	if (notification.status)
 | 
			
		||||
		notification.status = convertStatus(notification.status);
 | 
			
		||||
	if (notification.reaction)
 | 
			
		||||
		notification.reaction = convertReaction(notification.reaction);
 | 
			
		||||
	return notification;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function convertPoll(poll: Entity.Poll) {
 | 
			
		||||
	return simpleConvert(poll);
 | 
			
		||||
}
 | 
			
		||||
export function convertReaction(reaction: Entity.Reaction) {
 | 
			
		||||
	if (reaction.accounts) {
 | 
			
		||||
		reaction.accounts = reaction.accounts.map(convertAccount);
 | 
			
		||||
	}
 | 
			
		||||
	return reaction;
 | 
			
		||||
}
 | 
			
		||||
export function convertRelationship(relationship: Entity.Relationship) {
 | 
			
		||||
	return simpleConvert(relationship);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function convertStatus(status: Entity.Status) {
 | 
			
		||||
	status.account = convertAccount(status.account);
 | 
			
		||||
	status.id = convertId(status.id, IdConvertType.MastodonId);
 | 
			
		||||
	if (status.in_reply_to_account_id)
 | 
			
		||||
		status.in_reply_to_account_id = convertId(
 | 
			
		||||
			status.in_reply_to_account_id,
 | 
			
		||||
			IdConvertType.MastodonId,
 | 
			
		||||
		);
 | 
			
		||||
	if (status.in_reply_to_id)
 | 
			
		||||
		status.in_reply_to_id = convertId(status.in_reply_to_id, IdConvertType.MastodonId);
 | 
			
		||||
	status.media_attachments = status.media_attachments.map((attachment) =>
 | 
			
		||||
		convertAttachment(attachment),
 | 
			
		||||
	);
 | 
			
		||||
	status.mentions = status.mentions.map((mention) => ({
 | 
			
		||||
		...mention,
 | 
			
		||||
		id: convertId(mention.id, IdConvertType.MastodonId),
 | 
			
		||||
	}));
 | 
			
		||||
	if (status.poll) status.poll = convertPoll(status.poll);
 | 
			
		||||
	if (status.reblog) status.reblog = convertStatus(status.reblog);
 | 
			
		||||
	if (status.quote) status.quote = convertStatus(status.quote);
 | 
			
		||||
	status.reactions = status.reactions.map(convertReaction);
 | 
			
		||||
 | 
			
		||||
	return status;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function convertConversation(conversation: Entity.Conversation) {
 | 
			
		||||
	conversation.id = convertId(conversation.id, IdConvertType.MastodonId);
 | 
			
		||||
	conversation.accounts = conversation.accounts.map(convertAccount);
 | 
			
		||||
	if (conversation.last_status) {
 | 
			
		||||
		conversation.last_status = convertStatus(conversation.last_status);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return conversation;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								packages/backend/src/server/api/mastodon/endpoints/meta.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								packages/backend/src/server/api/mastodon/endpoints/meta.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,63 @@
 | 
			
		|||
import { Entity } from "megalodon";
 | 
			
		||||
import { MAX_NOTE_TEXT_LENGTH, FILE_TYPE_BROWSERSAFE } from "@/const.js";
 | 
			
		||||
import type { Config } from '@/config.js';
 | 
			
		||||
import type { MiMeta } from "@/models/Meta.js";
 | 
			
		||||
 | 
			
		||||
export async function getInstance(
 | 
			
		||||
	response: Entity.Instance,
 | 
			
		||||
	contact: Entity.Account,
 | 
			
		||||
    config: Config,
 | 
			
		||||
    meta: MiMeta,
 | 
			
		||||
) {
 | 
			
		||||
	return {
 | 
			
		||||
		uri: config.url,
 | 
			
		||||
		title: meta.name || "Sharkey",
 | 
			
		||||
		short_description:
 | 
			
		||||
			meta.description?.substring(0, 50) || "See real server website",
 | 
			
		||||
		description:
 | 
			
		||||
			meta.description ||
 | 
			
		||||
			"This is a vanilla Sharkey Instance. It doesn't seem to have a description.",
 | 
			
		||||
		email: response.email || "",
 | 
			
		||||
		version: `3.0.0 (compatible; Sharkey ${config.version})`,
 | 
			
		||||
		urls: response.urls,
 | 
			
		||||
		stats: {
 | 
			
		||||
			user_count: response.stats.user_count,
 | 
			
		||||
			status_count: response.stats.status_count,
 | 
			
		||||
			domain_count: response.stats.domain_count,
 | 
			
		||||
		},
 | 
			
		||||
		thumbnail: meta.backgroundImageUrl || "/static-assets/transparent.png",
 | 
			
		||||
		languages: meta.langs,
 | 
			
		||||
		registrations: !meta.disableRegistration || response.registrations,
 | 
			
		||||
		approval_required: !response.registrations,
 | 
			
		||||
		invites_enabled: response.registrations,
 | 
			
		||||
		configuration: {
 | 
			
		||||
			accounts: {
 | 
			
		||||
				max_featured_tags: 20,
 | 
			
		||||
			},
 | 
			
		||||
			statuses: {
 | 
			
		||||
				max_characters: MAX_NOTE_TEXT_LENGTH,
 | 
			
		||||
				max_media_attachments: 16,
 | 
			
		||||
				characters_reserved_per_url: response.uri.length,
 | 
			
		||||
			},
 | 
			
		||||
			media_attachments: {
 | 
			
		||||
				supported_mime_types: FILE_TYPE_BROWSERSAFE,
 | 
			
		||||
				image_size_limit: 10485760,
 | 
			
		||||
				image_matrix_limit: 16777216,
 | 
			
		||||
				video_size_limit: 41943040,
 | 
			
		||||
				video_frame_rate_limit: 60,
 | 
			
		||||
				video_matrix_limit: 2304000,
 | 
			
		||||
			},
 | 
			
		||||
			polls: {
 | 
			
		||||
				max_options: 10,
 | 
			
		||||
				max_characters_per_option: 50,
 | 
			
		||||
				min_expiration: 50,
 | 
			
		||||
				max_expiration: 2629746,
 | 
			
		||||
			},
 | 
			
		||||
			reactions: {
 | 
			
		||||
				max_reactions: 1,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		contact_account: contact,
 | 
			
		||||
		rules: [],
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										83
									
								
								packages/megalodon/package.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								packages/megalodon/package.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,83 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "megalodon",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "main": "./lib/src/index.js",
 | 
			
		||||
  "typings": "./lib/src/index.d.ts",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "build": "tsc -p ./",
 | 
			
		||||
		"build:debug": "pnpm run build",
 | 
			
		||||
    "lint": "pnpm biome check **/*.ts --apply",
 | 
			
		||||
		"format": "pnpm biome format --write src/**/*.ts",
 | 
			
		||||
    "doc": "typedoc --out ../docs ./src",
 | 
			
		||||
    "test": "NODE_ENV=test jest -u --maxWorkers=3"
 | 
			
		||||
  },
 | 
			
		||||
  "jest": {
 | 
			
		||||
    "moduleFileExtensions": [
 | 
			
		||||
      "ts",
 | 
			
		||||
      "js"
 | 
			
		||||
    ],
 | 
			
		||||
    "moduleNameMapper": {
 | 
			
		||||
      "^@/(.+)": "<rootDir>/src/$1",
 | 
			
		||||
      "^~/(.+)": "<rootDir>/$1"
 | 
			
		||||
    },
 | 
			
		||||
    "testMatch": [
 | 
			
		||||
      "**/test/**/*.spec.ts"
 | 
			
		||||
    ],
 | 
			
		||||
    "preset": "ts-jest/presets/default",
 | 
			
		||||
    "transform": {
 | 
			
		||||
      "^.+\\.(ts|tsx)$": "ts-jest"
 | 
			
		||||
    },
 | 
			
		||||
    "globals": {
 | 
			
		||||
      "ts-jest": {
 | 
			
		||||
        "tsconfig": "tsconfig.json"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "testEnvironment": "node"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@types/oauth": "^0.9.0",
 | 
			
		||||
    "@types/ws": "^8.5.4",
 | 
			
		||||
    "axios": "1.2.2",
 | 
			
		||||
    "dayjs": "^1.11.7",
 | 
			
		||||
    "form-data": "^4.0.0",
 | 
			
		||||
    "https-proxy-agent": "^5.0.1",
 | 
			
		||||
    "oauth": "^0.10.0",
 | 
			
		||||
    "object-assign-deep": "^0.4.0",
 | 
			
		||||
    "parse-link-header": "^2.0.0",
 | 
			
		||||
    "socks-proxy-agent": "^7.0.0",
 | 
			
		||||
    "typescript": "4.9.4",
 | 
			
		||||
    "uuid": "^9.0.0",
 | 
			
		||||
    "ws": "8.12.0",
 | 
			
		||||
    "async-lock": "1.4.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/core-js": "^2.5.0",
 | 
			
		||||
    "@types/form-data": "^2.5.0",
 | 
			
		||||
    "@types/jest": "^29.4.0",
 | 
			
		||||
    "@types/object-assign-deep": "^0.4.0",
 | 
			
		||||
    "@types/parse-link-header": "^2.0.0",
 | 
			
		||||
    "@types/uuid": "^9.0.0",
 | 
			
		||||
		"@types/node": "18.11.18",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.49.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.49.0",
 | 
			
		||||
    "@types/async-lock": "1.4.0",
 | 
			
		||||
    "eslint": "^8.32.0",
 | 
			
		||||
    "eslint-config-prettier": "^8.6.0",
 | 
			
		||||
    "eslint-config-standard": "^16.0.3",
 | 
			
		||||
    "eslint-plugin-import": "^2.27.5",
 | 
			
		||||
    "eslint-plugin-node": "^11.0.0",
 | 
			
		||||
    "eslint-plugin-prettier": "^4.2.1",
 | 
			
		||||
    "eslint-plugin-promise": "^6.1.1",
 | 
			
		||||
    "eslint-plugin-standard": "^5.0.0",
 | 
			
		||||
    "jest": "^29.4.0",
 | 
			
		||||
    "jest-worker": "^29.4.0",
 | 
			
		||||
    "lodash": "^4.17.14",
 | 
			
		||||
    "prettier": "^2.8.3",
 | 
			
		||||
    "ts-jest": "^29.0.5",
 | 
			
		||||
    "typedoc": "^0.23.24"
 | 
			
		||||
  },
 | 
			
		||||
  "directories": {
 | 
			
		||||
    "lib": "lib",
 | 
			
		||||
    "test": "test"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								packages/megalodon/src/axios.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/megalodon/src/axios.d.ts
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
declare module "axios/lib/adapters/http";
 | 
			
		||||
							
								
								
									
										13
									
								
								packages/megalodon/src/cancel.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/megalodon/src/cancel.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
export class RequestCanceledError extends Error {
 | 
			
		||||
	public isCancel: boolean;
 | 
			
		||||
 | 
			
		||||
	constructor(msg: string) {
 | 
			
		||||
		super(msg);
 | 
			
		||||
		this.isCancel = true;
 | 
			
		||||
		Object.setPrototypeOf(this, RequestCanceledError);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const isCancel = (value: any): boolean => {
 | 
			
		||||
	return value && value.isCancel;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										3
									
								
								packages/megalodon/src/converter.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/megalodon/src/converter.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
import MisskeyAPI from "./misskey/api_client";
 | 
			
		||||
 | 
			
		||||
export default MisskeyAPI.Converter;
 | 
			
		||||
							
								
								
									
										3
									
								
								packages/megalodon/src/default.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/megalodon/src/default.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
export const NO_REDIRECT = "urn:ietf:wg:oauth:2.0:oob";
 | 
			
		||||
export const DEFAULT_SCOPE = ["read", "write", "follow"];
 | 
			
		||||
export const DEFAULT_UA = "megalodon";
 | 
			
		||||
							
								
								
									
										27
									
								
								packages/megalodon/src/entities/account.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								packages/megalodon/src/entities/account.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
/// <reference path="emoji.ts" />
 | 
			
		||||
/// <reference path="source.ts" />
 | 
			
		||||
/// <reference path="field.ts" />
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type Account = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		username: string;
 | 
			
		||||
		acct: string;
 | 
			
		||||
		display_name: string;
 | 
			
		||||
		locked: boolean;
 | 
			
		||||
		created_at: string;
 | 
			
		||||
		followers_count: number;
 | 
			
		||||
		following_count: number;
 | 
			
		||||
		statuses_count: number;
 | 
			
		||||
		note: string;
 | 
			
		||||
		url: string;
 | 
			
		||||
		avatar: string;
 | 
			
		||||
		avatar_static: string;
 | 
			
		||||
		header: string;
 | 
			
		||||
		header_static: string;
 | 
			
		||||
		emojis: Array<Emoji>;
 | 
			
		||||
		moved: Account | null;
 | 
			
		||||
		fields: Array<Field>;
 | 
			
		||||
		bot: boolean | null;
 | 
			
		||||
		source?: Source;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								packages/megalodon/src/entities/activity.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/megalodon/src/entities/activity.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Activity = {
 | 
			
		||||
		week: string;
 | 
			
		||||
		statuses: string;
 | 
			
		||||
		logins: string;
 | 
			
		||||
		registrations: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								packages/megalodon/src/entities/announcement.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								packages/megalodon/src/entities/announcement.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
/// <reference path="tag.ts" />
 | 
			
		||||
/// <reference path="emoji.ts" />
 | 
			
		||||
/// <reference path="reaction.ts" />
 | 
			
		||||
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type Announcement = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		content: string;
 | 
			
		||||
		starts_at: string | null;
 | 
			
		||||
		ends_at: string | null;
 | 
			
		||||
		published: boolean;
 | 
			
		||||
		all_day: boolean;
 | 
			
		||||
		published_at: string;
 | 
			
		||||
		updated_at: string;
 | 
			
		||||
		read?: boolean;
 | 
			
		||||
		mentions: Array<AnnouncementAccount>;
 | 
			
		||||
		statuses: Array<AnnouncementStatus>;
 | 
			
		||||
		tags: Array<Tag>;
 | 
			
		||||
		emojis: Array<Emoji>;
 | 
			
		||||
		reactions: Array<Reaction>;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export type AnnouncementAccount = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		username: string;
 | 
			
		||||
		url: string;
 | 
			
		||||
		acct: string;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export type AnnouncementStatus = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		url: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/megalodon/src/entities/application.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/megalodon/src/entities/application.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Application = {
 | 
			
		||||
		name: string;
 | 
			
		||||
		website?: string | null;
 | 
			
		||||
		vapid_key?: string | null;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								packages/megalodon/src/entities/async_attachment.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/megalodon/src/entities/async_attachment.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
/// <reference path="attachment.ts" />
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type AsyncAttachment = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		type: "unknown" | "image" | "gifv" | "video" | "audio";
 | 
			
		||||
		url: string | null;
 | 
			
		||||
		remote_url: string | null;
 | 
			
		||||
		preview_url: string;
 | 
			
		||||
		text_url: string | null;
 | 
			
		||||
		meta: Meta | null;
 | 
			
		||||
		description: string | null;
 | 
			
		||||
		blurhash: string | null;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								packages/megalodon/src/entities/attachment.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								packages/megalodon/src/entities/attachment.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,49 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Sub = {
 | 
			
		||||
		// For Image, Gifv, and Video
 | 
			
		||||
		width?: number;
 | 
			
		||||
		height?: number;
 | 
			
		||||
		size?: string;
 | 
			
		||||
		aspect?: number;
 | 
			
		||||
 | 
			
		||||
		// For Gifv and Video
 | 
			
		||||
		frame_rate?: string;
 | 
			
		||||
 | 
			
		||||
		// For Audio, Gifv, and Video
 | 
			
		||||
		duration?: number;
 | 
			
		||||
		bitrate?: number;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export type Focus = {
 | 
			
		||||
		x: number;
 | 
			
		||||
		y: number;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export type Meta = {
 | 
			
		||||
		original?: Sub;
 | 
			
		||||
		small?: Sub;
 | 
			
		||||
		focus?: Focus;
 | 
			
		||||
		length?: string;
 | 
			
		||||
		duration?: number;
 | 
			
		||||
		fps?: number;
 | 
			
		||||
		size?: string;
 | 
			
		||||
		width?: number;
 | 
			
		||||
		height?: number;
 | 
			
		||||
		aspect?: number;
 | 
			
		||||
		audio_encode?: string;
 | 
			
		||||
		audio_bitrate?: string;
 | 
			
		||||
		audio_channel?: string;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export type Attachment = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		type: "unknown" | "image" | "gifv" | "video" | "audio";
 | 
			
		||||
		url: string;
 | 
			
		||||
		remote_url: string | null;
 | 
			
		||||
		preview_url: string | null;
 | 
			
		||||
		text_url: string | null;
 | 
			
		||||
		meta: Meta | null;
 | 
			
		||||
		description: string | null;
 | 
			
		||||
		blurhash: string | null;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								packages/megalodon/src/entities/card.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/megalodon/src/entities/card.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Card = {
 | 
			
		||||
		url: string;
 | 
			
		||||
		title: string;
 | 
			
		||||
		description: string;
 | 
			
		||||
		type: "link" | "photo" | "video" | "rich";
 | 
			
		||||
		image?: string;
 | 
			
		||||
		author_name?: string;
 | 
			
		||||
		author_url?: string;
 | 
			
		||||
		provider_name?: string;
 | 
			
		||||
		provider_url?: string;
 | 
			
		||||
		html?: string;
 | 
			
		||||
		width?: number;
 | 
			
		||||
		height?: number;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								packages/megalodon/src/entities/context.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/megalodon/src/entities/context.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
/// <reference path="status.ts" />
 | 
			
		||||
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type Context = {
 | 
			
		||||
		ancestors: Array<Status>;
 | 
			
		||||
		descendants: Array<Status>;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								packages/megalodon/src/entities/conversation.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/megalodon/src/entities/conversation.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
/// <reference path="account.ts" />
 | 
			
		||||
/// <reference path="status.ts" />
 | 
			
		||||
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type Conversation = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		accounts: Array<Account>;
 | 
			
		||||
		last_status: Status | null;
 | 
			
		||||
		unread: boolean;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								packages/megalodon/src/entities/emoji.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/megalodon/src/entities/emoji.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Emoji = {
 | 
			
		||||
		shortcode: string;
 | 
			
		||||
		static_url: string;
 | 
			
		||||
		url: string;
 | 
			
		||||
		visible_in_picker: boolean;
 | 
			
		||||
		category: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								packages/megalodon/src/entities/featured_tag.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/megalodon/src/entities/featured_tag.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type FeaturedTag = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		name: string;
 | 
			
		||||
		statuses_count: number;
 | 
			
		||||
		last_status_at: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/megalodon/src/entities/field.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/megalodon/src/entities/field.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Field = {
 | 
			
		||||
		name: string;
 | 
			
		||||
		value: string;
 | 
			
		||||
		verified_at: string | null;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								packages/megalodon/src/entities/filter.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/megalodon/src/entities/filter.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Filter = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		phrase: string;
 | 
			
		||||
		context: Array<FilterContext>;
 | 
			
		||||
		expires_at: string | null;
 | 
			
		||||
		irreversible: boolean;
 | 
			
		||||
		whole_word: boolean;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export type FilterContext = string;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/megalodon/src/entities/history.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/megalodon/src/entities/history.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type History = {
 | 
			
		||||
		day: string;
 | 
			
		||||
		uses: number;
 | 
			
		||||
		accounts: number;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								packages/megalodon/src/entities/identity_proof.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/megalodon/src/entities/identity_proof.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type IdentityProof = {
 | 
			
		||||
		provider: string;
 | 
			
		||||
		provider_username: string;
 | 
			
		||||
		updated_at: string;
 | 
			
		||||
		proof_url: string;
 | 
			
		||||
		profile_url: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								packages/megalodon/src/entities/instance.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								packages/megalodon/src/entities/instance.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
/// <reference path="account.ts" />
 | 
			
		||||
/// <reference path="urls.ts" />
 | 
			
		||||
/// <reference path="stats.ts" />
 | 
			
		||||
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type Instance = {
 | 
			
		||||
		uri: string;
 | 
			
		||||
		title: string;
 | 
			
		||||
		description: string;
 | 
			
		||||
		email: string;
 | 
			
		||||
		version: string;
 | 
			
		||||
		thumbnail: string | null;
 | 
			
		||||
		urls: URLs;
 | 
			
		||||
		stats: Stats;
 | 
			
		||||
		languages: Array<string>;
 | 
			
		||||
		contact_account: Account | null;
 | 
			
		||||
		max_toot_chars?: number;
 | 
			
		||||
		registrations?: boolean;
 | 
			
		||||
		configuration?: {
 | 
			
		||||
			statuses: {
 | 
			
		||||
				max_characters: number;
 | 
			
		||||
				max_media_attachments: number;
 | 
			
		||||
				characters_reserved_per_url: number;
 | 
			
		||||
			};
 | 
			
		||||
			media_attachments: {
 | 
			
		||||
				supported_mime_types: Array<string>;
 | 
			
		||||
				image_size_limit: number;
 | 
			
		||||
				image_matrix_limit: number;
 | 
			
		||||
				video_size_limit: number;
 | 
			
		||||
				video_frame_limit: number;
 | 
			
		||||
				video_matrix_limit: number;
 | 
			
		||||
			};
 | 
			
		||||
			polls: {
 | 
			
		||||
				max_options: number;
 | 
			
		||||
				max_characters_per_option: number;
 | 
			
		||||
				min_expiration: number;
 | 
			
		||||
				max_expiration: number;
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								packages/megalodon/src/entities/list.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/megalodon/src/entities/list.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type List = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		title: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								packages/megalodon/src/entities/marker.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								packages/megalodon/src/entities/marker.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Marker = {
 | 
			
		||||
		home?: {
 | 
			
		||||
			last_read_id: string;
 | 
			
		||||
			version: number;
 | 
			
		||||
			updated_at: string;
 | 
			
		||||
		};
 | 
			
		||||
		notifications?: {
 | 
			
		||||
			last_read_id: string;
 | 
			
		||||
			version: number;
 | 
			
		||||
			updated_at: string;
 | 
			
		||||
			unread_count?: number;
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								packages/megalodon/src/entities/mention.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/megalodon/src/entities/mention.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Mention = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		username: string;
 | 
			
		||||
		url: string;
 | 
			
		||||
		acct: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								packages/megalodon/src/entities/notification.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								packages/megalodon/src/entities/notification.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,15 @@
 | 
			
		|||
/// <reference path="account.ts" />
 | 
			
		||||
/// <reference path="status.ts" />
 | 
			
		||||
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type Notification = {
 | 
			
		||||
		account: Account;
 | 
			
		||||
		created_at: string;
 | 
			
		||||
		id: string;
 | 
			
		||||
		status?: Status;
 | 
			
		||||
		reaction?: Reaction;
 | 
			
		||||
		type: NotificationType;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export type NotificationType = string;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								packages/megalodon/src/entities/poll.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/megalodon/src/entities/poll.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
/// <reference path="poll_option.ts" />
 | 
			
		||||
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type Poll = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		expires_at: string | null;
 | 
			
		||||
		expired: boolean;
 | 
			
		||||
		multiple: boolean;
 | 
			
		||||
		votes_count: number;
 | 
			
		||||
		options: Array<PollOption>;
 | 
			
		||||
		voted: boolean;
 | 
			
		||||
		own_votes: Array<number>;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								packages/megalodon/src/entities/poll_option.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/megalodon/src/entities/poll_option.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type PollOption = {
 | 
			
		||||
		title: string;
 | 
			
		||||
		votes_count: number | null;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								packages/megalodon/src/entities/preferences.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/megalodon/src/entities/preferences.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Preferences = {
 | 
			
		||||
		"posting:default:visibility": "public" | "unlisted" | "private" | "direct";
 | 
			
		||||
		"posting:default:sensitive": boolean;
 | 
			
		||||
		"posting:default:language": string | null;
 | 
			
		||||
		"reading:expand:media": "default" | "show_all" | "hide_all";
 | 
			
		||||
		"reading:expand:spoilers": boolean;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								packages/megalodon/src/entities/push_subscription.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/megalodon/src/entities/push_subscription.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Alerts = {
 | 
			
		||||
		follow: boolean;
 | 
			
		||||
		favourite: boolean;
 | 
			
		||||
		mention: boolean;
 | 
			
		||||
		reblog: boolean;
 | 
			
		||||
		poll: boolean;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export type PushSubscription = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		endpoint: string;
 | 
			
		||||
		server_key: string;
 | 
			
		||||
		alerts: Alerts;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								packages/megalodon/src/entities/reaction.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/megalodon/src/entities/reaction.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
/// <reference path="account.ts" />
 | 
			
		||||
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type Reaction = {
 | 
			
		||||
		count: number;
 | 
			
		||||
		me: boolean;
 | 
			
		||||
		name: string;
 | 
			
		||||
		url?: string;
 | 
			
		||||
		static_url?: string;
 | 
			
		||||
		accounts?: Array<Account>;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								packages/megalodon/src/entities/relationship.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								packages/megalodon/src/entities/relationship.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Relationship = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		following: boolean;
 | 
			
		||||
		followed_by: boolean;
 | 
			
		||||
		delivery_following?: boolean;
 | 
			
		||||
		blocking: boolean;
 | 
			
		||||
		blocked_by: boolean;
 | 
			
		||||
		muting: boolean;
 | 
			
		||||
		muting_notifications: boolean;
 | 
			
		||||
		requested: boolean;
 | 
			
		||||
		domain_blocking: boolean;
 | 
			
		||||
		showing_reblogs: boolean;
 | 
			
		||||
		endorsed: boolean;
 | 
			
		||||
		notifying: boolean;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								packages/megalodon/src/entities/report.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/megalodon/src/entities/report.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Report = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		action_taken: string;
 | 
			
		||||
		comment: string;
 | 
			
		||||
		account_id: string;
 | 
			
		||||
		status_ids: Array<string>;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								packages/megalodon/src/entities/results.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/megalodon/src/entities/results.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
/// <reference path="account.ts" />
 | 
			
		||||
/// <reference path="status.ts" />
 | 
			
		||||
/// <reference path="tag.ts" />
 | 
			
		||||
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type Results = {
 | 
			
		||||
		accounts: Array<Account>;
 | 
			
		||||
		statuses: Array<Status>;
 | 
			
		||||
		hashtags: Array<Tag>;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								packages/megalodon/src/entities/scheduled_status.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/megalodon/src/entities/scheduled_status.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
/// <reference path="attachment.ts" />
 | 
			
		||||
/// <reference path="status_params.ts" />
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type ScheduledStatus = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		scheduled_at: string;
 | 
			
		||||
		params: StatusParams;
 | 
			
		||||
		media_attachments: Array<Attachment>;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								packages/megalodon/src/entities/source.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/megalodon/src/entities/source.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
/// <reference path="field.ts" />
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type Source = {
 | 
			
		||||
		privacy: string | null;
 | 
			
		||||
		sensitive: boolean | null;
 | 
			
		||||
		language: string | null;
 | 
			
		||||
		note: string;
 | 
			
		||||
		fields: Array<Field>;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/megalodon/src/entities/stats.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/megalodon/src/entities/stats.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Stats = {
 | 
			
		||||
		user_count: number;
 | 
			
		||||
		status_count: number;
 | 
			
		||||
		domain_count: number;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								packages/megalodon/src/entities/status.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								packages/megalodon/src/entities/status.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
/// <reference path="account.ts" />
 | 
			
		||||
/// <reference path="application.ts" />
 | 
			
		||||
/// <reference path="mention.ts" />
 | 
			
		||||
/// <reference path="tag.ts" />
 | 
			
		||||
/// <reference path="attachment.ts" />
 | 
			
		||||
/// <reference path="emoji.ts" />
 | 
			
		||||
/// <reference path="card.ts" />
 | 
			
		||||
/// <reference path="poll.ts" />
 | 
			
		||||
/// <reference path="reaction.ts" />
 | 
			
		||||
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type Status = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		uri: string;
 | 
			
		||||
		url: string;
 | 
			
		||||
		account: Account;
 | 
			
		||||
		in_reply_to_id: string | null;
 | 
			
		||||
		in_reply_to_account_id: string | null;
 | 
			
		||||
		reblog: Status | null;
 | 
			
		||||
		content: string;
 | 
			
		||||
		plain_content: string | null;
 | 
			
		||||
		created_at: string;
 | 
			
		||||
		emojis: Emoji[];
 | 
			
		||||
		replies_count: number;
 | 
			
		||||
		reblogs_count: number;
 | 
			
		||||
		favourites_count: number;
 | 
			
		||||
		reblogged: boolean | null;
 | 
			
		||||
		favourited: boolean | null;
 | 
			
		||||
		muted: boolean | null;
 | 
			
		||||
		sensitive: boolean;
 | 
			
		||||
		spoiler_text: string;
 | 
			
		||||
		visibility: "public" | "unlisted" | "private" | "direct";
 | 
			
		||||
		media_attachments: Array<Attachment>;
 | 
			
		||||
		mentions: Array<Mention>;
 | 
			
		||||
		tags: Array<Tag>;
 | 
			
		||||
		card: Card | null;
 | 
			
		||||
		poll: Poll | null;
 | 
			
		||||
		application: Application | null;
 | 
			
		||||
		language: string | null;
 | 
			
		||||
		pinned: boolean | null;
 | 
			
		||||
		reactions: Array<Reaction>;
 | 
			
		||||
		quote: Status | null;
 | 
			
		||||
		bookmarked: boolean;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								packages/megalodon/src/entities/status_edit.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								packages/megalodon/src/entities/status_edit.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
/// <reference path="account.ts" />
 | 
			
		||||
/// <reference path="application.ts" />
 | 
			
		||||
/// <reference path="mention.ts" />
 | 
			
		||||
/// <reference path="tag.ts" />
 | 
			
		||||
/// <reference path="attachment.ts" />
 | 
			
		||||
/// <reference path="emoji.ts" />
 | 
			
		||||
/// <reference path="card.ts" />
 | 
			
		||||
/// <reference path="poll.ts" />
 | 
			
		||||
/// <reference path="reaction.ts" />
 | 
			
		||||
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type StatusEdit = {
 | 
			
		||||
		account: Account;
 | 
			
		||||
		content: string;
 | 
			
		||||
		plain_content: string | null;
 | 
			
		||||
		created_at: string;
 | 
			
		||||
		emojis: Emoji[];
 | 
			
		||||
		sensitive: boolean;
 | 
			
		||||
		spoiler_text: string;
 | 
			
		||||
		media_attachments: Array<Attachment>;
 | 
			
		||||
		poll: Poll | null;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								packages/megalodon/src/entities/status_params.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/megalodon/src/entities/status_params.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type StatusParams = {
 | 
			
		||||
		text: string;
 | 
			
		||||
		in_reply_to_id: string | null;
 | 
			
		||||
		media_ids: Array<string> | null;
 | 
			
		||||
		sensitive: boolean | null;
 | 
			
		||||
		spoiler_text: string | null;
 | 
			
		||||
		visibility: "public" | "unlisted" | "private" | "direct";
 | 
			
		||||
		scheduled_at: string | null;
 | 
			
		||||
		application_id: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								packages/megalodon/src/entities/tag.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/megalodon/src/entities/tag.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
/// <reference path="history.ts" />
 | 
			
		||||
 | 
			
		||||
namespace Entity {
 | 
			
		||||
	export type Tag = {
 | 
			
		||||
		name: string;
 | 
			
		||||
		url: string;
 | 
			
		||||
		history: Array<History> | null;
 | 
			
		||||
		following?: boolean;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								packages/megalodon/src/entities/token.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/megalodon/src/entities/token.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type Token = {
 | 
			
		||||
		access_token: string;
 | 
			
		||||
		token_type: string;
 | 
			
		||||
		scope: string;
 | 
			
		||||
		created_at: number;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								packages/megalodon/src/entities/urls.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								packages/megalodon/src/entities/urls.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
namespace Entity {
 | 
			
		||||
	export type URLs = {
 | 
			
		||||
		streaming_api: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								packages/megalodon/src/entity.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								packages/megalodon/src/entity.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
/// <reference path="./entities/account.ts" />
 | 
			
		||||
/// <reference path="./entities/activity.ts" />
 | 
			
		||||
/// <reference path="./entities/announcement.ts" />
 | 
			
		||||
/// <reference path="./entities/application.ts" />
 | 
			
		||||
/// <reference path="./entities/async_attachment.ts" />
 | 
			
		||||
/// <reference path="./entities/attachment.ts" />
 | 
			
		||||
/// <reference path="./entities/card.ts" />
 | 
			
		||||
/// <reference path="./entities/context.ts" />
 | 
			
		||||
/// <reference path="./entities/conversation.ts" />
 | 
			
		||||
/// <reference path="./entities/emoji.ts" />
 | 
			
		||||
/// <reference path="./entities/featured_tag.ts" />
 | 
			
		||||
/// <reference path="./entities/field.ts" />
 | 
			
		||||
/// <reference path="./entities/filter.ts" />
 | 
			
		||||
/// <reference path="./entities/history.ts" />
 | 
			
		||||
/// <reference path="./entities/identity_proof.ts" />
 | 
			
		||||
/// <reference path="./entities/instance.ts" />
 | 
			
		||||
/// <reference path="./entities/list.ts" />
 | 
			
		||||
/// <reference path="./entities/marker.ts" />
 | 
			
		||||
/// <reference path="./entities/mention.ts" />
 | 
			
		||||
/// <reference path="./entities/notification.ts" />
 | 
			
		||||
/// <reference path="./entities/poll.ts" />
 | 
			
		||||
/// <reference path="./entities/poll_option.ts" />
 | 
			
		||||
/// <reference path="./entities/preferences.ts" />
 | 
			
		||||
/// <reference path="./entities/push_subscription.ts" />
 | 
			
		||||
/// <reference path="./entities/reaction.ts" />
 | 
			
		||||
/// <reference path="./entities/relationship.ts" />
 | 
			
		||||
/// <reference path="./entities/report.ts" />
 | 
			
		||||
/// <reference path="./entities/results.ts" />
 | 
			
		||||
/// <reference path="./entities/scheduled_status.ts" />
 | 
			
		||||
/// <reference path="./entities/source.ts" />
 | 
			
		||||
/// <reference path="./entities/stats.ts" />
 | 
			
		||||
/// <reference path="./entities/status.ts" />
 | 
			
		||||
/// <reference path="./entities/status_params.ts" />
 | 
			
		||||
/// <reference path="./entities/tag.ts" />
 | 
			
		||||
/// <reference path="./entities/token.ts" />
 | 
			
		||||
/// <reference path="./entities/urls.ts" />
 | 
			
		||||
 | 
			
		||||
export default Entity;
 | 
			
		||||
							
								
								
									
										11
									
								
								packages/megalodon/src/filter_context.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/megalodon/src/filter_context.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
import Entity from "./entity";
 | 
			
		||||
 | 
			
		||||
namespace FilterContext {
 | 
			
		||||
	export const Home: Entity.FilterContext = "home";
 | 
			
		||||
	export const Notifications: Entity.FilterContext = "notifications";
 | 
			
		||||
	export const Public: Entity.FilterContext = "public";
 | 
			
		||||
	export const Thread: Entity.FilterContext = "thread";
 | 
			
		||||
	export const Account: Entity.FilterContext = "account";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default FilterContext;
 | 
			
		||||
							
								
								
									
										32
									
								
								packages/megalodon/src/index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								packages/megalodon/src/index.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
import Response from "./response";
 | 
			
		||||
import OAuth from "./oauth";
 | 
			
		||||
import { isCancel, RequestCanceledError } from "./cancel";
 | 
			
		||||
import { ProxyConfig } from "./proxy_config";
 | 
			
		||||
import generator, {
 | 
			
		||||
	detector,
 | 
			
		||||
	MegalodonInterface,
 | 
			
		||||
	WebSocketInterface,
 | 
			
		||||
} from "./megalodon";
 | 
			
		||||
import Misskey from "./misskey";
 | 
			
		||||
import Entity from "./entity";
 | 
			
		||||
import NotificationType from "./notification";
 | 
			
		||||
import FilterContext from "./filter_context";
 | 
			
		||||
import Converter from "./converter";
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
	Response,
 | 
			
		||||
	OAuth,
 | 
			
		||||
	RequestCanceledError,
 | 
			
		||||
	isCancel,
 | 
			
		||||
	ProxyConfig,
 | 
			
		||||
	detector,
 | 
			
		||||
	MegalodonInterface,
 | 
			
		||||
	WebSocketInterface,
 | 
			
		||||
	NotificationType,
 | 
			
		||||
	FilterContext,
 | 
			
		||||
	Misskey,
 | 
			
		||||
	Entity,
 | 
			
		||||
	Converter,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default generator;
 | 
			
		||||
							
								
								
									
										1532
									
								
								packages/megalodon/src/megalodon.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1532
									
								
								packages/megalodon/src/megalodon.ts
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										3436
									
								
								packages/megalodon/src/misskey.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3436
									
								
								packages/megalodon/src/misskey.ts
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										727
									
								
								packages/megalodon/src/misskey/api_client.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										727
									
								
								packages/megalodon/src/misskey/api_client.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,727 @@
 | 
			
		|||
import axios, { AxiosResponse, AxiosRequestConfig } from "axios";
 | 
			
		||||
import dayjs from "dayjs";
 | 
			
		||||
import FormData from "form-data";
 | 
			
		||||
 | 
			
		||||
import { DEFAULT_UA } from "../default";
 | 
			
		||||
import proxyAgent, { ProxyConfig } from "../proxy_config";
 | 
			
		||||
import Response from "../response";
 | 
			
		||||
import MisskeyEntity from "./entity";
 | 
			
		||||
import MegalodonEntity from "../entity";
 | 
			
		||||
import WebSocket from "./web_socket";
 | 
			
		||||
import MisskeyNotificationType from "./notification";
 | 
			
		||||
import NotificationType from "../notification";
 | 
			
		||||
 | 
			
		||||
namespace MisskeyAPI {
 | 
			
		||||
	export namespace Entity {
 | 
			
		||||
		export type App = MisskeyEntity.App;
 | 
			
		||||
		export type Announcement = MisskeyEntity.Announcement;
 | 
			
		||||
		export type Blocking = MisskeyEntity.Blocking;
 | 
			
		||||
		export type Choice = MisskeyEntity.Choice;
 | 
			
		||||
		export type CreatedNote = MisskeyEntity.CreatedNote;
 | 
			
		||||
		export type Emoji = MisskeyEntity.Emoji;
 | 
			
		||||
		export type Favorite = MisskeyEntity.Favorite;
 | 
			
		||||
		export type Field = MisskeyEntity.Field;
 | 
			
		||||
		export type File = MisskeyEntity.File;
 | 
			
		||||
		export type Follower = MisskeyEntity.Follower;
 | 
			
		||||
		export type Following = MisskeyEntity.Following;
 | 
			
		||||
		export type FollowRequest = MisskeyEntity.FollowRequest;
 | 
			
		||||
		export type Hashtag = MisskeyEntity.Hashtag;
 | 
			
		||||
		export type List = MisskeyEntity.List;
 | 
			
		||||
		export type Meta = MisskeyEntity.Meta;
 | 
			
		||||
		export type Mute = MisskeyEntity.Mute;
 | 
			
		||||
		export type Note = MisskeyEntity.Note;
 | 
			
		||||
		export type Notification = MisskeyEntity.Notification;
 | 
			
		||||
		export type Poll = MisskeyEntity.Poll;
 | 
			
		||||
		export type Reaction = MisskeyEntity.Reaction;
 | 
			
		||||
		export type Relation = MisskeyEntity.Relation;
 | 
			
		||||
		export type User = MisskeyEntity.User;
 | 
			
		||||
		export type UserDetail = MisskeyEntity.UserDetail;
 | 
			
		||||
		export type UserDetailMe = MisskeyEntity.UserDetailMe;
 | 
			
		||||
		export type GetAll = MisskeyEntity.GetAll;
 | 
			
		||||
		export type UserKey = MisskeyEntity.UserKey;
 | 
			
		||||
		export type Session = MisskeyEntity.Session;
 | 
			
		||||
		export type Stats = MisskeyEntity.Stats;
 | 
			
		||||
		export type State = MisskeyEntity.State;
 | 
			
		||||
		export type APIEmoji = { emojis: Emoji[] };
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	export class Converter {
 | 
			
		||||
		private baseUrl: string;
 | 
			
		||||
		private instanceHost: string;
 | 
			
		||||
		private plcUrl: string;
 | 
			
		||||
		private modelOfAcct = {
 | 
			
		||||
			id: "1",
 | 
			
		||||
			username: "none",
 | 
			
		||||
			acct: "none",
 | 
			
		||||
			display_name: "none",
 | 
			
		||||
			locked: true,
 | 
			
		||||
			bot: true,
 | 
			
		||||
			discoverable: false,
 | 
			
		||||
			group: false,
 | 
			
		||||
			created_at: "1971-01-01T00:00:00.000Z",
 | 
			
		||||
			note: "",
 | 
			
		||||
			url: "plc",
 | 
			
		||||
			avatar: "plc",
 | 
			
		||||
			avatar_static: "plc",
 | 
			
		||||
			header: "plc",
 | 
			
		||||
			header_static: "plc",
 | 
			
		||||
			followers_count: -1,
 | 
			
		||||
			following_count: 0,
 | 
			
		||||
			statuses_count: 0,
 | 
			
		||||
			last_status_at: "1971-01-01T00:00:00.000Z",
 | 
			
		||||
			noindex: true,
 | 
			
		||||
			emojis: [],
 | 
			
		||||
			fields: [],
 | 
			
		||||
			moved: null,
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		constructor(baseUrl: string) {
 | 
			
		||||
			this.baseUrl = baseUrl;
 | 
			
		||||
			this.instanceHost = baseUrl.substring(baseUrl.indexOf("//") + 2);
 | 
			
		||||
			this.plcUrl = `${baseUrl}/static-assets/transparent.png`;
 | 
			
		||||
			this.modelOfAcct.url = this.plcUrl;
 | 
			
		||||
			this.modelOfAcct.avatar = this.plcUrl;
 | 
			
		||||
			this.modelOfAcct.avatar_static = this.plcUrl;
 | 
			
		||||
			this.modelOfAcct.header = this.plcUrl;
 | 
			
		||||
			this.modelOfAcct.header_static = this.plcUrl;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// FIXME: Properly render MFM instead of just escaping HTML characters.
 | 
			
		||||
		escapeMFM = (text: string): string =>
 | 
			
		||||
			text
 | 
			
		||||
				.replace(/&/g, "&")
 | 
			
		||||
				.replace(/</g, "<")
 | 
			
		||||
				.replace(/>/g, ">")
 | 
			
		||||
				.replace(/"/g, """)
 | 
			
		||||
				.replace(/'/g, "'")
 | 
			
		||||
				.replace(/`/g, "`")
 | 
			
		||||
				.replace(/\r?\n/g, "<br>");
 | 
			
		||||
 | 
			
		||||
		emoji = (e: Entity.Emoji): MegalodonEntity.Emoji => {
 | 
			
		||||
			return {
 | 
			
		||||
				shortcode: e.name,
 | 
			
		||||
				static_url: e.url,
 | 
			
		||||
				url: e.url,
 | 
			
		||||
				visible_in_picker: true,
 | 
			
		||||
				category: e.category,
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		field = (f: Entity.Field): MegalodonEntity.Field => ({
 | 
			
		||||
			name: f.name,
 | 
			
		||||
			value: this.escapeMFM(f.value),
 | 
			
		||||
			verified_at: null,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		user = (u: Entity.User): MegalodonEntity.Account => {
 | 
			
		||||
			let acct = u.username;
 | 
			
		||||
			let acctUrl = `https://${u.host || this.instanceHost}/@${u.username}`;
 | 
			
		||||
			if (u.host) {
 | 
			
		||||
				acct = `${u.username}@${u.host}`;
 | 
			
		||||
				acctUrl = `https://${u.host}/@${u.username}`;
 | 
			
		||||
			}
 | 
			
		||||
			return {
 | 
			
		||||
				id: u.id,
 | 
			
		||||
				username: u.username,
 | 
			
		||||
				acct: acct,
 | 
			
		||||
				display_name: u.name || u.username,
 | 
			
		||||
				locked: false,
 | 
			
		||||
				created_at: new Date().toISOString(),
 | 
			
		||||
				followers_count: 0,
 | 
			
		||||
				following_count: 0,
 | 
			
		||||
				statuses_count: 0,
 | 
			
		||||
				note: "",
 | 
			
		||||
				url: acctUrl,
 | 
			
		||||
				avatar: u.avatarUrl,
 | 
			
		||||
				avatar_static: u.avatarUrl,
 | 
			
		||||
				header: this.plcUrl,
 | 
			
		||||
				header_static: this.plcUrl,
 | 
			
		||||
				emojis: u.emojis.map((e) => this.emoji(e)),
 | 
			
		||||
				moved: null,
 | 
			
		||||
				fields: [],
 | 
			
		||||
				bot: false,
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		userDetail = (
 | 
			
		||||
			u: Entity.UserDetail,
 | 
			
		||||
			host: string,
 | 
			
		||||
		): MegalodonEntity.Account => {
 | 
			
		||||
			let acct = u.username;
 | 
			
		||||
			host = host.replace("https://", "");
 | 
			
		||||
			let acctUrl = `https://${host || u.host || this.instanceHost}/@${
 | 
			
		||||
				u.username
 | 
			
		||||
			}`;
 | 
			
		||||
			if (u.host) {
 | 
			
		||||
				acct = `${u.username}@${u.host}`;
 | 
			
		||||
				acctUrl = `https://${u.host}/@${u.username}`;
 | 
			
		||||
			}
 | 
			
		||||
			return {
 | 
			
		||||
				id: u.id,
 | 
			
		||||
				username: u.username,
 | 
			
		||||
				acct: acct,
 | 
			
		||||
				display_name: u.name || u.username,
 | 
			
		||||
				locked: u.isLocked,
 | 
			
		||||
				created_at: u.createdAt,
 | 
			
		||||
				followers_count: u.followersCount,
 | 
			
		||||
				following_count: u.followingCount,
 | 
			
		||||
				statuses_count: u.notesCount,
 | 
			
		||||
				note: u.description?.replace(/\n|\\n/g, "<br>") ?? "",
 | 
			
		||||
				url: acctUrl,
 | 
			
		||||
				avatar: u.avatarUrl,
 | 
			
		||||
				avatar_static: u.avatarUrl,
 | 
			
		||||
				header: u.bannerUrl ?? this.plcUrl,
 | 
			
		||||
				header_static: u.bannerUrl ?? this.plcUrl,
 | 
			
		||||
				emojis: u.emojis && u.emojis.length > 0 ? u.emojis.map((e) => this.emoji(e)) : [],
 | 
			
		||||
				moved: null,
 | 
			
		||||
				fields: u.fields.map((f) => this.field(f)),
 | 
			
		||||
				bot: u.isBot,
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		userPreferences = (
 | 
			
		||||
			u: MisskeyAPI.Entity.UserDetailMe,
 | 
			
		||||
			v: "public" | "unlisted" | "private" | "direct",
 | 
			
		||||
		): MegalodonEntity.Preferences => {
 | 
			
		||||
			return {
 | 
			
		||||
				"reading:expand:media": "default",
 | 
			
		||||
				"reading:expand:spoilers": false,
 | 
			
		||||
				"posting:default:language": u.lang,
 | 
			
		||||
				"posting:default:sensitive": u.alwaysMarkNsfw,
 | 
			
		||||
				"posting:default:visibility": v,
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		visibility = (
 | 
			
		||||
			v: "public" | "home" | "followers" | "specified",
 | 
			
		||||
		): "public" | "unlisted" | "private" | "direct" => {
 | 
			
		||||
			switch (v) {
 | 
			
		||||
				case "public":
 | 
			
		||||
					return v;
 | 
			
		||||
				case "home":
 | 
			
		||||
					return "unlisted";
 | 
			
		||||
				case "followers":
 | 
			
		||||
					return "private";
 | 
			
		||||
				case "specified":
 | 
			
		||||
					return "direct";
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		encodeVisibility = (
 | 
			
		||||
			v: "public" | "unlisted" | "private" | "direct",
 | 
			
		||||
		): "public" | "home" | "followers" | "specified" => {
 | 
			
		||||
			switch (v) {
 | 
			
		||||
				case "public":
 | 
			
		||||
					return v;
 | 
			
		||||
				case "unlisted":
 | 
			
		||||
					return "home";
 | 
			
		||||
				case "private":
 | 
			
		||||
					return "followers";
 | 
			
		||||
				case "direct":
 | 
			
		||||
					return "specified";
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		fileType = (
 | 
			
		||||
			s: string,
 | 
			
		||||
		): "unknown" | "image" | "gifv" | "video" | "audio" => {
 | 
			
		||||
			if (s === "image/gif") {
 | 
			
		||||
				return "gifv";
 | 
			
		||||
			}
 | 
			
		||||
			if (s.includes("image")) {
 | 
			
		||||
				return "image";
 | 
			
		||||
			}
 | 
			
		||||
			if (s.includes("video")) {
 | 
			
		||||
				return "video";
 | 
			
		||||
			}
 | 
			
		||||
			if (s.includes("audio")) {
 | 
			
		||||
				return "audio";
 | 
			
		||||
			}
 | 
			
		||||
			return "unknown";
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		file = (f: Entity.File): MegalodonEntity.Attachment => {
 | 
			
		||||
			return {
 | 
			
		||||
				id: f.id,
 | 
			
		||||
				type: this.fileType(f.type),
 | 
			
		||||
				url: f.url,
 | 
			
		||||
				remote_url: f.url,
 | 
			
		||||
				preview_url: f.thumbnailUrl,
 | 
			
		||||
				text_url: f.url,
 | 
			
		||||
				meta: {
 | 
			
		||||
					width: f.properties.width,
 | 
			
		||||
					height: f.properties.height,
 | 
			
		||||
				},
 | 
			
		||||
				description: f.comment,
 | 
			
		||||
				blurhash: f.blurhash,
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		follower = (f: Entity.Follower): MegalodonEntity.Account => {
 | 
			
		||||
			return this.user(f.follower);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		following = (f: Entity.Following): MegalodonEntity.Account => {
 | 
			
		||||
			return this.user(f.followee);
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		relation = (r: Entity.Relation): MegalodonEntity.Relationship => {
 | 
			
		||||
			return {
 | 
			
		||||
				id: r.id,
 | 
			
		||||
				following: r.isFollowing,
 | 
			
		||||
				followed_by: r.isFollowed,
 | 
			
		||||
				blocking: r.isBlocking,
 | 
			
		||||
				blocked_by: r.isBlocked,
 | 
			
		||||
				muting: r.isMuted,
 | 
			
		||||
				muting_notifications: false,
 | 
			
		||||
				requested: r.hasPendingFollowRequestFromYou,
 | 
			
		||||
				domain_blocking: false,
 | 
			
		||||
				showing_reblogs: true,
 | 
			
		||||
				endorsed: false,
 | 
			
		||||
				notifying: false,
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		choice = (c: Entity.Choice): MegalodonEntity.PollOption => {
 | 
			
		||||
			return {
 | 
			
		||||
				title: c.text,
 | 
			
		||||
				votes_count: c.votes,
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		poll = (p: Entity.Poll, id: string): MegalodonEntity.Poll => {
 | 
			
		||||
			const now = dayjs();
 | 
			
		||||
			const expire = dayjs(p.expiresAt);
 | 
			
		||||
			const count = p.choices.reduce((sum, choice) => sum + choice.votes, 0);
 | 
			
		||||
			return {
 | 
			
		||||
				id: id,
 | 
			
		||||
				expires_at: p.expiresAt,
 | 
			
		||||
				expired: now.isAfter(expire),
 | 
			
		||||
				multiple: p.multiple,
 | 
			
		||||
				votes_count: count,
 | 
			
		||||
				options: p.choices.map((c) => this.choice(c)),
 | 
			
		||||
				voted: p.choices.some((c) => c.isVoted),
 | 
			
		||||
				own_votes: p.choices
 | 
			
		||||
					.filter((c) => c.isVoted)
 | 
			
		||||
					.map((c) => p.choices.indexOf(c)),
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		note = (n: Entity.Note, host: string): MegalodonEntity.Status => {
 | 
			
		||||
			host = host.replace("https://", "");
 | 
			
		||||
 | 
			
		||||
			return {
 | 
			
		||||
				id: n.id,
 | 
			
		||||
				uri: n.uri ? n.uri : `https://${host}/notes/${n.id}`,
 | 
			
		||||
				url: n.uri ? n.uri : `https://${host}/notes/${n.id}`,
 | 
			
		||||
				account: this.user(n.user),
 | 
			
		||||
				in_reply_to_id: n.replyId,
 | 
			
		||||
				in_reply_to_account_id: n.reply?.userId ?? null,
 | 
			
		||||
				reblog: n.renote ? this.note(n.renote, host) : null,
 | 
			
		||||
				content: n.text ? this.escapeMFM(n.text) : "",
 | 
			
		||||
				plain_content: n.text ? n.text : null,
 | 
			
		||||
				created_at: n.createdAt,
 | 
			
		||||
				// Remove reaction emojis with names containing @ from the emojis list.
 | 
			
		||||
				emojis: n.emojis
 | 
			
		||||
					.filter((e) => e.name.indexOf("@") === -1)
 | 
			
		||||
					.map((e) => this.emoji(e)),
 | 
			
		||||
				replies_count: n.repliesCount,
 | 
			
		||||
				reblogs_count: n.renoteCount,
 | 
			
		||||
				favourites_count: this.getTotalReactions(n.reactions),
 | 
			
		||||
				reblogged: false,
 | 
			
		||||
				favourited: !!n.myReaction,
 | 
			
		||||
				muted: false,
 | 
			
		||||
				sensitive: n.files ? n.files.some((f) => f.isSensitive) : false,
 | 
			
		||||
				spoiler_text: n.cw ? n.cw : "",
 | 
			
		||||
				visibility: this.visibility(n.visibility),
 | 
			
		||||
				media_attachments: n.files ? n.files.map((f) => this.file(f)) : [],
 | 
			
		||||
				mentions: [],
 | 
			
		||||
				tags: [],
 | 
			
		||||
				card: null,
 | 
			
		||||
				poll: n.poll ? this.poll(n.poll, n.id) : null,
 | 
			
		||||
				application: null,
 | 
			
		||||
				language: null,
 | 
			
		||||
				pinned: null,
 | 
			
		||||
				// Use emojis list to provide URLs for emoji reactions.
 | 
			
		||||
				reactions: this.mapReactions(n.emojis, n.reactions, n.myReaction),
 | 
			
		||||
				bookmarked: false,
 | 
			
		||||
				quote: n.renote && n.text ? this.note(n.renote, host) : null,
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		mapReactions = (
 | 
			
		||||
			emojis: Array<MisskeyEntity.Emoji>,
 | 
			
		||||
			r: { [key: string]: number },
 | 
			
		||||
			myReaction?: string,
 | 
			
		||||
		): Array<MegalodonEntity.Reaction> => {
 | 
			
		||||
			// Map of emoji shortcodes to image URLs.
 | 
			
		||||
			const emojiUrls = new Map<string, string>(
 | 
			
		||||
				emojis.map((e) => [e.name, e.url]),
 | 
			
		||||
			);
 | 
			
		||||
			return Object.keys(r).map((key) => {
 | 
			
		||||
				// Strip colons from custom emoji reaction names to match emoji shortcodes.
 | 
			
		||||
				const shortcode = key.replaceAll(":", "");
 | 
			
		||||
				// If this is a custom emoji (vs. a Unicode emoji), find its image URL.
 | 
			
		||||
				const url = emojiUrls.get(shortcode);
 | 
			
		||||
				// Finally, remove trailing @. from local custom emoji reaction names.
 | 
			
		||||
				const name = shortcode.replace("@.", "");
 | 
			
		||||
				return {
 | 
			
		||||
					count: r[key],
 | 
			
		||||
					me: key === myReaction,
 | 
			
		||||
					name,
 | 
			
		||||
					url,
 | 
			
		||||
					// We don't actually have a static version of the asset, but clients expect one anyway.
 | 
			
		||||
					static_url: url,
 | 
			
		||||
				};
 | 
			
		||||
			});
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		getTotalReactions = (r: { [key: string]: number }): number => {
 | 
			
		||||
			return Object.values(r).length > 0
 | 
			
		||||
				? Object.values(r).reduce(
 | 
			
		||||
						(previousValue, currentValue) => previousValue + currentValue,
 | 
			
		||||
				  )
 | 
			
		||||
				: 0;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		reactions = (
 | 
			
		||||
			r: Array<Entity.Reaction>,
 | 
			
		||||
		): Array<MegalodonEntity.Reaction> => {
 | 
			
		||||
			const result: Array<MegalodonEntity.Reaction> = [];
 | 
			
		||||
			for (const e of r) {
 | 
			
		||||
				const i = result.findIndex((res) => res.name === e.type);
 | 
			
		||||
				if (i >= 0) {
 | 
			
		||||
					result[i].count++;
 | 
			
		||||
				} else {
 | 
			
		||||
					result.push({
 | 
			
		||||
						count: 1,
 | 
			
		||||
						me: false,
 | 
			
		||||
						name: e.type,
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return result;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		noteToConversation = (
 | 
			
		||||
			n: Entity.Note,
 | 
			
		||||
			host: string,
 | 
			
		||||
		): MegalodonEntity.Conversation => {
 | 
			
		||||
			const accounts: Array<MegalodonEntity.Account> = [this.user(n.user)];
 | 
			
		||||
			if (n.reply) {
 | 
			
		||||
				accounts.push(this.user(n.reply.user));
 | 
			
		||||
			}
 | 
			
		||||
			return {
 | 
			
		||||
				id: n.id,
 | 
			
		||||
				accounts: accounts,
 | 
			
		||||
				last_status: this.note(n, host),
 | 
			
		||||
				unread: false,
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		list = (l: Entity.List): MegalodonEntity.List => ({
 | 
			
		||||
			id: l.id,
 | 
			
		||||
			title: l.name,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		encodeNotificationType = (
 | 
			
		||||
			e: MegalodonEntity.NotificationType,
 | 
			
		||||
		): MisskeyEntity.NotificationType => {
 | 
			
		||||
			switch (e) {
 | 
			
		||||
				case NotificationType.Follow:
 | 
			
		||||
					return MisskeyNotificationType.Follow;
 | 
			
		||||
				case NotificationType.Mention:
 | 
			
		||||
					return MisskeyNotificationType.Reply;
 | 
			
		||||
				case NotificationType.Favourite:
 | 
			
		||||
				case NotificationType.Reaction:
 | 
			
		||||
					return MisskeyNotificationType.Reaction;
 | 
			
		||||
				case NotificationType.Reblog:
 | 
			
		||||
					return MisskeyNotificationType.Renote;
 | 
			
		||||
				case NotificationType.Poll:
 | 
			
		||||
					return MisskeyNotificationType.PollEnded;
 | 
			
		||||
				case NotificationType.FollowRequest:
 | 
			
		||||
					return MisskeyNotificationType.ReceiveFollowRequest;
 | 
			
		||||
				default:
 | 
			
		||||
					return e;
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		decodeNotificationType = (
 | 
			
		||||
			e: MisskeyEntity.NotificationType,
 | 
			
		||||
		): MegalodonEntity.NotificationType => {
 | 
			
		||||
			switch (e) {
 | 
			
		||||
				case MisskeyNotificationType.Follow:
 | 
			
		||||
					return NotificationType.Follow;
 | 
			
		||||
				case MisskeyNotificationType.Mention:
 | 
			
		||||
				case MisskeyNotificationType.Reply:
 | 
			
		||||
					return NotificationType.Mention;
 | 
			
		||||
				case MisskeyNotificationType.Renote:
 | 
			
		||||
				case MisskeyNotificationType.Quote:
 | 
			
		||||
					return NotificationType.Reblog;
 | 
			
		||||
				case MisskeyNotificationType.Reaction:
 | 
			
		||||
					return NotificationType.Reaction;
 | 
			
		||||
				case MisskeyNotificationType.PollEnded:
 | 
			
		||||
					return NotificationType.Poll;
 | 
			
		||||
				case MisskeyNotificationType.ReceiveFollowRequest:
 | 
			
		||||
					return NotificationType.FollowRequest;
 | 
			
		||||
				case MisskeyNotificationType.FollowRequestAccepted:
 | 
			
		||||
					return NotificationType.Follow;
 | 
			
		||||
				default:
 | 
			
		||||
					return e;
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		announcement = (a: Entity.Announcement): MegalodonEntity.Announcement => ({
 | 
			
		||||
			id: a.id,
 | 
			
		||||
			content: `<h1>${this.escapeMFM(a.title)}</h1>${this.escapeMFM(a.text)}`,
 | 
			
		||||
			starts_at: null,
 | 
			
		||||
			ends_at: null,
 | 
			
		||||
			published: true,
 | 
			
		||||
			all_day: false,
 | 
			
		||||
			published_at: a.createdAt,
 | 
			
		||||
			updated_at: a.updatedAt,
 | 
			
		||||
			read: a.isRead,
 | 
			
		||||
			mentions: [],
 | 
			
		||||
			statuses: [],
 | 
			
		||||
			tags: [],
 | 
			
		||||
			emojis: [],
 | 
			
		||||
			reactions: [],
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		notification = (
 | 
			
		||||
			n: Entity.Notification,
 | 
			
		||||
			host: string,
 | 
			
		||||
		): MegalodonEntity.Notification => {
 | 
			
		||||
			let notification = {
 | 
			
		||||
				id: n.id,
 | 
			
		||||
				account: n.user ? this.user(n.user) : this.modelOfAcct,
 | 
			
		||||
				created_at: n.createdAt,
 | 
			
		||||
				type: this.decodeNotificationType(n.type),
 | 
			
		||||
			};
 | 
			
		||||
			if (n.note) {
 | 
			
		||||
				notification = Object.assign(notification, {
 | 
			
		||||
					status: this.note(n.note, host),
 | 
			
		||||
				});
 | 
			
		||||
				if (notification.type === NotificationType.Poll) {
 | 
			
		||||
					notification = Object.assign(notification, {
 | 
			
		||||
						account: this.note(n.note, host).account,
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
				if (n.reaction) {
 | 
			
		||||
					notification = Object.assign(notification, {
 | 
			
		||||
						reaction: this.mapReactions(n.note.emojis, { [n.reaction]: 1 })[0],
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return notification;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		stats = (s: Entity.Stats): MegalodonEntity.Stats => {
 | 
			
		||||
			return {
 | 
			
		||||
				user_count: s.usersCount,
 | 
			
		||||
				status_count: s.notesCount,
 | 
			
		||||
				domain_count: s.instances,
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		meta = (m: Entity.Meta, s: Entity.Stats): MegalodonEntity.Instance => {
 | 
			
		||||
			const wss = m.uri.replace(/^https:\/\//, "wss://");
 | 
			
		||||
			return {
 | 
			
		||||
				uri: m.uri,
 | 
			
		||||
				title: m.name,
 | 
			
		||||
				description: m.description,
 | 
			
		||||
				email: m.maintainerEmail,
 | 
			
		||||
				version: m.version,
 | 
			
		||||
				thumbnail: m.bannerUrl,
 | 
			
		||||
				urls: {
 | 
			
		||||
					streaming_api: `${wss}/streaming`,
 | 
			
		||||
				},
 | 
			
		||||
				stats: this.stats(s),
 | 
			
		||||
				languages: m.langs,
 | 
			
		||||
				contact_account: null,
 | 
			
		||||
				max_toot_chars: m.maxNoteTextLength,
 | 
			
		||||
				registrations: !m.disableRegistration,
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		hashtag = (h: Entity.Hashtag): MegalodonEntity.Tag => {
 | 
			
		||||
			return {
 | 
			
		||||
				name: h.tag,
 | 
			
		||||
				url: h.tag,
 | 
			
		||||
				history: null,
 | 
			
		||||
				following: false,
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	export const DEFAULT_SCOPE = [
 | 
			
		||||
		"read:account",
 | 
			
		||||
		"write:account",
 | 
			
		||||
		"read:blocks",
 | 
			
		||||
		"write:blocks",
 | 
			
		||||
		"read:drive",
 | 
			
		||||
		"write:drive",
 | 
			
		||||
		"read:favorites",
 | 
			
		||||
		"write:favorites",
 | 
			
		||||
		"read:following",
 | 
			
		||||
		"write:following",
 | 
			
		||||
		"read:mutes",
 | 
			
		||||
		"write:mutes",
 | 
			
		||||
		"write:notes",
 | 
			
		||||
		"read:notifications",
 | 
			
		||||
		"write:notifications",
 | 
			
		||||
		"read:reactions",
 | 
			
		||||
		"write:reactions",
 | 
			
		||||
		"write:votes",
 | 
			
		||||
	];
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Interface
 | 
			
		||||
	 */
 | 
			
		||||
	export interface Interface {
 | 
			
		||||
		post<T = any>(
 | 
			
		||||
			path: string,
 | 
			
		||||
			params?: any,
 | 
			
		||||
			headers?: { [key: string]: string },
 | 
			
		||||
		): Promise<Response<T>>;
 | 
			
		||||
		cancel(): void;
 | 
			
		||||
		socket(
 | 
			
		||||
			channel:
 | 
			
		||||
				| "user"
 | 
			
		||||
				| "localTimeline"
 | 
			
		||||
				| "hybridTimeline"
 | 
			
		||||
				| "globalTimeline"
 | 
			
		||||
				| "conversation"
 | 
			
		||||
				| "list",
 | 
			
		||||
			listId?: string,
 | 
			
		||||
		): WebSocket;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Misskey API client.
 | 
			
		||||
	 *
 | 
			
		||||
	 * Usign axios for request, you will handle promises.
 | 
			
		||||
	 */
 | 
			
		||||
	export class Client implements Interface {
 | 
			
		||||
		private accessToken: string | null;
 | 
			
		||||
		private baseUrl: string;
 | 
			
		||||
		private userAgent: string;
 | 
			
		||||
		private abortController: AbortController;
 | 
			
		||||
		private proxyConfig: ProxyConfig | false = false;
 | 
			
		||||
		private converter: Converter;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * @param baseUrl hostname or base URL
 | 
			
		||||
		 * @param accessToken access token from OAuth2 authorization
 | 
			
		||||
		 * @param userAgent UserAgent is specified in header on request.
 | 
			
		||||
		 * @param proxyConfig Proxy setting, or set false if don't use proxy.
 | 
			
		||||
		 * @param converter Converter instance.
 | 
			
		||||
		 */
 | 
			
		||||
		constructor(
 | 
			
		||||
			baseUrl: string,
 | 
			
		||||
			accessToken: string | null,
 | 
			
		||||
			userAgent: string = DEFAULT_UA,
 | 
			
		||||
			proxyConfig: ProxyConfig | false = false,
 | 
			
		||||
			converter: Converter,
 | 
			
		||||
		) {
 | 
			
		||||
			this.accessToken = accessToken;
 | 
			
		||||
			this.baseUrl = baseUrl;
 | 
			
		||||
			this.userAgent = userAgent;
 | 
			
		||||
			this.proxyConfig = proxyConfig;
 | 
			
		||||
			this.abortController = new AbortController();
 | 
			
		||||
			this.converter = converter;
 | 
			
		||||
			axios.defaults.signal = this.abortController.signal;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * POST request to mastodon REST API.
 | 
			
		||||
		 * @param path relative path from baseUrl
 | 
			
		||||
		 * @param params Form data
 | 
			
		||||
		 * @param headers Request header object
 | 
			
		||||
		 */
 | 
			
		||||
		public async post<T>(
 | 
			
		||||
			path: string,
 | 
			
		||||
			params: any = {},
 | 
			
		||||
			headers: { [key: string]: string } = {},
 | 
			
		||||
		): Promise<Response<T>> {
 | 
			
		||||
			let options: AxiosRequestConfig = {
 | 
			
		||||
				headers: headers,
 | 
			
		||||
				maxContentLength: Infinity,
 | 
			
		||||
				maxBodyLength: Infinity,
 | 
			
		||||
			};
 | 
			
		||||
			if (this.proxyConfig) {
 | 
			
		||||
				options = Object.assign(options, {
 | 
			
		||||
					httpAgent: proxyAgent(this.proxyConfig),
 | 
			
		||||
					httpsAgent: proxyAgent(this.proxyConfig),
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
			let bodyParams = params;
 | 
			
		||||
			if (this.accessToken) {
 | 
			
		||||
				if (params instanceof FormData) {
 | 
			
		||||
					bodyParams.append("i", this.accessToken);
 | 
			
		||||
				} else {
 | 
			
		||||
					bodyParams = Object.assign(params, {
 | 
			
		||||
						i: this.accessToken,
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return axios
 | 
			
		||||
				.post<T>(this.baseUrl + path, bodyParams, options)
 | 
			
		||||
				.then((resp: AxiosResponse<T>) => {
 | 
			
		||||
					const res: Response<T> = {
 | 
			
		||||
						data: resp.data,
 | 
			
		||||
						status: resp.status,
 | 
			
		||||
						statusText: resp.statusText,
 | 
			
		||||
						headers: resp.headers,
 | 
			
		||||
					};
 | 
			
		||||
					return res;
 | 
			
		||||
				});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Cancel all requests in this instance.
 | 
			
		||||
		 * @returns void
 | 
			
		||||
		 */
 | 
			
		||||
		public cancel(): void {
 | 
			
		||||
			return this.abortController.abort();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Get connection and receive websocket connection for Misskey API.
 | 
			
		||||
		 *
 | 
			
		||||
		 * @param channel Channel name is user, localTimeline, hybridTimeline, globalTimeline, conversation or list.
 | 
			
		||||
		 * @param listId This parameter is required only list channel.
 | 
			
		||||
		 */
 | 
			
		||||
		public socket(
 | 
			
		||||
			channel:
 | 
			
		||||
				| "user"
 | 
			
		||||
				| "localTimeline"
 | 
			
		||||
				| "hybridTimeline"
 | 
			
		||||
				| "globalTimeline"
 | 
			
		||||
				| "conversation"
 | 
			
		||||
				| "list",
 | 
			
		||||
			listId?: string,
 | 
			
		||||
		): WebSocket {
 | 
			
		||||
			if (!this.accessToken) {
 | 
			
		||||
				throw new Error("accessToken is required");
 | 
			
		||||
			}
 | 
			
		||||
			const url = `${this.baseUrl}/streaming`;
 | 
			
		||||
			const streaming = new WebSocket(
 | 
			
		||||
				url,
 | 
			
		||||
				channel,
 | 
			
		||||
				this.accessToken,
 | 
			
		||||
				listId,
 | 
			
		||||
				this.userAgent,
 | 
			
		||||
				this.proxyConfig,
 | 
			
		||||
				this.converter,
 | 
			
		||||
			);
 | 
			
		||||
			process.nextTick(() => {
 | 
			
		||||
				streaming.start();
 | 
			
		||||
			});
 | 
			
		||||
			return streaming;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default MisskeyAPI;
 | 
			
		||||
							
								
								
									
										6
									
								
								packages/megalodon/src/misskey/entities/GetAll.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/megalodon/src/misskey/entities/GetAll.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type GetAll = {
 | 
			
		||||
		tutorial: number;
 | 
			
		||||
		defaultNoteVisibility: "public" | "home" | "followers" | "specified";
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								packages/megalodon/src/misskey/entities/announcement.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/megalodon/src/misskey/entities/announcement.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type Announcement = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		updatedAt: string;
 | 
			
		||||
		text: string;
 | 
			
		||||
		title: string;
 | 
			
		||||
		isRead?: boolean;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								packages/megalodon/src/misskey/entities/app.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/megalodon/src/misskey/entities/app.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type App = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		name: string;
 | 
			
		||||
		callbackUrl: string;
 | 
			
		||||
		permission: Array<string>;
 | 
			
		||||
		secret: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								packages/megalodon/src/misskey/entities/blocking.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/megalodon/src/misskey/entities/blocking.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
/// <reference path="userDetail.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type Blocking = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		blockeeId: string;
 | 
			
		||||
		blockee: UserDetail;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/megalodon/src/misskey/entities/createdNote.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/megalodon/src/misskey/entities/createdNote.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
/// <reference path="note.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type CreatedNote = {
 | 
			
		||||
		createdNote: Note;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								packages/megalodon/src/misskey/entities/emoji.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/megalodon/src/misskey/entities/emoji.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type Emoji = {
 | 
			
		||||
		name: string;
 | 
			
		||||
		host: string | null;
 | 
			
		||||
		url: string;
 | 
			
		||||
		aliases: Array<string>;
 | 
			
		||||
		category: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								packages/megalodon/src/misskey/entities/favorite.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/megalodon/src/misskey/entities/favorite.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
/// <reference path="note.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type Favorite = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		noteId: string;
 | 
			
		||||
		note: Note;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/megalodon/src/misskey/entities/field.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/megalodon/src/misskey/entities/field.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type Field = {
 | 
			
		||||
		name: string;
 | 
			
		||||
		value: string;
 | 
			
		||||
		verified?: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								packages/megalodon/src/misskey/entities/file.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								packages/megalodon/src/misskey/entities/file.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type File = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		name: string;
 | 
			
		||||
		type: string;
 | 
			
		||||
		md5: string;
 | 
			
		||||
		size: number;
 | 
			
		||||
		isSensitive: boolean;
 | 
			
		||||
		properties: {
 | 
			
		||||
			width: number;
 | 
			
		||||
			height: number;
 | 
			
		||||
			avgColor: string;
 | 
			
		||||
		};
 | 
			
		||||
		url: string;
 | 
			
		||||
		thumbnailUrl: string;
 | 
			
		||||
		comment: string;
 | 
			
		||||
		blurhash: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								packages/megalodon/src/misskey/entities/followRequest.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/megalodon/src/misskey/entities/followRequest.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
/// <reference path="user.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type FollowRequest = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		follower: User;
 | 
			
		||||
		followee: User;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								packages/megalodon/src/misskey/entities/follower.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/megalodon/src/misskey/entities/follower.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
/// <reference path="userDetail.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type Follower = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		followeeId: string;
 | 
			
		||||
		followerId: string;
 | 
			
		||||
		follower: UserDetail;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								packages/megalodon/src/misskey/entities/following.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/megalodon/src/misskey/entities/following.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
/// <reference path="userDetail.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type Following = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		followeeId: string;
 | 
			
		||||
		followerId: string;
 | 
			
		||||
		followee: UserDetail;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/megalodon/src/misskey/entities/hashtag.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/megalodon/src/misskey/entities/hashtag.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type Hashtag = {
 | 
			
		||||
		tag: string;
 | 
			
		||||
		chart: Array<number>;
 | 
			
		||||
		usersCount: number;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								packages/megalodon/src/misskey/entities/list.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/megalodon/src/misskey/entities/list.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type List = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		name: string;
 | 
			
		||||
		userIds: Array<string>;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								packages/megalodon/src/misskey/entities/meta.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/megalodon/src/misskey/entities/meta.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
/// <reference path="emoji.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type Meta = {
 | 
			
		||||
		maintainerName: string;
 | 
			
		||||
		maintainerEmail: string;
 | 
			
		||||
		name: string;
 | 
			
		||||
		version: string;
 | 
			
		||||
		uri: string;
 | 
			
		||||
		description: string;
 | 
			
		||||
		langs: Array<string>;
 | 
			
		||||
		disableRegistration: boolean;
 | 
			
		||||
		disableLocalTimeline: boolean;
 | 
			
		||||
		bannerUrl: string;
 | 
			
		||||
		maxNoteTextLength: 3000;
 | 
			
		||||
		emojis: Array<Emoji>;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								packages/megalodon/src/misskey/entities/mute.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/megalodon/src/misskey/entities/mute.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
/// <reference path="userDetail.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type Mute = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		muteeId: string;
 | 
			
		||||
		mutee: UserDetail;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								packages/megalodon/src/misskey/entities/note.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								packages/megalodon/src/misskey/entities/note.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
/// <reference path="user.ts" />
 | 
			
		||||
/// <reference path="emoji.ts" />
 | 
			
		||||
/// <reference path="file.ts" />
 | 
			
		||||
/// <reference path="poll.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type Note = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		userId: string;
 | 
			
		||||
		user: User;
 | 
			
		||||
		text: string | null;
 | 
			
		||||
		cw: string | null;
 | 
			
		||||
		visibility: "public" | "home" | "followers" | "specified";
 | 
			
		||||
		renoteCount: number;
 | 
			
		||||
		repliesCount: number;
 | 
			
		||||
		reactions: { [key: string]: number };
 | 
			
		||||
		emojis: Array<Emoji>;
 | 
			
		||||
		fileIds: Array<string>;
 | 
			
		||||
		files: Array<File>;
 | 
			
		||||
		replyId: string | null;
 | 
			
		||||
		renoteId: string | null;
 | 
			
		||||
		uri?: string;
 | 
			
		||||
		reply?: Note;
 | 
			
		||||
		renote?: Note;
 | 
			
		||||
		viaMobile?: boolean;
 | 
			
		||||
		tags?: Array<string>;
 | 
			
		||||
		poll?: Poll;
 | 
			
		||||
		mentions?: Array<string>;
 | 
			
		||||
		myReaction?: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								packages/megalodon/src/misskey/entities/notification.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								packages/megalodon/src/misskey/entities/notification.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
/// <reference path="user.ts" />
 | 
			
		||||
/// <reference path="note.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type Notification = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		// https://github.com/syuilo/misskey/blob/056942391aee135eb6c77aaa63f6ed5741d701a6/src/models/entities/notification.ts#L50-L62
 | 
			
		||||
		type: NotificationType;
 | 
			
		||||
		userId: string;
 | 
			
		||||
		user: User;
 | 
			
		||||
		note?: Note;
 | 
			
		||||
		reaction?: string;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export type NotificationType = string;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								packages/megalodon/src/misskey/entities/poll.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/megalodon/src/misskey/entities/poll.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type Choice = {
 | 
			
		||||
		text: string;
 | 
			
		||||
		votes: number;
 | 
			
		||||
		isVoted: boolean;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export type Poll = {
 | 
			
		||||
		multiple: boolean;
 | 
			
		||||
		expiresAt: string;
 | 
			
		||||
		choices: Array<Choice>;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								packages/megalodon/src/misskey/entities/reaction.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/megalodon/src/misskey/entities/reaction.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
/// <reference path="user.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type Reaction = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		user: User;
 | 
			
		||||
		url?: string;
 | 
			
		||||
		type: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								packages/megalodon/src/misskey/entities/relation.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/megalodon/src/misskey/entities/relation.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type Relation = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		isFollowing: boolean;
 | 
			
		||||
		hasPendingFollowRequestFromYou: boolean;
 | 
			
		||||
		hasPendingFollowRequestToYou: boolean;
 | 
			
		||||
		isFollowed: boolean;
 | 
			
		||||
		isBlocking: boolean;
 | 
			
		||||
		isBlocked: boolean;
 | 
			
		||||
		isMuted: boolean;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								packages/megalodon/src/misskey/entities/session.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								packages/megalodon/src/misskey/entities/session.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type Session = {
 | 
			
		||||
		token: string;
 | 
			
		||||
		url: string;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								packages/megalodon/src/misskey/entities/state.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/megalodon/src/misskey/entities/state.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type State = {
 | 
			
		||||
		isFavorited: boolean;
 | 
			
		||||
		isMutedThread: boolean;
 | 
			
		||||
		isWatching: boolean;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								packages/megalodon/src/misskey/entities/stats.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/megalodon/src/misskey/entities/stats.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
namespace MisskeyEntity {
 | 
			
		||||
	export type Stats = {
 | 
			
		||||
		notesCount: number;
 | 
			
		||||
		originalNotesCount: number;
 | 
			
		||||
		usersCount: number;
 | 
			
		||||
		originalUsersCount: number;
 | 
			
		||||
		instances: number;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								packages/megalodon/src/misskey/entities/user.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/megalodon/src/misskey/entities/user.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
/// <reference path="emoji.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type User = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		name: string;
 | 
			
		||||
		username: string;
 | 
			
		||||
		host: string | null;
 | 
			
		||||
		avatarUrl: string;
 | 
			
		||||
		avatarColor: string;
 | 
			
		||||
		emojis: Array<Emoji>;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								packages/megalodon/src/misskey/entities/userDetail.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								packages/megalodon/src/misskey/entities/userDetail.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
/// <reference path="emoji.ts" />
 | 
			
		||||
/// <reference path="field.ts" />
 | 
			
		||||
/// <reference path="note.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type UserDetail = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		name: string;
 | 
			
		||||
		username: string;
 | 
			
		||||
		host: string | null;
 | 
			
		||||
		avatarUrl: string;
 | 
			
		||||
		avatarColor: string;
 | 
			
		||||
		isAdmin: boolean;
 | 
			
		||||
		isModerator: boolean;
 | 
			
		||||
		isBot: boolean;
 | 
			
		||||
		isCat: boolean;
 | 
			
		||||
		emojis: Array<Emoji>;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		bannerUrl: string;
 | 
			
		||||
		bannerColor: string;
 | 
			
		||||
		isLocked: boolean;
 | 
			
		||||
		isSilenced: boolean;
 | 
			
		||||
		isSuspended: boolean;
 | 
			
		||||
		description: string;
 | 
			
		||||
		followersCount: number;
 | 
			
		||||
		followingCount: number;
 | 
			
		||||
		notesCount: number;
 | 
			
		||||
		avatarId: string;
 | 
			
		||||
		bannerId: string;
 | 
			
		||||
		pinnedNoteIds?: Array<string>;
 | 
			
		||||
		pinnedNotes?: Array<Note>;
 | 
			
		||||
		fields: Array<Field>;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								packages/megalodon/src/misskey/entities/userDetailMe.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								packages/megalodon/src/misskey/entities/userDetailMe.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
/// <reference path="emoji.ts" />
 | 
			
		||||
/// <reference path="field.ts" />
 | 
			
		||||
/// <reference path="note.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type UserDetailMe = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		name: string;
 | 
			
		||||
		username: string;
 | 
			
		||||
		host: string | null;
 | 
			
		||||
		avatarUrl: string;
 | 
			
		||||
		avatarColor: string;
 | 
			
		||||
		isAdmin: boolean;
 | 
			
		||||
		isModerator: boolean;
 | 
			
		||||
		isBot: boolean;
 | 
			
		||||
		isCat: boolean;
 | 
			
		||||
		emojis: Array<Emoji>;
 | 
			
		||||
		createdAt: string;
 | 
			
		||||
		bannerUrl: string;
 | 
			
		||||
		bannerColor: string;
 | 
			
		||||
		isLocked: boolean;
 | 
			
		||||
		isSilenced: boolean;
 | 
			
		||||
		isSuspended: boolean;
 | 
			
		||||
		description: string;
 | 
			
		||||
		followersCount: number;
 | 
			
		||||
		followingCount: number;
 | 
			
		||||
		notesCount: number;
 | 
			
		||||
		avatarId: string;
 | 
			
		||||
		bannerId: string;
 | 
			
		||||
		pinnedNoteIds?: Array<string>;
 | 
			
		||||
		pinnedNotes?: Array<Note>;
 | 
			
		||||
		fields: Array<Field>;
 | 
			
		||||
		alwaysMarkNsfw: boolean;
 | 
			
		||||
		lang: string | null;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								packages/megalodon/src/misskey/entities/userkey.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/megalodon/src/misskey/entities/userkey.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
/// <reference path="user.ts" />
 | 
			
		||||
 | 
			
		||||
namespace MisskeyEntity {
 | 
			
		||||
	export type UserKey = {
 | 
			
		||||
		accessToken: string;
 | 
			
		||||
		user: User;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								packages/megalodon/src/misskey/entity.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								packages/megalodon/src/misskey/entity.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
/// <reference path="entities/app.ts" />
 | 
			
		||||
/// <reference path="entities/announcement.ts" />
 | 
			
		||||
/// <reference path="entities/blocking.ts" />
 | 
			
		||||
/// <reference path="entities/createdNote.ts" />
 | 
			
		||||
/// <reference path="entities/emoji.ts" />
 | 
			
		||||
/// <reference path="entities/favorite.ts" />
 | 
			
		||||
/// <reference path="entities/field.ts" />
 | 
			
		||||
/// <reference path="entities/file.ts" />
 | 
			
		||||
/// <reference path="entities/follower.ts" />
 | 
			
		||||
/// <reference path="entities/following.ts" />
 | 
			
		||||
/// <reference path="entities/followRequest.ts" />
 | 
			
		||||
/// <reference path="entities/hashtag.ts" />
 | 
			
		||||
/// <reference path="entities/list.ts" />
 | 
			
		||||
/// <reference path="entities/meta.ts" />
 | 
			
		||||
/// <reference path="entities/mute.ts" />
 | 
			
		||||
/// <reference path="entities/note.ts" />
 | 
			
		||||
/// <reference path="entities/notification.ts" />
 | 
			
		||||
/// <reference path="entities/poll.ts" />
 | 
			
		||||
/// <reference path="entities/reaction.ts" />
 | 
			
		||||
/// <reference path="entities/relation.ts" />
 | 
			
		||||
/// <reference path="entities/user.ts" />
 | 
			
		||||
/// <reference path="entities/userDetail.ts" />
 | 
			
		||||
/// <reference path="entities/userDetailMe.ts" />
 | 
			
		||||
/// <reference path="entities/userkey.ts" />
 | 
			
		||||
/// <reference path="entities/session.ts" />
 | 
			
		||||
/// <reference path="entities/stats.ts" />
 | 
			
		||||
 | 
			
		||||
export default MisskeyEntity;
 | 
			
		||||
							
								
								
									
										18
									
								
								packages/megalodon/src/misskey/notification.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/megalodon/src/misskey/notification.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
import MisskeyEntity from "./entity";
 | 
			
		||||
 | 
			
		||||
namespace MisskeyNotificationType {
 | 
			
		||||
	export const Follow: MisskeyEntity.NotificationType = "follow";
 | 
			
		||||
	export const Mention: MisskeyEntity.NotificationType = "mention";
 | 
			
		||||
	export const Reply: MisskeyEntity.NotificationType = "reply";
 | 
			
		||||
	export const Renote: MisskeyEntity.NotificationType = "renote";
 | 
			
		||||
	export const Quote: MisskeyEntity.NotificationType = "quote";
 | 
			
		||||
	export const Reaction: MisskeyEntity.NotificationType = "favourite";
 | 
			
		||||
	export const PollEnded: MisskeyEntity.NotificationType = "pollEnded";
 | 
			
		||||
	export const ReceiveFollowRequest: MisskeyEntity.NotificationType =
 | 
			
		||||
		"receiveFollowRequest";
 | 
			
		||||
	export const FollowRequestAccepted: MisskeyEntity.NotificationType =
 | 
			
		||||
		"followRequestAccepted";
 | 
			
		||||
	export const GroupInvited: MisskeyEntity.NotificationType = "groupInvited";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default MisskeyNotificationType;
 | 
			
		||||
							
								
								
									
										458
									
								
								packages/megalodon/src/misskey/web_socket.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										458
									
								
								packages/megalodon/src/misskey/web_socket.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,458 @@
 | 
			
		|||
import WS from "ws";
 | 
			
		||||
import dayjs, { Dayjs } from "dayjs";
 | 
			
		||||
import { v4 as uuid } from "uuid";
 | 
			
		||||
import { EventEmitter } from "events";
 | 
			
		||||
import { WebSocketInterface } from "../megalodon";
 | 
			
		||||
import proxyAgent, { ProxyConfig } from "../proxy_config";
 | 
			
		||||
import MisskeyAPI from "./api_client";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * WebSocket
 | 
			
		||||
 * Misskey is not support http streaming. It supports websocket instead of streaming.
 | 
			
		||||
 * So this class connect to Misskey server with WebSocket.
 | 
			
		||||
 */
 | 
			
		||||
export default class WebSocket
 | 
			
		||||
	extends EventEmitter
 | 
			
		||||
	implements WebSocketInterface
 | 
			
		||||
{
 | 
			
		||||
	public url: string;
 | 
			
		||||
	public channel:
 | 
			
		||||
		| "user"
 | 
			
		||||
		| "localTimeline"
 | 
			
		||||
		| "hybridTimeline"
 | 
			
		||||
		| "globalTimeline"
 | 
			
		||||
		| "conversation"
 | 
			
		||||
		| "list";
 | 
			
		||||
	public parser: any;
 | 
			
		||||
	public headers: { [key: string]: string };
 | 
			
		||||
	public proxyConfig: ProxyConfig | false = false;
 | 
			
		||||
	public listId: string | null = null;
 | 
			
		||||
	private _converter: MisskeyAPI.Converter;
 | 
			
		||||
	private _accessToken: string;
 | 
			
		||||
	private _reconnectInterval: number;
 | 
			
		||||
	private _reconnectMaxAttempts: number;
 | 
			
		||||
	private _reconnectCurrentAttempts: number;
 | 
			
		||||
	private _connectionClosed: boolean;
 | 
			
		||||
	private _client: WS | null = null;
 | 
			
		||||
	private _channelID: string;
 | 
			
		||||
	private _pongReceivedTimestamp: Dayjs;
 | 
			
		||||
	private _heartbeatInterval = 60000;
 | 
			
		||||
	private _pongWaiting = false;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @param url Full url of websocket: e.g. wss://misskey.io/streaming
 | 
			
		||||
	 * @param channel Channel name is user, localTimeline, hybridTimeline, globalTimeline, conversation or list.
 | 
			
		||||
	 * @param accessToken The access token.
 | 
			
		||||
	 * @param listId This parameter is required when you specify list as channel.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor(
 | 
			
		||||
		url: string,
 | 
			
		||||
		channel:
 | 
			
		||||
			| "user"
 | 
			
		||||
			| "localTimeline"
 | 
			
		||||
			| "hybridTimeline"
 | 
			
		||||
			| "globalTimeline"
 | 
			
		||||
			| "conversation"
 | 
			
		||||
			| "list",
 | 
			
		||||
		accessToken: string,
 | 
			
		||||
		listId: string | undefined,
 | 
			
		||||
		userAgent: string,
 | 
			
		||||
		proxyConfig: ProxyConfig | false = false,
 | 
			
		||||
		converter: MisskeyAPI.Converter,
 | 
			
		||||
	) {
 | 
			
		||||
		super();
 | 
			
		||||
		this.url = url;
 | 
			
		||||
		this.parser = new Parser();
 | 
			
		||||
		this.channel = channel;
 | 
			
		||||
		this.headers = {
 | 
			
		||||
			"User-Agent": userAgent,
 | 
			
		||||
		};
 | 
			
		||||
		if (listId === undefined) {
 | 
			
		||||
			this.listId = null;
 | 
			
		||||
		} else {
 | 
			
		||||
			this.listId = listId;
 | 
			
		||||
		}
 | 
			
		||||
		this.proxyConfig = proxyConfig;
 | 
			
		||||
		this._accessToken = accessToken;
 | 
			
		||||
		this._reconnectInterval = 10000;
 | 
			
		||||
		this._reconnectMaxAttempts = Infinity;
 | 
			
		||||
		this._reconnectCurrentAttempts = 0;
 | 
			
		||||
		this._connectionClosed = false;
 | 
			
		||||
		this._channelID = uuid();
 | 
			
		||||
		this._pongReceivedTimestamp = dayjs();
 | 
			
		||||
		this._converter = converter;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Start websocket connection.
 | 
			
		||||
	 */
 | 
			
		||||
	public start() {
 | 
			
		||||
		this._connectionClosed = false;
 | 
			
		||||
		this._resetRetryParams();
 | 
			
		||||
		this._startWebSocketConnection();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private baseUrlToHost(baseUrl: string): string {
 | 
			
		||||
		return baseUrl.replace("https://", "");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Reset connection and start new websocket connection.
 | 
			
		||||
	 */
 | 
			
		||||
	private _startWebSocketConnection() {
 | 
			
		||||
		this._resetConnection();
 | 
			
		||||
		this._setupParser();
 | 
			
		||||
		this._client = this._connect();
 | 
			
		||||
		this._bindSocket(this._client);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Stop current connection.
 | 
			
		||||
	 */
 | 
			
		||||
	public stop() {
 | 
			
		||||
		this._connectionClosed = true;
 | 
			
		||||
		this._resetConnection();
 | 
			
		||||
		this._resetRetryParams();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Clean up current connection, and listeners.
 | 
			
		||||
	 */
 | 
			
		||||
	private _resetConnection() {
 | 
			
		||||
		if (this._client) {
 | 
			
		||||
			this._client.close(1000);
 | 
			
		||||
			this._client.removeAllListeners();
 | 
			
		||||
			this._client = null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (this.parser) {
 | 
			
		||||
			this.parser.removeAllListeners();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Resets the parameters used in reconnect.
 | 
			
		||||
	 */
 | 
			
		||||
	private _resetRetryParams() {
 | 
			
		||||
		this._reconnectCurrentAttempts = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Connect to the endpoint.
 | 
			
		||||
	 */
 | 
			
		||||
	private _connect(): WS {
 | 
			
		||||
		let options: WS.ClientOptions = {
 | 
			
		||||
			headers: this.headers,
 | 
			
		||||
		};
 | 
			
		||||
		if (this.proxyConfig) {
 | 
			
		||||
			options = Object.assign(options, {
 | 
			
		||||
				agent: proxyAgent(this.proxyConfig),
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
		const cli: WS = new WS(`${this.url}?i=${this._accessToken}`, options);
 | 
			
		||||
		return cli;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Connect specified channels in websocket.
 | 
			
		||||
	 */
 | 
			
		||||
	private _channel() {
 | 
			
		||||
		if (!this._client) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		switch (this.channel) {
 | 
			
		||||
			case "conversation":
 | 
			
		||||
				this._client.send(
 | 
			
		||||
					JSON.stringify({
 | 
			
		||||
						type: "connect",
 | 
			
		||||
						body: {
 | 
			
		||||
							channel: "main",
 | 
			
		||||
							id: this._channelID,
 | 
			
		||||
						},
 | 
			
		||||
					}),
 | 
			
		||||
				);
 | 
			
		||||
				break;
 | 
			
		||||
			case "user":
 | 
			
		||||
				this._client.send(
 | 
			
		||||
					JSON.stringify({
 | 
			
		||||
						type: "connect",
 | 
			
		||||
						body: {
 | 
			
		||||
							channel: "main",
 | 
			
		||||
							id: this._channelID,
 | 
			
		||||
						},
 | 
			
		||||
					}),
 | 
			
		||||
				);
 | 
			
		||||
				this._client.send(
 | 
			
		||||
					JSON.stringify({
 | 
			
		||||
						type: "connect",
 | 
			
		||||
						body: {
 | 
			
		||||
							channel: "homeTimeline",
 | 
			
		||||
							id: this._channelID,
 | 
			
		||||
						},
 | 
			
		||||
					}),
 | 
			
		||||
				);
 | 
			
		||||
				break;
 | 
			
		||||
			case "list":
 | 
			
		||||
				this._client.send(
 | 
			
		||||
					JSON.stringify({
 | 
			
		||||
						type: "connect",
 | 
			
		||||
						body: {
 | 
			
		||||
							channel: "userList",
 | 
			
		||||
							id: this._channelID,
 | 
			
		||||
							params: {
 | 
			
		||||
								listId: this.listId,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					}),
 | 
			
		||||
				);
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
				this._client.send(
 | 
			
		||||
					JSON.stringify({
 | 
			
		||||
						type: "connect",
 | 
			
		||||
						body: {
 | 
			
		||||
							channel: this.channel,
 | 
			
		||||
							id: this._channelID,
 | 
			
		||||
						},
 | 
			
		||||
					}),
 | 
			
		||||
				);
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Reconnects to the same endpoint.
 | 
			
		||||
	 */
 | 
			
		||||
 | 
			
		||||
	private _reconnect() {
 | 
			
		||||
		setTimeout(() => {
 | 
			
		||||
			// Skip reconnect when client is connecting.
 | 
			
		||||
			// https://github.com/websockets/ws/blob/7.2.1/lib/websocket.js#L365
 | 
			
		||||
			if (this._client && this._client.readyState === WS.CONNECTING) {
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (this._reconnectCurrentAttempts < this._reconnectMaxAttempts) {
 | 
			
		||||
				this._reconnectCurrentAttempts++;
 | 
			
		||||
				this._clearBinding();
 | 
			
		||||
				if (this._client) {
 | 
			
		||||
					// In reconnect, we want to close the connection immediately,
 | 
			
		||||
					// because recoonect is necessary when some problems occur.
 | 
			
		||||
					this._client.terminate();
 | 
			
		||||
				}
 | 
			
		||||
				// Call connect methods
 | 
			
		||||
				console.log("Reconnecting");
 | 
			
		||||
				this._client = this._connect();
 | 
			
		||||
				this._bindSocket(this._client);
 | 
			
		||||
			}
 | 
			
		||||
		}, this._reconnectInterval);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Clear binding event for websocket client.
 | 
			
		||||
	 */
 | 
			
		||||
	private _clearBinding() {
 | 
			
		||||
		if (this._client) {
 | 
			
		||||
			this._client.removeAllListeners("close");
 | 
			
		||||
			this._client.removeAllListeners("pong");
 | 
			
		||||
			this._client.removeAllListeners("open");
 | 
			
		||||
			this._client.removeAllListeners("message");
 | 
			
		||||
			this._client.removeAllListeners("error");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Bind event for web socket client.
 | 
			
		||||
	 * @param client A WebSocket instance.
 | 
			
		||||
	 */
 | 
			
		||||
	private _bindSocket(client: WS) {
 | 
			
		||||
		client.on("close", (code: number, _reason: Buffer) => {
 | 
			
		||||
			if (code === 1000) {
 | 
			
		||||
				this.emit("close", {});
 | 
			
		||||
			} else {
 | 
			
		||||
				console.log(`Closed connection with ${code}`);
 | 
			
		||||
				if (!this._connectionClosed) {
 | 
			
		||||
					this._reconnect();
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		client.on("pong", () => {
 | 
			
		||||
			this._pongWaiting = false;
 | 
			
		||||
			this.emit("pong", {});
 | 
			
		||||
			this._pongReceivedTimestamp = dayjs();
 | 
			
		||||
			// It is required to anonymous function since get this scope in checkAlive.
 | 
			
		||||
			setTimeout(
 | 
			
		||||
				() => this._checkAlive(this._pongReceivedTimestamp),
 | 
			
		||||
				this._heartbeatInterval,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
		client.on("open", () => {
 | 
			
		||||
			this.emit("connect", {});
 | 
			
		||||
			this._channel();
 | 
			
		||||
			// Call first ping event.
 | 
			
		||||
			setTimeout(() => {
 | 
			
		||||
				client.ping("");
 | 
			
		||||
			}, 10000);
 | 
			
		||||
		});
 | 
			
		||||
		client.on("message", (data: WS.Data, isBinary: boolean) => {
 | 
			
		||||
			this.parser.parse(data, isBinary, this._channelID);
 | 
			
		||||
		});
 | 
			
		||||
		client.on("error", (err: Error) => {
 | 
			
		||||
			this.emit("error", err);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Set up parser when receive message.
 | 
			
		||||
	 */
 | 
			
		||||
	private _setupParser() {
 | 
			
		||||
		this.parser.on("update", (note: MisskeyAPI.Entity.Note) => {
 | 
			
		||||
			this.emit(
 | 
			
		||||
				"update",
 | 
			
		||||
				this._converter.note(note, this.baseUrlToHost(this.url)),
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
		this.parser.on(
 | 
			
		||||
			"notification",
 | 
			
		||||
			(notification: MisskeyAPI.Entity.Notification) => {
 | 
			
		||||
				this.emit(
 | 
			
		||||
					"notification",
 | 
			
		||||
					this._converter.notification(
 | 
			
		||||
						notification,
 | 
			
		||||
						this.baseUrlToHost(this.url),
 | 
			
		||||
					),
 | 
			
		||||
				);
 | 
			
		||||
			},
 | 
			
		||||
		);
 | 
			
		||||
		this.parser.on("conversation", (note: MisskeyAPI.Entity.Note) => {
 | 
			
		||||
			this.emit(
 | 
			
		||||
				"conversation",
 | 
			
		||||
				this._converter.noteToConversation(note, this.baseUrlToHost(this.url)),
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
		this.parser.on("error", (err: Error) => {
 | 
			
		||||
			this.emit("parser-error", err);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Call ping and wait to pong.
 | 
			
		||||
	 */
 | 
			
		||||
	private _checkAlive(timestamp: Dayjs) {
 | 
			
		||||
		const now: Dayjs = dayjs();
 | 
			
		||||
		// Block multiple calling, if multiple pong event occur.
 | 
			
		||||
		// It the duration is less than interval, through ping.
 | 
			
		||||
		if (
 | 
			
		||||
			now.diff(timestamp) > this._heartbeatInterval - 1000 &&
 | 
			
		||||
			!this._connectionClosed
 | 
			
		||||
		) {
 | 
			
		||||
			// Skip ping when client is connecting.
 | 
			
		||||
			// https://github.com/websockets/ws/blob/7.2.1/lib/websocket.js#L289
 | 
			
		||||
			if (this._client && this._client.readyState !== WS.CONNECTING) {
 | 
			
		||||
				this._pongWaiting = true;
 | 
			
		||||
				this._client.ping("");
 | 
			
		||||
				setTimeout(() => {
 | 
			
		||||
					if (this._pongWaiting) {
 | 
			
		||||
						this._pongWaiting = false;
 | 
			
		||||
						this._reconnect();
 | 
			
		||||
					}
 | 
			
		||||
				}, 10000);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Parser
 | 
			
		||||
 * This class provides parser for websocket message.
 | 
			
		||||
 */
 | 
			
		||||
export class Parser extends EventEmitter {
 | 
			
		||||
	/**
 | 
			
		||||
	 * @param message Message body of websocket.
 | 
			
		||||
	 * @param channelID Parse only messages which has same channelID.
 | 
			
		||||
	 */
 | 
			
		||||
	public parse(data: WS.Data, isBinary: boolean, channelID: string) {
 | 
			
		||||
		const message = isBinary ? data : data.toString();
 | 
			
		||||
		if (typeof message !== "string") {
 | 
			
		||||
			this.emit("heartbeat", {});
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (message === "") {
 | 
			
		||||
			this.emit("heartbeat", {});
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		let obj: {
 | 
			
		||||
			type: string;
 | 
			
		||||
			body: {
 | 
			
		||||
				id: string;
 | 
			
		||||
				type: string;
 | 
			
		||||
				body: any;
 | 
			
		||||
			};
 | 
			
		||||
		};
 | 
			
		||||
		let body: {
 | 
			
		||||
			id: string;
 | 
			
		||||
			type: string;
 | 
			
		||||
			body: any;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			obj = JSON.parse(message);
 | 
			
		||||
			if (obj.type !== "channel") {
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			if (!obj.body) {
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
			body = obj.body;
 | 
			
		||||
			if (body.id !== channelID) {
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		} catch (err) {
 | 
			
		||||
			this.emit(
 | 
			
		||||
				"error",
 | 
			
		||||
				new Error(
 | 
			
		||||
					`Error parsing websocket reply: ${message}, error message: ${err}`,
 | 
			
		||||
				),
 | 
			
		||||
			);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		switch (body.type) {
 | 
			
		||||
			case "note":
 | 
			
		||||
				this.emit("update", body.body as MisskeyAPI.Entity.Note);
 | 
			
		||||
				break;
 | 
			
		||||
			case "notification":
 | 
			
		||||
				this.emit("notification", body.body as MisskeyAPI.Entity.Notification);
 | 
			
		||||
				break;
 | 
			
		||||
			case "mention": {
 | 
			
		||||
				const note = body.body as MisskeyAPI.Entity.Note;
 | 
			
		||||
				if (note.visibility === "specified") {
 | 
			
		||||
					this.emit("conversation", note);
 | 
			
		||||
				}
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
			// When renote and followed event, the same notification will be received.
 | 
			
		||||
			case "renote":
 | 
			
		||||
			case "followed":
 | 
			
		||||
			case "follow":
 | 
			
		||||
			case "unfollow":
 | 
			
		||||
			case "receiveFollowRequest":
 | 
			
		||||
			case "meUpdated":
 | 
			
		||||
			case "readAllNotifications":
 | 
			
		||||
			case "readAllUnreadSpecifiedNotes":
 | 
			
		||||
			case "readAllAntennas":
 | 
			
		||||
			case "readAllUnreadMentions":
 | 
			
		||||
			case "unreadNotification":
 | 
			
		||||
				// Ignore these events
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
				this.emit(
 | 
			
		||||
					"error",
 | 
			
		||||
					new Error(`Unknown event has received: ${JSON.stringify(body)}`),
 | 
			
		||||
				);
 | 
			
		||||
				break;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								packages/megalodon/src/notification.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/megalodon/src/notification.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
import Entity from "./entity";
 | 
			
		||||
 | 
			
		||||
namespace NotificationType {
 | 
			
		||||
	export const Follow: Entity.NotificationType = "follow";
 | 
			
		||||
	export const Favourite: Entity.NotificationType = "favourite";
 | 
			
		||||
	export const Reblog: Entity.NotificationType = "reblog";
 | 
			
		||||
	export const Mention: Entity.NotificationType = "mention";
 | 
			
		||||
	export const Reaction: Entity.NotificationType = "reaction";
 | 
			
		||||
	export const FollowRequest: Entity.NotificationType = "follow_request";
 | 
			
		||||
	export const Status: Entity.NotificationType = "status";
 | 
			
		||||
	export const Poll: Entity.NotificationType = "poll";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default NotificationType;
 | 
			
		||||
							
								
								
									
										123
									
								
								packages/megalodon/src/oauth.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								packages/megalodon/src/oauth.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,123 @@
 | 
			
		|||
/**
 | 
			
		||||
 * OAuth
 | 
			
		||||
 * Response data when oauth request.
 | 
			
		||||
 **/
 | 
			
		||||
namespace OAuth {
 | 
			
		||||
	export type AppDataFromServer = {
 | 
			
		||||
		id: string;
 | 
			
		||||
		name: string;
 | 
			
		||||
		website: string | null;
 | 
			
		||||
		redirect_uri: string;
 | 
			
		||||
		client_id: string;
 | 
			
		||||
		client_secret: string;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export type TokenDataFromServer = {
 | 
			
		||||
		access_token: string;
 | 
			
		||||
		token_type: string;
 | 
			
		||||
		scope: string;
 | 
			
		||||
		created_at: number;
 | 
			
		||||
		expires_in: number | null;
 | 
			
		||||
		refresh_token: string | null;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	export class AppData {
 | 
			
		||||
		public url: string | null;
 | 
			
		||||
		public session_token: string | null;
 | 
			
		||||
		constructor(
 | 
			
		||||
			public id: string,
 | 
			
		||||
			public name: string,
 | 
			
		||||
			public website: string | null,
 | 
			
		||||
			public redirect_uri: string,
 | 
			
		||||
			public client_id: string,
 | 
			
		||||
			public client_secret: string,
 | 
			
		||||
		) {
 | 
			
		||||
			this.url = null;
 | 
			
		||||
			this.session_token = null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Serialize raw application data from server
 | 
			
		||||
		 * @param raw from server
 | 
			
		||||
		 */
 | 
			
		||||
		static from(raw: AppDataFromServer) {
 | 
			
		||||
			return new this(
 | 
			
		||||
				raw.id,
 | 
			
		||||
				raw.name,
 | 
			
		||||
				raw.website,
 | 
			
		||||
				raw.redirect_uri,
 | 
			
		||||
				raw.client_id,
 | 
			
		||||
				raw.client_secret,
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		get redirectUri() {
 | 
			
		||||
			return this.redirect_uri;
 | 
			
		||||
		}
 | 
			
		||||
		get clientId() {
 | 
			
		||||
			return this.client_id;
 | 
			
		||||
		}
 | 
			
		||||
		get clientSecret() {
 | 
			
		||||
			return this.client_secret;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	export class TokenData {
 | 
			
		||||
		public _scope: string;
 | 
			
		||||
		constructor(
 | 
			
		||||
			public access_token: string,
 | 
			
		||||
			public token_type: string,
 | 
			
		||||
			scope: string,
 | 
			
		||||
			public created_at: number,
 | 
			
		||||
			public expires_in: number | null = null,
 | 
			
		||||
			public refresh_token: string | null = null,
 | 
			
		||||
		) {
 | 
			
		||||
			this._scope = scope;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Serialize raw token data from server
 | 
			
		||||
		 * @param raw from server
 | 
			
		||||
		 */
 | 
			
		||||
		static from(raw: TokenDataFromServer) {
 | 
			
		||||
			return new this(
 | 
			
		||||
				raw.access_token,
 | 
			
		||||
				raw.token_type,
 | 
			
		||||
				raw.scope,
 | 
			
		||||
				raw.created_at,
 | 
			
		||||
				raw.expires_in,
 | 
			
		||||
				raw.refresh_token,
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * OAuth Aceess Token
 | 
			
		||||
		 */
 | 
			
		||||
		get accessToken() {
 | 
			
		||||
			return this.access_token;
 | 
			
		||||
		}
 | 
			
		||||
		get tokenType() {
 | 
			
		||||
			return this.token_type;
 | 
			
		||||
		}
 | 
			
		||||
		get scope() {
 | 
			
		||||
			return this._scope;
 | 
			
		||||
		}
 | 
			
		||||
		/**
 | 
			
		||||
		 * Application ID
 | 
			
		||||
		 */
 | 
			
		||||
		get createdAt() {
 | 
			
		||||
			return this.created_at;
 | 
			
		||||
		}
 | 
			
		||||
		get expiresIn() {
 | 
			
		||||
			return this.expires_in;
 | 
			
		||||
		}
 | 
			
		||||
		/**
 | 
			
		||||
		 * OAuth Refresh Token
 | 
			
		||||
		 */
 | 
			
		||||
		get refreshToken() {
 | 
			
		||||
			return this.refresh_token;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default OAuth;
 | 
			
		||||
							
								
								
									
										94
									
								
								packages/megalodon/src/parser.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								packages/megalodon/src/parser.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,94 @@
 | 
			
		|||
import { EventEmitter } from "events";
 | 
			
		||||
import Entity from "./entity";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Parser
 | 
			
		||||
 * Parse response data in streaming.
 | 
			
		||||
 **/
 | 
			
		||||
export class Parser extends EventEmitter {
 | 
			
		||||
	private message: string;
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		super();
 | 
			
		||||
		this.message = "";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public parse(chunk: string) {
 | 
			
		||||
		// skip heartbeats
 | 
			
		||||
		if (chunk === ":thump\n") {
 | 
			
		||||
			this.emit("heartbeat", {});
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this.message += chunk;
 | 
			
		||||
		chunk = this.message;
 | 
			
		||||
 | 
			
		||||
		const size: number = chunk.length;
 | 
			
		||||
		let start = 0;
 | 
			
		||||
		let offset = 0;
 | 
			
		||||
		let curr: string | undefined;
 | 
			
		||||
		let next: string | undefined;
 | 
			
		||||
 | 
			
		||||
		while (offset < size) {
 | 
			
		||||
			curr = chunk[offset];
 | 
			
		||||
			next = chunk[offset + 1];
 | 
			
		||||
 | 
			
		||||
			if (curr === "\n" && next === "\n") {
 | 
			
		||||
				const piece: string = chunk.slice(start, offset);
 | 
			
		||||
 | 
			
		||||
				offset += 2;
 | 
			
		||||
				start = offset;
 | 
			
		||||
 | 
			
		||||
				if (!piece.length) continue; // empty object
 | 
			
		||||
 | 
			
		||||
				const root: Array<string> = piece.split("\n");
 | 
			
		||||
 | 
			
		||||
				// should never happen, as long as mastodon doesn't change API messages
 | 
			
		||||
				if (root.length !== 2) continue;
 | 
			
		||||
 | 
			
		||||
				// remove event and data markers
 | 
			
		||||
				const event: string = root[0].substr(7);
 | 
			
		||||
				const data: string = root[1].substr(6);
 | 
			
		||||
 | 
			
		||||
				let jsonObj = {};
 | 
			
		||||
				try {
 | 
			
		||||
					jsonObj = JSON.parse(data);
 | 
			
		||||
				} catch (err) {
 | 
			
		||||
					// delete event does not have json object
 | 
			
		||||
					if (event !== "delete") {
 | 
			
		||||
						this.emit(
 | 
			
		||||
							"error",
 | 
			
		||||
							new Error(
 | 
			
		||||
								`Error parsing API reply: '${piece}', error message: '${err}'`,
 | 
			
		||||
							),
 | 
			
		||||
						);
 | 
			
		||||
						continue;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				switch (event) {
 | 
			
		||||
					case "update":
 | 
			
		||||
						this.emit("update", jsonObj as Entity.Status);
 | 
			
		||||
						break;
 | 
			
		||||
					case "notification":
 | 
			
		||||
						this.emit("notification", jsonObj as Entity.Notification);
 | 
			
		||||
						break;
 | 
			
		||||
					case "conversation":
 | 
			
		||||
						this.emit("conversation", jsonObj as Entity.Conversation);
 | 
			
		||||
						break;
 | 
			
		||||
					case "delete":
 | 
			
		||||
						// When delete, data is an ID of the deleted status
 | 
			
		||||
						this.emit("delete", data);
 | 
			
		||||
						break;
 | 
			
		||||
					default:
 | 
			
		||||
						this.emit(
 | 
			
		||||
							"error",
 | 
			
		||||
							new Error(`Unknown event has received: ${event}`),
 | 
			
		||||
						);
 | 
			
		||||
						continue;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			offset++;
 | 
			
		||||
		}
 | 
			
		||||
		this.message = chunk.slice(start, size);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										92
									
								
								packages/megalodon/src/proxy_config.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								packages/megalodon/src/proxy_config.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,92 @@
 | 
			
		|||
import { HttpsProxyAgent, HttpsProxyAgentOptions } from "https-proxy-agent";
 | 
			
		||||
import { SocksProxyAgent, SocksProxyAgentOptions } from "socks-proxy-agent";
 | 
			
		||||
 | 
			
		||||
export type ProxyConfig = {
 | 
			
		||||
	host: string;
 | 
			
		||||
	port: number;
 | 
			
		||||
	auth?: {
 | 
			
		||||
		username: string;
 | 
			
		||||
		password: string;
 | 
			
		||||
	};
 | 
			
		||||
	protocol:
 | 
			
		||||
		| "http"
 | 
			
		||||
		| "https"
 | 
			
		||||
		| "socks4"
 | 
			
		||||
		| "socks4a"
 | 
			
		||||
		| "socks5"
 | 
			
		||||
		| "socks5h"
 | 
			
		||||
		| "socks";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ProxyProtocolError extends Error {}
 | 
			
		||||
 | 
			
		||||
const proxyAgent = (
 | 
			
		||||
	proxyConfig: ProxyConfig,
 | 
			
		||||
): HttpsProxyAgent | SocksProxyAgent => {
 | 
			
		||||
	switch (proxyConfig.protocol) {
 | 
			
		||||
		case "http": {
 | 
			
		||||
			let options: HttpsProxyAgentOptions = {
 | 
			
		||||
				host: proxyConfig.host,
 | 
			
		||||
				port: proxyConfig.port,
 | 
			
		||||
				secureProxy: false,
 | 
			
		||||
			};
 | 
			
		||||
			if (proxyConfig.auth) {
 | 
			
		||||
				options = Object.assign(options, {
 | 
			
		||||
					auth: `${proxyConfig.auth.username}:${proxyConfig.auth.password}`,
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
			const httpsAgent = new HttpsProxyAgent(options);
 | 
			
		||||
			return httpsAgent;
 | 
			
		||||
		}
 | 
			
		||||
		case "https": {
 | 
			
		||||
			let options: HttpsProxyAgentOptions = {
 | 
			
		||||
				host: proxyConfig.host,
 | 
			
		||||
				port: proxyConfig.port,
 | 
			
		||||
				secureProxy: true,
 | 
			
		||||
			};
 | 
			
		||||
			if (proxyConfig.auth) {
 | 
			
		||||
				options = Object.assign(options, {
 | 
			
		||||
					auth: `${proxyConfig.auth.username}:${proxyConfig.auth.password}`,
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
			const httpsAgent = new HttpsProxyAgent(options);
 | 
			
		||||
			return httpsAgent;
 | 
			
		||||
		}
 | 
			
		||||
		case "socks4":
 | 
			
		||||
		case "socks4a": {
 | 
			
		||||
			let options: SocksProxyAgentOptions = {
 | 
			
		||||
				type: 4,
 | 
			
		||||
				hostname: proxyConfig.host,
 | 
			
		||||
				port: proxyConfig.port,
 | 
			
		||||
			};
 | 
			
		||||
			if (proxyConfig.auth) {
 | 
			
		||||
				options = Object.assign(options, {
 | 
			
		||||
					userId: proxyConfig.auth.username,
 | 
			
		||||
					password: proxyConfig.auth.password,
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
			const socksAgent = new SocksProxyAgent(options);
 | 
			
		||||
			return socksAgent;
 | 
			
		||||
		}
 | 
			
		||||
		case "socks5":
 | 
			
		||||
		case "socks5h":
 | 
			
		||||
		case "socks": {
 | 
			
		||||
			let options: SocksProxyAgentOptions = {
 | 
			
		||||
				type: 5,
 | 
			
		||||
				hostname: proxyConfig.host,
 | 
			
		||||
				port: proxyConfig.port,
 | 
			
		||||
			};
 | 
			
		||||
			if (proxyConfig.auth) {
 | 
			
		||||
				options = Object.assign(options, {
 | 
			
		||||
					userId: proxyConfig.auth.username,
 | 
			
		||||
					password: proxyConfig.auth.password,
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
			const socksAgent = new SocksProxyAgent(options);
 | 
			
		||||
			return socksAgent;
 | 
			
		||||
		}
 | 
			
		||||
		default:
 | 
			
		||||
			throw new ProxyProtocolError("protocol is not accepted");
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
export default proxyAgent;
 | 
			
		||||
							
								
								
									
										8
									
								
								packages/megalodon/src/response.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/megalodon/src/response.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
type Response<T = any> = {
 | 
			
		||||
	data: T;
 | 
			
		||||
	status: number;
 | 
			
		||||
	statusText: string;
 | 
			
		||||
	headers: any;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Response;
 | 
			
		||||
							
								
								
									
										27
									
								
								packages/megalodon/test/integration/megalodon.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								packages/megalodon/test/integration/megalodon.spec.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
import { detector } from '../../src/index'
 | 
			
		||||
 | 
			
		||||
describe('detector', () => {
 | 
			
		||||
  describe('mastodon', () => {
 | 
			
		||||
    const url = 'https://fedibird.com'
 | 
			
		||||
    it('should be mastodon', async () => {
 | 
			
		||||
      const mastodon = await detector(url)
 | 
			
		||||
      expect(mastodon).toEqual('mastodon')
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  describe('pleroma', () => {
 | 
			
		||||
    const url = 'https://pleroma.soykaf.com'
 | 
			
		||||
    it('should be pleroma', async () => {
 | 
			
		||||
      const pleroma = await detector(url)
 | 
			
		||||
      expect(pleroma).toEqual('pleroma')
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  describe('misskey', () => {
 | 
			
		||||
    const url = 'https://misskey.io'
 | 
			
		||||
    it('should be misskey', async () => {
 | 
			
		||||
      const misskey = await detector(url)
 | 
			
		||||
      expect(misskey).toEqual('misskey')
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										204
									
								
								packages/megalodon/test/integration/misskey.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								packages/megalodon/test/integration/misskey.spec.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,204 @@
 | 
			
		|||
import MisskeyEntity from '@/misskey/entity'
 | 
			
		||||
import MisskeyNotificationType from '@/misskey/notification'
 | 
			
		||||
import Misskey from '@/misskey'
 | 
			
		||||
import MegalodonNotificationType from '@/notification'
 | 
			
		||||
import axios, { AxiosResponse } from 'axios'
 | 
			
		||||
 | 
			
		||||
jest.mock('axios')
 | 
			
		||||
 | 
			
		||||
const user: MisskeyEntity.User = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  name: 'test_user',
 | 
			
		||||
  username: 'TestUser',
 | 
			
		||||
  host: 'misskey.io',
 | 
			
		||||
  avatarUrl: 'https://example.com/icon.png',
 | 
			
		||||
  avatarColor: '#000000',
 | 
			
		||||
  emojis: []
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const note: MisskeyEntity.Note = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
  userId: '1',
 | 
			
		||||
  user: user,
 | 
			
		||||
  text: 'hogehoge',
 | 
			
		||||
  cw: null,
 | 
			
		||||
  visibility: 'public',
 | 
			
		||||
  renoteCount: 0,
 | 
			
		||||
  repliesCount: 0,
 | 
			
		||||
  reactions: {},
 | 
			
		||||
  emojis: [],
 | 
			
		||||
  fileIds: [],
 | 
			
		||||
  files: [],
 | 
			
		||||
  replyId: null,
 | 
			
		||||
  renoteId: null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const follow: MisskeyEntity.Notification = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
  userId: user.id,
 | 
			
		||||
  user: user,
 | 
			
		||||
  type: MisskeyNotificationType.Follow
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const mention: MisskeyEntity.Notification = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
  userId: user.id,
 | 
			
		||||
  user: user,
 | 
			
		||||
  type: MisskeyNotificationType.Mention,
 | 
			
		||||
  note: note
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const reply: MisskeyEntity.Notification = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
  userId: user.id,
 | 
			
		||||
  user: user,
 | 
			
		||||
  type: MisskeyNotificationType.Reply,
 | 
			
		||||
  note: note
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const renote: MisskeyEntity.Notification = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
  userId: user.id,
 | 
			
		||||
  user: user,
 | 
			
		||||
  type: MisskeyNotificationType.Renote,
 | 
			
		||||
  note: note
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const quote: MisskeyEntity.Notification = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
  userId: user.id,
 | 
			
		||||
  user: user,
 | 
			
		||||
  type: MisskeyNotificationType.Quote,
 | 
			
		||||
  note: note
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const reaction: MisskeyEntity.Notification = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
  userId: user.id,
 | 
			
		||||
  user: user,
 | 
			
		||||
  type: MisskeyNotificationType.Reaction,
 | 
			
		||||
  note: note,
 | 
			
		||||
  reaction: '♥'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const pollVote: MisskeyEntity.Notification = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
  userId: user.id,
 | 
			
		||||
  user: user,
 | 
			
		||||
  type: MisskeyNotificationType.PollEnded,
 | 
			
		||||
  note: note
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const receiveFollowRequest: MisskeyEntity.Notification = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
  userId: user.id,
 | 
			
		||||
  user: user,
 | 
			
		||||
  type: MisskeyNotificationType.ReceiveFollowRequest
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const followRequestAccepted: MisskeyEntity.Notification = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
  userId: user.id,
 | 
			
		||||
  user: user,
 | 
			
		||||
  type: MisskeyNotificationType.FollowRequestAccepted
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const groupInvited: MisskeyEntity.Notification = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
  userId: user.id,
 | 
			
		||||
  user: user,
 | 
			
		||||
  type: MisskeyNotificationType.GroupInvited
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
;(axios.CancelToken.source as any).mockImplementation(() => {
 | 
			
		||||
  return {
 | 
			
		||||
    token: {
 | 
			
		||||
      throwIfRequested: () => {},
 | 
			
		||||
      promise: {
 | 
			
		||||
        then: () => {},
 | 
			
		||||
        catch: () => {}
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
describe('getNotifications', () => {
 | 
			
		||||
  const client = new Misskey('http://localhost', 'sample token')
 | 
			
		||||
  const cases: Array<{ event: MisskeyEntity.Notification; expected: Entity.NotificationType; title: string }> = [
 | 
			
		||||
    {
 | 
			
		||||
      event: follow,
 | 
			
		||||
      expected: MegalodonNotificationType.Follow,
 | 
			
		||||
      title: 'follow'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      event: mention,
 | 
			
		||||
      expected: MegalodonNotificationType.Mention,
 | 
			
		||||
      title: 'mention'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      event: reply,
 | 
			
		||||
      expected: MegalodonNotificationType.Mention,
 | 
			
		||||
      title: 'reply'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      event: renote,
 | 
			
		||||
      expected: MegalodonNotificationType.Reblog,
 | 
			
		||||
      title: 'renote'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      event: quote,
 | 
			
		||||
      expected: MegalodonNotificationType.Reblog,
 | 
			
		||||
      title: 'quote'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      event: reaction,
 | 
			
		||||
      expected: MegalodonNotificationType.Reaction,
 | 
			
		||||
      title: 'reaction'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      event: pollVote,
 | 
			
		||||
      expected: MegalodonNotificationType.Poll,
 | 
			
		||||
      title: 'pollVote'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      event: receiveFollowRequest,
 | 
			
		||||
      expected: MegalodonNotificationType.FollowRequest,
 | 
			
		||||
      title: 'receiveFollowRequest'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      event: followRequestAccepted,
 | 
			
		||||
      expected: MegalodonNotificationType.Follow,
 | 
			
		||||
      title: 'followRequestAccepted'
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      event: groupInvited,
 | 
			
		||||
      expected: MisskeyNotificationType.GroupInvited,
 | 
			
		||||
      title: 'groupInvited'
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
  cases.forEach(c => {
 | 
			
		||||
    it(`should be ${c.title} event`, async () => {
 | 
			
		||||
      const mockResponse: AxiosResponse<Array<MisskeyEntity.Notification>> = {
 | 
			
		||||
        data: [c.event],
 | 
			
		||||
        status: 200,
 | 
			
		||||
        statusText: '200OK',
 | 
			
		||||
        headers: {},
 | 
			
		||||
        config: {}
 | 
			
		||||
      }
 | 
			
		||||
      ;(axios.post as any).mockResolvedValue(mockResponse)
 | 
			
		||||
      const res = await client.getNotifications()
 | 
			
		||||
      expect(res.data[0].type).toEqual(c.expected)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										233
									
								
								packages/megalodon/test/unit/misskey/api_client.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								packages/megalodon/test/unit/misskey/api_client.spec.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,233 @@
 | 
			
		|||
import MisskeyAPI from '@/misskey/api_client'
 | 
			
		||||
import MegalodonEntity from '@/entity'
 | 
			
		||||
import MisskeyEntity from '@/misskey/entity'
 | 
			
		||||
import MegalodonNotificationType from '@/notification'
 | 
			
		||||
import MisskeyNotificationType from '@/misskey/notification'
 | 
			
		||||
 | 
			
		||||
const user: MisskeyEntity.User = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  name: 'test_user',
 | 
			
		||||
  username: 'TestUser',
 | 
			
		||||
  host: 'misskey.io',
 | 
			
		||||
  avatarUrl: 'https://example.com/icon.png',
 | 
			
		||||
  avatarColor: '#000000',
 | 
			
		||||
  emojis: []
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const converter: MisskeyAPI.Converter = new MisskeyAPI.Converter("https://example.com")
 | 
			
		||||
 | 
			
		||||
describe('api_client', () => {
 | 
			
		||||
  describe('notification', () => {
 | 
			
		||||
    describe('encode', () => {
 | 
			
		||||
      it('megalodon notification type should be encoded to misskey notification type', () => {
 | 
			
		||||
        const cases: Array<{ src: MegalodonEntity.NotificationType; dist: MisskeyEntity.NotificationType }> = [
 | 
			
		||||
          {
 | 
			
		||||
            src: MegalodonNotificationType.Follow,
 | 
			
		||||
            dist: MisskeyNotificationType.Follow
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MegalodonNotificationType.Mention,
 | 
			
		||||
            dist: MisskeyNotificationType.Reply
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MegalodonNotificationType.Favourite,
 | 
			
		||||
            dist: MisskeyNotificationType.Reaction
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MegalodonNotificationType.Reaction,
 | 
			
		||||
            dist: MisskeyNotificationType.Reaction
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MegalodonNotificationType.Reblog,
 | 
			
		||||
            dist: MisskeyNotificationType.Renote
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MegalodonNotificationType.Poll,
 | 
			
		||||
            dist: MisskeyNotificationType.PollEnded
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MegalodonNotificationType.FollowRequest,
 | 
			
		||||
            dist: MisskeyNotificationType.ReceiveFollowRequest
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
        cases.forEach(c => {
 | 
			
		||||
          expect(converter.encodeNotificationType(c.src)).toEqual(c.dist)
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
    describe('decode', () => {
 | 
			
		||||
      it('misskey notification type should be decoded to megalodon notification type', () => {
 | 
			
		||||
        const cases: Array<{ src: MisskeyEntity.NotificationType; dist: MegalodonEntity.NotificationType }> = [
 | 
			
		||||
          {
 | 
			
		||||
            src: MisskeyNotificationType.Follow,
 | 
			
		||||
            dist: MegalodonNotificationType.Follow
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MisskeyNotificationType.Mention,
 | 
			
		||||
            dist: MegalodonNotificationType.Mention
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MisskeyNotificationType.Reply,
 | 
			
		||||
            dist: MegalodonNotificationType.Mention
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MisskeyNotificationType.Renote,
 | 
			
		||||
            dist: MegalodonNotificationType.Reblog
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MisskeyNotificationType.Quote,
 | 
			
		||||
            dist: MegalodonNotificationType.Reblog
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MisskeyNotificationType.Reaction,
 | 
			
		||||
            dist: MegalodonNotificationType.Reaction
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MisskeyNotificationType.PollEnded,
 | 
			
		||||
            dist: MegalodonNotificationType.Poll
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MisskeyNotificationType.ReceiveFollowRequest,
 | 
			
		||||
            dist: MegalodonNotificationType.FollowRequest
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            src: MisskeyNotificationType.FollowRequestAccepted,
 | 
			
		||||
            dist: MegalodonNotificationType.Follow
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
        cases.forEach(c => {
 | 
			
		||||
          expect(converter.decodeNotificationType(c.src)).toEqual(c.dist)
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
  describe('reactions', () => {
 | 
			
		||||
    it('should be mapped', () => {
 | 
			
		||||
      const misskeyReactions = [
 | 
			
		||||
        {
 | 
			
		||||
          id: '1',
 | 
			
		||||
          createdAt: '2020-04-21T13:04:13.968Z',
 | 
			
		||||
          user: {
 | 
			
		||||
            id: '81u70uwsja',
 | 
			
		||||
            name: 'h3poteto',
 | 
			
		||||
            username: 'h3poteto',
 | 
			
		||||
            host: null,
 | 
			
		||||
            avatarUrl: 'https://s3.arkjp.net/misskey/thumbnail-63807d97-20ca-40ba-9493-179aa48065c1.png',
 | 
			
		||||
            avatarColor: 'rgb(146,189,195)',
 | 
			
		||||
            emojis: []
 | 
			
		||||
          },
 | 
			
		||||
          type: '❤'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          id: '2',
 | 
			
		||||
          createdAt: '2020-04-21T13:04:13.968Z',
 | 
			
		||||
          user: {
 | 
			
		||||
            id: '81u70uwsja',
 | 
			
		||||
            name: 'h3poteto',
 | 
			
		||||
            username: 'h3poteto',
 | 
			
		||||
            host: null,
 | 
			
		||||
            avatarUrl: 'https://s3.arkjp.net/misskey/thumbnail-63807d97-20ca-40ba-9493-179aa48065c1.png',
 | 
			
		||||
            avatarColor: 'rgb(146,189,195)',
 | 
			
		||||
            emojis: []
 | 
			
		||||
          },
 | 
			
		||||
          type: '❤'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          id: '3',
 | 
			
		||||
          createdAt: '2020-04-21T13:04:13.968Z',
 | 
			
		||||
          user: {
 | 
			
		||||
            id: '81u70uwsja',
 | 
			
		||||
            name: 'h3poteto',
 | 
			
		||||
            username: 'h3poteto',
 | 
			
		||||
            host: null,
 | 
			
		||||
            avatarUrl: 'https://s3.arkjp.net/misskey/thumbnail-63807d97-20ca-40ba-9493-179aa48065c1.png',
 | 
			
		||||
            avatarColor: 'rgb(146,189,195)',
 | 
			
		||||
            emojis: []
 | 
			
		||||
          },
 | 
			
		||||
          type: '☺'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          id: '4',
 | 
			
		||||
          createdAt: '2020-04-21T13:04:13.968Z',
 | 
			
		||||
          user: {
 | 
			
		||||
            id: '81u70uwsja',
 | 
			
		||||
            name: 'h3poteto',
 | 
			
		||||
            username: 'h3poteto',
 | 
			
		||||
            host: null,
 | 
			
		||||
            avatarUrl: 'https://s3.arkjp.net/misskey/thumbnail-63807d97-20ca-40ba-9493-179aa48065c1.png',
 | 
			
		||||
            avatarColor: 'rgb(146,189,195)',
 | 
			
		||||
            emojis: []
 | 
			
		||||
          },
 | 
			
		||||
          type: '❤'
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
 | 
			
		||||
      const reactions = converter.reactions(misskeyReactions)
 | 
			
		||||
      expect(reactions).toEqual([
 | 
			
		||||
        {
 | 
			
		||||
          count: 3,
 | 
			
		||||
          me: false,
 | 
			
		||||
          name: '❤'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          count: 1,
 | 
			
		||||
          me: false,
 | 
			
		||||
          name: '☺'
 | 
			
		||||
        }
 | 
			
		||||
      ])
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  describe('status', () => {
 | 
			
		||||
    describe('plain content', () => {
 | 
			
		||||
      it('should be exported plain content and html content', () => {
 | 
			
		||||
        const plainContent = 'hoge\nfuga\nfuga'
 | 
			
		||||
        const content = 'hoge<br>fuga<br>fuga'
 | 
			
		||||
        const note: MisskeyEntity.Note = {
 | 
			
		||||
          id: '1',
 | 
			
		||||
          createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
          userId: '1',
 | 
			
		||||
          user: user,
 | 
			
		||||
          text: plainContent,
 | 
			
		||||
          cw: null,
 | 
			
		||||
          visibility: 'public',
 | 
			
		||||
          renoteCount: 0,
 | 
			
		||||
          repliesCount: 0,
 | 
			
		||||
          reactions: {},
 | 
			
		||||
          emojis: [],
 | 
			
		||||
          fileIds: [],
 | 
			
		||||
          files: [],
 | 
			
		||||
          replyId: null,
 | 
			
		||||
          renoteId: null
 | 
			
		||||
        }
 | 
			
		||||
        const megalodonStatus = converter.note(note, user.host || 'misskey.io')
 | 
			
		||||
        expect(megalodonStatus.plain_content).toEqual(plainContent)
 | 
			
		||||
        expect(megalodonStatus.content).toEqual(content)
 | 
			
		||||
      })
 | 
			
		||||
      it('html tags should be escaped', () => {
 | 
			
		||||
        const plainContent = '<p>hoge\nfuga\nfuga<p>'
 | 
			
		||||
        const content = '<p>hoge<br>fuga<br>fuga<p>'
 | 
			
		||||
        const note: MisskeyEntity.Note = {
 | 
			
		||||
          id: '1',
 | 
			
		||||
          createdAt: '2021-02-01T01:49:29',
 | 
			
		||||
          userId: '1',
 | 
			
		||||
          user: user,
 | 
			
		||||
          text: plainContent,
 | 
			
		||||
          cw: null,
 | 
			
		||||
          visibility: 'public',
 | 
			
		||||
          renoteCount: 0,
 | 
			
		||||
          repliesCount: 0,
 | 
			
		||||
          reactions: {},
 | 
			
		||||
          emojis: [],
 | 
			
		||||
          fileIds: [],
 | 
			
		||||
          files: [],
 | 
			
		||||
          replyId: null,
 | 
			
		||||
          renoteId: null
 | 
			
		||||
        }
 | 
			
		||||
        const megalodonStatus = converter.note(note, user.host || 'misskey.io')
 | 
			
		||||
        expect(megalodonStatus.plain_content).toEqual(plainContent)
 | 
			
		||||
        expect(megalodonStatus.content).toEqual(content)
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										152
									
								
								packages/megalodon/test/unit/parser.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								packages/megalodon/test/unit/parser.spec.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,152 @@
 | 
			
		|||
import { Parser } from '@/parser'
 | 
			
		||||
import Entity from '@/entity'
 | 
			
		||||
 | 
			
		||||
const account: Entity.Account = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  username: 'h3poteto',
 | 
			
		||||
  acct: 'h3poteto@pleroma.io',
 | 
			
		||||
  display_name: 'h3poteto',
 | 
			
		||||
  locked: false,
 | 
			
		||||
  created_at: '2019-03-26T21:30:32',
 | 
			
		||||
  followers_count: 10,
 | 
			
		||||
  following_count: 10,
 | 
			
		||||
  statuses_count: 100,
 | 
			
		||||
  note: 'engineer',
 | 
			
		||||
  url: 'https://pleroma.io',
 | 
			
		||||
  avatar: '',
 | 
			
		||||
  avatar_static: '',
 | 
			
		||||
  header: '',
 | 
			
		||||
  header_static: '',
 | 
			
		||||
  emojis: [],
 | 
			
		||||
  moved: null,
 | 
			
		||||
  fields: [],
 | 
			
		||||
  bot: false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const status: Entity.Status = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  uri: 'http://example.com',
 | 
			
		||||
  url: 'http://example.com',
 | 
			
		||||
  account: account,
 | 
			
		||||
  in_reply_to_id: null,
 | 
			
		||||
  in_reply_to_account_id: null,
 | 
			
		||||
  reblog: null,
 | 
			
		||||
  content: 'hoge',
 | 
			
		||||
  plain_content: 'hoge',
 | 
			
		||||
  created_at: '2019-03-26T21:40:32',
 | 
			
		||||
  emojis: [],
 | 
			
		||||
  replies_count: 0,
 | 
			
		||||
  reblogs_count: 0,
 | 
			
		||||
  favourites_count: 0,
 | 
			
		||||
  reblogged: null,
 | 
			
		||||
  favourited: null,
 | 
			
		||||
  muted: null,
 | 
			
		||||
  sensitive: false,
 | 
			
		||||
  spoiler_text: '',
 | 
			
		||||
  visibility: 'public',
 | 
			
		||||
  media_attachments: [],
 | 
			
		||||
  mentions: [],
 | 
			
		||||
  tags: [],
 | 
			
		||||
  card: null,
 | 
			
		||||
  poll: null,
 | 
			
		||||
  application: {
 | 
			
		||||
    name: 'Web'
 | 
			
		||||
  } as Entity.Application,
 | 
			
		||||
  language: null,
 | 
			
		||||
  pinned: null,
 | 
			
		||||
  reactions: [],
 | 
			
		||||
  bookmarked: false,
 | 
			
		||||
  quote: null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const notification: Entity.Notification = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  account: account,
 | 
			
		||||
  status: status,
 | 
			
		||||
  type: 'favourite',
 | 
			
		||||
  created_at: '2019-04-01T17:01:32'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const conversation: Entity.Conversation = {
 | 
			
		||||
  id: '1',
 | 
			
		||||
  accounts: [account],
 | 
			
		||||
  last_status: status,
 | 
			
		||||
  unread: true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
describe('Parser', () => {
 | 
			
		||||
  let parser: Parser
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    parser = new Parser()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  describe('parse', () => {
 | 
			
		||||
    describe('message is heartbeat', () => {
 | 
			
		||||
      const message: string = ':thump\n'
 | 
			
		||||
      it('should be called', () => {
 | 
			
		||||
        const spy = jest.fn()
 | 
			
		||||
        parser.on('heartbeat', spy)
 | 
			
		||||
        parser.parse(message)
 | 
			
		||||
        expect(spy).toHaveBeenLastCalledWith({})
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    describe('message is not json', () => {
 | 
			
		||||
      describe('event is delete', () => {
 | 
			
		||||
        const message = `event: delete\ndata: 12asdf34\n\n`
 | 
			
		||||
        it('should be called', () => {
 | 
			
		||||
          const spy = jest.fn()
 | 
			
		||||
          parser.once('delete', spy)
 | 
			
		||||
          parser.parse(message)
 | 
			
		||||
          expect(spy).toHaveBeenCalledWith('12asdf34')
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      describe('event is not delete', () => {
 | 
			
		||||
        const message = `event: event\ndata: 12asdf34\n\n`
 | 
			
		||||
        it('should be error', () => {
 | 
			
		||||
          const error = jest.fn()
 | 
			
		||||
          const deleted = jest.fn()
 | 
			
		||||
          parser.once('error', error)
 | 
			
		||||
          parser.once('delete', deleted)
 | 
			
		||||
          parser.parse(message)
 | 
			
		||||
          expect(error).toHaveBeenCalled()
 | 
			
		||||
          expect(deleted).not.toHaveBeenCalled()
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    describe('message is json', () => {
 | 
			
		||||
      describe('event is update', () => {
 | 
			
		||||
        const message = `event: update\ndata: ${JSON.stringify(status)}\n\n`
 | 
			
		||||
        it('should be called', () => {
 | 
			
		||||
          const spy = jest.fn()
 | 
			
		||||
          parser.once('update', spy)
 | 
			
		||||
          parser.parse(message)
 | 
			
		||||
          expect(spy).toHaveBeenCalledWith(status)
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      describe('event is notification', () => {
 | 
			
		||||
        const message = `event: notification\ndata: ${JSON.stringify(notification)}\n\n`
 | 
			
		||||
        it('should be called', () => {
 | 
			
		||||
          const spy = jest.fn()
 | 
			
		||||
          parser.once('notification', spy)
 | 
			
		||||
          parser.parse(message)
 | 
			
		||||
          expect(spy).toHaveBeenCalledWith(notification)
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      describe('event is conversation', () => {
 | 
			
		||||
        const message = `event: conversation\ndata: ${JSON.stringify(conversation)}\n\n`
 | 
			
		||||
        it('should be called', () => {
 | 
			
		||||
          const spy = jest.fn()
 | 
			
		||||
          parser.once('conversation', spy)
 | 
			
		||||
          parser.parse(message)
 | 
			
		||||
          expect(spy).toHaveBeenCalledWith(conversation)
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										64
									
								
								packages/megalodon/tsconfig.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								packages/megalodon/tsconfig.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    /* Basic Options */
 | 
			
		||||
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
 | 
			
		||||
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
 | 
			
		||||
    "lib": ["es2021", "dom"],                 /* Specify library files to be included in the compilation. */
 | 
			
		||||
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
 | 
			
		||||
    // "checkJs": true,                       /* Report errors in .js files. */
 | 
			
		||||
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
 | 
			
		||||
    "declaration": true,                      /* Generates corresponding '.d.ts' file. */
 | 
			
		||||
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
 | 
			
		||||
    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
 | 
			
		||||
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
 | 
			
		||||
    "outDir": "./lib",                        /* Redirect output structure to the directory. */
 | 
			
		||||
    "rootDir": "./",                          /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
 | 
			
		||||
    // "composite": true,                     /* Enable project compilation */
 | 
			
		||||
    "removeComments": true,                   /* Do not emit comments to output. */
 | 
			
		||||
    // "noEmit": true,                        /* Do not emit outputs. */
 | 
			
		||||
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
 | 
			
		||||
    "downlevelIteration": true,               /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
 | 
			
		||||
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
 | 
			
		||||
 | 
			
		||||
    /* Strict Type-Checking Options */
 | 
			
		||||
    "strict": true,                           /* Enable all strict type-checking options. */
 | 
			
		||||
    "noImplicitAny": true,                    /* Raise error on expressions and declarations with an implied 'any' type. */
 | 
			
		||||
    "strictNullChecks": true,                 /* Enable strict null checks. */
 | 
			
		||||
    "strictFunctionTypes": true,              /* Enable strict checking of function types. */
 | 
			
		||||
    "strictPropertyInitialization": true,     /* Enable strict checking of property initialization in classes. */
 | 
			
		||||
    "noImplicitThis": true,                   /* Raise error on 'this' expressions with an implied 'any' type. */
 | 
			
		||||
    "alwaysStrict": true,                     /* Parse in strict mode and emit "use strict" for each source file. */
 | 
			
		||||
 | 
			
		||||
    /* Additional Checks */
 | 
			
		||||
    "noUnusedLocals": false,                   /* Report errors on unused locals. */
 | 
			
		||||
    "noUnusedParameters": true,               /* Report errors on unused parameters. */
 | 
			
		||||
    "noImplicitReturns": true,                /* Report error when not all code paths in function return a value. */
 | 
			
		||||
    "noFallthroughCasesInSwitch": true,       /* Report errors for fallthrough cases in switch statement. */
 | 
			
		||||
 | 
			
		||||
    /* Module Resolution Options */
 | 
			
		||||
    "moduleResolution": "node",               /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
 | 
			
		||||
    "baseUrl": "./",                          /* Base directory to resolve non-absolute module names. */
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@*": ["src*"],
 | 
			
		||||
      "~*": ["./*"]
 | 
			
		||||
    },                                        /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
 | 
			
		||||
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
 | 
			
		||||
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
 | 
			
		||||
    // "types": [],                           /* Type declaration files to be included in compilation. */
 | 
			
		||||
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
 | 
			
		||||
    "esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
 | 
			
		||||
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
 | 
			
		||||
 | 
			
		||||
    /* Source Map Options */
 | 
			
		||||
    // "sourceRoot": "./",                    /* Specify the location where debugger should locate TypeScript files instead of source locations. */
 | 
			
		||||
    // "mapRoot": "./",                       /* Specify the location where debugger should locate map files instead of generated locations. */
 | 
			
		||||
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
 | 
			
		||||
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
 | 
			
		||||
 | 
			
		||||
    /* Experimental Options */
 | 
			
		||||
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
 | 
			
		||||
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
 | 
			
		||||
  },
 | 
			
		||||
  "include": ["./src", "./test"],
 | 
			
		||||
  "exclude": ["node_modules", "example"]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -250,7 +250,7 @@ export async function createEmptyNotification(): Promise<void> {
 | 
			
		|||
		await globalThis.registration.showNotification(
 | 
			
		||||
			(new URL(origin)).host,
 | 
			
		||||
			{
 | 
			
		||||
				body: `Misskey v${_VERSION_}`,
 | 
			
		||||
				body: `Sharkey v${_VERSION_}`,
 | 
			
		||||
				silent: true,
 | 
			
		||||
				badge: iconUrl('null'),
 | 
			
		||||
				tag: 'read_notification',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										871
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										871
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
				
			
			@ -3,3 +3,4 @@ packages:
 | 
			
		|||
 - 'packages/frontend'
 | 
			
		||||
 - 'packages/sw'
 | 
			
		||||
 - 'packages/misskey-js'
 | 
			
		||||
 - 'packages/megalodon'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue