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)
+ }
+}