From 5ab9c28ead6131f843596e847fe775703abbdba7 Mon Sep 17 00:00:00 2001 From: AkiraFukushima Date: Sun, 15 Mar 2020 17:47:40 +0900 Subject: [PATCH] Fix megalodon class for 3.0.0 in main --- src/main/account.ts | 24 ++++--- src/main/auth.ts | 32 ++++----- src/main/index.ts | 73 ++++++++++---------- src/main/websocket.ts | 150 +++++++++++++++++++++++++++--------------- 4 files changed, 161 insertions(+), 118 deletions(-) diff --git a/src/main/account.ts b/src/main/account.ts index 849db67c..bcf3cc4b 100644 --- a/src/main/account.ts +++ b/src/main/account.ts @@ -1,5 +1,5 @@ import { isEmpty } from 'lodash' -import Mastodon, { Account as RemoteAccount, ProxyConfig } from 'megalodon' +import generator, { detector, Entity, ProxyConfig } from 'megalodon' import Datastore from 'nedb' import log from 'electron-log' import { LocalAccount } from '~/src/types/localAccount' @@ -295,10 +295,11 @@ export default class Account { * @return {LocalAccount} updated account */ async refresh(account: LocalAccount, proxy: ProxyConfig | false): Promise { - let client = new Mastodon(account.accessToken!, account.baseURL + '/api/v1', 'Whalebird', proxy) + const sns = await detector(account.baseURL, proxy) + let client = generator(sns, account.baseURL, account.accessToken, 'Whalebird', proxy) let json = {} try { - const res = await client.get('/accounts/verify_credentials') + const res = await client.verifyAccountCredentials() json = { username: res.data.username, accountId: res.data.id, @@ -311,9 +312,9 @@ export default class Account { if (!account.refreshToken) { throw new RefreshTokenDoesNotExist() } - const token = await Mastodon.refreshToken(account.clientId, account.clientSecret, account.refreshToken, account.baseURL, proxy) - client = new Mastodon(token.access_token, account.baseURL + '/api/v1', 'Whalebird', proxy) - const res = await client.get('/accounts/verify_credentials') + const token = await client.refreshToken(account.clientId, account.clientSecret, account.refreshToken) + client = generator(sns, account.baseURL, token.access_token, 'Whalebird', proxy) + const res = await client.verifyAccountCredentials() json = { username: res.data.username, accountId: res.data.id, @@ -326,9 +327,14 @@ export default class Account { } // Confirm the access token, and check duplicate - async fetchAccount(account: LocalAccount, accessToken: string, proxy: ProxyConfig | false): Promise { - const client = new Mastodon(accessToken, account.baseURL + '/api/v1', 'Whalebird', proxy) - const res = await client.get('/accounts/verify_credentials') + async fetchAccount( + sns: 'mastodon' | 'pleroma' | 'misskey', + account: LocalAccount, + accessToken: string, + proxy: ProxyConfig | false + ): Promise { + const client = generator(sns, account.baseURL, accessToken, 'Whalebird', proxy) + const res = await client.verifyAccountCredentials() const query = { baseURL: account.baseURL, username: res.data.username diff --git a/src/main/auth.ts b/src/main/auth.ts index 918ee667..54132814 100644 --- a/src/main/auth.ts +++ b/src/main/auth.ts @@ -1,10 +1,10 @@ -import Mastodon, { OAuth, ProxyConfig } from 'megalodon' +import generator, { ProxyConfig, detector } from 'megalodon' import Account from './account' import { LocalAccount } from '~/src/types/localAccount' const appName = 'Whalebird' const appURL = 'https://whalebird.org' -const scope = 'read write follow' +const scopes = ['read', 'write', 'follow'] export default class Authentication { private db: Account @@ -32,15 +32,12 @@ export default class Authentication { async getAuthorizationUrl(domain = 'mastodon.social', proxy: ProxyConfig | false): Promise { this.setOtherInstance(domain) - const res = await Mastodon.registerApp( - appName, - { - scopes: scope, - website: appURL - }, - this.baseURL, - proxy - ) + const sns = await detector(this.baseURL, proxy) + const client = generator(sns, this.baseURL, null, 'Whalebird', proxy) + const res = await client.registerApp(appName, { + scopes: scopes, + website: appURL + }) this.clientId = res.clientId this.clientSecret = res.clientSecret @@ -71,14 +68,9 @@ export default class Authentication { } async getAccessToken(code: string, proxy: ProxyConfig | false): Promise { - const tokenData: OAuth.TokenData = await Mastodon.fetchAccessToken( - this.clientId, - this.clientSecret, - code, - this.baseURL, - 'urn:ietf:wg:oauth:2.0:oob', - proxy - ) + const sns = await detector(this.baseURL, proxy) + const client = generator(sns, this.baseURL, null, 'Whalebird', proxy) + const tokenData = await client.fetchAccessToken(this.clientId, this.clientSecret, code, 'urn:ietf:wg:oauth:2.0:oob') const search = { baseURL: this.baseURL, domain: this.domain, @@ -88,7 +80,7 @@ export default class Authentication { const rec = await this.db.searchAccount(search) const accessToken = tokenData.accessToken const refreshToken = tokenData.refreshToken - const data = await this.db.fetchAccount(rec, accessToken, proxy) + const data = await this.db.fetchAccount(sns, rec, accessToken, proxy) await this.db.updateAccount(rec._id!, { username: data.username, accountId: data.id, diff --git a/src/main/index.ts b/src/main/index.ts index 7f7fcf74..3e42ccf7 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -24,14 +24,14 @@ import path from 'path' import ContextMenu from 'electron-context-menu' import { initSplashScreen, Config } from '@trodi/electron-splashscreen' import openAboutWindow from 'about-window' -import { Status, Notification as RemoteNotification, Account as RemoteAccount } from 'megalodon' +import { Entity, detector } from 'megalodon' import sanitizeHtml from 'sanitize-html' import AutoLaunch from 'auto-launch' import pkg from '~/package.json' import Authentication from './auth' import Account from './account' -import WebSocket, { StreamingURL } from './websocket' +import { StreamingURL, UserStreaming, DirectStreaming, LocalStreaming, PublicStreaming, ListStreaming, TagStreaming } from './websocket' import Preferences from './preferences' import Fonts from './fonts' import Hashtags from './hashtags' @@ -530,7 +530,7 @@ ipcMain.on('reset-badge', () => { }) // user streaming -let userStreamings: { [key: string]: WebSocket | null } = {} +let userStreamings: { [key: string]: UserStreaming | null } = {} ipcMain.on('start-all-user-streamings', (event: IpcMainEvent, accounts: Array) => { accounts.map(async account => { @@ -543,10 +543,11 @@ ipcMain.on('start-all-user-streamings', (event: IpcMainEvent, accounts: Array { + const sns = await detector(acct.baseURL, proxy) + const url = await StreamingURL(sns, acct, proxy) + userStreamings[id] = new UserStreaming(sns, acct, url, proxy) + userStreamings[id]!.start( + async (update: Entity.Status) => { if (!event.sender.isDestroyed()) { event.sender.send(`update-start-all-user-streamings-${id}`, update) } @@ -557,7 +558,7 @@ ipcMain.on('start-all-user-streamings', (event: IpcMainEvent, accounts: Array console.error(err)) }, - (notification: RemoteNotification) => { + (notification: Entity.Notification) => { const preferences = new Preferences(preferencesDBPath) preferences.load().then(conf => { const options = createNotification(notification, conf.notification.notify) @@ -641,7 +642,7 @@ type StreamingSetting = { account: LocalAccount } -let directMessagesStreaming: WebSocket | null = null +let directMessagesStreaming: DirectStreaming | null = null ipcMain.on('start-directmessages-streaming', async (event: IpcMainEvent, obj: StreamingSetting) => { const { account } = obj @@ -654,11 +655,11 @@ ipcMain.on('start-directmessages-streaming', async (event: IpcMainEvent, obj: St directMessagesStreaming = null } const proxy = await proxyConfiguration.forMastodon() - const url = await StreamingURL(acct, proxy) - directMessagesStreaming = new WebSocket(acct, url, proxy) + const sns = await detector(acct.baseURL, proxy) + const url = await StreamingURL(sns, acct, proxy) + directMessagesStreaming = new DirectStreaming(sns, acct, url, proxy) directMessagesStreaming.start( - 'direct', - (update: Status) => { + (update: Entity.Status) => { if (!event.sender.isDestroyed()) { event.sender.send('update-start-directmessages-streaming', update) } @@ -690,7 +691,7 @@ ipcMain.on('stop-directmessages-streaming', () => { } }) -let localStreaming: WebSocket | null = null +let localStreaming: LocalStreaming | null = null ipcMain.on('start-local-streaming', async (event: IpcMainEvent, obj: StreamingSetting) => { const { account } = obj @@ -703,11 +704,11 @@ ipcMain.on('start-local-streaming', async (event: IpcMainEvent, obj: StreamingSe localStreaming = null } const proxy = await proxyConfiguration.forMastodon() - const url = await StreamingURL(acct, proxy) - localStreaming = new WebSocket(acct, url, proxy) + const sns = await detector(acct.baseURL, proxy) + const url = await StreamingURL(sns, acct, proxy) + localStreaming = new LocalStreaming(sns, acct, url, proxy) localStreaming.start( - 'public:local', - (update: Status) => { + (update: Entity.Status) => { if (!event.sender.isDestroyed()) { event.sender.send('update-start-local-streaming', update) } @@ -739,7 +740,7 @@ ipcMain.on('stop-local-streaming', () => { } }) -let publicStreaming: WebSocket | null = null +let publicStreaming: PublicStreaming | null = null ipcMain.on('start-public-streaming', async (event: IpcMainEvent, obj: StreamingSetting) => { const { account } = obj @@ -752,11 +753,11 @@ ipcMain.on('start-public-streaming', async (event: IpcMainEvent, obj: StreamingS publicStreaming = null } const proxy = await proxyConfiguration.forMastodon() - const url = await StreamingURL(acct, proxy) - publicStreaming = new WebSocket(acct, url, proxy) + const sns = await detector(acct.baseURL, proxy) + const url = await StreamingURL(sns, acct, proxy) + publicStreaming = new PublicStreaming(sns, acct, url, proxy) publicStreaming.start( - 'public', - (update: Status) => { + (update: Entity.Status) => { if (!event.sender.isDestroyed()) { event.sender.send('update-start-public-streaming', update) } @@ -788,7 +789,7 @@ ipcMain.on('stop-public-streaming', () => { } }) -let listStreaming: WebSocket | null = null +let listStreaming: ListStreaming | null = null type ListID = { listID: string @@ -805,11 +806,12 @@ ipcMain.on('start-list-streaming', async (event: IpcMainEvent, obj: ListID & Str listStreaming = null } const proxy = await proxyConfiguration.forMastodon() - const url = await StreamingURL(acct, proxy) - listStreaming = new WebSocket(acct, url, proxy) + const sns = await detector(acct.baseURL, proxy) + const url = await StreamingURL(sns, acct, proxy) + listStreaming = new ListStreaming(sns, acct, url, proxy) listStreaming.start( - `list&list=${listID}`, - (update: Status) => { + listID, + (update: Entity.Status) => { if (!event.sender.isDestroyed()) { event.sender.send('update-start-list-streaming', update) } @@ -841,7 +843,7 @@ ipcMain.on('stop-list-streaming', () => { } }) -let tagStreaming: WebSocket | null = null +let tagStreaming: TagStreaming | null = null type Tag = { tag: string @@ -858,11 +860,12 @@ ipcMain.on('start-tag-streaming', async (event: IpcMainEvent, obj: Tag & Streami tagStreaming = null } const proxy = await proxyConfiguration.forMastodon() - const url = await StreamingURL(acct, proxy) - tagStreaming = new WebSocket(acct, url, proxy) + const sns = await detector(acct.baseURL, proxy) + const url = await StreamingURL(sns, acct, proxy) + tagStreaming = new TagStreaming(sns, acct, url, proxy) tagStreaming.start( - `hashtag&tag=${tag}`, - (update: Status) => { + tag, + (update: Entity.Status) => { if (!event.sender.isDestroyed()) { event.sender.send('update-start-tag-streaming', update) } @@ -1375,7 +1378,7 @@ async function reopenWindow() { } } -const createNotification = (notification: RemoteNotification, notifyConfig: Notify): NotificationConstructorOptions | null => { +const createNotification = (notification: Entity.Notification, notifyConfig: Notify): NotificationConstructorOptions | null => { switch (notification.type) { case 'favourite': if (notifyConfig.favourite) { @@ -1422,7 +1425,7 @@ const createNotification = (notification: RemoteNotification, notifyConfig: Noti return null } -const username = (account: RemoteAccount): string => { +const username = (account: Entity.Account): string => { if (account.display_name !== '') { return account.display_name } else { diff --git a/src/main/websocket.ts b/src/main/websocket.ts index 71733778..8d862bdd 100644 --- a/src/main/websocket.ts +++ b/src/main/websocket.ts @@ -1,73 +1,39 @@ -import Mastodon, { WebSocket as SocketListener, Status, Notification, Instance, Response, ProxyConfig } from 'megalodon' +import generator, { MegalodonInterface, WebSocketInterface, Entity, ProxyConfig } from 'megalodon' import log from 'electron-log' import { LocalAccount } from '~/src/types/localAccount' -const StreamingURL = async (account: LocalAccount, proxy: ProxyConfig | false): Promise => { +const StreamingURL = async ( + sns: 'mastodon' | 'pleroma' | 'misskey', + account: LocalAccount, + proxy: ProxyConfig | false +): Promise => { if (!account.accessToken) { throw new Error('access token is empty') } - const client = new Mastodon(account.accessToken, account.baseURL + '/api/v1', 'Whalebird', proxy) - const res: Response = await client.get('/instance') + const client = generator(sns, account.baseURL, account.accessToken, 'Whalebird', proxy) + const res = await client.getInstance() return res.data.urls.streaming_api } export { StreamingURL } -export default class WebSocket { - private client: Mastodon - private listener: SocketListener | null +class WebSocket { + public client: MegalodonInterface + public listener: WebSocketInterface | null - constructor(account: LocalAccount, streamingURL: string, proxy: ProxyConfig | false) { + constructor(sns: 'mastodon' | 'pleroma' | 'misskey', account: LocalAccount, streamingURL: string, proxy: ProxyConfig | false) { const url = streamingURL.replace(/^https:\/\//, 'wss://') - this.client = new Mastodon(account.accessToken!, url + '/api/v1', 'Whalebird', proxy) + this.client = generator(sns, url, account.accessToken, 'Whalebird', proxy) this.listener = null } - startUserStreaming(updateCallback: Function, notificationCallback: Function, deleteCallback: Function, errCallback: Function) { - this.listener = this.client.socket('/streaming', 'user') + public bindListener(updateCallback: Function, deleteCallback: Function, errCallback: Function) { + if (!this.listener) { + log.error('listener does not exist') + return + } - this.listener.on('connect', _ => { - log.info('/streaming/?stream=user started') - }) - - this.listener.on('update', (status: Status) => { - updateCallback(status) - }) - - this.listener.on('notification', (notification: Notification) => { - notificationCallback(notification) - }) - - this.listener.on('delete', (id: string) => { - deleteCallback(id) - }) - - this.listener.on('error', (err: Error) => { - errCallback(err) - }) - - this.listener.on('parser-error', (err: Error) => { - errCallback(err) - }) - } - - /** - * Start new custom streaming with websocket. - * @param stream Path of streaming. - * @param updateCallback A callback function which is called update. - * @param errCallback A callback function which ic called error. - * When local timeline, the path is `public:local`. - * When public timeline, the path is `public`. - * When hashtag timeline, the path is `hashtag&tag=tag_name`. - * When list timeline, the path is `list&list=list_id`. - */ - start(stream: string, updateCallback: Function, deleteCallback: Function, errCallback: Function) { - this.listener = this.client.socket('/streaming', stream) - this.listener.on('connect', _ => { - log.info(`/streaming/?stream=${stream} started`) - }) - - this.listener.on('update', (status: Status) => { + this.listener.on('update', (status: Entity.Status) => { updateCallback(status) }) @@ -84,7 +50,7 @@ export default class WebSocket { }) } - stop() { + public stop() { if (this.listener) { this.listener.removeAllListeners('connect') this.listener.removeAllListeners('update') @@ -102,3 +68,79 @@ export default class WebSocket { } } } + +export class UserStreaming extends WebSocket { + public start(updateCallback: Function, notificationCallback: Function, deleteCallback: Function, errCallback: Function) { + this.listener = this.client.userSocket() + + this.listener.on('connect', _ => { + log.info('user streaming is started') + }) + + this.listener.on('notification', (notification: Entity.Notification) => { + notificationCallback(notification) + }) + + this.bindListener(updateCallback, deleteCallback, errCallback) + } +} + +export class DirectStreaming extends WebSocket { + public start(updateCallback: Function, deleteCallback: Function, errCallback: Function) { + this.listener = this.client.directSocket() + + this.listener.on('connect', _ => { + log.info('direct streaming is started') + }) + + this.bindListener(updateCallback, deleteCallback, errCallback) + } +} + +export class LocalStreaming extends WebSocket { + public start(updateCallback: Function, deleteCallback: Function, errCallback: Function) { + this.listener = this.client.localSocket() + + this.listener.on('connect', _ => { + log.info('local streaming is started') + }) + + this.bindListener(updateCallback, deleteCallback, errCallback) + } +} + +export class PublicStreaming extends WebSocket { + public start(updateCallback: Function, deleteCallback: Function, errCallback: Function) { + this.listener = this.client.publicSocket() + + this.listener.on('connect', _ => { + log.info('public streaming is started') + }) + + this.bindListener(updateCallback, deleteCallback, errCallback) + } +} + +export class ListStreaming extends WebSocket { + public start(listID: string, updateCallback: Function, deleteCallback: Function, errCallback: Function) { + this.listener = this.client.listSocket(listID) + + this.listener.on('connect', _ => { + log.info('list streaming is started') + }) + + this.bindListener(updateCallback, deleteCallback, errCallback) + } +} + +export class TagStreaming extends WebSocket { + public start(tag: string, updateCallback: Function, deleteCallback: Function, errCallback: Function) { + this.listener = this.client.tagSocket(tag) + + this.listener.on('connect', _ => { + log.info('tag streaming is started') + }) + + this.bindListener(updateCallback, deleteCallback, errCallback) + } +}