Merge branch 'master' of github.com:syuilo/misskey into swagger
This commit is contained in:
		
						commit
						0420fee5d2
					
				
					 26 changed files with 147 additions and 193 deletions
				
			
		| 
						 | 
				
			
			@ -1,6 +1,9 @@
 | 
			
		|||
language: node_js
 | 
			
		||||
node_js:
 | 
			
		||||
  - "7.3.0"
 | 
			
		||||
services:
 | 
			
		||||
  - mongodb
 | 
			
		||||
  - redis-server
 | 
			
		||||
before_script:
 | 
			
		||||
  - "mkdir -p ./.config && cp ./.ci-files/config.yml ./.config"
 | 
			
		||||
env:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										11
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								CHANGELOG.md
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,2 +1,11 @@
 | 
			
		|||
# 1
 | 
			
		||||
# Server
 | 
			
		||||
## 1
 | 
			
		||||
First version
 | 
			
		||||
 | 
			
		||||
# API
 | 
			
		||||
## 2
 | 
			
		||||
* パラメータ: _userkey --> i
 | 
			
		||||
* トークンは、アクセストークン + アプリのシークレットキーをsha512したものに
 | 
			
		||||
 | 
			
		||||
## 1
 | 
			
		||||
First version
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,5 +70,7 @@ block content
 | 
			
		|||
				| 次に、<code>#{api_url}/auth/session/userkey</code>へ<code>app_secret</code>としてApp Secretを、<code>token</code>としてセッションのトークンをパラメータとして付与したリクエストを送信してください。
 | 
			
		||||
				br
 | 
			
		||||
				| 上手くいけば、認証したユーザーのアクセストークンがレスポンスとして取得できます。おめでとうございます!
 | 
			
		||||
			p
 | 
			
		||||
				| 以降アクセストークンは、<strong>ユーザーのアクセストークン+アプリのシークレットキーをsha512したもの</strong>として扱います。
 | 
			
		||||
 | 
			
		||||
	p アクセストークンを取得できたら、あとは簡単です。REST APIなら、リクエストにアクセストークンを<code>_userkey</code>(「自分のアクセストークンを取得したい場合」の方法で取得したアクセストークンの場合は<code>i</code>)としてパラメータに含めるだけです。
 | 
			
		||||
	p アクセストークンを取得できたら、あとは簡単です。REST APIなら、リクエストにアクセストークンを<code>i</code>としてパラメータに含めるだけです。
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -149,6 +149,7 @@ const aliasifyConfig = {
 | 
			
		|||
		'chart.js': './node_modules/chart.js/src/chart.js',
 | 
			
		||||
		'textarea-caret-position': './node_modules/textarea-caret/index.js',
 | 
			
		||||
		'misskey-text': './src/common/text/index.js',
 | 
			
		||||
		'nyaize': './node_modules/nyaize/built/index.js',
 | 
			
		||||
		'strength.js': './node_modules/syuilo-password-strength/strength.js',
 | 
			
		||||
		'cropper': './node_modules/cropperjs/dist/cropper.js',
 | 
			
		||||
		'Sortable': './node_modules/sortablejs/Sortable.js',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@
 | 
			
		|||
    "@types/browserify": "12.0.30",
 | 
			
		||||
    "@types/chalk": "0.4.31",
 | 
			
		||||
    "@types/compression": "0.0.33",
 | 
			
		||||
    "@types/cors": "0.0.33",
 | 
			
		||||
    "@types/cors": "2.8.0",
 | 
			
		||||
    "@types/elasticsearch": "5.0.9",
 | 
			
		||||
    "@types/escape-html": "0.0.19",
 | 
			
		||||
    "@types/event-stream": "3.3.30",
 | 
			
		||||
| 
						 | 
				
			
			@ -63,13 +63,14 @@
 | 
			
		|||
    "babel-preset-stage-3": "6.17.0",
 | 
			
		||||
    "bcrypt": "1.0.2",
 | 
			
		||||
    "body-parser": "1.15.2",
 | 
			
		||||
    "browserify": "13.1.1",
 | 
			
		||||
    "browserify": "13.3.0",
 | 
			
		||||
    "browserify-livescript": "0.2.3",
 | 
			
		||||
    "chalk": "1.1.3",
 | 
			
		||||
    "chart.js": "2.4.0",
 | 
			
		||||
    "compression": "1.6.2",
 | 
			
		||||
    "cors": "2.8.1",
 | 
			
		||||
    "cropperjs": "1.0.0-beta",
 | 
			
		||||
    "crypto": "0.0.3",
 | 
			
		||||
    "deepcopy": "0.6.3",
 | 
			
		||||
    "del": "2.2.2",
 | 
			
		||||
    "elasticsearch": "12.1.3",
 | 
			
		||||
| 
						 | 
				
			
			@ -99,10 +100,11 @@
 | 
			
		|||
    "livescript": "1.5.0",
 | 
			
		||||
    "mime-types": "2.1.13",
 | 
			
		||||
    "mocha": "3.2.0",
 | 
			
		||||
    "mongodb": "2.2.16",
 | 
			
		||||
    "mongodb": "2.2.19",
 | 
			
		||||
    "ms": "0.7.2",
 | 
			
		||||
    "multer": "1.2.1",
 | 
			
		||||
    "nprogress": "0.2.0",
 | 
			
		||||
    "nyaize": "0.0.2",
 | 
			
		||||
    "page": "1.7.1",
 | 
			
		||||
    "prominence": "0.2.0",
 | 
			
		||||
    "pug": "2.0.0-beta6",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,8 @@
 | 
			
		|||
import * as express from 'express';
 | 
			
		||||
import App from './models/app';
 | 
			
		||||
import User from './models/user';
 | 
			
		||||
import Userkey from './models/userkey';
 | 
			
		||||
import AccessToken from './models/access-token';
 | 
			
		||||
import isNativeToken from './common/is-native-token';
 | 
			
		||||
 | 
			
		||||
export interface IAuthContext {
 | 
			
		||||
	/**
 | 
			
		||||
| 
						 | 
				
			
			@ -20,10 +21,14 @@ export interface IAuthContext {
 | 
			
		|||
	isSecure: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default (req: express.Request) =>
 | 
			
		||||
	new Promise<IAuthContext>(async (resolve, reject) => {
 | 
			
		||||
export default (req: express.Request) => new Promise<IAuthContext>(async (resolve, reject) => {
 | 
			
		||||
	const token = req.body['i'];
 | 
			
		||||
	if (token) {
 | 
			
		||||
 | 
			
		||||
	if (token == null) {
 | 
			
		||||
		return resolve({ app: null, user: null, isSecure: false });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (isNativeToken(token)) {
 | 
			
		||||
		const user = await User
 | 
			
		||||
			.findOne({ token: token });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -36,26 +41,21 @@ export default (req: express.Request) =>
 | 
			
		|||
			user: user,
 | 
			
		||||
			isSecure: true
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const userkey = req.headers['userkey'] || req.body['_userkey'];
 | 
			
		||||
	if (userkey) {
 | 
			
		||||
		const userkeyDoc = await Userkey.findOne({
 | 
			
		||||
			key: userkey
 | 
			
		||||
	} else {
 | 
			
		||||
		const accessToken = await AccessToken.findOne({
 | 
			
		||||
			hash: token
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (userkeyDoc === null) {
 | 
			
		||||
			return reject('invalid userkey');
 | 
			
		||||
		if (accessToken === null) {
 | 
			
		||||
			return reject('invalid signature');
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const app = await App
 | 
			
		||||
			.findOne({ _id: userkeyDoc.app_id });
 | 
			
		||||
			.findOne({ _id: accessToken.app_id });
 | 
			
		||||
 | 
			
		||||
		const user = await User
 | 
			
		||||
			.findOne({ _id: userkeyDoc.user_id });
 | 
			
		||||
			.findOne({ _id: accessToken.user_id });
 | 
			
		||||
 | 
			
		||||
		return resolve({ app: app, user: user, isSecure: false });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return resolve({ app: null, user: null, isSecure: false });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								src/api/common/is-native-token.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/api/common/is-native-token.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
export default (token: string) => token[0] == '!';
 | 
			
		||||
| 
						 | 
				
			
			@ -4,8 +4,10 @@
 | 
			
		|||
 * Module dependencies
 | 
			
		||||
 */
 | 
			
		||||
import rndstr from 'rndstr';
 | 
			
		||||
const crypto = require('crypto');
 | 
			
		||||
import App from '../../models/app';
 | 
			
		||||
import AuthSess from '../../models/auth-session';
 | 
			
		||||
import Userkey from '../../models/userkey';
 | 
			
		||||
import AccessToken from '../../models/access-token';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @swagger
 | 
			
		||||
| 
						 | 
				
			
			@ -41,35 +43,46 @@ module.exports = (params, user) =>
 | 
			
		|||
	new Promise(async (res, rej) =>
 | 
			
		||||
{
 | 
			
		||||
	// Get 'token' parameter
 | 
			
		||||
	const token = params.token;
 | 
			
		||||
	if (token == null) {
 | 
			
		||||
	const sesstoken = params.token;
 | 
			
		||||
	if (sesstoken == null) {
 | 
			
		||||
		return rej('token is required');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Fetch token
 | 
			
		||||
	const session = await AuthSess
 | 
			
		||||
		.findOne({ token: token });
 | 
			
		||||
		.findOne({ token: sesstoken });
 | 
			
		||||
 | 
			
		||||
	if (session === null) {
 | 
			
		||||
		return rej('session not found');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Generate userkey
 | 
			
		||||
	const key = rndstr('a-zA-Z0-9', 32);
 | 
			
		||||
	// Generate access token
 | 
			
		||||
	const token = rndstr('a-zA-Z0-9', 32);
 | 
			
		||||
 | 
			
		||||
	// Fetch exist userkey
 | 
			
		||||
	const exist = await Userkey.findOne({
 | 
			
		||||
	// Fetch exist access token
 | 
			
		||||
	const exist = await AccessToken.findOne({
 | 
			
		||||
		app_id: session.app_id,
 | 
			
		||||
		user_id: user._id,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	if (exist === null) {
 | 
			
		||||
		// Insert userkey doc
 | 
			
		||||
		await Userkey.insert({
 | 
			
		||||
		// Lookup app
 | 
			
		||||
		const app = await App.findOne({
 | 
			
		||||
			app_id: session.app_id
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// Generate Hash
 | 
			
		||||
		const sha512 = crypto.createHash('sha512');
 | 
			
		||||
		sha512.update(token + app.secret);
 | 
			
		||||
		const hash = sha512.digest('hex');
 | 
			
		||||
 | 
			
		||||
		// Insert access token doc
 | 
			
		||||
		await AccessToken.insert({
 | 
			
		||||
			created_at: new Date(),
 | 
			
		||||
			app_id: session.app_id,
 | 
			
		||||
			user_id: user._id,
 | 
			
		||||
			key: key
 | 
			
		||||
			token: token,
 | 
			
		||||
			hash: hash
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@
 | 
			
		|||
 */
 | 
			
		||||
import App from '../../../models/app';
 | 
			
		||||
import AuthSess from '../../../models/auth-session';
 | 
			
		||||
import Userkey from '../../../models/userkey';
 | 
			
		||||
import AccessToken from '../../../models/access-token';
 | 
			
		||||
import serialize from '../../../serializers/user';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
| 
						 | 
				
			
			@ -89,8 +89,8 @@ module.exports = (params) =>
 | 
			
		|||
		return rej('this session is not allowed yet');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Lookup userkey
 | 
			
		||||
	const userkey = await Userkey.findOne({
 | 
			
		||||
	// Lookup access token
 | 
			
		||||
	const accessToken = await AccessToken.findOne({
 | 
			
		||||
		app_id: app._id,
 | 
			
		||||
		user_id: session.user_id
 | 
			
		||||
	});
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +102,7 @@ module.exports = (params) =>
 | 
			
		|||
 | 
			
		||||
	// Response
 | 
			
		||||
	res({
 | 
			
		||||
		userkey: userkey.key,
 | 
			
		||||
		access_token: accessToken.token,
 | 
			
		||||
		user: await serialize(session.user_id, null, {
 | 
			
		||||
			detail: true
 | 
			
		||||
		})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,9 +19,19 @@ module.exports = (params, me) =>
 | 
			
		|||
	new Promise(async (res, rej) =>
 | 
			
		||||
{
 | 
			
		||||
	// Get 'user_id' parameter
 | 
			
		||||
	const userId = params.user_id;
 | 
			
		||||
	if (userId === undefined || userId === null) {
 | 
			
		||||
		return rej('user_id is required');
 | 
			
		||||
	let userId = params.user_id;
 | 
			
		||||
	if (userId === undefined || userId === null || userId === '') {
 | 
			
		||||
		userId = null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get 'username' parameter
 | 
			
		||||
	let username = params.username;
 | 
			
		||||
	if (username === undefined || username === null || username === '') {
 | 
			
		||||
		username = null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (userId === null && username === null) {
 | 
			
		||||
		return rej('user_id or username is required');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get 'with_replies' parameter
 | 
			
		||||
| 
						 | 
				
			
			@ -62,9 +72,9 @@ module.exports = (params, me) =>
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// Lookup user
 | 
			
		||||
	const user = await User.findOne({
 | 
			
		||||
		_id: new mongo.ObjectID(userId)
 | 
			
		||||
	});
 | 
			
		||||
	const user = userId !== null
 | 
			
		||||
		? await User.findOne({ _id: new mongo.ObjectID(userId) })
 | 
			
		||||
		: await User.findOne({ username_lower: username.toLowerCase() });
 | 
			
		||||
 | 
			
		||||
	if (user === null) {
 | 
			
		||||
		return rej('user not found');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										6
									
								
								src/api/models/access-token.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/api/models/access-token.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
const collection = global.db.collection('access_tokens');
 | 
			
		||||
 | 
			
		||||
collection.createIndex('token');
 | 
			
		||||
collection.createIndex('hash');
 | 
			
		||||
 | 
			
		||||
export default collection;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +0,0 @@
 | 
			
		|||
const collection = global.db.collection('userkeys');
 | 
			
		||||
 | 
			
		||||
collection.createIndex('key');
 | 
			
		||||
 | 
			
		||||
export default collection;
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +48,7 @@ export default async (req: express.Request, res: express.Response) => {
 | 
			
		|||
	const hash = bcrypt.hashSync(password, salt);
 | 
			
		||||
 | 
			
		||||
	// Generate secret
 | 
			
		||||
	const secret = rndstr('a-zA-Z0-9', 32);
 | 
			
		||||
	const secret = '!' + rndstr('a-zA-Z0-9', 32);
 | 
			
		||||
 | 
			
		||||
	// Create account
 | 
			
		||||
	const inserted = await User.insert({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ import * as mongo from 'mongodb';
 | 
			
		|||
import deepcopy = require('deepcopy');
 | 
			
		||||
import App from '../models/app';
 | 
			
		||||
import User from '../models/user';
 | 
			
		||||
import Userkey from '../models/userkey';
 | 
			
		||||
import AccessToken from '../models/access-token';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Serialize an app
 | 
			
		||||
| 
						 | 
				
			
			@ -71,7 +71,7 @@ export default (
 | 
			
		|||
 | 
			
		||||
	if (me) {
 | 
			
		||||
		// 既に連携しているか
 | 
			
		||||
		const exist = await Userkey.count({
 | 
			
		||||
		const exist = await AccessToken.count({
 | 
			
		||||
			app_id: _app.id,
 | 
			
		||||
			user_id: me,
 | 
			
		||||
		}, {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,8 @@ import * as http from 'http';
 | 
			
		|||
import * as websocket from 'websocket';
 | 
			
		||||
import * as redis from 'redis';
 | 
			
		||||
import User from './models/user';
 | 
			
		||||
import AccessToken from './models/access-token';
 | 
			
		||||
import isNativeToken from './common/is-native-token';
 | 
			
		||||
 | 
			
		||||
import homeStream from './stream/home';
 | 
			
		||||
import messagingStream from './stream/messaging';
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +19,13 @@ module.exports = (server: http.Server) => {
 | 
			
		|||
	ws.on('request', async (request) => {
 | 
			
		||||
		const connection = request.accept();
 | 
			
		||||
 | 
			
		||||
		const user = await authenticate(connection);
 | 
			
		||||
		const user = await authenticate(connection, request.resourceURL.query.i);
 | 
			
		||||
 | 
			
		||||
		if (user == null) {
 | 
			
		||||
			connection.send('authentication-failed');
 | 
			
		||||
			connection.close();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Connect to Redis
 | 
			
		||||
		const subscriber = redis.createClient(
 | 
			
		||||
| 
						 | 
				
			
			@ -41,29 +49,36 @@ module.exports = (server: http.Server) => {
 | 
			
		|||
	});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function authenticate(connection: websocket.connection): Promise<any> {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
		// Listen first message
 | 
			
		||||
		connection.once('message', async (data) => {
 | 
			
		||||
			const msg = JSON.parse(data.utf8Data);
 | 
			
		||||
 | 
			
		||||
function authenticate(connection: websocket.connection, token: string): Promise<any> {
 | 
			
		||||
	return new Promise(async (resolve, reject) => {
 | 
			
		||||
		if (isNativeToken(token)) {
 | 
			
		||||
			// Fetch user
 | 
			
		||||
			// SELECT _id
 | 
			
		||||
			const user = await User
 | 
			
		||||
				.findOne({
 | 
			
		||||
					token: msg.i
 | 
			
		||||
					token: token
 | 
			
		||||
				}, {
 | 
			
		||||
					_id: true
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
			if (user === null) {
 | 
			
		||||
				connection.close();
 | 
			
		||||
				return;
 | 
			
		||||
			resolve(user);
 | 
			
		||||
		} else {
 | 
			
		||||
			const accessToken = await AccessToken.findOne({
 | 
			
		||||
				hash: token
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			if (accessToken == null) {
 | 
			
		||||
				return reject('invalid signature');
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			connection.send('authenticated');
 | 
			
		||||
			// Fetch user
 | 
			
		||||
			// SELECT _id
 | 
			
		||||
			const user = await User
 | 
			
		||||
				.findOne({ _id: accessToken.user_id }, {
 | 
			
		||||
					_id: true
 | 
			
		||||
				});
 | 
			
		||||
 | 
			
		||||
			resolve(user);
 | 
			
		||||
		});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,7 @@ try {
 | 
			
		|||
checkForUpdate();
 | 
			
		||||
 | 
			
		||||
// Get token from cookie
 | 
			
		||||
const i = (document.cookie.match(/i=(\w+)/) || [null, null])[1];
 | 
			
		||||
const i = (document.cookie.match(/i=(!\w+)/) || [null, null])[1];
 | 
			
		||||
 | 
			
		||||
// ユーザーをフェッチしてコールバックする
 | 
			
		||||
module.exports = callback => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ module.exports = (date) ->
 | 
			
		|||
 | 
			
		||||
	text =
 | 
			
		||||
		date.get-full-year! + \年 +
 | 
			
		||||
		date.get-month!     + \月 +
 | 
			
		||||
		date.get-month! + 1 + \月 +
 | 
			
		||||
		date.get-date!      + \日 +
 | 
			
		||||
		' ' +
 | 
			
		||||
		date.get-hours!     + \時 +
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ class Connection
 | 
			
		|||
		@event = riot.observable!
 | 
			
		||||
		@me = me
 | 
			
		||||
		host = CONFIG.api.url.replace \http \ws
 | 
			
		||||
		@socket = new ReconnectingWebSocket "#{host}/messaging?otherparty=#{otherparty}"
 | 
			
		||||
		@socket = new ReconnectingWebSocket "#{host}/messaging?i=#{me.token}&otherparty=#{otherparty}"
 | 
			
		||||
 | 
			
		||||
		@socket.add-event-listener \open @on-open
 | 
			
		||||
		@socket.add-event-listener \message @on-message
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,13 +9,12 @@ module.exports = (me) ~>
 | 
			
		|||
	state-ev = riot.observable!
 | 
			
		||||
	event = riot.observable!
 | 
			
		||||
 | 
			
		||||
	socket = new ReconnectingWebSocket CONFIG.api.url.replace \http \ws
 | 
			
		||||
	host = CONFIG.api.url.replace \http \ws
 | 
			
		||||
	socket = new ReconnectingWebSocket "#{host}?i=#{me.token}"
 | 
			
		||||
 | 
			
		||||
	socket.onopen = ~>
 | 
			
		||||
		state := \connected
 | 
			
		||||
		state-ev.trigger \connected
 | 
			
		||||
		socket.send JSON.stringify do
 | 
			
		||||
			i: me.token
 | 
			
		||||
 | 
			
		||||
	socket.onclose = ~>
 | 
			
		||||
		state := \reconnecting
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
const riot = require('riot');
 | 
			
		||||
const nyaize = require('nyaize').default;
 | 
			
		||||
 | 
			
		||||
module.exports = function(tokens, shouldBreak, escape) {
 | 
			
		||||
	if (shouldBreak == null) {
 | 
			
		||||
| 
						 | 
				
			
			@ -34,10 +35,7 @@ module.exports = function(tokens, shouldBreak, escape) {
 | 
			
		|||
	}).join('');
 | 
			
		||||
 | 
			
		||||
	if (me && me.data && me.data.nya) {
 | 
			
		||||
		text = text.replace(/な/g, 'にゃ')
 | 
			
		||||
			.replace(/ニャ/g, 'にゃ')
 | 
			
		||||
			.replace(/にゃでにゃで/g, 'なでなで')
 | 
			
		||||
			.replace(/ニャデニャデ/g, 'ナデナデ');
 | 
			
		||||
		text = nyaize(text);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return text;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ script.
 | 
			
		|||
 | 
			
		||||
	@absolute =
 | 
			
		||||
		@time.get-full-year! + \年 +
 | 
			
		||||
		@time.get-month!     + \月 +
 | 
			
		||||
		@time.get-month! + 1 + \月 +
 | 
			
		||||
		@time.get-date!      + \日 +
 | 
			
		||||
		' ' +
 | 
			
		||||
		@time.get-hours!     + \時 +
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,11 +32,6 @@ boot(me => {
 | 
			
		|||
	// Register mixins
 | 
			
		||||
	mixins(me);
 | 
			
		||||
 | 
			
		||||
	// Debug
 | 
			
		||||
	if (me != null && me.data.debug) {
 | 
			
		||||
		riot.mount(document.body.appendChild(document.createElement('mk-log-window')));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Start routing
 | 
			
		||||
	route(me);
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -99,5 +99,3 @@ require './tags/user-followers-window.tag'
 | 
			
		|||
require './tags/list-user.tag'
 | 
			
		||||
require './tags/ui-notification.tag'
 | 
			
		||||
require './tags/signin-history.tag'
 | 
			
		||||
require './tags/log.tag'
 | 
			
		||||
require './tags/log-window.tag'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,20 +0,0 @@
 | 
			
		|||
mk-log-window
 | 
			
		||||
	mk-window@window(width={ '600px' }, height={ '400px' })
 | 
			
		||||
		<yield to="header">
 | 
			
		||||
		i.fa.fa-terminal
 | 
			
		||||
		| Log
 | 
			
		||||
		</yield>
 | 
			
		||||
		<yield to="content">
 | 
			
		||||
		mk-log
 | 
			
		||||
		</yield>
 | 
			
		||||
 | 
			
		||||
style.
 | 
			
		||||
	> mk-window
 | 
			
		||||
		[data-yield='header']
 | 
			
		||||
			> i
 | 
			
		||||
				margin-right 4px
 | 
			
		||||
 | 
			
		||||
script.
 | 
			
		||||
	@on \mount ~>
 | 
			
		||||
		@refs.window.on \closed ~>
 | 
			
		||||
			@unmount!
 | 
			
		||||
| 
						 | 
				
			
			@ -1,62 +0,0 @@
 | 
			
		|||
mk-log
 | 
			
		||||
	header
 | 
			
		||||
		button.follow(class={ following: following }, onclick={ follow }) Follow
 | 
			
		||||
	div.logs@logs
 | 
			
		||||
		code(each={ logs })
 | 
			
		||||
			span.date { date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds() }
 | 
			
		||||
			span.message { message }
 | 
			
		||||
 | 
			
		||||
style.
 | 
			
		||||
	display block
 | 
			
		||||
	height 100%
 | 
			
		||||
	color #fff
 | 
			
		||||
	background #000
 | 
			
		||||
 | 
			
		||||
	> header
 | 
			
		||||
		height 32px
 | 
			
		||||
		background #343a42
 | 
			
		||||
 | 
			
		||||
		> button
 | 
			
		||||
			line-height 32px
 | 
			
		||||
 | 
			
		||||
		> .follow
 | 
			
		||||
			position absolute
 | 
			
		||||
			top 0
 | 
			
		||||
			right 0
 | 
			
		||||
 | 
			
		||||
			&.following
 | 
			
		||||
				color #ff0
 | 
			
		||||
 | 
			
		||||
	> .logs
 | 
			
		||||
		height calc(100% - 32px)
 | 
			
		||||
		overflow auto
 | 
			
		||||
 | 
			
		||||
		> code
 | 
			
		||||
			display block
 | 
			
		||||
			padding 4px 8px
 | 
			
		||||
 | 
			
		||||
			&:hover
 | 
			
		||||
				background rgba(#fff, 0.15)
 | 
			
		||||
 | 
			
		||||
			> .date
 | 
			
		||||
				margin-right 8px
 | 
			
		||||
				opacity 0.5
 | 
			
		||||
 | 
			
		||||
script.
 | 
			
		||||
	@mixin \log
 | 
			
		||||
 | 
			
		||||
	@following = true
 | 
			
		||||
 | 
			
		||||
	@on \mount ~>
 | 
			
		||||
		@log-event.on \log @on-log
 | 
			
		||||
 | 
			
		||||
	@on \unmount ~>
 | 
			
		||||
		@log-event.off \log @on-log
 | 
			
		||||
 | 
			
		||||
	@follow = ~>
 | 
			
		||||
		@following = true
 | 
			
		||||
 | 
			
		||||
	@on-log = ~>
 | 
			
		||||
		@update!
 | 
			
		||||
		if @following
 | 
			
		||||
			@refs.logs.scroll-top = @refs.logs.scroll-height
 | 
			
		||||
| 
						 | 
				
			
			@ -3,11 +3,10 @@ mk-user-preview
 | 
			
		|||
		img.avatar(src={ user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
 | 
			
		||||
	div.main
 | 
			
		||||
		header
 | 
			
		||||
			div.left
 | 
			
		||||
				a.name(href={ CONFIG.url + '/' + user.username })
 | 
			
		||||
					| { user.name }
 | 
			
		||||
				span.username
 | 
			
		||||
					| @{ user.username }
 | 
			
		||||
			a.name(href={ CONFIG.url + '/' + user.username })
 | 
			
		||||
				| { user.name }
 | 
			
		||||
			span.username
 | 
			
		||||
				| @{ user.username }
 | 
			
		||||
		div.body
 | 
			
		||||
			div.bio { user.bio }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -57,36 +56,26 @@ style.
 | 
			
		|||
			width calc(100% - 74px)
 | 
			
		||||
 | 
			
		||||
		> header
 | 
			
		||||
			white-space nowrap
 | 
			
		||||
 | 
			
		||||
			@media (min-width 500px)
 | 
			
		||||
				margin-bottom 2px
 | 
			
		||||
 | 
			
		||||
			&:after
 | 
			
		||||
				content ""
 | 
			
		||||
				display block
 | 
			
		||||
				clear both
 | 
			
		||||
			> .name
 | 
			
		||||
				display inline
 | 
			
		||||
				margin 0
 | 
			
		||||
				padding 0
 | 
			
		||||
				color #777
 | 
			
		||||
				font-size 1em
 | 
			
		||||
				font-weight 700
 | 
			
		||||
				text-align left
 | 
			
		||||
				text-decoration none
 | 
			
		||||
 | 
			
		||||
			> .left
 | 
			
		||||
				float left
 | 
			
		||||
				&:hover
 | 
			
		||||
					text-decoration underline
 | 
			
		||||
 | 
			
		||||
				> .name
 | 
			
		||||
					display inline
 | 
			
		||||
					margin 0
 | 
			
		||||
					padding 0
 | 
			
		||||
					color #777
 | 
			
		||||
					font-size 1em
 | 
			
		||||
					font-weight 700
 | 
			
		||||
					text-align left
 | 
			
		||||
					text-decoration none
 | 
			
		||||
 | 
			
		||||
					&:hover
 | 
			
		||||
						text-decoration underline
 | 
			
		||||
 | 
			
		||||
				> .username
 | 
			
		||||
					text-align left
 | 
			
		||||
					margin 0 0 0 8px
 | 
			
		||||
					color #ccc
 | 
			
		||||
			> .username
 | 
			
		||||
				text-align left
 | 
			
		||||
				margin 0 0 0 8px
 | 
			
		||||
				color #ccc
 | 
			
		||||
 | 
			
		||||
		> .body
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue