2023-07-06 00:36:26 +02:00
|
|
|
import FormData from 'form-data'
|
|
|
|
import AsyncLock from 'async-lock';
|
|
|
|
|
|
|
|
import MisskeyAPI from './misskey/api_client'
|
|
|
|
import { DEFAULT_UA } from './default'
|
|
|
|
import { ProxyConfig } from './proxy_config'
|
|
|
|
import OAuth from './oauth'
|
|
|
|
import Response from './response'
|
|
|
|
import Entity from './entity'
|
|
|
|
import { MegalodonInterface, WebSocketInterface, NoImplementedError, ArgumentError, UnexpectedError } from './megalodon'
|
|
|
|
import MegalodonEntity from "@/entity";
|
|
|
|
import fs from "node:fs";
|
2023-07-07 19:41:32 +02:00
|
|
|
import MisskeyNotificationType from "./misskey/notification";
|
2023-07-06 00:36:26 +02:00
|
|
|
|
|
|
|
type AccountCache = {
|
|
|
|
locks: AsyncLock,
|
|
|
|
accounts: Entity.Account[]
|
|
|
|
}
|
|
|
|
|
|
|
|
export default class Misskey implements MegalodonInterface {
|
|
|
|
public client: MisskeyAPI.Interface
|
|
|
|
public converter: MisskeyAPI.Converter
|
|
|
|
public baseUrl: string
|
|
|
|
public proxyConfig: ProxyConfig | false
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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.
|
|
|
|
*/
|
|
|
|
constructor(
|
|
|
|
baseUrl: string,
|
|
|
|
accessToken: string | null = null,
|
|
|
|
userAgent: string | null = DEFAULT_UA,
|
|
|
|
proxyConfig: ProxyConfig | false = false
|
|
|
|
) {
|
|
|
|
let token: string = ''
|
|
|
|
if (accessToken) {
|
|
|
|
token = accessToken
|
|
|
|
}
|
|
|
|
let agent: string = DEFAULT_UA
|
|
|
|
if (userAgent) {
|
|
|
|
agent = userAgent
|
|
|
|
}
|
|
|
|
this.converter = new MisskeyAPI.Converter(baseUrl)
|
|
|
|
this.client = new MisskeyAPI.Client(baseUrl, token, agent, proxyConfig, this.converter)
|
|
|
|
this.baseUrl = baseUrl
|
|
|
|
this.proxyConfig = proxyConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
private baseUrlToHost(baseUrl: string): string {
|
|
|
|
return baseUrl.replace('https://', '')
|
|
|
|
}
|
|
|
|
|
|
|
|
public cancel(): void {
|
|
|
|
return this.client.cancel()
|
|
|
|
}
|
|
|
|
|
|
|
|
public async registerApp(
|
|
|
|
client_name: string,
|
|
|
|
options: Partial<{ scopes: Array<string>; redirect_uris: string; website: string }> = {
|
|
|
|
scopes: MisskeyAPI.DEFAULT_SCOPE,
|
|
|
|
redirect_uris: this.baseUrl
|
|
|
|
}
|
|
|
|
): Promise<OAuth.AppData> {
|
|
|
|
return this.createApp(client_name, options).then(async appData => {
|
|
|
|
return this.generateAuthUrlAndToken(appData.client_secret).then(session => {
|
|
|
|
appData.url = session.url
|
|
|
|
appData.session_token = session.token
|
|
|
|
return appData
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/app/create
|
|
|
|
*
|
|
|
|
* Create an application.
|
|
|
|
* @param client_name Your application's name.
|
|
|
|
* @param options Form data.
|
|
|
|
*/
|
|
|
|
public async createApp(
|
|
|
|
client_name: string,
|
|
|
|
options: Partial<{ scopes: Array<string>; redirect_uris: string; website: string }> = {
|
|
|
|
scopes: MisskeyAPI.DEFAULT_SCOPE,
|
|
|
|
redirect_uris: this.baseUrl
|
|
|
|
}
|
|
|
|
): Promise<OAuth.AppData> {
|
|
|
|
const redirect_uris = options.redirect_uris || this.baseUrl
|
|
|
|
const scopes = options.scopes || MisskeyAPI.DEFAULT_SCOPE
|
|
|
|
|
|
|
|
const params: {
|
|
|
|
name: string
|
|
|
|
description: string
|
|
|
|
permission: Array<string>
|
|
|
|
callbackUrl: string
|
|
|
|
} = {
|
|
|
|
name: client_name,
|
|
|
|
description: '',
|
|
|
|
permission: scopes,
|
|
|
|
callbackUrl: redirect_uris
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The response is:
|
|
|
|
{
|
|
|
|
"id": "xxxxxxxxxx",
|
|
|
|
"name": "string",
|
|
|
|
"callbackUrl": "string",
|
|
|
|
"permission": [
|
|
|
|
"string"
|
|
|
|
],
|
|
|
|
"secret": "string"
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
return this.client.post<MisskeyAPI.Entity.App>('/api/app/create', params).then((res: Response<MisskeyAPI.Entity.App>) => {
|
|
|
|
const appData: OAuth.AppDataFromServer = {
|
|
|
|
id: res.data.id,
|
|
|
|
name: res.data.name,
|
|
|
|
website: null,
|
|
|
|
redirect_uri: res.data.callbackUrl,
|
|
|
|
client_id: '',
|
|
|
|
client_secret: res.data.secret
|
|
|
|
}
|
|
|
|
return OAuth.AppData.from(appData)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/auth/session/generate
|
|
|
|
*/
|
|
|
|
public async generateAuthUrlAndToken(clientSecret: string): Promise<MisskeyAPI.Entity.Session> {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Session>('/api/auth/session/generate', {
|
|
|
|
appSecret: clientSecret
|
|
|
|
})
|
|
|
|
.then((res: Response<MisskeyAPI.Entity.Session>) => res.data)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// apps
|
|
|
|
// ======================================
|
|
|
|
public async verifyAppCredentials(): Promise<Response<Entity.Application>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// apps/oauth
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/auth/session/userkey
|
|
|
|
*
|
|
|
|
* @param _client_id This parameter is not used in this method.
|
|
|
|
* @param client_secret Application secret key which will be provided in createApp.
|
|
|
|
* @param session_token Session token string which will be provided in generateAuthUrlAndToken.
|
|
|
|
* @param _redirect_uri This parameter is not used in this method.
|
|
|
|
*/
|
|
|
|
public async fetchAccessToken(
|
|
|
|
_client_id: string | null,
|
|
|
|
client_secret: string,
|
|
|
|
session_token: string,
|
|
|
|
_redirect_uri?: string
|
|
|
|
): Promise<OAuth.TokenData> {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.UserKey>('/api/auth/session/userkey', {
|
|
|
|
appSecret: client_secret,
|
|
|
|
token: session_token
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
const token = new OAuth.TokenData(res.data.accessToken, 'misskey', '', 0, null, null)
|
|
|
|
return token
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async refreshToken(_client_id: string, _client_secret: string, _refresh_token: string): Promise<OAuth.TokenData> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async revokeToken(_client_id: string, _client_secret: string, _token: string): Promise<Response<{}>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts
|
|
|
|
// ======================================
|
|
|
|
public async registerAccount(
|
|
|
|
_username: string,
|
|
|
|
_email: string,
|
|
|
|
_password: string,
|
|
|
|
_agreement: boolean,
|
|
|
|
_locale: string,
|
|
|
|
_reason?: string | null
|
|
|
|
): Promise<Response<Entity.Token>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/i
|
|
|
|
*/
|
|
|
|
public async verifyAccountCredentials(): Promise<Response<Entity.Account>> {
|
|
|
|
return this.client.post<MisskeyAPI.Entity.UserDetail>('/api/i').then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.userDetail(res.data, this.baseUrlToHost(this.baseUrl))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/i/update
|
|
|
|
*/
|
|
|
|
public async updateCredentials(options?: {
|
|
|
|
discoverable?: boolean
|
|
|
|
bot?: boolean
|
|
|
|
display_name?: string
|
|
|
|
note?: string
|
|
|
|
avatar?: string
|
|
|
|
header?: string
|
|
|
|
locked?: boolean
|
|
|
|
source?: {
|
|
|
|
privacy?: string
|
|
|
|
sensitive?: boolean
|
|
|
|
language?: string
|
|
|
|
} | null
|
|
|
|
fields_attributes?: Array<{ name: string; value: string }>
|
|
|
|
}): Promise<Response<Entity.Account>> {
|
|
|
|
let params = {}
|
|
|
|
if (options) {
|
|
|
|
if (options.bot !== undefined) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
isBot: options.bot
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.display_name) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
name: options.display_name
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.note) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
description: options.note
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.locked !== undefined) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
isLocked: options.locked
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.source) {
|
|
|
|
if (options.source.language) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
lang: options.source.language
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.source.sensitive) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
alwaysMarkNsfw: options.source.sensitive
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.client.post<MisskeyAPI.Entity.UserDetail>('/api/i', params).then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.userDetail(res.data, this.baseUrlToHost(this.baseUrl))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/show
|
|
|
|
*/
|
|
|
|
public async getAccount(id: string): Promise<Response<Entity.Account>> {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.UserDetail>('/api/users/show', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.userDetail(res.data, this.baseUrlToHost(this.baseUrl))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getAccountByName(user: string, host: string | null): Promise<Response<Entity.Account>> {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.UserDetail>('/api/users/show', {
|
|
|
|
username: user,
|
|
|
|
host: host ?? null
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.userDetail(res.data, this.baseUrlToHost(this.baseUrl))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/notes
|
|
|
|
*/
|
|
|
|
public async getAccountStatuses(
|
|
|
|
id: string,
|
|
|
|
options?: {
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
pinned?: boolean
|
|
|
|
exclude_replies: boolean
|
|
|
|
exclude_reblogs: boolean
|
|
|
|
only_media?: boolean
|
|
|
|
}
|
|
|
|
): Promise<Response<Array<Entity.Status>>> {
|
|
|
|
const accountCache = this.getFreshAccountCache();
|
|
|
|
|
|
|
|
if (options?.pinned) {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.UserDetail>('/api/users/show', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
.then(async res => {
|
|
|
|
if (res.data.pinnedNotes) {
|
|
|
|
return {
|
|
|
|
...res,
|
2023-07-07 20:15:24 +02:00
|
|
|
data: await Promise.all(res.data.pinnedNotes.map(n => this.noteWithDetails(n, this.baseUrlToHost(this.baseUrl), accountCache)))
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return {...res, data: []}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
let params = {
|
|
|
|
userId: id
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.since_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.since_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.exclude_replies) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
includeReplies: false
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.exclude_reblogs) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
includeMyRenotes: false
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.only_media) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
withFiles: options.only_media
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.Note>>('/api/users/notes', params).then(async res => {
|
2023-07-07 20:15:24 +02:00
|
|
|
const statuses: Array<Entity.Status> = await Promise.all(res.data.map(note => this.noteWithDetails(note, this.baseUrlToHost(this.baseUrl), accountCache)))
|
2023-07-06 00:36:26 +02:00
|
|
|
return Object.assign(res, {
|
|
|
|
data: statuses
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getAccountFavourites(
|
|
|
|
id: string,
|
|
|
|
options?: {
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
}
|
|
|
|
): Promise<Response<Array<Entity.Status>>> {
|
|
|
|
const accountCache = this.getFreshAccountCache();
|
|
|
|
|
|
|
|
let params = {
|
|
|
|
userId: id
|
|
|
|
};
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
2023-07-09 23:53:15 +02:00
|
|
|
limit: options.limit <= 100 ? options.limit : 100
|
2023-07-06 00:36:26 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.since_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.since_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.Favorite>>('/api/users/reactions', params).then(async res => {
|
|
|
|
return Object.assign(res, {
|
2023-07-07 20:15:24 +02:00
|
|
|
data: await Promise.all(res.data.map(fav => this.noteWithDetails(fav.note, this.baseUrlToHost(this.baseUrl), accountCache)))
|
2023-07-06 00:36:26 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async subscribeAccount(_id: string): Promise<Response<Entity.Relationship>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async unsubscribeAccount(_id: string): Promise<Response<Entity.Relationship>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/followers
|
|
|
|
*/
|
|
|
|
public async getAccountFollowers(
|
|
|
|
id: string,
|
|
|
|
options?: {
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
}
|
|
|
|
): Promise<Response<Array<Entity.Account>>> {
|
|
|
|
let params = {
|
|
|
|
userId: id
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
2023-07-09 00:20:14 +02:00
|
|
|
limit: options.limit <= 100 ? options.limit : 100
|
2023-07-06 00:36:26 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 40
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 40
|
|
|
|
})
|
|
|
|
}
|
2023-07-09 00:31:24 +02:00
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.Follower>>('/api/users/followers', params).then(async res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: (await Promise.all(res.data.map(async f => (this.getAccount(f.followerId)).then(p => p.data))))
|
|
|
|
})
|
|
|
|
})
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/following
|
|
|
|
*/
|
|
|
|
public async getAccountFollowing(
|
|
|
|
id: string,
|
|
|
|
options?: {
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
}
|
|
|
|
): Promise<Response<Array<Entity.Account>>> {
|
|
|
|
let params = {
|
|
|
|
userId: id
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
2023-07-09 00:20:14 +02:00
|
|
|
limit: options.limit <= 100 ? options.limit : 100
|
2023-07-06 00:36:26 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-07-09 00:31:24 +02:00
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.Following>>('/api/users/following', params).then(async res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: (await Promise.all(res.data.map(async f => (this.getAccount(f.followeeId)).then(p => p.data))))
|
|
|
|
})
|
|
|
|
})
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async getAccountLists(_id: string): Promise<Response<Array<Entity.List>>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getIdentityProof(_id: string): Promise<Response<Array<Entity.IdentityProof>>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/following/create
|
|
|
|
*/
|
|
|
|
public async followAccount(id: string, _options?: { reblog?: boolean }): Promise<Response<Entity.Relationship>> {
|
|
|
|
await this.client.post<{}>('/api/following/create', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Relation>('/api/users/relation', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.relation(res.data)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/following/delete
|
|
|
|
*/
|
|
|
|
public async unfollowAccount(id: string): Promise<Response<Entity.Relationship>> {
|
|
|
|
await this.client.post<{}>('/api/following/delete', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Relation>('/api/users/relation', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.relation(res.data)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/blocking/create
|
|
|
|
*/
|
|
|
|
public async blockAccount(id: string): Promise<Response<Entity.Relationship>> {
|
|
|
|
await this.client.post<{}>('/api/blocking/create', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Relation>('/api/users/relation', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.relation(res.data)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/blocking/delete
|
|
|
|
*/
|
|
|
|
public async unblockAccount(id: string): Promise<Response<Entity.Relationship>> {
|
|
|
|
await this.client.post<{}>('/api/blocking/delete', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Relation>('/api/users/relation', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.relation(res.data)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/mute/create
|
|
|
|
*/
|
|
|
|
public async muteAccount(id: string, _notifications: boolean): Promise<Response<Entity.Relationship>> {
|
|
|
|
await this.client.post<{}>('/api/mute/create', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Relation>('/api/users/relation', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.relation(res.data)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/mute/delete
|
|
|
|
*/
|
|
|
|
public async unmuteAccount(id: string): Promise<Response<Entity.Relationship>> {
|
|
|
|
await this.client.post<{}>('/api/mute/delete', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Relation>('/api/users/relation', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.relation(res.data)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async pinAccount(_id: string): Promise<Response<Entity.Relationship>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async unpinAccount(_id: string): Promise<Response<Entity.Relationship>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/relation
|
|
|
|
*
|
|
|
|
* @param id The accountID, for example `'1sdfag'`
|
|
|
|
*/
|
|
|
|
public async getRelationship(id: string): Promise<Response<Entity.Relationship>> {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Relation>('/api/users/relation', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.relation(res.data)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/relation
|
|
|
|
*
|
|
|
|
* @param id Array of account ID, for example `['1sdfag', 'ds12aa']`.
|
|
|
|
*/
|
|
|
|
public async getRelationships(ids: Array<string>): Promise<Response<Array<Entity.Relationship>>> {
|
|
|
|
return Promise.all(ids.map(id => this.getRelationship(id))).then(results => ({
|
|
|
|
...results[0],
|
|
|
|
data: results.map(r => r.data)
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/search
|
|
|
|
*/
|
|
|
|
public async searchAccount(
|
|
|
|
q: string,
|
|
|
|
options?: {
|
|
|
|
following?: boolean
|
|
|
|
resolve?: boolean
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
}
|
|
|
|
): Promise<Response<Array<Entity.Account>>> {
|
|
|
|
let params = {
|
|
|
|
query: q,
|
|
|
|
detail: true
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.resolve !== undefined) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
localOnly: options.resolve
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 40
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 40
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.UserDetail>>('/api/users/search', params).then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: res.data.map(u => this.converter.userDetail(u, this.baseUrlToHost(this.baseUrl)))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/bookmarks
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/i/favorites
|
|
|
|
*/
|
|
|
|
public async getBookmarks(options?: {
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
min_id?: string
|
|
|
|
}): Promise<Response<Array<Entity.Status>>> {
|
|
|
|
const accountCache = this.getFreshAccountCache();
|
|
|
|
|
|
|
|
let params = {}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
2023-07-09 23:53:15 +02:00
|
|
|
limit: options.limit <= 100 ? options.limit : 100
|
2023-07-06 00:36:26 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 40
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.min_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.min_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 40
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.Favorite>>('/api/i/favorites', params).then(async res => {
|
|
|
|
return Object.assign(res, {
|
2023-07-07 20:15:24 +02:00
|
|
|
data: await Promise.all(res.data.map(s => this.noteWithDetails(s.note, this.baseUrlToHost(this.baseUrl), accountCache)))
|
2023-07-06 00:36:26 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/favourites
|
|
|
|
// ======================================
|
|
|
|
public async getFavourites(options?: { limit?: number; max_id?: string; min_id?: string }): Promise<Response<Array<Entity.Status>>> {
|
|
|
|
const userId = await this.client.post<MisskeyAPI.Entity.UserDetail>('/api/i').then(res => res.data.id);
|
|
|
|
return this.getAccountFavourites(userId, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/mutes
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/mute/list
|
|
|
|
*/
|
|
|
|
public async getMutes(options?: { limit?: number; max_id?: string; min_id?: string }): Promise<Response<Array<Entity.Account>>> {
|
|
|
|
let params = {}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 40
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.min_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.min_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 40
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.Mute>>('/api/mute/list', params).then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: res.data.map(mute => this.converter.userDetail(mute.mutee, this.baseUrlToHost(this.baseUrl)))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/blocks
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/blocking/list
|
|
|
|
*/
|
|
|
|
public async getBlocks(options?: { limit?: number; max_id?: string; min_id?: string }): Promise<Response<Array<Entity.Account>>> {
|
|
|
|
let params = {}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 40
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.min_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.min_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 40
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.Blocking>>('/api/blocking/list', params).then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: res.data.map(blocking => this.converter.userDetail(blocking.blockee, this.baseUrlToHost(this.baseUrl)))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/domain_blocks
|
|
|
|
// ======================================
|
|
|
|
public async getDomainBlocks(_options?: { limit?: number; max_id?: string; min_id?: string }): Promise<Response<Array<string>>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async blockDomain(_domain: string): Promise<Response<{}>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async unblockDomain(_domain: string): Promise<Response<{}>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/filters
|
|
|
|
// ======================================
|
|
|
|
public async getFilters(): Promise<Response<Array<Entity.Filter>>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getFilter(_id: string): Promise<Response<Entity.Filter>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async createFilter(
|
|
|
|
_phrase: string,
|
|
|
|
_context: Array<string>,
|
|
|
|
_options?: {
|
|
|
|
irreversible?: boolean
|
|
|
|
whole_word?: boolean
|
|
|
|
expires_in?: string
|
|
|
|
}
|
|
|
|
): Promise<Response<Entity.Filter>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async updateFilter(
|
|
|
|
_id: string,
|
|
|
|
_phrase: string,
|
|
|
|
_context: Array<string>,
|
|
|
|
_options?: {
|
|
|
|
irreversible?: boolean
|
|
|
|
whole_word?: boolean
|
|
|
|
expires_in?: string
|
|
|
|
}
|
|
|
|
): Promise<Response<Entity.Filter>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async deleteFilter(_id: string): Promise<Response<Entity.Filter>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/reports
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/users/report-abuse
|
|
|
|
*/
|
|
|
|
public async report(
|
|
|
|
account_id: string,
|
|
|
|
comment: string,
|
|
|
|
_options?: {
|
|
|
|
status_ids?: Array<string>
|
|
|
|
forward?: boolean
|
|
|
|
}
|
|
|
|
): Promise<Response<Entity.Report>> {
|
|
|
|
return this.client
|
|
|
|
.post<{}>('/api/users/report-abuse', {
|
|
|
|
userId: account_id,
|
|
|
|
comment: comment
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: {
|
|
|
|
id: '',
|
|
|
|
action_taken: '',
|
|
|
|
comment: comment,
|
|
|
|
account_id: account_id,
|
|
|
|
status_ids: []
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/follow_requests
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/following/requests/list
|
|
|
|
*/
|
|
|
|
public async getFollowRequests(_limit?: number): Promise<Response<Array<Entity.Account>>> {
|
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.FollowRequest>>('/api/following/requests/list').then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: res.data.map(r => this.converter.user(r.follower))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/following/requests/accept
|
|
|
|
*/
|
|
|
|
public async acceptFollowRequest(id: string): Promise<Response<Entity.Relationship>> {
|
|
|
|
await this.client.post<{}>('/api/following/requests/accept', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Relation>('/api/users/relation', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.relation(res.data)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/following/requests/reject
|
|
|
|
*/
|
|
|
|
public async rejectFollowRequest(id: string): Promise<Response<Entity.Relationship>> {
|
|
|
|
await this.client.post<{}>('/api/following/requests/reject', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Relation>('/api/users/relation', {
|
|
|
|
userId: id
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.relation(res.data)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/endorsements
|
|
|
|
// ======================================
|
|
|
|
public async getEndorsements(_options?: {
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
}): Promise<Response<Array<Entity.Account>>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/featured_tags
|
|
|
|
// ======================================
|
|
|
|
public async getFeaturedTags(): Promise<Response<Array<Entity.FeaturedTag>>> {
|
|
|
|
return this.getAccountFeaturedTags();
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getAccountFeaturedTags(): Promise<Response<Array<Entity.FeaturedTag>>> {
|
|
|
|
const tags : Entity.FeaturedTag[] = [];
|
|
|
|
const res : Response = {
|
|
|
|
headers: undefined,
|
|
|
|
statusText: "",
|
|
|
|
status: 200,
|
|
|
|
data: tags
|
|
|
|
};
|
|
|
|
return new Promise(resolve => resolve(res))
|
|
|
|
}
|
|
|
|
|
|
|
|
public async createFeaturedTag(_name: string): Promise<Response<Entity.FeaturedTag>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async deleteFeaturedTag(_id: string): Promise<Response<{}>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getSuggestedTags(): Promise<Response<Array<Entity.Tag>>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/preferences
|
|
|
|
// ======================================
|
|
|
|
public async getPreferences(): Promise<Response<Entity.Preferences>> {
|
2023-07-10 18:50:12 +02:00
|
|
|
return this.client.post<MisskeyAPI.Entity.UserDetailMe>('/api/i').then(async res => {
|
|
|
|
return Object.assign(res, {
|
|
|
|
data: this.converter.userPreferences(res.data, await this.getDefaultPostPrivacy())
|
|
|
|
})
|
|
|
|
})
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/suggestions
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/users/recommendation
|
|
|
|
*/
|
|
|
|
public async getSuggestions(limit?: number): Promise<Response<Array<Entity.Account>>> {
|
|
|
|
let params = {}
|
|
|
|
if (limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.UserDetail>>('/api/users/recommendation', params)
|
|
|
|
.then(res => ({ ...res, data: res.data.map(u => this.converter.userDetail(u, this.baseUrlToHost(this.baseUrl))) }))
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// accounts/tags
|
|
|
|
// ======================================
|
|
|
|
public async getFollowedTags(): Promise<Response<Array<Entity.Tag>>> {
|
|
|
|
const tags : Entity.Tag[] = [];
|
|
|
|
const res : Response = {
|
|
|
|
headers: undefined,
|
|
|
|
statusText: "",
|
|
|
|
status: 200,
|
|
|
|
data: tags
|
|
|
|
};
|
|
|
|
return new Promise(resolve => resolve(res))
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getTag(_id: string): Promise<Response<Entity.Tag>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async followTag(_id: string): Promise<Response<Entity.Tag>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async unfollowTag(_id: string): Promise<Response<Entity.Tag>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// statuses
|
|
|
|
// ======================================
|
|
|
|
public async postStatus(
|
|
|
|
status: string,
|
|
|
|
options?: {
|
|
|
|
media_ids?: Array<string>
|
|
|
|
poll?: { options: Array<string>; expires_in: number; multiple?: boolean; hide_totals?: boolean }
|
|
|
|
in_reply_to_id?: string
|
|
|
|
sensitive?: boolean
|
|
|
|
spoiler_text?: string
|
|
|
|
visibility?: 'public' | 'unlisted' | 'private' | 'direct'
|
|
|
|
scheduled_at?: string
|
|
|
|
language?: string
|
|
|
|
quote_id?: string
|
|
|
|
}
|
|
|
|
): Promise<Response<Entity.Status>> {
|
|
|
|
let params = {
|
|
|
|
text: status
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.media_ids) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
fileIds: options.media_ids
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.poll) {
|
|
|
|
let pollParam = {
|
|
|
|
choices: options.poll.options,
|
|
|
|
expiresAt: null,
|
2023-07-09 22:59:04 +02:00
|
|
|
expiredAfter: options.poll.expires_in * 1000
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
if (options.poll.multiple !== undefined) {
|
|
|
|
pollParam = Object.assign(pollParam, {
|
|
|
|
multiple: options.poll.multiple
|
|
|
|
})
|
|
|
|
}
|
|
|
|
params = Object.assign(params, {
|
|
|
|
poll: pollParam
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.in_reply_to_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
replyId: options.in_reply_to_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.sensitive) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
cw: ''
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.spoiler_text) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
cw: options.spoiler_text
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.visibility) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
visibility: this.converter.encodeVisibility(options.visibility)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.quote_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
renoteId: options.quote_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.CreatedNote>('/api/notes/create', params)
|
|
|
|
.then(async res => ({
|
|
|
|
...res,
|
2023-07-07 20:15:24 +02:00
|
|
|
data: await this.noteWithDetails(res.data.createdNote, this.baseUrlToHost(this.baseUrl), this.getFreshAccountCache())
|
2023-07-06 00:36:26 +02:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/show
|
|
|
|
*/
|
|
|
|
public async getStatus(id: string): Promise<Response<Entity.Status>> {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
|
|
|
|
noteId: id
|
|
|
|
})
|
2023-07-07 20:15:24 +02:00
|
|
|
.then(async res => ({ ...res, data: await this.noteWithDetails(res.data, this.baseUrlToHost(this.baseUrl), this.getFreshAccountCache())}));
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private getFreshAccountCache() :AccountCache {
|
|
|
|
return {
|
|
|
|
locks: new AsyncLock(),
|
|
|
|
accounts: []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-07 22:06:26 +02:00
|
|
|
public async notificationWithDetails(n: MisskeyAPI.Entity.Notification, host: string, cache: AccountCache): Promise<MegalodonEntity.Notification> {
|
|
|
|
const notification = this.converter.notification(n, host);
|
|
|
|
if (n.note)
|
|
|
|
notification.status = await this.noteWithDetails(n.note, host, cache);
|
2023-07-09 21:47:49 +02:00
|
|
|
if (notification.account)
|
|
|
|
notification.account = (await this.getAccount(notification.account.id)).data
|
2023-07-07 22:06:26 +02:00
|
|
|
return notification;
|
|
|
|
}
|
|
|
|
|
2023-07-07 20:15:24 +02:00
|
|
|
public async noteWithDetails(n: MisskeyAPI.Entity.Note, host: string, cache: AccountCache): Promise<MegalodonEntity.Status> {
|
|
|
|
const status = await this.addUserDetailsToStatus(this.converter.note(n, host), cache);
|
2023-07-10 00:05:52 +02:00
|
|
|
status.bookmarked = await this.isStatusBookmarked(n.id);
|
2023-07-07 20:15:24 +02:00
|
|
|
return this.addMentionsToStatus(status, cache);
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
2023-07-10 00:05:52 +02:00
|
|
|
public async isStatusBookmarked(id: string) : Promise<boolean> {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.State>('/api/notes/state', {
|
|
|
|
noteId: id
|
|
|
|
}).then(p => p.data.isFavorited ?? false);
|
|
|
|
}
|
|
|
|
|
2023-07-07 20:15:24 +02:00
|
|
|
public async addUserDetailsToStatus(status: Entity.Status, cache: AccountCache) : Promise<Entity.Status> {
|
|
|
|
if (status.account.followers_count === 0 && status.account.followers_count === 0 && status.account.statuses_count === 0)
|
|
|
|
status.account = await this.getAccountCached(status.account.id, status.account.acct, cache) ?? status.account;
|
|
|
|
|
2023-07-07 23:18:43 +02:00
|
|
|
if (status.reblog != null)
|
|
|
|
status.reblog = await this.addUserDetailsToStatus(status.reblog, cache);
|
|
|
|
|
|
|
|
if (status.quote != null)
|
|
|
|
status.quote = await this.addUserDetailsToStatus(status.quote, cache);
|
|
|
|
|
2023-07-07 20:15:24 +02:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2023-07-06 00:36:26 +02:00
|
|
|
public async addMentionsToStatus(status: Entity.Status, cache: AccountCache) : Promise<Entity.Status> {
|
2023-07-07 20:15:24 +02:00
|
|
|
if (status.mentions.length > 0)
|
|
|
|
return status;
|
|
|
|
|
2023-07-07 23:18:43 +02:00
|
|
|
if (status.reblog != null)
|
|
|
|
status.reblog = await this.addMentionsToStatus(status.reblog, cache);
|
|
|
|
|
|
|
|
if (status.quote != null)
|
|
|
|
status.quote = await this.addMentionsToStatus(status.quote, cache);
|
|
|
|
|
2023-07-06 00:36:26 +02:00
|
|
|
status.mentions = (await this.getMentions(status.plain_content!, cache)).filter(p => p != null);
|
|
|
|
for (const m of status.mentions.filter((value, index, array) => array.indexOf(value) === index)) {
|
2023-07-08 02:01:02 +02:00
|
|
|
if (m.acct == m.username)
|
|
|
|
status.content = status.content.replace(`@${m.acct}@${this.baseUrlToHost(this.baseUrl)}`, `@${m.acct}`);
|
2023-07-06 00:36:26 +02:00
|
|
|
status.content = status.content.replace(`@${m.acct}`, `<a href="${m.url}" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@${m.acct}</a>`);
|
|
|
|
}
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getMentions(text: string, cache: AccountCache): Promise<Entity.Mention[]> {
|
2023-07-06 01:44:29 +02:00
|
|
|
const mentions :Entity.Mention[] = [];
|
|
|
|
|
|
|
|
if (text == undefined)
|
|
|
|
return mentions;
|
|
|
|
|
2023-07-06 00:36:26 +02:00
|
|
|
const mentionMatch = text.matchAll(/(?<=^|\s)@(?<user>.*?)(?:@(?<host>.*?)|)(?=\s|$)/g);
|
|
|
|
|
|
|
|
for (const m of mentionMatch) {
|
2023-07-07 18:58:24 +02:00
|
|
|
try {
|
|
|
|
if (m.groups == null)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const account = await this.getAccountByNameCached(m.groups.user, m.groups.host, cache);
|
|
|
|
|
|
|
|
if (account == null)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
mentions.push({
|
|
|
|
id: account.id,
|
|
|
|
url: account.url,
|
|
|
|
username: account.username,
|
|
|
|
acct: account.acct
|
|
|
|
});
|
|
|
|
}
|
|
|
|
catch {}
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return mentions;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getAccountByNameCached(user: string, host: string | null, cache: AccountCache): Promise<Entity.Account | undefined | null> {
|
|
|
|
const acctToFind = host == null ? user : `${user}@${host}`;
|
|
|
|
|
|
|
|
return await cache.locks.acquire(acctToFind, async () => {
|
|
|
|
const cacheHit = cache.accounts.find(p => p.acct === acctToFind);
|
|
|
|
const account = cacheHit ?? (await this.getAccountByName(user, host ?? null)).data;
|
|
|
|
|
|
|
|
if (!account) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cacheHit == null) {
|
|
|
|
cache.accounts.push(account);
|
|
|
|
}
|
|
|
|
|
|
|
|
return account;
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-07-07 20:15:24 +02:00
|
|
|
public async getAccountCached(id: string, acct: string, cache: AccountCache): Promise<Entity.Account | undefined | null> {
|
|
|
|
return await cache.locks.acquire(acct, async () => {
|
|
|
|
const cacheHit = cache.accounts.find(p => p.id === id);
|
|
|
|
const account = cacheHit ?? (await this.getAccount(id)).data;
|
|
|
|
|
|
|
|
if (!account) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cacheHit == null) {
|
|
|
|
cache.accounts.push(account);
|
|
|
|
}
|
|
|
|
|
|
|
|
return account;
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-07-06 00:36:26 +02:00
|
|
|
public async editStatus(
|
|
|
|
_id: string,
|
|
|
|
_options: {
|
|
|
|
status?: string
|
|
|
|
spoiler_text?: string
|
|
|
|
sensitive?: boolean
|
|
|
|
media_ids?: Array<string>
|
|
|
|
poll?: { options?: Array<string>; expires_in?: number; multiple?: boolean; hide_totals?: boolean }
|
|
|
|
}
|
|
|
|
): Promise<Response<Entity.Status>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/delete
|
|
|
|
*/
|
|
|
|
public async deleteStatus(id: string): Promise<Response<{}>> {
|
|
|
|
return this.client.post<{}>('/api/notes/delete', {
|
|
|
|
noteId: id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/children
|
|
|
|
*/
|
|
|
|
public async getStatusContext(
|
|
|
|
id: string,
|
|
|
|
options?: { limit?: number; max_id?: string; since_id?: string }
|
|
|
|
): Promise<Response<Entity.Context>> {
|
|
|
|
let params = {
|
|
|
|
noteId: id
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit,
|
|
|
|
depth: 12
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 30,
|
|
|
|
depth: 12
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.since_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.since_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 30,
|
|
|
|
depth: 12
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/children', params).then(async res => {
|
|
|
|
const accountCache = this.getFreshAccountCache();
|
|
|
|
const conversation = await this.client.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/conversation', params);
|
2023-07-07 20:15:24 +02:00
|
|
|
const parents = await Promise.all(conversation.data.map(n => this.noteWithDetails(n, this.baseUrlToHost(this.baseUrl), accountCache)));
|
2023-07-06 00:36:26 +02:00
|
|
|
|
|
|
|
const context: Entity.Context = {
|
|
|
|
ancestors: parents.reverse(),
|
2023-07-07 20:15:24 +02:00
|
|
|
descendants: this.dfs(await Promise.all(res.data.map(n => this.noteWithDetails(n, this.baseUrlToHost(this.baseUrl), accountCache))))
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
return {
|
|
|
|
...res,
|
|
|
|
data: context
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
private dfs(graph: Entity.Status[]) {
|
|
|
|
// we don't need to run dfs if we have zero or one elements
|
|
|
|
if (graph.length <= 1) {
|
|
|
|
return graph;
|
|
|
|
}
|
|
|
|
|
|
|
|
// sort the graph first, so we can grab the correct starting point
|
|
|
|
graph = graph.sort((a, b) => {
|
|
|
|
if (a.id < b.id) return -1;
|
|
|
|
if (a.id > b.id) return 1;
|
|
|
|
return 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
const initialPostId = graph[0].in_reply_to_id;
|
|
|
|
|
|
|
|
// populate stack with all top level replies
|
|
|
|
const stack = graph.filter(reply => reply.in_reply_to_id === initialPostId).reverse();
|
|
|
|
const visited = new Set();
|
|
|
|
const result = [];
|
|
|
|
|
|
|
|
while (stack.length) {
|
|
|
|
const currentPost = stack.pop();
|
|
|
|
|
|
|
|
if (currentPost === undefined)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
if (!visited.has(currentPost)) {
|
|
|
|
visited.add(currentPost);
|
|
|
|
result.push(currentPost);
|
|
|
|
|
|
|
|
for (const reply of graph.filter(reply => reply.in_reply_to_id === currentPost.id).reverse()) {
|
|
|
|
stack.push(reply);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getStatusHistory(): Promise<Response<Array<Entity.StatusEdit>>> {
|
|
|
|
// FIXME: stub, implement once we have note edit history in the database
|
|
|
|
const history : Entity.StatusEdit[] = [];
|
|
|
|
const res : Response = {
|
|
|
|
headers: undefined,
|
|
|
|
statusText: "",
|
|
|
|
status: 200,
|
|
|
|
data: history
|
|
|
|
};
|
|
|
|
return new Promise(resolve => resolve(res))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/renotes
|
|
|
|
*/
|
|
|
|
public async getStatusRebloggedBy(id: string): Promise<Response<Array<Entity.Account>>> {
|
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/renotes', {
|
|
|
|
noteId: id
|
|
|
|
})
|
2023-07-07 22:51:07 +02:00
|
|
|
.then(async res => ({
|
2023-07-06 00:36:26 +02:00
|
|
|
...res,
|
2023-07-07 22:51:07 +02:00
|
|
|
data: (await Promise.all(res.data.map(n => this.getAccount(n.user.id)))).map(p => p.data)
|
2023-07-06 00:36:26 +02:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
2023-07-07 22:40:29 +02:00
|
|
|
public async getStatusFavouritedBy(id: string): Promise<Response<Array<Entity.Account>>> {
|
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.Reaction>>('/api/notes/reactions', {
|
|
|
|
noteId: id
|
|
|
|
})
|
2023-07-07 22:51:07 +02:00
|
|
|
.then(async res => ({
|
2023-07-07 22:40:29 +02:00
|
|
|
...res,
|
2023-07-07 22:51:07 +02:00
|
|
|
data: (await Promise.all(res.data.map(n => this.getAccount(n.user.id)))).map(p => p.data)
|
2023-07-07 22:40:29 +02:00
|
|
|
}))
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async favouriteStatus(id: string): Promise<Response<Entity.Status>> {
|
|
|
|
return this.createEmojiReaction(id, await this.getDefaultFavoriteEmoji());
|
|
|
|
}
|
|
|
|
|
|
|
|
private async getDefaultFavoriteEmoji(): Promise<string> {
|
|
|
|
// NOTE: get-unsecure is calckey's extension.
|
|
|
|
// Misskey doesn't have this endpoint and regular `/i/registry/get` won't work
|
|
|
|
// unless you have a 'nativeToken', which is reserved for the frontend webapp.
|
|
|
|
|
|
|
|
return await this.client
|
|
|
|
.post<Array<string>>('/api/i/registry/get-unsecure', {
|
|
|
|
key: 'reactions',
|
|
|
|
scope: ['client', 'base'],
|
|
|
|
})
|
|
|
|
.then(res => res.data[0] ?? '⭐');
|
|
|
|
}
|
|
|
|
|
2023-07-10 18:50:12 +02:00
|
|
|
private async getDefaultPostPrivacy(): Promise<'public' | 'unlisted' | 'private' | 'direct'> {
|
2023-07-10 18:50:07 +02:00
|
|
|
// NOTE: get-unsecure is calckey's extension.
|
|
|
|
// Misskey doesn't have this endpoint and regular `/i/registry/get` won't work
|
|
|
|
// unless you have a 'nativeToken', which is reserved for the frontend webapp.
|
|
|
|
|
|
|
|
return this.client
|
|
|
|
.post<string>('/api/i/registry/get-unsecure', {
|
|
|
|
key: 'defaultNoteVisibility',
|
|
|
|
scope: ['client', 'base'],
|
|
|
|
})
|
|
|
|
.then(res => {
|
|
|
|
if (!res.data || (res.data != 'public' && res.data != 'home' && res.data != 'followers' && res.data != 'specified'))
|
|
|
|
return 'public';
|
|
|
|
return this.converter.visibility(res.data);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-07-06 00:36:26 +02:00
|
|
|
public async unfavouriteStatus(id: string): Promise<Response<Entity.Status>> {
|
|
|
|
// NOTE: Misskey allows only one reaction per status, so we don't need to care what that emoji was.
|
|
|
|
return this.deleteEmojiReaction(id, '');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/create
|
|
|
|
*/
|
|
|
|
public async reblogStatus(id: string): Promise<Response<Entity.Status>> {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.CreatedNote>('/api/notes/create', {
|
|
|
|
renoteId: id
|
|
|
|
})
|
|
|
|
.then(async res => ({
|
|
|
|
...res,
|
2023-07-07 20:15:24 +02:00
|
|
|
data: await this.noteWithDetails(res.data.createdNote, this.baseUrlToHost(this.baseUrl), this.getFreshAccountCache())
|
2023-07-06 00:36:26 +02:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/unrenote
|
|
|
|
*/
|
|
|
|
public async unreblogStatus(id: string): Promise<Response<Entity.Status>> {
|
|
|
|
await this.client.post<{}>('/api/notes/unrenote', {
|
|
|
|
noteId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
|
|
|
|
noteId: id
|
|
|
|
})
|
2023-07-07 20:15:24 +02:00
|
|
|
.then(async res => ({...res, data: await this.noteWithDetails(res.data, this.baseUrlToHost(this.baseUrl), this.getFreshAccountCache())}))
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/favorites/create
|
|
|
|
*/
|
|
|
|
public async bookmarkStatus(id: string): Promise<Response<Entity.Status>> {
|
|
|
|
await this.client.post<{}>('/api/notes/favorites/create', {
|
|
|
|
noteId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
|
|
|
|
noteId: id
|
|
|
|
})
|
2023-07-07 20:15:24 +02:00
|
|
|
.then(async res => ({...res, data: await this.noteWithDetails(res.data, this.baseUrlToHost(this.baseUrl), this.getFreshAccountCache())}))
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/favorites/delete
|
|
|
|
*/
|
|
|
|
public async unbookmarkStatus(id: string): Promise<Response<Entity.Status>> {
|
|
|
|
await this.client.post<{}>('/api/notes/favorites/delete', {
|
|
|
|
noteId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
|
|
|
|
noteId: id
|
|
|
|
})
|
2023-07-07 20:15:24 +02:00
|
|
|
.then(async res => ({...res, data: await this.noteWithDetails(res.data, this.baseUrlToHost(this.baseUrl), this.getFreshAccountCache())}))
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async muteStatus(_id: string): Promise<Response<Entity.Status>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async unmuteStatus(_id: string): Promise<Response<Entity.Status>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/i/pin
|
|
|
|
*/
|
|
|
|
public async pinStatus(id: string): Promise<Response<Entity.Status>> {
|
|
|
|
await this.client.post<{}>('/api/i/pin', {
|
|
|
|
noteId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
|
|
|
|
noteId: id
|
|
|
|
})
|
2023-07-07 20:15:24 +02:00
|
|
|
.then(async res => ({...res, data: await this.noteWithDetails(res.data, this.baseUrlToHost(this.baseUrl), this.getFreshAccountCache())}))
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/i/unpin
|
|
|
|
*/
|
|
|
|
public async unpinStatus(id: string): Promise<Response<Entity.Status>> {
|
|
|
|
await this.client.post<{}>('/api/i/unpin', {
|
|
|
|
noteId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
|
|
|
|
noteId: id
|
|
|
|
})
|
2023-07-07 20:15:24 +02:00
|
|
|
.then(async res => ({...res, data: await this.noteWithDetails(res.data, this.baseUrlToHost(this.baseUrl), this.getFreshAccountCache())}))
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// statuses/media
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/drive/files/create
|
|
|
|
*/
|
2023-07-08 23:35:27 +02:00
|
|
|
public async uploadMedia(file: any, options?: { description?: string; focus?: string }): Promise<Response<Entity.Attachment>> {
|
2023-07-06 00:36:26 +02:00
|
|
|
const formData = new FormData()
|
2023-07-08 23:35:27 +02:00
|
|
|
formData.append('file', fs.createReadStream(file.path), {
|
2023-07-08 23:50:25 +02:00
|
|
|
contentType: file.mimetype
|
2023-07-08 23:35:27 +02:00
|
|
|
})
|
|
|
|
|
2023-07-08 23:50:25 +02:00
|
|
|
if (file.originalname != null && file.originalname !== 'file')
|
2023-07-08 23:35:27 +02:00
|
|
|
formData.append('name', file.originalname);
|
|
|
|
|
2023-07-08 23:50:25 +02:00
|
|
|
if (options?.description != null)
|
2023-07-08 23:35:27 +02:00
|
|
|
formData.append('comment', options.description);
|
|
|
|
|
2023-07-06 00:36:26 +02:00
|
|
|
let headers: { [key: string]: string } = {}
|
|
|
|
if (typeof formData.getHeaders === 'function') {
|
|
|
|
headers = formData.getHeaders()
|
|
|
|
}
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.File>('/api/drive/files/create', formData, headers)
|
2023-07-08 23:35:27 +02:00
|
|
|
.then(res => ({ ...res, data: this.converter.file(res.data) }))
|
|
|
|
}
|
2023-07-06 00:36:26 +02:00
|
|
|
|
|
|
|
public async getMedia(id: string): Promise<Response<Entity.Attachment>> {
|
|
|
|
const res = await this.client.post<MisskeyAPI.Entity.File>('/api/drive/files/show', { fileId: id })
|
|
|
|
return { ...res, data: this.converter.file(res.data) }
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/drive/files/update
|
|
|
|
*/
|
|
|
|
public async updateMedia(
|
|
|
|
id: string,
|
|
|
|
options?: {
|
|
|
|
file?: any
|
|
|
|
description?: string
|
|
|
|
focus?: string
|
|
|
|
is_sensitive?: boolean
|
|
|
|
}
|
|
|
|
): Promise<Response<Entity.Attachment>> {
|
|
|
|
let params = {
|
|
|
|
fileId: id
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.is_sensitive !== undefined) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
isSensitive: options.is_sensitive
|
|
|
|
})
|
|
|
|
}
|
2023-07-08 23:35:27 +02:00
|
|
|
|
|
|
|
if (options.description !== undefined) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
comment: options.description
|
|
|
|
})
|
|
|
|
}
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.File>('/api/drive/files/update', params)
|
|
|
|
.then(res => ({ ...res, data: this.converter.file(res.data) }))
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// statuses/polls
|
|
|
|
// ======================================
|
2023-07-08 01:42:51 +02:00
|
|
|
public async getPoll(id: string): Promise<Response<Entity.Poll>> {
|
|
|
|
const res = await this.getStatus(id);
|
|
|
|
if (res.data.poll == null)
|
|
|
|
throw new Error('poll not found');
|
|
|
|
return { ...res, data: res.data.poll }
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/polls/vote
|
|
|
|
*/
|
2023-07-08 01:42:51 +02:00
|
|
|
public async votePoll(id: string, choices: Array<number>): Promise<Response<Entity.Poll>> {
|
|
|
|
if (!id) {
|
2023-07-06 00:36:26 +02:00
|
|
|
return new Promise((_, reject) => {
|
2023-07-08 01:42:51 +02:00
|
|
|
const err = new ArgumentError('id is required')
|
2023-07-06 00:36:26 +02:00
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
2023-07-08 01:42:51 +02:00
|
|
|
|
|
|
|
for (const c of choices) {
|
|
|
|
const params = {
|
|
|
|
noteId: id,
|
|
|
|
choice: +c
|
|
|
|
}
|
|
|
|
await this.client.post<{}>('/api/notes/polls/vote', params)
|
|
|
|
}
|
|
|
|
|
2023-07-06 00:36:26 +02:00
|
|
|
const res = await this.client
|
|
|
|
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
|
2023-07-08 01:42:51 +02:00
|
|
|
noteId: id
|
2023-07-06 00:36:26 +02:00
|
|
|
})
|
|
|
|
.then(async res => {
|
2023-07-07 20:15:24 +02:00
|
|
|
const note = await this.noteWithDetails(res.data, this.baseUrlToHost(this.baseUrl), this.getFreshAccountCache())
|
2023-07-06 00:36:26 +02:00
|
|
|
return {...res, data: note.poll}
|
|
|
|
})
|
|
|
|
if (!res.data) {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new UnexpectedError('poll does not exist')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return { ...res, data: res.data }
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// statuses/scheduled_statuses
|
|
|
|
// ======================================
|
|
|
|
public async getScheduledStatuses(_options?: {
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
min_id?: string
|
|
|
|
}): Promise<Response<Array<Entity.ScheduledStatus>>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getScheduledStatus(_id: string): Promise<Response<Entity.ScheduledStatus>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async scheduleStatus(_id: string, _scheduled_at?: string | null): Promise<Response<Entity.ScheduledStatus>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async cancelScheduledStatus(_id: string): Promise<Response<{}>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// timelines
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/notes/global-timeline
|
|
|
|
*/
|
|
|
|
public async getPublicTimeline(options?: {
|
|
|
|
only_media?: boolean
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
min_id?: string
|
|
|
|
}): Promise<Response<Array<Entity.Status>>> {
|
|
|
|
const accountCache = this.getFreshAccountCache();
|
|
|
|
|
|
|
|
let params = {}
|
|
|
|
if (options) {
|
|
|
|
if (options.only_media !== undefined) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
withFiles: options.only_media
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.since_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.since_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.min_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.min_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/global-timeline', params)
|
|
|
|
.then(async res => ({
|
|
|
|
...res,
|
2023-07-09 03:17:41 +02:00
|
|
|
data: (await Promise.all(res.data.map(n => this.noteWithDetails(n, this.baseUrlToHost(this.baseUrl), accountCache)))).sort(this.sortByIdDesc)
|
2023-07-06 00:36:26 +02:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/local-timeline
|
|
|
|
*/
|
|
|
|
public async getLocalTimeline(options?: {
|
|
|
|
only_media?: boolean
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
min_id?: string
|
|
|
|
}): Promise<Response<Array<Entity.Status>>> {
|
|
|
|
const accountCache = this.getFreshAccountCache();
|
|
|
|
|
|
|
|
let params = {}
|
|
|
|
if (options) {
|
|
|
|
if (options.only_media !== undefined) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
withFiles: options.only_media
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.since_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.since_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.min_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.min_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/local-timeline', params)
|
|
|
|
.then(async res => ({
|
|
|
|
...res,
|
2023-07-09 03:17:41 +02:00
|
|
|
data: (await Promise.all(res.data.map(n => this.noteWithDetails(n, this.baseUrlToHost(this.baseUrl), accountCache)))).sort(this.sortByIdDesc)
|
2023-07-06 00:36:26 +02:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/search-by-tag
|
|
|
|
*/
|
|
|
|
public async getTagTimeline(
|
|
|
|
hashtag: string,
|
|
|
|
options?: {
|
|
|
|
local?: boolean
|
|
|
|
only_media?: boolean
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
min_id?: string
|
|
|
|
}
|
|
|
|
): Promise<Response<Array<Entity.Status>>> {
|
|
|
|
const accountCache = this.getFreshAccountCache();
|
|
|
|
|
|
|
|
let params = {
|
|
|
|
tag: hashtag
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.only_media !== undefined) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
withFiles: options.only_media
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.since_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.since_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.min_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.min_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/search-by-tag', params)
|
|
|
|
.then(async res => ({
|
|
|
|
...res,
|
2023-07-09 03:17:41 +02:00
|
|
|
data: (await Promise.all(res.data.map(n => this.noteWithDetails(n, this.baseUrlToHost(this.baseUrl), accountCache)))).sort(this.sortByIdDesc)
|
2023-07-06 00:36:26 +02:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/timeline
|
|
|
|
*/
|
|
|
|
public async getHomeTimeline(options?: {
|
|
|
|
local?: boolean
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
min_id?: string
|
|
|
|
}): Promise<Response<Array<Entity.Status>>> {
|
|
|
|
const accountCache = this.getFreshAccountCache();
|
|
|
|
|
|
|
|
let params = {
|
|
|
|
withFiles: false
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.since_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.since_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.min_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.min_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/timeline', params)
|
|
|
|
.then(async res => ({
|
|
|
|
...res,
|
2023-07-09 03:17:41 +02:00
|
|
|
data: (await Promise.all(res.data.map(n => this.noteWithDetails(n, this.baseUrlToHost(this.baseUrl), accountCache)))).sort(this.sortByIdDesc)
|
2023-07-06 00:36:26 +02:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/user-list-timeline
|
|
|
|
*/
|
|
|
|
public async getListTimeline(
|
|
|
|
list_id: string,
|
|
|
|
options?: {
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
min_id?: string
|
|
|
|
}
|
|
|
|
): Promise<Response<Array<Entity.Status>>> {
|
|
|
|
const accountCache = this.getFreshAccountCache();
|
|
|
|
|
|
|
|
let params = {
|
|
|
|
listId: list_id,
|
|
|
|
withFiles: false
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.since_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.since_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.min_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.min_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/user-list-timeline', params)
|
2023-07-09 03:17:41 +02:00
|
|
|
.then(async res => ({ ...res, data: (await Promise.all(res.data.map(n => this.noteWithDetails(n, this.baseUrlToHost(this.baseUrl), accountCache)))).sort(this.sortByIdDesc) }))
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// timelines/conversations
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/notes/mentions
|
|
|
|
*/
|
|
|
|
public async getConversationTimeline(options?: {
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
min_id?: string
|
|
|
|
}): Promise<Response<Array<Entity.Conversation>>> {
|
|
|
|
let params = {
|
|
|
|
visibility: 'specified'
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.since_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.since_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.min_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.min_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/mentions', params)
|
|
|
|
.then(res => ({ ...res, data: res.data.map(n => this.converter.noteToConversation(n, this.baseUrlToHost(this.baseUrl))) }))
|
|
|
|
// FIXME: ^ this should also parse mentions
|
|
|
|
}
|
|
|
|
|
|
|
|
public async deleteConversation(_id: string): Promise<Response<{}>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async readConversation(_id: string): Promise<Response<Entity.Conversation>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-07-09 03:17:41 +02:00
|
|
|
private sortByIdDesc(a: Entity.Status, b: Entity.Status): number {
|
|
|
|
if (a.id < b.id)
|
|
|
|
return 1;
|
|
|
|
if (a.id > b.id)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-07-06 00:36:26 +02:00
|
|
|
// ======================================
|
|
|
|
// timelines/lists
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/users/lists/list
|
|
|
|
*/
|
|
|
|
public async getLists(): Promise<Response<Array<Entity.List>>> {
|
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.List>>('/api/users/lists/list')
|
|
|
|
.then(res => ({ ...res, data: res.data.map(l => this.converter.list(l)) }))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/lists/show
|
|
|
|
*/
|
|
|
|
public async getList(id: string): Promise<Response<Entity.List>> {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.List>('/api/users/lists/show', {
|
|
|
|
listId: id
|
|
|
|
})
|
|
|
|
.then(res => ({ ...res, data: this.converter.list(res.data) }))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/lists/create
|
|
|
|
*/
|
|
|
|
public async createList(title: string): Promise<Response<Entity.List>> {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.List>('/api/users/lists/create', {
|
|
|
|
name: title
|
|
|
|
})
|
|
|
|
.then(res => ({ ...res, data: this.converter.list(res.data) }))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/lists/update
|
|
|
|
*/
|
|
|
|
public async updateList(id: string, title: string): Promise<Response<Entity.List>> {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.List>('/api/users/lists/update', {
|
|
|
|
listId: id,
|
|
|
|
name: title
|
|
|
|
})
|
|
|
|
.then(res => ({ ...res, data: this.converter.list(res.data) }))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/lists/delete
|
|
|
|
*/
|
|
|
|
public async deleteList(id: string): Promise<Response<{}>> {
|
|
|
|
return this.client.post<{}>('/api/users/lists/delete', {
|
|
|
|
listId: id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/lists/show
|
|
|
|
*/
|
|
|
|
public async getAccountsInList(
|
|
|
|
id: string,
|
|
|
|
_options?: {
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
}
|
|
|
|
): Promise<Response<Array<Entity.Account>>> {
|
|
|
|
const res = await this.client.post<MisskeyAPI.Entity.List>('/api/users/lists/show', {
|
|
|
|
listId: id
|
|
|
|
})
|
|
|
|
const promise = res.data.userIds.map(userId => this.getAccount(userId))
|
|
|
|
const accounts = await Promise.all(promise)
|
|
|
|
return { ...res, data: accounts.map(r => r.data) }
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/lists/push
|
|
|
|
*/
|
|
|
|
public async addAccountsToList(id: string, account_ids: Array<string>): Promise<Response<{}>> {
|
|
|
|
return this.client.post<{}>('/api/users/lists/push', {
|
|
|
|
listId: id,
|
|
|
|
userId: account_ids[0]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/users/lists/pull
|
|
|
|
*/
|
|
|
|
public async deleteAccountsFromList(id: string, account_ids: Array<string>): Promise<Response<{}>> {
|
|
|
|
return this.client.post<{}>('/api/users/lists/pull', {
|
|
|
|
listId: id,
|
|
|
|
userId: account_ids[0]
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// timelines/markers
|
|
|
|
// ======================================
|
|
|
|
public async getMarkers(_timeline: Array<string>): Promise<Response<Entity.Marker | {}>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async saveMarkers(_options?: {
|
|
|
|
home?: { last_read_id: string }
|
|
|
|
notifications?: { last_read_id: string }
|
|
|
|
}): Promise<Response<Entity.Marker>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// notifications
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/i/notifications
|
|
|
|
*/
|
|
|
|
public async getNotifications(options?: {
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
since_id?: string
|
|
|
|
min_id?: string
|
|
|
|
exclude_type?: Array<Entity.NotificationType>
|
|
|
|
account_id?: string
|
|
|
|
}): Promise<Response<Array<Entity.Notification>>> {
|
|
|
|
let params = {}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
2023-07-09 03:44:41 +02:00
|
|
|
limit: options.limit <= 100 ? options.limit : 100
|
2023-07-06 00:36:26 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.since_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.since_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.min_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.min_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.exclude_type) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
excludeType: options.exclude_type.map(e => this.converter.encodeNotificationType(e))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
2023-07-07 22:06:26 +02:00
|
|
|
const cache = this.getFreshAccountCache();
|
2023-07-06 00:36:26 +02:00
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.Notification>>('/api/i/notifications', params)
|
2023-07-07 22:06:26 +02:00
|
|
|
.then(async res => ({
|
2023-07-07 19:41:32 +02:00
|
|
|
...res,
|
2023-07-07 22:06:26 +02:00
|
|
|
data: await Promise.all(res.data
|
2023-07-07 19:41:32 +02:00
|
|
|
.filter(p => p.type != MisskeyNotificationType.FollowRequestAccepted) // these aren't supported on mastodon
|
2023-07-07 22:06:26 +02:00
|
|
|
.map(n => this.notificationWithDetails(n, this.baseUrlToHost(this.baseUrl), cache)))
|
|
|
|
}))
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async getNotification(_id: string): Promise<Response<Entity.Notification>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notifications/mark-all-as-read
|
|
|
|
*/
|
|
|
|
public async dismissNotifications(): Promise<Response<{}>> {
|
|
|
|
return this.client.post<{}>('/api/notifications/mark-all-as-read')
|
|
|
|
}
|
|
|
|
|
|
|
|
public async dismissNotification(_id: string): Promise<Response<{}>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async readNotifications(_options: {
|
|
|
|
id?: string
|
|
|
|
max_id?: string
|
|
|
|
}): Promise<Response<Entity.Notification | Array<Entity.Notification>>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('mastodon does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// notifications/push
|
|
|
|
// ======================================
|
|
|
|
public async subscribePushNotification(
|
|
|
|
_subscription: { endpoint: string; keys: { p256dh: string; auth: string } },
|
|
|
|
_data?: { alerts: { follow?: boolean; favourite?: boolean; reblog?: boolean; mention?: boolean; poll?: boolean } } | null
|
|
|
|
): Promise<Response<Entity.PushSubscription>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getPushSubscription(): Promise<Response<Entity.PushSubscription>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async updatePushSubscription(
|
|
|
|
_data?: { alerts: { follow?: boolean; favourite?: boolean; reblog?: boolean; mention?: boolean; poll?: boolean } } | null
|
|
|
|
): Promise<Response<Entity.PushSubscription>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* DELETE /api/v1/push/subscription
|
|
|
|
*/
|
|
|
|
public async deletePushSubscription(): Promise<Response<{}>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// search
|
|
|
|
// ======================================
|
|
|
|
public async search(
|
|
|
|
q: string,
|
|
|
|
type: 'accounts' | 'hashtags' | 'statuses',
|
|
|
|
options?: {
|
|
|
|
limit?: number
|
|
|
|
max_id?: string
|
|
|
|
min_id?: string
|
|
|
|
resolve?: boolean
|
|
|
|
offset?: number
|
|
|
|
following?: boolean
|
|
|
|
account_id?: string
|
|
|
|
exclude_unreviewed?: boolean
|
|
|
|
}
|
|
|
|
): Promise<Response<Entity.Results>> {
|
|
|
|
const accountCache = this.getFreshAccountCache();
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case 'accounts': {
|
2023-07-08 03:24:11 +02:00
|
|
|
if (q.startsWith("http://") || q.startsWith("https://")) {
|
|
|
|
return this.client.post('/api/ap/show', {uri: q}).then(async res => {
|
|
|
|
if (res.status != 200 || res.data.type != 'User') {
|
|
|
|
res.status = 200;
|
|
|
|
res.statusText = "OK";
|
|
|
|
res.data = {
|
|
|
|
accounts: [],
|
|
|
|
statuses: [],
|
|
|
|
hashtags: []
|
|
|
|
};
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
const account = await this.converter.userDetail(res.data.object as MisskeyAPI.Entity.UserDetail, this.baseUrlToHost(this.baseUrl));
|
|
|
|
|
|
|
|
return {
|
|
|
|
...res,
|
|
|
|
data: {
|
|
|
|
accounts: options?.max_id && options?.max_id >= account.id ? [] : [account],
|
|
|
|
statuses: [],
|
|
|
|
hashtags: []
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})
|
|
|
|
}
|
2023-07-06 00:36:26 +02:00
|
|
|
let params = {
|
|
|
|
query: q
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.offset) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
offset: options.offset
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.resolve) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
localOnly: options.resolve
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: 20
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const match = q.match(/^@(?<user>.*?)(?:@(?<host>.*?)|)$/);
|
|
|
|
if (match) {
|
|
|
|
const lookupQuery = {
|
|
|
|
username: match.groups?.user,
|
|
|
|
host: match.groups?.host
|
|
|
|
};
|
|
|
|
|
|
|
|
const result = await this.client.post<MisskeyAPI.Entity.UserDetail>('/api/users/show', lookupQuery).then(res => ({
|
|
|
|
...res,
|
|
|
|
data: {
|
|
|
|
accounts: [this.converter.userDetail(res.data, this.baseUrlToHost(this.baseUrl))],
|
|
|
|
statuses: [],
|
|
|
|
hashtags: []
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
|
|
|
if (result.status !== 200) {
|
|
|
|
result.status = 200;
|
|
|
|
result.statusText = "OK";
|
|
|
|
result.data = {
|
|
|
|
accounts: [],
|
|
|
|
statuses: [],
|
|
|
|
hashtags: []
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch {}
|
|
|
|
|
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.UserDetail>>('/api/users/search', params).then(res => ({
|
|
|
|
...res,
|
|
|
|
data: {
|
|
|
|
accounts: res.data.map(u => this.converter.userDetail(u, this.baseUrlToHost(this.baseUrl))),
|
|
|
|
statuses: [],
|
|
|
|
hashtags: []
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
case 'statuses': {
|
2023-07-08 03:24:11 +02:00
|
|
|
if (q.startsWith("http://") || q.startsWith("https://")) {
|
|
|
|
return this.client.post('/api/ap/show', {uri: q}).then(async res => {
|
|
|
|
if (res.status != 200 || res.data.type != 'Note') {
|
|
|
|
res.status = 200;
|
|
|
|
res.statusText = "OK";
|
|
|
|
res.data = {
|
|
|
|
accounts: [],
|
|
|
|
statuses: [],
|
|
|
|
hashtags: []
|
|
|
|
};
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
const post = await this.noteWithDetails(res.data.object as MisskeyAPI.Entity.Note, this.baseUrlToHost(this.baseUrl), accountCache);
|
|
|
|
|
|
|
|
return {
|
|
|
|
...res,
|
|
|
|
data: {
|
|
|
|
accounts: [],
|
|
|
|
statuses: options?.max_id && options.max_id >= post.id ? [] : [post],
|
|
|
|
hashtags: []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2023-07-06 00:36:26 +02:00
|
|
|
let params = {
|
|
|
|
query: q
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.offset) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
offset: options.offset
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.max_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
untilId: options.max_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.min_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
sinceId: options.min_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.account_id) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
userId: options.account_id
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/search', params).then(async res => ({
|
|
|
|
...res,
|
|
|
|
data: {
|
|
|
|
accounts: [],
|
2023-07-07 20:15:24 +02:00
|
|
|
statuses: await Promise.all(res.data.map(n => this.noteWithDetails(n, this.baseUrlToHost(this.baseUrl), accountCache))),
|
2023-07-06 00:36:26 +02:00
|
|
|
hashtags: []
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
case 'hashtags': {
|
|
|
|
let params = {
|
|
|
|
query: q
|
|
|
|
}
|
|
|
|
if (options) {
|
|
|
|
if (options.limit) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
limit: options.limit
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (options.offset) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
offset: options.offset
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this.client.post<Array<string>>('/api/hashtags/search', params).then(res => ({
|
|
|
|
...res,
|
|
|
|
data: {
|
|
|
|
accounts: [],
|
|
|
|
statuses: [],
|
|
|
|
hashtags: res.data.map(h => ({ name: h, url: h, history: null, following: false }))
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// instance
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/meta
|
|
|
|
* POST /api/stats
|
|
|
|
*/
|
|
|
|
public async getInstance(): Promise<Response<Entity.Instance>> {
|
|
|
|
const meta = await this.client.post<MisskeyAPI.Entity.Meta>('/api/meta').then(res => res.data)
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Stats>('/api/stats')
|
|
|
|
.then(res => ({ ...res, data: this.converter.meta(meta, res.data) }))
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getInstancePeers(): Promise<Response<Array<string>>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getInstanceActivity(): Promise<Response<Array<Entity.Activity>>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// instance/trends
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/hashtags/trend
|
|
|
|
*/
|
|
|
|
public async getInstanceTrends(_limit?: number | null): Promise<Response<Array<Entity.Tag>>> {
|
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.Hashtag>>('/api/hashtags/trend')
|
|
|
|
.then(res => ({ ...res, data: res.data.map(h => this.converter.hashtag(h)) }))
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// instance/directory
|
|
|
|
// ======================================
|
|
|
|
public async getInstanceDirectory(_options?: {
|
|
|
|
limit?: number
|
|
|
|
offset?: number
|
|
|
|
order?: 'active' | 'new'
|
|
|
|
local?: boolean
|
|
|
|
}): Promise<Response<Array<Entity.Account>>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// instance/custom_emojis
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/meta
|
|
|
|
*/
|
|
|
|
public async getInstanceCustomEmojis(): Promise<Response<Array<Entity.Emoji>>> {
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Meta>('/api/meta')
|
|
|
|
.then(res => ({ ...res, data: res.data.emojis.map(e => this.converter.emoji(e)) }))
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// instance/announcements
|
|
|
|
// ======================================
|
|
|
|
public async getInstanceAnnouncements(with_dismissed?: boolean | null): Promise<Response<Array<Entity.Announcement>>> {
|
|
|
|
let params = {}
|
|
|
|
if (with_dismissed) {
|
|
|
|
params = Object.assign(params, {
|
|
|
|
withUnreads: with_dismissed
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return this.client.post<Array<MisskeyAPI.Entity.Announcement>>('/api/announcements', params).then(res => ({
|
|
|
|
...res,
|
|
|
|
data: res.data.map(t => this.converter.announcement(t))
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
public async dismissInstanceAnnouncement(id: string): Promise<Response<{}>> {
|
|
|
|
return this.client.post<{}>('/api/i/read-announcement', { announcementId: id })
|
|
|
|
}
|
|
|
|
|
|
|
|
// ======================================
|
|
|
|
// Emoji reactions
|
|
|
|
// ======================================
|
|
|
|
/**
|
|
|
|
* POST /api/notes/reactions/create
|
|
|
|
*
|
|
|
|
* @param {string} id Target note ID.
|
|
|
|
* @param {string} emoji Reaction emoji string. This string is raw unicode emoji.
|
|
|
|
*/
|
|
|
|
public async createEmojiReaction(id: string, emoji: string): Promise<Response<Entity.Status>> {
|
|
|
|
await this.client.post<{}>('/api/notes/reactions/create', {
|
|
|
|
noteId: id,
|
|
|
|
reaction: emoji
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
|
|
|
|
noteId: id
|
|
|
|
})
|
2023-07-07 20:15:24 +02:00
|
|
|
.then(async res => ({...res, data: await this.noteWithDetails(res.data, this.baseUrlToHost(this.baseUrl), this.getFreshAccountCache())}))
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* POST /api/notes/reactions/delete
|
|
|
|
*/
|
|
|
|
public async deleteEmojiReaction(id: string, _emoji: string): Promise<Response<Entity.Status>> {
|
|
|
|
await this.client.post<{}>('/api/notes/reactions/delete', {
|
|
|
|
noteId: id
|
|
|
|
})
|
|
|
|
return this.client
|
|
|
|
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
|
|
|
|
noteId: id
|
|
|
|
})
|
2023-07-07 20:15:24 +02:00
|
|
|
.then(async res => ({...res, data: await this.noteWithDetails(res.data, this.baseUrlToHost(this.baseUrl), this.getFreshAccountCache())}))
|
2023-07-06 00:36:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async getEmojiReactions(id: string): Promise<Response<Array<Entity.Reaction>>> {
|
|
|
|
return this.client
|
|
|
|
.post<Array<MisskeyAPI.Entity.Reaction>>('/api/notes/reactions', {
|
|
|
|
noteId: id
|
|
|
|
})
|
|
|
|
.then(res => ({
|
|
|
|
...res,
|
|
|
|
data: this.converter.reactions(res.data)
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getEmojiReaction(_id: string, _emoji: string): Promise<Response<Entity.Reaction>> {
|
|
|
|
return new Promise((_, reject) => {
|
|
|
|
const err = new NoImplementedError('misskey does not support')
|
|
|
|
reject(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public userSocket(): WebSocketInterface {
|
|
|
|
return this.client.socket('user')
|
|
|
|
}
|
|
|
|
|
|
|
|
public publicSocket(): WebSocketInterface {
|
|
|
|
return this.client.socket('globalTimeline')
|
|
|
|
}
|
|
|
|
|
|
|
|
public localSocket(): WebSocketInterface {
|
|
|
|
return this.client.socket('localTimeline')
|
|
|
|
}
|
|
|
|
|
|
|
|
public tagSocket(_tag: string): WebSocketInterface {
|
|
|
|
throw new NoImplementedError('TODO: implement')
|
|
|
|
}
|
|
|
|
|
|
|
|
public listSocket(list_id: string): WebSocketInterface {
|
|
|
|
return this.client.socket('list', list_id)
|
|
|
|
}
|
|
|
|
|
|
|
|
public directSocket(): WebSocketInterface {
|
|
|
|
return this.client.socket('conversation')
|
|
|
|
}
|
|
|
|
}
|