Various fixes & refactoring

This commit is contained in:
Laura Hausmann 2023-07-02 18:58:12 +02:00
parent 8958bd1b6e
commit 332371c481
Signed by: zotan
GPG key ID: D044E84C5BE01605
4 changed files with 209 additions and 175 deletions

View file

@ -10,6 +10,7 @@ import { MegalodonInterface, WebSocketInterface, NoImplementedError, ArgumentErr
export default class Misskey implements MegalodonInterface {
public client: MisskeyAPI.Interface
public converter: MisskeyAPI.Converter
public baseUrl: string
public proxyConfig: ProxyConfig | false
@ -33,7 +34,8 @@ export default class Misskey implements MegalodonInterface {
if (userAgent) {
agent = userAgent
}
this.client = new MisskeyAPI.Client(baseUrl, token, agent, proxyConfig)
this.converter = new MisskeyAPI.Converter(baseUrl)
this.client = new MisskeyAPI.Client(baseUrl, token, agent, proxyConfig, this.converter)
this.baseUrl = baseUrl
this.proxyConfig = proxyConfig
}
@ -203,7 +205,7 @@ export default class Misskey implements MegalodonInterface {
public async verifyAccountCredentials(): Promise<Response<Entity.Account>> {
return this.client.post<MisskeyAPI.Entity.UserDetail>('/api/i').then(res => {
return Object.assign(res, {
data: MisskeyAPI.Converter.userDetail(res.data, this.baseUrlToHost(this.baseUrl))
data: this.converter.userDetail(res.data, this.baseUrlToHost(this.baseUrl))
})
})
}
@ -263,7 +265,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client.post<MisskeyAPI.Entity.UserDetail>('/api/i', params).then(res => {
return Object.assign(res, {
data: MisskeyAPI.Converter.userDetail(res.data, this.baseUrlToHost(this.baseUrl))
data: this.converter.userDetail(res.data, this.baseUrlToHost(this.baseUrl))
})
})
}
@ -278,7 +280,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => {
return Object.assign(res, {
data: MisskeyAPI.Converter.userDetail(res.data, this.baseUrlToHost(this.baseUrl))
data: this.converter.userDetail(res.data, this.baseUrlToHost(this.baseUrl))
})
})
}
@ -305,7 +307,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => {
if (res.data.pinnedNotes) {
return { ...res, data: res.data.pinnedNotes.map(n => MisskeyAPI.Converter.note(n, this.baseUrlToHost(this.baseUrl))) }
return { ...res, data: res.data.pinnedNotes.map(n => this.converter.note(n, this.baseUrlToHost(this.baseUrl))) }
}
return { ...res, data: [] }
})
@ -347,7 +349,7 @@ export default class Misskey implements MegalodonInterface {
}
}
return this.client.post<Array<MisskeyAPI.Entity.Note>>('/api/users/notes', params).then(res => {
const statuses: Array<Entity.Status> = res.data.map(note => MisskeyAPI.Converter.note(note, this.baseUrlToHost(this.baseUrl)))
const statuses: Array<Entity.Status> = res.data.map(note => this.converter.note(note, this.baseUrlToHost(this.baseUrl)))
return Object.assign(res, {
data: statuses
})
@ -384,7 +386,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client.post<Array<MisskeyAPI.Entity.Favorite>>('/api/users/reactions', params).then(res => {
return Object.assign(res, {
data: res.data.map(fav => MisskeyAPI.Converter.note(fav.note, this.baseUrlToHost(this.baseUrl)))
data: res.data.map(fav => this.converter.note(fav.note, this.baseUrlToHost(this.baseUrl)))
})
})
}
@ -426,7 +428,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client.post<Array<MisskeyAPI.Entity.Follower>>('/api/users/followers', params).then(res => {
return Object.assign(res, {
data: res.data.map(f => MisskeyAPI.Converter.follower(f))
data: res.data.map(f => this.converter.follower(f))
})
})
}
@ -454,7 +456,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client.post<Array<MisskeyAPI.Entity.Following>>('/api/users/following', params).then(res => {
return Object.assign(res, {
data: res.data.map(f => MisskeyAPI.Converter.following(f))
data: res.data.map(f => this.converter.following(f))
})
})
}
@ -486,7 +488,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => {
return Object.assign(res, {
data: MisskeyAPI.Converter.relation(res.data)
data: this.converter.relation(res.data)
})
})
}
@ -504,7 +506,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => {
return Object.assign(res, {
data: MisskeyAPI.Converter.relation(res.data)
data: this.converter.relation(res.data)
})
})
}
@ -522,7 +524,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => {
return Object.assign(res, {
data: MisskeyAPI.Converter.relation(res.data)
data: this.converter.relation(res.data)
})
})
}
@ -540,7 +542,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => {
return Object.assign(res, {
data: MisskeyAPI.Converter.relation(res.data)
data: this.converter.relation(res.data)
})
})
}
@ -558,7 +560,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => {
return Object.assign(res, {
data: MisskeyAPI.Converter.relation(res.data)
data: this.converter.relation(res.data)
})
})
}
@ -576,7 +578,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => {
return Object.assign(res, {
data: MisskeyAPI.Converter.relation(res.data)
data: this.converter.relation(res.data)
})
})
}
@ -607,7 +609,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => {
return Object.assign(res, {
data: MisskeyAPI.Converter.relation(res.data)
data: this.converter.relation(res.data)
})
})
}
@ -655,7 +657,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client.post<Array<MisskeyAPI.Entity.UserDetail>>('/api/users/search', params).then(res => {
return Object.assign(res, {
data: res.data.map(u => MisskeyAPI.Converter.userDetail(u, this.baseUrlToHost(this.baseUrl)))
data: res.data.map(u => this.converter.userDetail(u, this.baseUrlToHost(this.baseUrl)))
})
})
}
@ -692,7 +694,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client.post<Array<MisskeyAPI.Entity.Favorite>>('/api/i/favorites', params).then(res => {
return Object.assign(res, {
data: res.data.map(s => MisskeyAPI.Converter.note(s.note, this.baseUrlToHost(this.baseUrl)))
data: res.data.map(s => this.converter.note(s.note, this.baseUrlToHost(this.baseUrl)))
})
})
}
@ -732,7 +734,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client.post<Array<MisskeyAPI.Entity.Mute>>('/api/mute/list', params).then(res => {
return Object.assign(res, {
data: res.data.map(mute => MisskeyAPI.Converter.userDetail(mute.mutee, this.baseUrlToHost(this.baseUrl)))
data: res.data.map(mute => this.converter.userDetail(mute.mutee, this.baseUrlToHost(this.baseUrl)))
})
})
}
@ -764,7 +766,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client.post<Array<MisskeyAPI.Entity.Blocking>>('/api/blocking/list', params).then(res => {
return Object.assign(res, {
data: res.data.map(blocking => MisskeyAPI.Converter.userDetail(blocking.blockee, this.baseUrlToHost(this.baseUrl)))
data: res.data.map(blocking => this.converter.userDetail(blocking.blockee, this.baseUrlToHost(this.baseUrl)))
})
})
}
@ -889,7 +891,7 @@ export default class Misskey implements MegalodonInterface {
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 => MisskeyAPI.Converter.user(r.follower))
data: res.data.map(r => this.converter.user(r.follower))
})
})
}
@ -907,7 +909,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => {
return Object.assign(res, {
data: MisskeyAPI.Converter.relation(res.data)
data: this.converter.relation(res.data)
})
})
}
@ -925,7 +927,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => {
return Object.assign(res, {
data: MisskeyAPI.Converter.relation(res.data)
data: this.converter.relation(res.data)
})
})
}
@ -994,7 +996,7 @@ export default class Misskey implements MegalodonInterface {
scope: ['client', 'base'],
}).then(ga => {
return Object.assign(res, {
data: MisskeyAPI.Converter.userPreferences(res.data, ga.data)
data: this.converter.userPreferences(res.data, ga.data)
})
})
*/
@ -1002,7 +1004,7 @@ export default class Misskey implements MegalodonInterface {
// TODO:
// FIXME: get this from api
return Object.assign(res, {
data: MisskeyAPI.Converter.userPreferences(res.data, {defaultNoteVisibility: "followers", tutorial: -1})
data: this.converter.userPreferences(res.data, {defaultNoteVisibility: "followers", tutorial: -1})
})
})
}
@ -1022,7 +1024,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client
.post<Array<MisskeyAPI.Entity.UserDetail>>('/api/users/recommendation', params)
.then(res => ({ ...res, data: res.data.map(u => MisskeyAPI.Converter.userDetail(u, this.baseUrlToHost(this.baseUrl))) }))
.then(res => ({ ...res, data: res.data.map(u => this.converter.userDetail(u, this.baseUrlToHost(this.baseUrl))) }))
}
// ======================================
@ -1107,7 +1109,7 @@ export default class Misskey implements MegalodonInterface {
}
if (options.visibility) {
params = Object.assign(params, {
visibility: MisskeyAPI.Converter.encodeVisibility(options.visibility)
visibility: this.converter.encodeVisibility(options.visibility)
})
}
if (options.quote_id) {
@ -1118,7 +1120,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client
.post<MisskeyAPI.Entity.CreatedNote>('/api/notes/create', params)
.then(res => ({ ...res, data: MisskeyAPI.Converter.note(res.data.createdNote, this.baseUrlToHost(this.baseUrl)) }))
.then(res => ({ ...res, data: this.converter.note(res.data.createdNote, this.baseUrlToHost(this.baseUrl)) }))
}
/**
@ -1129,7 +1131,7 @@ export default class Misskey implements MegalodonInterface {
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
noteId: id
})
.then(res => ({ ...res, data: MisskeyAPI.Converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
.then(res => ({ ...res, data: this.converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
}
public async editStatus(
@ -1197,25 +1199,25 @@ export default class Misskey implements MegalodonInterface {
depth: 12
})
}
return this.client.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/children', params).then(res => {
return this.client.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/children', params).then(async res => {
console.log(JSON.stringify(res, null, 2));
const parent : Entity.Status[] = [];
const parents: Entity.Status[] = [];
let lastNote = await this.getStatus(params.noteId);
this.getStatus(params.noteId).then(res => {
console.log("getting status");
if (res.data.in_reply_to_id != null){
console.log("replyid not null");
this.getStatus(res.data.in_reply_to_id).then(res => {
console.log("setting parent to " + res.data.id);
parent.push(res.data);
});
}
});
for (let i = 0; i < 10; i++) {
const parentId = this.getParent(lastNote.data);
if (parentId == null)
break;
const parent = await this.getStatus(parentId);
parents.push(parent.data);
lastNote = parent;
}
const context: Entity.Context = {
ancestors: parent,
descendants: this.dfs(res.data.map(n => MisskeyAPI.Converter.note(n, this.baseUrlToHost(this.baseUrl))))
ancestors: parents.reverse(),
descendants: this.dfs(res.data.map(n => this.converter.note(n, this.baseUrlToHost(this.baseUrl))))
}
return {
...res,
@ -1224,6 +1226,10 @@ export default class Misskey implements MegalodonInterface {
})
}
private getParent(note: Entity.Status) {
return note.in_reply_to_id ?? null;
}
private dfs(graph: Entity.Status[]) {
// we don't need to run dfs if we have zero or one elements
if (graph.length <= 1) {
@ -1274,7 +1280,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => ({
...res,
data: res.data.map(n => MisskeyAPI.Converter.user(n.user))
data: res.data.map(n => this.converter.user(n.user))
}))
}
@ -1315,7 +1321,7 @@ export default class Misskey implements MegalodonInterface {
.post<MisskeyAPI.Entity.CreatedNote>('/api/notes/create', {
renoteId: id
})
.then(res => ({ ...res, data: MisskeyAPI.Converter.note(res.data.createdNote, this.baseUrlToHost(this.baseUrl)) }))
.then(res => ({ ...res, data: this.converter.note(res.data.createdNote, this.baseUrlToHost(this.baseUrl)) }))
}
/**
@ -1329,7 +1335,7 @@ export default class Misskey implements MegalodonInterface {
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
noteId: id
})
.then(res => ({ ...res, data: MisskeyAPI.Converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
.then(res => ({ ...res, data: this.converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
}
/**
@ -1343,7 +1349,7 @@ export default class Misskey implements MegalodonInterface {
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
noteId: id
})
.then(res => ({ ...res, data: MisskeyAPI.Converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
.then(res => ({ ...res, data: this.converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
}
/**
@ -1357,7 +1363,7 @@ export default class Misskey implements MegalodonInterface {
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
noteId: id
})
.then(res => ({ ...res, data: MisskeyAPI.Converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
.then(res => ({ ...res, data: this.converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
}
public async muteStatus(_id: string): Promise<Response<Entity.Status>> {
@ -1385,7 +1391,7 @@ export default class Misskey implements MegalodonInterface {
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
noteId: id
})
.then(res => ({ ...res, data: MisskeyAPI.Converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
.then(res => ({ ...res, data: this.converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
}
/**
@ -1399,7 +1405,7 @@ export default class Misskey implements MegalodonInterface {
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
noteId: id
})
.then(res => ({ ...res, data: MisskeyAPI.Converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
.then(res => ({ ...res, data: this.converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
}
// ======================================
@ -1420,12 +1426,12 @@ export default class Misskey implements MegalodonInterface {
}
return this.client
.post<MisskeyAPI.Entity.File>('/api/drive/files/create', formData, headers)
.then(res => ({ ...res, data: MisskeyAPI.Converter.file(res.data) }))
.then(res => ({ ...res, data: this.converter.file(res.data) }))
}
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: MisskeyAPI.Converter.file(res.data) }
return { ...res, data: this.converter.file(res.data) }
}
/**
@ -1452,7 +1458,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client
.post<MisskeyAPI.Entity.File>('/api/drive/files/update', params)
.then(res => ({ ...res, data: MisskeyAPI.Converter.file(res.data) }))
.then(res => ({ ...res, data: this.converter.file(res.data) }))
}
// ======================================
@ -1485,7 +1491,7 @@ export default class Misskey implements MegalodonInterface {
noteId: status_id
})
.then(res => {
const note = MisskeyAPI.Converter.note(res.data, this.baseUrlToHost(this.baseUrl))
const note = this.converter.note(res.data, this.baseUrlToHost(this.baseUrl))
return { ...res, data: note.poll }
})
if (!res.data) {
@ -1576,7 +1582,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/global-timeline', params)
.then(res => ({ ...res, data: res.data.map(n => MisskeyAPI.Converter.note(n, this.baseUrlToHost(this.baseUrl))) }))
.then(res => ({ ...res, data: res.data.map(n => this.converter.note(n, this.baseUrlToHost(this.baseUrl))) }))
}
/**
@ -1619,7 +1625,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/local-timeline', params)
.then(res => ({ ...res, data: res.data.map(n => MisskeyAPI.Converter.note(n, this.baseUrlToHost(this.baseUrl))) }))
.then(res => ({ ...res, data: res.data.map(n => this.converter.note(n, this.baseUrlToHost(this.baseUrl))) }))
}
/**
@ -1668,7 +1674,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/search-by-tag', params)
.then(res => ({ ...res, data: res.data.map(n => MisskeyAPI.Converter.note(n, this.baseUrlToHost(this.baseUrl))) }))
.then(res => ({ ...res, data: res.data.map(n => this.converter.note(n, this.baseUrlToHost(this.baseUrl))) }))
}
/**
@ -1708,7 +1714,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/timeline', params)
.then(res => ({ ...res, data: res.data.map(n => MisskeyAPI.Converter.note(n, this.baseUrlToHost(this.baseUrl))) }))
.then(res => ({ ...res, data: res.data.map(n => this.converter.note(n, this.baseUrlToHost(this.baseUrl))) }))
}
/**
@ -1751,7 +1757,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/user-list-timeline', params)
.then(res => ({ ...res, data: res.data.map(n => MisskeyAPI.Converter.note(n, this.baseUrlToHost(this.baseUrl))) }))
.then(res => ({ ...res, data: res.data.map(n => this.converter.note(n, this.baseUrlToHost(this.baseUrl))) }))
}
// ======================================
@ -1793,7 +1799,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client
.post<Array<MisskeyAPI.Entity.Note>>('/api/notes/mentions', params)
.then(res => ({ ...res, data: res.data.map(n => MisskeyAPI.Converter.noteToConversation(n, this.baseUrlToHost(this.baseUrl))) }))
.then(res => ({ ...res, data: res.data.map(n => this.converter.noteToConversation(n, this.baseUrlToHost(this.baseUrl))) }))
}
public async deleteConversation(_id: string): Promise<Response<{}>> {
@ -1819,7 +1825,7 @@ export default class Misskey implements MegalodonInterface {
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 => MisskeyAPI.Converter.list(l)) }))
.then(res => ({ ...res, data: res.data.map(l => this.converter.list(l)) }))
}
/**
@ -1830,7 +1836,7 @@ export default class Misskey implements MegalodonInterface {
.post<MisskeyAPI.Entity.List>('/api/users/lists/show', {
listId: id
})
.then(res => ({ ...res, data: MisskeyAPI.Converter.list(res.data) }))
.then(res => ({ ...res, data: this.converter.list(res.data) }))
}
/**
@ -1841,7 +1847,7 @@ export default class Misskey implements MegalodonInterface {
.post<MisskeyAPI.Entity.List>('/api/users/lists/create', {
name: title
})
.then(res => ({ ...res, data: MisskeyAPI.Converter.list(res.data) }))
.then(res => ({ ...res, data: this.converter.list(res.data) }))
}
/**
@ -1853,7 +1859,7 @@ export default class Misskey implements MegalodonInterface {
listId: id,
name: title
})
.then(res => ({ ...res, data: MisskeyAPI.Converter.list(res.data) }))
.then(res => ({ ...res, data: this.converter.list(res.data) }))
}
/**
@ -1962,13 +1968,13 @@ export default class Misskey implements MegalodonInterface {
}
if (options.exclude_type) {
params = Object.assign(params, {
excludeType: options.exclude_type.map(e => MisskeyAPI.Converter.encodeNotificationType(e))
excludeType: options.exclude_type.map(e => this.converter.encodeNotificationType(e))
})
}
}
return this.client
.post<Array<MisskeyAPI.Entity.Notification>>('/api/i/notifications', params)
.then(res => ({ ...res, data: res.data.map(n => MisskeyAPI.Converter.notification(n, this.baseUrlToHost(this.baseUrl))) }))
.then(res => ({ ...res, data: res.data.map(n => this.converter.notification(n, this.baseUrlToHost(this.baseUrl))) }))
}
public async getNotification(_id: string): Promise<Response<Entity.Notification>> {
@ -2083,7 +2089,7 @@ export default class Misskey implements MegalodonInterface {
return this.client.post<Array<MisskeyAPI.Entity.UserDetail>>('/api/users/search', params).then(res => ({
...res,
data: {
accounts: res.data.map(u => MisskeyAPI.Converter.userDetail(u, this.baseUrlToHost(this.baseUrl))),
accounts: res.data.map(u => this.converter.userDetail(u, this.baseUrlToHost(this.baseUrl))),
statuses: [],
hashtags: []
}
@ -2124,7 +2130,7 @@ export default class Misskey implements MegalodonInterface {
...res,
data: {
accounts: [],
statuses: res.data.map(n => MisskeyAPI.Converter.note(n, this.baseUrlToHost(this.baseUrl))),
statuses: res.data.map(n => this.converter.note(n, this.baseUrlToHost(this.baseUrl))),
hashtags: []
}
}))
@ -2168,7 +2174,7 @@ export default class Misskey implements MegalodonInterface {
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: MisskeyAPI.Converter.meta(meta, res.data) }))
.then(res => ({ ...res, data: this.converter.meta(meta, res.data) }))
}
public async getInstancePeers(): Promise<Response<Array<string>>> {
@ -2194,7 +2200,7 @@ export default class Misskey implements MegalodonInterface {
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 => MisskeyAPI.Converter.hashtag(h)) }))
.then(res => ({ ...res, data: res.data.map(h => this.converter.hashtag(h)) }))
}
// ======================================
@ -2221,7 +2227,7 @@ export default class Misskey implements MegalodonInterface {
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 => MisskeyAPI.Converter.emoji(e)) }))
.then(res => ({ ...res, data: res.data.emojis.map(e => this.converter.emoji(e)) }))
}
// ======================================
@ -2236,7 +2242,7 @@ export default class Misskey implements MegalodonInterface {
}
return this.client.post<Array<MisskeyAPI.Entity.Announcement>>('/api/announcements', params).then(res => ({
...res,
data: res.data.map(t => MisskeyAPI.Converter.announcement(t))
data: res.data.map(t => this.converter.announcement(t))
}))
}
@ -2262,7 +2268,7 @@ export default class Misskey implements MegalodonInterface {
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
noteId: id
})
.then(res => ({ ...res, data: MisskeyAPI.Converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
.then(res => ({ ...res, data: this.converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
}
/**
@ -2276,7 +2282,7 @@ export default class Misskey implements MegalodonInterface {
.post<MisskeyAPI.Entity.Note>('/api/notes/show', {
noteId: id
})
.then(res => ({ ...res, data: MisskeyAPI.Converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
.then(res => ({ ...res, data: this.converter.note(res.data, this.baseUrlToHost(this.baseUrl)) }))
}
public async getEmojiReactions(id: string): Promise<Response<Array<Entity.Reaction>>> {
@ -2286,7 +2292,7 @@ export default class Misskey implements MegalodonInterface {
})
.then(res => ({
...res,
data: MisskeyAPI.Converter.reactions(res.data)
data: this.converter.reactions(res.data)
}))
}

View file

@ -44,9 +44,51 @@ namespace MisskeyAPI {
export type APIEmoji = { emojis: Emoji[] }
}
export namespace Converter {
export class Converter {
private baseUrl: string
private instanceHost: string
private plcUrl: string
private modelOfAcct = {
id: "1",
username: 'none',
acct: 'none',
display_name: 'none',
locked: true,
bot: true,
discoverable: false,
group: false,
created_at: '1971-01-01T00:00:00.000Z',
note: '',
url: 'plc',
avatar: 'plc',
avatar_static: 'plc',
header: 'plc',
header_static: 'plc',
followers_count: -1,
following_count: 0,
statuses_count: 0,
last_status_at: '1971-01-01T00:00:00.000Z',
noindex: true,
emojis: [],
fields: [],
moved: null
}
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
this.instanceHost = baseUrl.substring(baseUrl.indexOf('//') + 2);
this.plcUrl = `${baseUrl}/static-assets/transparent.png`;
this.modelOfAcct.url = this.plcUrl;
this.modelOfAcct.avatar = this.plcUrl;
this.modelOfAcct.avatar_static = this.plcUrl;
this.modelOfAcct.header = this.plcUrl;
this.modelOfAcct.header_static = this.plcUrl;
}
// FIXME: Properly render MFM instead of just escaping HTML characters.
const escapeMFM = (text: string): string => text
escapeMFM = (text: string): string => text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
@ -55,7 +97,7 @@ namespace MisskeyAPI {
.replace(/`/g, '&#x60;')
.replace(/\r?\n/g, '<br>');
export const emoji = (e: Entity.Emoji): MegalodonEntity.Emoji => {
emoji = (e: Entity.Emoji): MegalodonEntity.Emoji => {
return {
shortcode: e.name,
static_url: e.url,
@ -65,15 +107,15 @@ namespace MisskeyAPI {
}
}
export const field = (f: Entity.Field): MegalodonEntity.Field => ({
field = (f: Entity.Field): MegalodonEntity.Field => ({
name: f.name,
value: escapeMFM(f.value),
value: this.escapeMFM(f.value),
verified_at: null
})
export const user = (u: Entity.User): MegalodonEntity.Account => {
user = (u: Entity.User): MegalodonEntity.Account => {
let acct = u.username
let acctUrl = `https://${u.host || 'example.com'}/@${u.username}`
let acctUrl = `https://${u.host || this.instanceHost}/@${u.username}`
if (u.host) {
acct = `${u.username}@${u.host}`
acctUrl = `https://${u.host}/@${u.username}`
@ -91,20 +133,20 @@ namespace MisskeyAPI {
note: '',
url: acctUrl,
avatar: u.avatarUrl,
avatar_static: u.avatarColor,
header: 'https://http.cat/404', // FIXME
header_static: 'https://http.cat/404', // FIXME
emojis: u.emojis.map(e => emoji(e)),
avatar_static: u.avatarUrl,
header: this.plcUrl, // FIXME
header_static: this.plcUrl, // FIXME
emojis: u.emojis.map(e => this.emoji(e)),
moved: null,
fields: [],
bot: false
}
}
export const userDetail = (u: Entity.UserDetail, host: string): MegalodonEntity.Account => {
userDetail = (u: Entity.UserDetail, host: string): MegalodonEntity.Account => {
let acct = u.username
host = host.replace('https://', '')
let acctUrl = `https://${host || u.host || 'example.com'}/@${u.username}`
let acctUrl = `https://${host || u.host || this.instanceHost}/@${u.username}`
if (u.host) {
acct = `${u.username}@${u.host}`
acctUrl = `https://${u.host}/@${u.username}`
@ -122,27 +164,27 @@ namespace MisskeyAPI {
note: u.description,
url: acct,
avatar: u.avatarUrl,
avatar_static: u.avatarColor,
header: u.bannerUrl,
header_static: u.bannerColor,
emojis: u.emojis.map(e => emoji(e)),
avatar_static: u.avatarUrl,
header: u.bannerUrl ?? this.plcUrl,
header_static: u.bannerUrl ?? this.plcUrl,
emojis: u.emojis.map(e => this.emoji(e)),
moved: null,
fields: u.fields.map(f => field(f)),
fields: u.fields.map(f => this.field(f)),
bot: u.isBot,
}
}
export const userPreferences = (u: MisskeyAPI.Entity.UserDetailMe, g: MisskeyAPI.Entity.GetAll): MegalodonEntity.Preferences => {
userPreferences = (u: MisskeyAPI.Entity.UserDetailMe, g: MisskeyAPI.Entity.GetAll): MegalodonEntity.Preferences => {
return {
"reading:expand:media": "default",
"reading:expand:spoilers": false,
"posting:default:language": u.lang,
"posting:default:sensitive": u.alwaysMarkNsfw,
"posting:default:visibility": visibility(g.defaultNoteVisibility)
"posting:default:visibility": this.visibility(g.defaultNoteVisibility)
}
}
export const visibility = (v: 'public' | 'home' | 'followers' | 'specified'): 'public' | 'unlisted' | 'private' | 'direct' => {
visibility = (v: 'public' | 'home' | 'followers' | 'specified'): 'public' | 'unlisted' | 'private' | 'direct' => {
switch (v) {
case 'public':
return v
@ -155,7 +197,7 @@ namespace MisskeyAPI {
}
}
export const encodeVisibility = (v: 'public' | 'unlisted' | 'private' | 'direct'): 'public' | 'home' | 'followers' | 'specified' => {
encodeVisibility = (v: 'public' | 'unlisted' | 'private' | 'direct'): 'public' | 'home' | 'followers' | 'specified' => {
switch (v) {
case 'public':
return v
@ -168,7 +210,7 @@ namespace MisskeyAPI {
}
}
export const fileType = (s: string): 'unknown' | 'image' | 'gifv' | 'video' | 'audio' => {
fileType = (s: string): 'unknown' | 'image' | 'gifv' | 'video' | 'audio' => {
if (s === 'image/gif') {
return 'gifv'
}
@ -184,10 +226,10 @@ namespace MisskeyAPI {
return 'unknown'
}
export const file = (f: Entity.File): MegalodonEntity.Attachment => {
file = (f: Entity.File): MegalodonEntity.Attachment => {
return {
id: f.id,
type: fileType(f.type),
type: this.fileType(f.type),
url: f.url,
remote_url: f.url,
preview_url: f.thumbnailUrl,
@ -201,15 +243,15 @@ namespace MisskeyAPI {
}
}
export const follower = (f: Entity.Follower): MegalodonEntity.Account => {
return user(f.follower)
follower = (f: Entity.Follower): MegalodonEntity.Account => {
return this.user(f.follower)
}
export const following = (f: Entity.Following): MegalodonEntity.Account => {
return user(f.followee)
following = (f: Entity.Following): MegalodonEntity.Account => {
return this.user(f.followee)
}
export const relation = (r: Entity.Relation): MegalodonEntity.Relationship => {
relation = (r: Entity.Relation): MegalodonEntity.Relationship => {
return {
id: r.id,
following: r.isFollowing,
@ -226,14 +268,14 @@ namespace MisskeyAPI {
}
}
export const choice = (c: Entity.Choice): MegalodonEntity.PollOption => {
choice = (c: Entity.Choice): MegalodonEntity.PollOption => {
return {
title: c.text,
votes_count: c.votes
}
}
export const poll = (p: Entity.Poll): MegalodonEntity.Poll => {
poll = (p: Entity.Poll): MegalodonEntity.Poll => {
const now = dayjs()
const expire = dayjs(p.expiresAt)
const count = p.choices.reduce((sum, choice) => sum + choice.votes, 0)
@ -243,49 +285,49 @@ namespace MisskeyAPI {
expired: now.isAfter(expire),
multiple: p.multiple,
votes_count: count,
options: p.choices.map(c => choice(c)),
options: p.choices.map(c => this.choice(c)),
voted: p.choices.some(c => c.isVoted)
}
}
export const note = (n: Entity.Note, host: string): MegalodonEntity.Status => {
note = (n: Entity.Note, host: string): MegalodonEntity.Status => {
host = host.replace('https://', '')
return {
id: n.id,
uri: n.uri ? n.uri : `https://${host}/notes/${n.id}`,
url: n.uri ? n.uri : `https://${host}/notes/${n.id}`,
account: user(n.user),
account: this.user(n.user),
in_reply_to_id: n.replyId,
in_reply_to_account_id: n.reply?.userId ?? null,
reblog: n.renote ? note(n.renote, host) : null,
content: n.text ? escapeMFM(n.text) : '',
reblog: n.renote ? this.note(n.renote, host) : null,
content: n.text ? this.escapeMFM(n.text) : '',
plain_content: n.text ? n.text : null,
created_at: n.createdAt,
emojis: n.emojis.map(e => emoji(e)),
emojis: n.emojis.map(e => this.emoji(e)),
replies_count: n.repliesCount,
reblogs_count: n.renoteCount,
favourites_count: getTotalReactions(n.reactions), // FIXME: instead get # of default reaction emoji reactions
favourites_count: this.getTotalReactions(n.reactions), // FIXME: instead get # of default reaction emoji reactions
reblogged: false,
favourited: !!n.myReaction,
muted: false,
sensitive: n.files ? n.files.some(f => f.isSensitive) : false,
spoiler_text: n.cw ? n.cw : '',
visibility: visibility(n.visibility),
media_attachments: n.files ? n.files.map(f => file(f)) : [],
visibility: this.visibility(n.visibility),
media_attachments: n.files ? n.files.map(f => this.file(f)) : [],
mentions: [],
tags: [],
card: null,
poll: n.poll ? poll(n.poll) : null,
poll: n.poll ? this.poll(n.poll) : null,
application: null,
language: null,
pinned: null,
emoji_reactions: mapReactions(n.reactions, n.myReaction),
emoji_reactions: this.mapReactions(n.reactions, n.myReaction),
bookmarked: false,
quote: n.renote && n.text ? note(n.renote, host) : null
quote: n.renote && n.text ? this.note(n.renote, host) : null
}
}
export const mapReactions = (r: { [key: string]: number }, myReaction?: string): Array<MegalodonEntity.Reaction> => {
mapReactions = (r: { [key: string]: number }, myReaction?: string): Array<MegalodonEntity.Reaction> => {
return Object.keys(r).map(key => {
if (myReaction && key === myReaction) {
return {
@ -302,11 +344,11 @@ namespace MisskeyAPI {
})
}
export const getTotalReactions = (r: { [key: string]: number }): number => {
getTotalReactions = (r: { [key: string]: number }): number => {
return Object.values(r).length > 0 ? Object.values(r).reduce((previousValue, currentValue) => previousValue + currentValue) : 0
}
export const reactions = (r: Array<Entity.Reaction>): Array<MegalodonEntity.Reaction> => {
reactions = (r: Array<Entity.Reaction>): Array<MegalodonEntity.Reaction> => {
const result: Array<MegalodonEntity.Reaction> = []
for (const e of r) {
const i = result.findIndex(res => res.name === e.type)
@ -323,25 +365,25 @@ namespace MisskeyAPI {
return result
}
export const noteToConversation = (n: Entity.Note, host: string): MegalodonEntity.Conversation => {
const accounts: Array<MegalodonEntity.Account> = [user(n.user)]
noteToConversation = (n: Entity.Note, host: string): MegalodonEntity.Conversation => {
const accounts: Array<MegalodonEntity.Account> = [this.user(n.user)]
if (n.reply) {
accounts.push(user(n.reply.user))
accounts.push(this.user(n.reply.user))
}
return {
id: n.id,
accounts: accounts,
last_status: note(n, host),
last_status: this.note(n, host),
unread: false
}
}
export const list = (l: Entity.List): MegalodonEntity.List => ({
list = (l: Entity.List): MegalodonEntity.List => ({
id: l.id,
title: l.name
})
export const encodeNotificationType = (e: MegalodonEntity.NotificationType): MisskeyEntity.NotificationType => {
encodeNotificationType = (e: MegalodonEntity.NotificationType): MisskeyEntity.NotificationType => {
switch (e) {
case NotificationType.Follow:
return MisskeyNotificationType.Follow
@ -361,7 +403,7 @@ namespace MisskeyAPI {
}
}
export const decodeNotificationType = (e: MisskeyEntity.NotificationType): MegalodonEntity.NotificationType => {
decodeNotificationType = (e: MisskeyEntity.NotificationType): MegalodonEntity.NotificationType => {
switch (e) {
case MisskeyNotificationType.Follow:
return NotificationType.Follow
@ -383,35 +425,12 @@ namespace MisskeyAPI {
return e
}
}
const modelOfAcct = {
id: "1",
username: 'none',
acct: 'none',
display_name: 'none',
locked: true,
bot: true,
discoverable: false,
group: false,
created_at: '1971-01-01T00:00:00.000Z',
note: '',
url: 'https://http.cat/404',
avatar: 'https://http.cat/404',
avatar_static: 'https://http.cat/404',
header: 'https://http.cat/404',
header_static: 'https://http.cat/404',
followers_count: -1,
following_count: 0,
statuses_count: 0,
last_status_at: '1971-01-01T00:00:00.000Z',
noindex: true,
emojis: [],
fields: [],
moved: null
}
export const announcement = (a: Entity.Announcement): MegalodonEntity.Announcement => ({
announcement = (a: Entity.Announcement): MegalodonEntity.Announcement => ({
id: a.id,
content: `<h1>${escapeMFM(a.title)}</h1>${escapeMFM(a.text)}`,
content: `<h1>${this.escapeMFM(a.title)}</h1>${this.escapeMFM(a.text)}`,
starts_at: null,
ends_at: null,
published: true,
@ -426,16 +445,16 @@ namespace MisskeyAPI {
reactions: [],
})
export const notification = (n: Entity.Notification, host: string): MegalodonEntity.Notification => {
notification = (n: Entity.Notification, host: string): MegalodonEntity.Notification => {
let notification = {
id: n.id,
account: n.user ? user(n.user) : modelOfAcct,
account: n.user ? this.user(n.user) : this.modelOfAcct,
created_at: n.createdAt,
type: decodeNotificationType(n.type)
type: this.decodeNotificationType(n.type)
}
if (n.note) {
notification = Object.assign(notification, {
status: note(n.note, host)
status: this.note(n.note, host)
})
}
if (n.reaction) {
@ -446,7 +465,7 @@ namespace MisskeyAPI {
return notification
}
export const stats = (s: Entity.Stats): MegalodonEntity.Stats => {
stats = (s: Entity.Stats): MegalodonEntity.Stats => {
return {
user_count: s.usersCount,
status_count: s.notesCount,
@ -454,7 +473,7 @@ namespace MisskeyAPI {
}
}
export const meta = (m: Entity.Meta, s: Entity.Stats): MegalodonEntity.Instance => {
meta = (m: Entity.Meta, s: Entity.Stats): MegalodonEntity.Instance => {
const wss = m.uri.replace(/^https:\/\//, 'wss://')
return {
uri: m.uri,
@ -466,7 +485,7 @@ namespace MisskeyAPI {
urls: {
streaming_api: `${wss}/streaming`
},
stats: stats(s),
stats: this.stats(s),
languages: m.langs,
contact_account: null,
max_toot_chars: m.maxNoteTextLength,
@ -474,7 +493,7 @@ namespace MisskeyAPI {
}
}
export const hashtag = (h: Entity.Hashtag): MegalodonEntity.Tag => {
hashtag = (h: Entity.Hashtag): MegalodonEntity.Tag => {
return {
name: h.tag,
url: h.tag,
@ -525,19 +544,22 @@ namespace MisskeyAPI {
private userAgent: string
private abortController: AbortController
private proxyConfig: ProxyConfig | false = false
private converter: Converter
/**
* @param baseUrl hostname or base URL
* @param accessToken access token from OAuth2 authorization
* @param userAgent UserAgent is specified in header on request.
* @param proxyConfig Proxy setting, or set false if don't use proxy.
* @param converter Converter instance.
*/
constructor(baseUrl: string, accessToken: string | null, userAgent: string = DEFAULT_UA, proxyConfig: ProxyConfig | false = false) {
constructor(baseUrl: string, accessToken: string | null, userAgent: string = DEFAULT_UA, proxyConfig: ProxyConfig | false = false, converter: Converter) {
this.accessToken = accessToken
this.baseUrl = baseUrl
this.userAgent = userAgent
this.proxyConfig = proxyConfig
this.abortController = new AbortController()
this.converter = converter
axios.defaults.signal = this.abortController.signal
}
@ -570,6 +592,7 @@ namespace MisskeyAPI {
}
}
console.log(`sending request to ${this.baseUrl}${path} with params:`);
console.log(JSON.stringify(bodyParams, null, 2));
return axios.post<T>(this.baseUrl + path, bodyParams, options).then((resp: AxiosResponse<T>) => {
@ -605,7 +628,7 @@ namespace MisskeyAPI {
throw new Error('accessToken is required')
}
const url = `${this.baseUrl}/streaming`
const streaming = new WebSocket(url, channel, this.accessToken, listId, this.userAgent, this.proxyConfig)
const streaming = new WebSocket(url, channel, this.accessToken, listId, this.userAgent, this.proxyConfig, this.converter)
process.nextTick(() => {
streaming.start()
})

View file

@ -18,6 +18,7 @@ export default class WebSocket extends EventEmitter implements WebSocketInterfac
public headers: { [key: string]: string }
public proxyConfig: ProxyConfig | false = false
public listId: string | null = null
private _converter: MisskeyAPI.Converter
private _accessToken: string
private _reconnectInterval: number
private _reconnectMaxAttempts: number
@ -41,7 +42,8 @@ export default class WebSocket extends EventEmitter implements WebSocketInterfac
accessToken: string,
listId: string | undefined,
userAgent: string,
proxyConfig: ProxyConfig | false = false
proxyConfig: ProxyConfig | false = false,
converter: MisskeyAPI.Converter
) {
super()
this.url = url
@ -63,6 +65,7 @@ export default class WebSocket extends EventEmitter implements WebSocketInterfac
this._connectionClosed = false
this._channelID = uuid()
this._pongReceivedTimestamp = dayjs()
this._converter = converter
}
/**
@ -286,13 +289,13 @@ export default class WebSocket extends EventEmitter implements WebSocketInterfac
*/
private _setupParser() {
this.parser.on('update', (note: MisskeyAPI.Entity.Note) => {
this.emit('update', MisskeyAPI.Converter.note(note, this.baseUrlToHost(this.url)))
this.emit('update', this._converter.note(note, this.baseUrlToHost(this.url)))
})
this.parser.on('notification', (notification: MisskeyAPI.Entity.Notification) => {
this.emit('notification', MisskeyAPI.Converter.notification(notification, this.baseUrlToHost(this.url)))
this.emit('notification', this._converter.notification(notification, this.baseUrlToHost(this.url)))
})
this.parser.on('conversation', (note: MisskeyAPI.Entity.Note) => {
this.emit('conversation', MisskeyAPI.Converter.noteToConversation(note, this.baseUrlToHost(this.url)))
this.emit('conversation', this._converter.noteToConversation(note, this.baseUrlToHost(this.url)))
})
this.parser.on('error', (err: Error) => {
this.emit('parser-error', err)

View file

@ -14,6 +14,8 @@ const user: MisskeyEntity.User = {
emojis: []
}
const converter: MisskeyAPI.Converter = new MisskeyAPI.Converter("https://example.com")
describe('api_client', () => {
describe('notification', () => {
describe('encode', () => {
@ -49,7 +51,7 @@ describe('api_client', () => {
}
]
cases.forEach(c => {
expect(MisskeyAPI.Converter.encodeNotificationType(c.src)).toEqual(c.dist)
expect(converter.encodeNotificationType(c.src)).toEqual(c.dist)
})
})
})
@ -94,7 +96,7 @@ describe('api_client', () => {
}
]
cases.forEach(c => {
expect(MisskeyAPI.Converter.decodeNotificationType(c.src)).toEqual(c.dist)
expect(converter.decodeNotificationType(c.src)).toEqual(c.dist)
})
})
})
@ -160,7 +162,7 @@ describe('api_client', () => {
}
]
const reactions = MisskeyAPI.Converter.reactions(misskeyReactions)
const reactions = converter.reactions(misskeyReactions)
expect(reactions).toEqual([
{
count: 3,
@ -198,7 +200,7 @@ describe('api_client', () => {
replyId: null,
renoteId: null
}
const megalodonStatus = MisskeyAPI.Converter.note(note, user.host || 'misskey.io')
const megalodonStatus = converter.note(note, user.host || 'misskey.io')
expect(megalodonStatus.plain_content).toEqual(plainContent)
expect(megalodonStatus.content).toEqual(content)
})
@ -222,7 +224,7 @@ describe('api_client', () => {
replyId: null,
renoteId: null
}
const megalodonStatus = MisskeyAPI.Converter.note(note, user.host || 'misskey.io')
const megalodonStatus = converter.note(note, user.host || 'misskey.io')
expect(megalodonStatus.plain_content).toEqual(plainContent)
expect(megalodonStatus.content).toEqual(content)
})