diff --git a/docs/setup.en.md b/docs/setup.en.md
index 83392d0d9a..3a1c7bff92 100644
--- a/docs/setup.en.md
+++ b/docs/setup.en.md
@@ -57,13 +57,6 @@ npm install web-push -g
web-push generate-vapid-keys
```
-*(optional)* Create a twitter application
-----------------------------------------------------------------
-If you want to enable the twitter integration, you need to create a twitter app at [https://developer.twitter.com/en/apply/user](https://developer.twitter.com/en/apply/user).
-
-In the app you need to set the oauth callback url as : https://misskey-instance/api/tw/cb
-
-
*5.* Make configuration file
----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index c3a9848b72..572c8ccdfe 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1095,6 +1095,16 @@ admin/views/instance.vue:
enable-recaptcha: "reCAPTCHAを有効にする"
recaptcha-site-key: "reCAPTCHA site key"
recaptcha-secret-key: "reCAPTCHA secret key"
+ twitter-integration-config: "Twitter連携の設定"
+ twitter-integration-info: "コールバックURLは /api/tw/cb に設定します。"
+ enable-twitter-integration: "Twitter連携を有効にする"
+ twitter-integration-consumer-key: "Consumer key"
+ twitter-integration-consumer-secret: "Consumer secret"
+ github-integration-config: "GitHub連携の設定"
+ github-integration-info: "コールバックURLは /api/gh/cb に設定します。"
+ enable-github-integration: "GitHub連携を有効にする"
+ github-integration-client-id: "Client ID"
+ github-integration-client-secret: "Client secret"
proxy-account-config: "プロキシアカウントの設定"
proxy-account-info: "プロキシアカウントは、特定の条件下でユーザーのリモートフォローを代行するアカウントです。例えば、ユーザーがリモートユーザーをリストに入れたとき、リストに入れられたユーザーを誰もフォローしていないとアクティビティがサーバーに配達されないため、代わりにプロキシアカウントがフォローするようにします。"
proxy-account-username: "プロキシアカウントのユーザー名"
diff --git a/src/client/app/admin/views/instance.vue b/src/client/app/admin/views/instance.vue
index 815cea6313..a463bdc735 100644
--- a/src/client/app/admin/views/instance.vue
+++ b/src/client/app/admin/views/instance.vue
@@ -53,6 +53,28 @@
Code: {{ inviteCode }}
+
+
+ %i18n:@twitter-integration-config%
+
+ %i18n:@enable-twitter-integration%
+ %i18n:@twitter-integration-info%
+ %i18n:@twitter-integration-consumer-key%
+ %i18n:@twitter-integration-consumer-secret%
+ %i18n:@save%
+
+
+
+
+ %i18n:@github-integration-config%
+
+ %i18n:@enable-github-integration%
+ %i18n:@github-integration-info%
+ %i18n:@github-integration-client-id%
+ %i18n:@github-integration-client-secret%
+ %i18n:@save%
+
+
@@ -77,6 +99,12 @@ export default Vue.extend({
enableRecaptcha: false,
recaptchaSiteKey: null,
recaptchaSecretKey: null,
+ enableTwitterIntegration: false,
+ twitterConsumerKey: null,
+ twitterConsumerSecret: null,
+ enableGithubIntegration: false,
+ githubClientId: null,
+ githubClientSecret: null,
proxyAccount: null,
inviteCode: null,
};
@@ -98,6 +126,12 @@ export default Vue.extend({
this.recaptchaSiteKey = meta.recaptchaSiteKey;
this.recaptchaSecretKey = meta.recaptchaSecretKey;
this.proxyAccount = meta.proxyAccount;
+ this.enableTwitterIntegration = meta.enableTwitterIntegration;
+ this.twitterConsumerKey = meta.twitterConsumerKey;
+ this.twitterConsumerSecret = meta.twitterConsumerSecret;
+ this.enableGithubIntegration = meta.enableGithubIntegration;
+ this.githubClientId = meta.githubClientId;
+ this.githubClientSecret = meta.githubClientSecret;
});
},
@@ -131,6 +165,12 @@ export default Vue.extend({
recaptchaSiteKey: this.recaptchaSiteKey,
recaptchaSecretKey: this.recaptchaSecretKey,
proxyAccount: this.proxyAccount,
+ enableTwitterIntegration: this.enableTwitterIntegration,
+ twitterConsumerKey: this.twitterConsumerKey,
+ twitterConsumerSecret: this.twitterConsumerSecret,
+ enableGithubIntegration: this.enableGithubIntegration,
+ githubClientId: this.githubClientId,
+ githubClientSecret: this.githubClientSecret,
}).then(() => {
this.$swal({
type: 'success',
diff --git a/src/config/types.ts b/src/config/types.ts
index 98fa2660f4..f9cb9d8659 100644
--- a/src/config/types.ts
+++ b/src/config/types.ts
@@ -40,14 +40,7 @@ export type Source = {
summalyProxy?: string;
accesslog?: string;
- twitter?: {
- consumer_key: string;
- consumer_secret: string;
- };
- github?: {
- client_id: string;
- client_secret: string;
- };
+
github_bot?: {
hook_secret: string;
username: string;
diff --git a/src/misc/fetch-meta.ts b/src/misc/fetch-meta.ts
index e8ff1aca7c..622ad54394 100644
--- a/src/misc/fetch-meta.ts
+++ b/src/misc/fetch-meta.ts
@@ -11,7 +11,9 @@ const defaultMeta: any = {
originalNotesCount: 0,
originalUsersCount: 0
},
- maxNoteTextLength: 1000
+ maxNoteTextLength: 1000,
+ enableTwitterIntegration: false,
+ enableGithubIntegration: false,
};
export default async function(): Promise {
diff --git a/src/models/meta.ts b/src/models/meta.ts
index f62117a0ba..a12747ea3c 100644
--- a/src/models/meta.ts
+++ b/src/models/meta.ts
@@ -99,6 +99,32 @@ if ((config as any).maintainer) {
}
});
}
+if ((config as any).twitter) {
+ Meta.findOne({}).then(m => {
+ if (m != null && m.enableTwitterIntegration == null) {
+ Meta.update({}, {
+ $set: {
+ enableTwitterIntegration: true,
+ twitterConsumerKey: (config as any).twitter.consumer_key,
+ twitterConsumerSecret: (config as any).twitter.consumer_secret
+ }
+ });
+ }
+ });
+}
+if ((config as any).github) {
+ Meta.findOne({}).then(m => {
+ if (m != null && m.enableGithubIntegration == null) {
+ Meta.update({}, {
+ $set: {
+ enableGithubIntegration: true,
+ githubClientId: (config as any).github.client_id,
+ githubClientSecret: (config as any).github.client_secret
+ }
+ });
+ }
+ });
+}
export type IMeta = {
name?: string;
@@ -157,4 +183,12 @@ export type IMeta = {
* Max allowed note text length in charactors
*/
maxNoteTextLength?: number;
+
+ enableTwitterIntegration?: boolean;
+ twitterConsumerKey?: string;
+ twitterConsumerSecret?: string;
+
+ enableGithubIntegration?: boolean;
+ githubClientId?: string;
+ githubClientSecret?: string;
};
diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts
index d45a8759f2..39d7ef86b9 100644
--- a/src/server/api/endpoints/admin/update-meta.ts
+++ b/src/server/api/endpoints/admin/update-meta.ts
@@ -137,7 +137,49 @@ export const meta = {
desc: {
'ja-JP': 'インスタンスの対象言語'
}
- }
+ },
+
+ enableTwitterIntegration: {
+ validator: $.bool.optional,
+ desc: {
+ 'ja-JP': 'Twitter連携機能を有効にするか否か'
+ }
+ },
+
+ twitterConsumerKey: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': 'TwitterアプリのConsumer key'
+ }
+ },
+
+ twitterConsumerSecret: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': 'TwitterアプリのConsumer secret'
+ }
+ },
+
+ enableGithubIntegration: {
+ validator: $.bool.optional,
+ desc: {
+ 'ja-JP': 'GitHub連携機能を有効にするか否か'
+ }
+ },
+
+ githubClientId: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': 'GitHubアプリのClient ID'
+ }
+ },
+
+ githubClientSecret: {
+ validator: $.str.optional.nullable,
+ desc: {
+ 'ja-JP': 'GitHubアプリのClient secret'
+ }
+ },
}
};
@@ -216,6 +258,30 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
set.langs = ps.langs;
}
+ if (ps.enableTwitterIntegration !== undefined) {
+ set.enableTwitterIntegration = ps.enableTwitterIntegration;
+ }
+
+ if (ps.twitterConsumerKey !== undefined) {
+ set.twitterConsumerKey = ps.twitterConsumerKey;
+ }
+
+ if (ps.twitterConsumerSecret !== undefined) {
+ set.twitterConsumerSecret = ps.twitterConsumerSecret;
+ }
+
+ if (ps.enableGithubIntegration !== undefined) {
+ set.enableGithubIntegration = ps.enableGithubIntegration;
+ }
+
+ if (ps.githubClientId !== undefined) {
+ set.githubClientId = ps.githubClientId;
+ }
+
+ if (ps.githubClientSecret !== undefined) {
+ set.githubClientSecret = ps.githubClientSecret;
+ }
+
await Meta.update({}, {
$set: set
}, { upsert: true });
diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts
index 625a9519d7..7cd72d3cc3 100644
--- a/src/server/api/endpoints/meta.ts
+++ b/src/server/api/endpoints/meta.ts
@@ -77,8 +77,8 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
elasticsearch: config.elasticsearch ? true : false,
recaptcha: instance.enableRecaptcha,
objectStorage: config.drive && config.drive.storage === 'minio',
- twitter: config.twitter ? true : false,
- github: config.github ? true : false,
+ twitter: instance.enableTwitterIntegration,
+ github: instance.enableGithubIntegration,
serviceWorker: config.sw ? true : false,
userRecommendation: config.user_recommendation ? config.user_recommendation : {}
};
diff --git a/src/server/api/index.ts b/src/server/api/index.ts
index 33e98f650a..bb8bad8bbe 100644
--- a/src/server/api/index.ts
+++ b/src/server/api/index.ts
@@ -44,6 +44,7 @@ router.post('/signup', require('./private/signup').default);
router.post('/signin', require('./private/signin').default);
router.use(require('./service/github').routes());
+router.use(require('./service/github-bot').routes());
router.use(require('./service/twitter').routes());
router.use(require('./mastodon').routes());
diff --git a/src/server/api/service/github-bot.ts b/src/server/api/service/github-bot.ts
new file mode 100644
index 0000000000..cb038363f3
--- /dev/null
+++ b/src/server/api/service/github-bot.ts
@@ -0,0 +1,156 @@
+import * as EventEmitter from 'events';
+import * as Router from 'koa-router';
+import * as request from 'request';
+import User, { IUser } from '../../../models/user';
+import createNote from '../../../services/note/create';
+import config from '../../../config';
+const crypto = require('crypto');
+
+const handler = new EventEmitter();
+
+let bot: IUser;
+
+const post = async (text: string, home = true) => {
+ if (bot == null) {
+ const account = await User.findOne({
+ usernameLower: config.github_bot.username.toLowerCase()
+ });
+
+ if (account == null) {
+ console.warn(`GitHub hook bot specified, but not found: @${config.github_bot.username}`);
+ return;
+ } else {
+ bot = account;
+ }
+ }
+
+ createNote(bot, { text, visibility: home ? 'home' : 'public' });
+};
+
+// Init router
+const router = new Router();
+
+if (config.github_bot) {
+ const secret = config.github_bot.hook_secret;
+
+ router.post('/hooks/github', ctx => {
+ const body = JSON.stringify(ctx.request.body);
+ const hash = crypto.createHmac('sha1', secret).update(body).digest('hex');
+ const sig1 = new Buffer(ctx.headers['x-hub-signature']);
+ const sig2 = new Buffer(`sha1=${hash}`);
+
+ // シグネチャ比較
+ if (sig1.equals(sig2)) {
+ handler.emit(ctx.headers['x-github-event'], ctx.request.body);
+ ctx.status = 204;
+ } else {
+ ctx.status = 400;
+ }
+ });
+}
+
+module.exports = router;
+
+handler.on('status', event => {
+ const state = event.state;
+ switch (state) {
+ case 'error':
+ case 'failure':
+ const commit = event.commit;
+ const parent = commit.parents[0];
+
+ // Fetch parent status
+ request({
+ url: `${parent.url}/statuses`,
+ proxy: config.proxy,
+ headers: {
+ 'User-Agent': 'misskey'
+ }
+ }, (err, res, body) => {
+ if (err) {
+ console.error(err);
+ return;
+ }
+ const parentStatuses = JSON.parse(body);
+ const parentState = parentStatuses[0].state;
+ const stillFailed = parentState == 'failure' || parentState == 'error';
+ if (stillFailed) {
+ post(`**⚠️BUILD STILL FAILED⚠️**: ?[${commit.commit.message}](${commit.html_url})`);
+ } else {
+ post(`**🚨BUILD FAILED🚨**: →→→?[${commit.commit.message}](${commit.html_url})←←←`);
+ }
+ });
+ break;
+ }
+});
+
+handler.on('push', event => {
+ const ref = event.ref;
+ switch (ref) {
+ case 'refs/heads/master':
+ const pusher = event.pusher;
+ const compare = event.compare;
+ const commits: any[] = event.commits;
+ post([
+ `Pushed by **${pusher.name}** with ?[${commits.length} commit${commits.length > 1 ? 's' : ''}](${compare}):`,
+ commits.reverse().map(commit => `・[?[${commit.id.substr(0, 7)}](${commit.url})] ${commit.message.split('\n')[0]}`).join('\n'),
+ ].join('\n'));
+ break;
+ case 'refs/heads/release':
+ const commit = event.commits[0];
+ post(`RELEASED: ${commit.message}`);
+ break;
+ }
+});
+
+handler.on('issues', event => {
+ const issue = event.issue;
+ const action = event.action;
+ let title: string;
+ switch (action) {
+ case 'opened': title = 'Issue opened'; break;
+ case 'closed': title = 'Issue closed'; break;
+ case 'reopened': title = 'Issue reopened'; break;
+ default: return;
+ }
+ post(`${title}: <${issue.number}>「${issue.title}」\n${issue.html_url}`);
+});
+
+handler.on('issue_comment', event => {
+ const issue = event.issue;
+ const comment = event.comment;
+ const action = event.action;
+ let text: string;
+ switch (action) {
+ case 'created': text = `Commented to「${issue.title}」:${comment.user.login}「${comment.body}」\n${comment.html_url}`; break;
+ default: return;
+ }
+ post(text);
+});
+
+handler.on('watch', event => {
+ const sender = event.sender;
+ post(`(((⭐️))) Starred by **${sender.login}** (((⭐️)))`, false);
+});
+
+handler.on('fork', event => {
+ const repo = event.forkee;
+ post(`🍴 Forked:\n${repo.html_url} 🍴`);
+});
+
+handler.on('pull_request', event => {
+ const pr = event.pull_request;
+ const action = event.action;
+ let text: string;
+ switch (action) {
+ case 'opened': text = `New Pull Request:「${pr.title}」\n${pr.html_url}`; break;
+ case 'reopened': text = `Pull Request Reopened:「${pr.title}」\n${pr.html_url}`; break;
+ case 'closed':
+ text = pr.merged
+ ? `Pull Request Merged!:「${pr.title}」\n${pr.html_url}`
+ : `Pull Request Closed:「${pr.title}」\n${pr.html_url}`;
+ break;
+ default: return;
+ }
+ post(text);
+});
diff --git a/src/server/api/service/github.ts b/src/server/api/service/github.ts
index 617bd7d088..4dce856c2a 100644
--- a/src/server/api/service/github.ts
+++ b/src/server/api/service/github.ts
@@ -1,37 +1,14 @@
-import * as EventEmitter from 'events';
import * as Koa from 'koa';
import * as Router from 'koa-router';
import * as request from 'request';
import { OAuth2 } from 'oauth';
-import User, { IUser, pack, ILocalUser } from '../../../models/user';
-import createNote from '../../../services/note/create';
+import User, { pack, ILocalUser } from '../../../models/user';
import config from '../../../config';
import { publishMainStream } from '../../../stream';
import redis from '../../../db/redis';
import uuid = require('uuid');
import signin from '../common/signin';
-const crypto = require('crypto');
-
-const handler = new EventEmitter();
-
-let bot: IUser;
-
-const post = async (text: string, home = true) => {
- if (bot == null) {
- const account = await User.findOne({
- usernameLower: config.github_bot.username.toLowerCase()
- });
-
- if (account == null) {
- console.warn(`GitHub hook bot specified, but not found: @${config.github_bot.username}`);
- return;
- } else {
- bot = account;
- }
- }
-
- createNote(bot, { text, visibility: home ? 'home' : 'public' });
-};
+import fetchMeta from '../../../misc/fetch-meta';
function getUserToken(ctx: Koa.Context) {
return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1];
@@ -80,337 +57,218 @@ router.get('/disconnect/github', async ctx => {
}));
});
-if (!config.github || !redis) {
- router.get('/connect/github', ctx => {
- ctx.body = '現在GitHubへ接続できません (このインスタンスではGitHubはサポートされていません)';
+async function getOath2() {
+ const meta = await fetchMeta();
+
+ if (meta.enableGithubIntegration) {
+ return new OAuth2(
+ meta.githubClientId,
+ meta.githubClientSecret,
+ 'https://github.com/',
+ 'login/oauth/authorize',
+ 'login/oauth/access_token');
+ } else {
+ return null;
+ }
+}
+
+router.get('/connect/github', async ctx => {
+ if (!compareOrigin(ctx)) {
+ ctx.throw(400, 'invalid origin');
+ return;
+ }
+
+ const userToken = getUserToken(ctx);
+ if (!userToken) {
+ ctx.throw(400, 'signin required');
+ return;
+ }
+
+ const params = {
+ redirect_uri: `${config.url}/api/gh/cb`,
+ scope: ['read:user'],
+ state: uuid()
+ };
+
+ redis.set(userToken, JSON.stringify(params));
+
+ const oauth2 = await getOath2();
+ ctx.redirect(oauth2.getAuthorizeUrl(params));
+});
+
+router.get('/signin/github', async ctx => {
+ const sessid = uuid();
+
+ const params = {
+ redirect_uri: `${config.url}/api/gh/cb`,
+ scope: ['read:user'],
+ state: uuid()
+ };
+
+ const expires = 1000 * 60 * 60; // 1h
+ ctx.cookies.set('signin_with_github_session_id', sessid, {
+ path: '/',
+ domain: config.host,
+ secure: config.url.startsWith('https'),
+ httpOnly: true,
+ expires: new Date(Date.now() + expires),
+ maxAge: expires
});
- router.get('/signin/github', ctx => {
- ctx.body = '現在GitHubへ接続できません (このインスタンスではGitHubはサポートされていません)';
- });
-} else {
- const oauth2 = new OAuth2(
- config.github.client_id,
- config.github.client_secret,
- 'https://github.com/',
- 'login/oauth/authorize',
- 'login/oauth/access_token');
+ redis.set(sessid, JSON.stringify(params));
- router.get('/connect/github', async ctx => {
- if (!compareOrigin(ctx)) {
- ctx.throw(400, 'invalid origin');
+ const oauth2 = await getOath2();
+ ctx.redirect(oauth2.getAuthorizeUrl(params));
+});
+
+router.get('/gh/cb', async ctx => {
+ const userToken = getUserToken(ctx);
+
+ const oauth2 = await getOath2();
+
+ if (!userToken) {
+ const sessid = ctx.cookies.get('signin_with_github_session_id');
+
+ if (!sessid) {
+ ctx.throw(400, 'invalid session');
return;
}
- const userToken = getUserToken(ctx);
- if (!userToken) {
- ctx.throw(400, 'signin required');
+ const code = ctx.query.code;
+
+ if (!code) {
+ ctx.throw(400, 'invalid session');
return;
}
- const params = {
- redirect_uri: `${config.url}/api/gh/cb`,
- scope: ['read:user'],
- state: uuid()
- };
-
- redis.set(userToken, JSON.stringify(params));
- ctx.redirect(oauth2.getAuthorizeUrl(params));
- });
-
- router.get('/signin/github', async ctx => {
- const sessid = uuid();
-
- const params = {
- redirect_uri: `${config.url}/api/gh/cb`,
- scope: ['read:user'],
- state: uuid()
- };
-
- const expires = 1000 * 60 * 60; // 1h
- ctx.cookies.set('signin_with_github_session_id', sessid, {
- path: '/',
- domain: config.host,
- secure: config.url.startsWith('https'),
- httpOnly: true,
- expires: new Date(Date.now() + expires),
- maxAge: expires
+ const { redirect_uri, state } = await new Promise((res, rej) => {
+ redis.get(sessid, async (_, state) => {
+ res(JSON.parse(state));
+ });
});
- redis.set(sessid, JSON.stringify(params));
- ctx.redirect(oauth2.getAuthorizeUrl(params));
- });
+ if (ctx.query.state !== state) {
+ ctx.throw(400, 'invalid session');
+ return;
+ }
- router.get('/gh/cb', async ctx => {
- const userToken = getUserToken(ctx);
-
- if (!userToken) {
- const sessid = ctx.cookies.get('signin_with_github_session_id');
-
- if (!sessid) {
- ctx.throw(400, 'invalid session');
- return;
- }
-
- const code = ctx.query.code;
-
- if (!code) {
- ctx.throw(400, 'invalid session');
- return;
- }
-
- const { redirect_uri, state } = await new Promise((res, rej) => {
- redis.get(sessid, async (_, state) => {
- res(JSON.parse(state));
- });
- });
-
- if (ctx.query.state !== state) {
- ctx.throw(400, 'invalid session');
- return;
- }
-
- const { accessToken } = await new Promise((res, rej) =>
- oauth2.getOAuthAccessToken(
- code,
- { redirect_uri },
- (err, accessToken, refresh, result) => {
- if (err)
- rej(err);
- else if (result.error)
- rej(result.error);
- else
- res({ accessToken });
- }));
-
- const { login, id } = await new Promise((res, rej) =>
- request({
- url: 'https://api.github.com/user',
- headers: {
- 'Accept': 'application/vnd.github.v3+json',
- 'Authorization': `bearer ${accessToken}`,
- 'User-Agent': config.user_agent
- }
- }, (err, response, body) => {
+ const { accessToken } = await new Promise((res, rej) =>
+ oauth2.getOAuthAccessToken(
+ code,
+ { redirect_uri },
+ (err, accessToken, refresh, result) => {
if (err)
rej(err);
+ else if (result.error)
+ rej(result.error);
else
- res(JSON.parse(body));
+ res({ accessToken });
}));
- if (!login || !id) {
- ctx.throw(400, 'invalid session');
- return;
- }
-
- const user = await User.findOne({
- host: null,
- 'github.id': id
- }) as ILocalUser;
-
- if (!user) {
- ctx.throw(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`);
- return;
- }
-
- signin(ctx, user, true);
- } else {
- const code = ctx.query.code;
-
- if (!code) {
- ctx.throw(400, 'invalid session');
- return;
- }
-
- const { redirect_uri, state } = await new Promise((res, rej) => {
- redis.get(userToken, async (_, state) => {
- res(JSON.parse(state));
- });
- });
-
- if (ctx.query.state !== state) {
- ctx.throw(400, 'invalid session');
- return;
- }
-
- const { accessToken } = await new Promise((res, rej) =>
- oauth2.getOAuthAccessToken(
- code,
- { redirect_uri },
- (err, accessToken, refresh, result) => {
- if (err)
- rej(err);
- else if (result.error)
- rej(result.error);
- else
- res({ accessToken });
- }));
-
- const { login, id } = await new Promise((res, rej) =>
- request({
- url: 'https://api.github.com/user',
- headers: {
- 'Accept': 'application/vnd.github.v3+json',
- 'Authorization': `bearer ${accessToken}`,
- 'User-Agent': config.user_agent
- }
- }, (err, response, body) => {
- if (err)
- rej(err);
- else
- res(JSON.parse(body));
- }));
-
- if (!login || !id) {
- ctx.throw(400, 'invalid session');
- return;
- }
-
- const user = await User.findOneAndUpdate({
- host: null,
- token: userToken
- }, {
- $set: {
- github: {
- accessToken,
- id,
- login
- }
+ const { login, id } = await new Promise((res, rej) =>
+ request({
+ url: 'https://api.github.com/user',
+ headers: {
+ 'Accept': 'application/vnd.github.v3+json',
+ 'Authorization': `bearer ${accessToken}`,
+ 'User-Agent': config.user_agent
}
- });
-
- ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`;
-
- // Publish i updated event
- publishMainStream(user._id, 'meUpdated', await pack(user, user, {
- detail: true,
- includeSecrets: true
+ }, (err, response, body) => {
+ if (err)
+ rej(err);
+ else
+ res(JSON.parse(body));
}));
+
+ if (!login || !id) {
+ ctx.throw(400, 'invalid session');
+ return;
}
- });
-}
-if (config.github_bot) {
- const secret = config.github_bot.hook_secret;
+ const user = await User.findOne({
+ host: null,
+ 'github.id': id
+ }) as ILocalUser;
- router.post('/hooks/github', ctx => {
- const body = JSON.stringify(ctx.request.body);
- const hash = crypto.createHmac('sha1', secret).update(body).digest('hex');
- const sig1 = new Buffer(ctx.headers['x-hub-signature']);
- const sig2 = new Buffer(`sha1=${hash}`);
-
- // シグネチャ比較
- if (sig1.equals(sig2)) {
- handler.emit(ctx.headers['x-github-event'], ctx.request.body);
- ctx.status = 204;
- } else {
- ctx.status = 400;
+ if (!user) {
+ ctx.throw(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`);
+ return;
}
- });
-}
+
+ signin(ctx, user, true);
+ } else {
+ const code = ctx.query.code;
+
+ if (!code) {
+ ctx.throw(400, 'invalid session');
+ return;
+ }
+
+ const { redirect_uri, state } = await new Promise((res, rej) => {
+ redis.get(userToken, async (_, state) => {
+ res(JSON.parse(state));
+ });
+ });
+
+ if (ctx.query.state !== state) {
+ ctx.throw(400, 'invalid session');
+ return;
+ }
+
+ const { accessToken } = await new Promise((res, rej) =>
+ oauth2.getOAuthAccessToken(
+ code,
+ { redirect_uri },
+ (err, accessToken, refresh, result) => {
+ if (err)
+ rej(err);
+ else if (result.error)
+ rej(result.error);
+ else
+ res({ accessToken });
+ }));
+
+ const { login, id } = await new Promise((res, rej) =>
+ request({
+ url: 'https://api.github.com/user',
+ headers: {
+ 'Accept': 'application/vnd.github.v3+json',
+ 'Authorization': `bearer ${accessToken}`,
+ 'User-Agent': config.user_agent
+ }
+ }, (err, response, body) => {
+ if (err)
+ rej(err);
+ else
+ res(JSON.parse(body));
+ }));
+
+ if (!login || !id) {
+ ctx.throw(400, 'invalid session');
+ return;
+ }
+
+ const user = await User.findOneAndUpdate({
+ host: null,
+ token: userToken
+ }, {
+ $set: {
+ github: {
+ accessToken,
+ id,
+ login
+ }
+ }
+ });
+
+ ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`;
+
+ // Publish i updated event
+ publishMainStream(user._id, 'meUpdated', await pack(user, user, {
+ detail: true,
+ includeSecrets: true
+ }));
+ }
+});
module.exports = router;
-
-handler.on('status', event => {
- const state = event.state;
- switch (state) {
- case 'error':
- case 'failure':
- const commit = event.commit;
- const parent = commit.parents[0];
-
- // Fetch parent status
- request({
- url: `${parent.url}/statuses`,
- proxy: config.proxy,
- headers: {
- 'User-Agent': 'misskey'
- }
- }, (err, res, body) => {
- if (err) {
- console.error(err);
- return;
- }
- const parentStatuses = JSON.parse(body);
- const parentState = parentStatuses[0].state;
- const stillFailed = parentState == 'failure' || parentState == 'error';
- if (stillFailed) {
- post(`**⚠️BUILD STILL FAILED⚠️**: ?[${commit.commit.message}](${commit.html_url})`);
- } else {
- post(`**🚨BUILD FAILED🚨**: →→→?[${commit.commit.message}](${commit.html_url})←←←`);
- }
- });
- break;
- }
-});
-
-handler.on('push', event => {
- const ref = event.ref;
- switch (ref) {
- case 'refs/heads/master':
- const pusher = event.pusher;
- const compare = event.compare;
- const commits: any[] = event.commits;
- post([
- `Pushed by **${pusher.name}** with ?[${commits.length} commit${commits.length > 1 ? 's' : ''}](${compare}):`,
- commits.reverse().map(commit => `・[?[${commit.id.substr(0, 7)}](${commit.url})] ${commit.message.split('\n')[0]}`).join('\n'),
- ].join('\n'));
- break;
- case 'refs/heads/release':
- const commit = event.commits[0];
- post(`RELEASED: ${commit.message}`);
- break;
- }
-});
-
-handler.on('issues', event => {
- const issue = event.issue;
- const action = event.action;
- let title: string;
- switch (action) {
- case 'opened': title = 'Issue opened'; break;
- case 'closed': title = 'Issue closed'; break;
- case 'reopened': title = 'Issue reopened'; break;
- default: return;
- }
- post(`${title}: <${issue.number}>「${issue.title}」\n${issue.html_url}`);
-});
-
-handler.on('issue_comment', event => {
- const issue = event.issue;
- const comment = event.comment;
- const action = event.action;
- let text: string;
- switch (action) {
- case 'created': text = `Commented to「${issue.title}」:${comment.user.login}「${comment.body}」\n${comment.html_url}`; break;
- default: return;
- }
- post(text);
-});
-
-handler.on('watch', event => {
- const sender = event.sender;
- post(`(((⭐️))) Starred by **${sender.login}** (((⭐️)))`, false);
-});
-
-handler.on('fork', event => {
- const repo = event.forkee;
- post(`🍴 Forked:\n${repo.html_url} 🍴`);
-});
-
-handler.on('pull_request', event => {
- const pr = event.pull_request;
- const action = event.action;
- let text: string;
- switch (action) {
- case 'opened': text = `New Pull Request:「${pr.title}」\n${pr.html_url}`; break;
- case 'reopened': text = `Pull Request Reopened:「${pr.title}」\n${pr.html_url}`; break;
- case 'closed':
- text = pr.merged
- ? `Pull Request Merged!:「${pr.title}」\n${pr.html_url}`
- : `Pull Request Closed:「${pr.title}」\n${pr.html_url}`;
- break;
- default: return;
- }
- post(text);
-});
diff --git a/src/server/api/service/twitter.ts b/src/server/api/service/twitter.ts
index 6c3cdaa138..ced3e8accd 100644
--- a/src/server/api/service/twitter.ts
+++ b/src/server/api/service/twitter.ts
@@ -7,6 +7,7 @@ import User, { pack, ILocalUser } from '../../../models/user';
import { publishMainStream } from '../../../stream';
import config from '../../../config';
import signin from '../common/signin';
+import fetchMeta from '../../../misc/fetch-meta';
function getUserToken(ctx: Koa.Context) {
return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1];
@@ -55,131 +56,133 @@ router.get('/disconnect/twitter', async ctx => {
}));
});
-if (config.twitter == null || redis == null) {
- router.get('/connect/twitter', ctx => {
- ctx.body = '現在Twitterへ接続できません (このインスタンスではTwitterはサポートされていません)';
- });
+async function getTwAuth() {
+ const meta = await fetchMeta();
- router.get('/signin/twitter', ctx => {
- ctx.body = '現在Twitterへ接続できません (このインスタンスではTwitterはサポートされていません)';
- });
-} else {
- const twAuth = autwh({
- consumerKey: config.twitter.consumer_key,
- consumerSecret: config.twitter.consumer_secret,
- callbackUrl: `${config.url}/api/tw/cb`
- });
-
- router.get('/connect/twitter', async ctx => {
- if (!compareOrigin(ctx)) {
- ctx.throw(400, 'invalid origin');
- return;
- }
-
- const userToken = getUserToken(ctx);
- if (userToken == null) {
- ctx.throw(400, 'signin required');
- return;
- }
-
- const twCtx = await twAuth.begin();
- redis.set(userToken, JSON.stringify(twCtx));
- ctx.redirect(twCtx.url);
- });
-
- router.get('/signin/twitter', async ctx => {
- const twCtx = await twAuth.begin();
-
- const sessid = uuid();
-
- redis.set(sessid, JSON.stringify(twCtx));
-
- const expires = 1000 * 60 * 60; // 1h
- ctx.cookies.set('signin_with_twitter_session_id', sessid, {
- path: '/',
- domain: config.host,
- secure: config.url.startsWith('https'),
- httpOnly: true,
- expires: new Date(Date.now() + expires),
- maxAge: expires
+ if (meta.enableTwitterIntegration) {
+ return autwh({
+ consumerKey: meta.twitterConsumerKey,
+ consumerSecret: meta.twitterConsumerSecret,
+ callbackUrl: `${config.url}/api/tw/cb`
});
-
- ctx.redirect(twCtx.url);
- });
-
- router.get('/tw/cb', async ctx => {
- const userToken = getUserToken(ctx);
-
- if (userToken == null) {
- const sessid = ctx.cookies.get('signin_with_twitter_session_id');
-
- if (sessid == null) {
- ctx.throw(400, 'invalid session');
- return;
- }
-
- const get = new Promise((res, rej) => {
- redis.get(sessid, async (_, twCtx) => {
- res(twCtx);
- });
- });
-
- const twCtx = await get;
-
- const result = await twAuth.done(JSON.parse(twCtx), ctx.query.oauth_verifier);
-
- const user = await User.findOne({
- host: null,
- 'twitter.userId': result.userId
- }) as ILocalUser;
-
- if (user == null) {
- ctx.throw(404, `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`);
- return;
- }
-
- signin(ctx, user, true);
- } else {
- const verifier = ctx.query.oauth_verifier;
-
- if (verifier == null) {
- ctx.throw(400, 'invalid session');
- return;
- }
-
- const get = new Promise((res, rej) => {
- redis.get(userToken, async (_, twCtx) => {
- res(twCtx);
- });
- });
-
- const twCtx = await get;
-
- const result = await twAuth.done(JSON.parse(twCtx), verifier);
-
- const user = await User.findOneAndUpdate({
- host: null,
- token: userToken
- }, {
- $set: {
- twitter: {
- accessToken: result.accessToken,
- accessTokenSecret: result.accessTokenSecret,
- userId: result.userId,
- screenName: result.screenName
- }
- }
- });
-
- ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`;
-
- // Publish i updated event
- publishMainStream(user._id, 'meUpdated', await pack(user, user, {
- detail: true,
- includeSecrets: true
- }));
- }
- });
+ } else {
+ return null;
+ }
}
+router.get('/connect/twitter', async ctx => {
+ if (!compareOrigin(ctx)) {
+ ctx.throw(400, 'invalid origin');
+ return;
+ }
+
+ const userToken = getUserToken(ctx);
+ if (userToken == null) {
+ ctx.throw(400, 'signin required');
+ return;
+ }
+
+ const twAuth = await getTwAuth();
+ const twCtx = await twAuth.begin();
+ redis.set(userToken, JSON.stringify(twCtx));
+ ctx.redirect(twCtx.url);
+});
+
+router.get('/signin/twitter', async ctx => {
+ const twAuth = await getTwAuth();
+ const twCtx = await twAuth.begin();
+
+ const sessid = uuid();
+
+ redis.set(sessid, JSON.stringify(twCtx));
+
+ const expires = 1000 * 60 * 60; // 1h
+ ctx.cookies.set('signin_with_twitter_session_id', sessid, {
+ path: '/',
+ domain: config.host,
+ secure: config.url.startsWith('https'),
+ httpOnly: true,
+ expires: new Date(Date.now() + expires),
+ maxAge: expires
+ });
+
+ ctx.redirect(twCtx.url);
+});
+
+router.get('/tw/cb', async ctx => {
+ const userToken = getUserToken(ctx);
+
+ const twAuth = await getTwAuth();
+
+ if (userToken == null) {
+ const sessid = ctx.cookies.get('signin_with_twitter_session_id');
+
+ if (sessid == null) {
+ ctx.throw(400, 'invalid session');
+ return;
+ }
+
+ const get = new Promise((res, rej) => {
+ redis.get(sessid, async (_, twCtx) => {
+ res(twCtx);
+ });
+ });
+
+ const twCtx = await get;
+
+ const result = await twAuth.done(JSON.parse(twCtx), ctx.query.oauth_verifier);
+
+ const user = await User.findOne({
+ host: null,
+ 'twitter.userId': result.userId
+ }) as ILocalUser;
+
+ if (user == null) {
+ ctx.throw(404, `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`);
+ return;
+ }
+
+ signin(ctx, user, true);
+ } else {
+ const verifier = ctx.query.oauth_verifier;
+
+ if (verifier == null) {
+ ctx.throw(400, 'invalid session');
+ return;
+ }
+
+ const get = new Promise((res, rej) => {
+ redis.get(userToken, async (_, twCtx) => {
+ res(twCtx);
+ });
+ });
+
+ const twCtx = await get;
+
+ const result = await twAuth.done(JSON.parse(twCtx), verifier);
+
+ const user = await User.findOneAndUpdate({
+ host: null,
+ token: userToken
+ }, {
+ $set: {
+ twitter: {
+ accessToken: result.accessToken,
+ accessTokenSecret: result.accessTokenSecret,
+ userId: result.userId,
+ screenName: result.screenName
+ }
+ }
+ });
+
+ ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`;
+
+ // Publish i updated event
+ publishMainStream(user._id, 'meUpdated', await pack(user, user, {
+ detail: true,
+ includeSecrets: true
+ }));
+ }
+});
+
module.exports = router;