Refactorng

This commit is contained in:
syuilo 2018-07-07 19:19:00 +09:00
parent 865fd25af1
commit aa4ef6745a
132 changed files with 180 additions and 212 deletions

4
src/misc/acct/parse.ts Normal file
View file

@ -0,0 +1,4 @@
export default (acct: string) => {
const splitted = acct.split('@', 2);
return { username: splitted[0], host: splitted[1] || null };
};

5
src/misc/acct/render.ts Normal file
View file

@ -0,0 +1,5 @@
import { IUser } from '../../models/user';
export default (user: IUser) => {
return user.host === null ? user.username : `${user.username}@${user.host}`;
};

33
src/misc/cafy-id.ts Normal file
View file

@ -0,0 +1,33 @@
import * as mongo from 'mongodb';
import { Context } from 'cafy';
export const isAnId = (x: any) => mongo.ObjectID.isValid(x);
export const isNotAnId = (x: any) => !isAnId(x);
/**
* ID
*/
export default class ID extends Context<mongo.ObjectID> {
constructor() {
super();
this.transform = v => {
if (isAnId(v) && !mongo.ObjectID.prototype.isPrototypeOf(v)) {
return new mongo.ObjectID(v);
} else {
return v;
}
};
this.push(v => {
if (!mongo.ObjectID.prototype.isPrototypeOf(v) && isNotAnId(v)) {
return new Error('must-be-an-id');
}
return true;
});
}
public getType() {
return super.getType('string');
}
}

35
src/misc/cli/indicator.ts Normal file
View file

@ -0,0 +1,35 @@
import * as readline from 'readline';
/**
* Indicator
*/
export default class {
private clock: NodeJS.Timer;
constructor(text: string) {
let i = 0; // Dots counter
draw();
this.clock = setInterval(draw, 300);
function draw(): void {
cll();
i = (i + 1) % 4;
const dots = new Array(i + 1).join('.');
process.stdout.write(text + dots); // Write text
}
}
public end(): void {
clearInterval(this.clock);
cll();
}
}
/**
* Clear current line
*/
function cll(): void {
readline.clearLine(process.stdout, 0); // Clear current text
readline.cursorTo(process.stdout, 0, null); // Move cursor to the head of line
}

View file

@ -0,0 +1,85 @@
import { EventEmitter } from 'events';
import * as readline from 'readline';
import chalk from 'chalk';
/**
* Progress bar
*/
export default class extends EventEmitter {
public max: number;
public value: number;
public text: string;
private indicator: number;
constructor(max: number, text: string = null) {
super();
this.max = max;
this.value = 0;
this.text = text;
this.indicator = 0;
this.draw();
const iclock = setInterval(() => {
this.indicator = (this.indicator + 1) % 4;
this.draw();
}, 200);
this.on('complete', () => {
clearInterval(iclock);
});
}
public increment(): void {
this.value++;
this.draw();
// Check if it is fulfilled
if (this.value === this.max) {
this.indicator = null;
cll();
process.stdout.write(`${this.render()} -> ${chalk.bold('Complete')}\n`);
this.emit('complete');
}
}
public draw(): void {
const str = this.render();
cll();
process.stdout.write(str);
}
private render(): string {
const width = 30;
const t = this.text ? `${this.text} ` : '';
const v = Math.floor((this.value / this.max) * width);
const vs = new Array(v + 1).join('*');
const p = width - v;
const ps = new Array(p + 1).join(' ');
const percentage = Math.floor((this.value / this.max) * 100);
const percentages = chalk.gray(`(${percentage} %)`);
let i: string;
switch (this.indicator) {
case 0: i = '-'; break;
case 1: i = '\\'; break;
case 2: i = '|'; break;
case 3: i = '/'; break;
case null: i = '+'; break;
}
return `${i} ${t}[${vs}${ps}] ${this.value} / ${this.max} ${percentages}`;
}
}
/**
* Clear current line
*/
function cll(): void {
readline.clearLine(process.stdout, 0); // Clear current text
readline.cursorTo(process.stdout, 0, null); // Move cursor to the head of line
}

View file

@ -0,0 +1,33 @@
import Logger from './logger';
import { execSync } from 'child_process';
export default class {
private logger: Logger;
constructor() {
this.logger = new Logger('Deps');
}
public showAll(): void {
this.show('MongoDB', 'mongo --version', x => x.match(/^MongoDB shell version:? (.*)\r?\n/));
this.show('Redis', 'redis-server --version', x => x.match(/v=([0-9\.]*)/));
this.show('ImageMagick', 'magick -version', x => x.match(/^Version: ImageMagick (.+?)\r?\n/));
}
public show(serviceName: string, command: string, transform: (x: string) => RegExpMatchArray): void {
try {
// ステータス0以外のときにexecSyncはstderrをコンソール上に出力してしまうので
// プロセスからのstderrをすべて無視するように stdio オプションをセット
const x = execSync(command, { stdio: ['pipe', 'pipe', 'ignore'] });
const ver = transform(x.toString());
if (ver != null) {
this.logger.info(`${serviceName} ${ver[1]} found`);
} else {
this.logger.warn(`${serviceName} not found`);
this.logger.warn(`Regexp used for version check of ${serviceName} is probably messed up`);
}
} catch (e) {
this.logger.warn(`${serviceName} not found`);
}
}
}

View file

@ -0,0 +1,14 @@
import Logger from './logger';
export default class {
public static show(): void {
const env = process.env.NODE_ENV;
const logger = new Logger('Env');
logger.info(typeof env == 'undefined' ? 'NODE_ENV is not set' : `NODE_ENV: ${env}`);
if (env !== 'production') {
logger.warn('The environment is not in production mode');
logger.warn('Do not use for production purpose');
}
}
}

View file

@ -0,0 +1,50 @@
/**
* 稿
* @param {*} note (packされた)稿
*/
const summarize = (note: any): string => {
if (note.deletedAt) {
return '(削除された投稿)';
}
if (note.isHidden) {
return '(非公開の投稿)';
}
let summary = '';
// 本文
summary += note.text ? note.text : '';
// メディアが添付されているとき
if (note.media.length != 0) {
summary += ` (${note.media.length}つのメディア)`;
}
// 投票が添付されているとき
if (note.poll) {
summary += ' (投票)';
}
// 返信のとき
if (note.replyId) {
if (note.reply) {
summary += ` RE: ${summarize(note.reply)}`;
} else {
summary += ' RE: ...';
}
}
// Renoteのとき
if (note.renoteId) {
if (note.renote) {
summary += ` RP: ${summarize(note.renote)}`;
} else {
summary += ' RP: ...';
}
}
return summary.trim();
};
export default summarize;

View file

@ -0,0 +1,28 @@
import getUserName from './get-user-name';
import getNoteSummary from './get-note-summary';
import getReactionEmoji from './get-reaction-emoji';
/**
*
* @param notification
*/
export default function(notification: any): string {
switch (notification.type) {
case 'follow':
return `${getUserName(notification.user)}にフォローされました`;
case 'mention':
return `言及されました:\n${getUserName(notification.user)}${getNoteSummary(notification.note)}`;
case 'reply':
return `返信されました:\n${getUserName(notification.user)}${getNoteSummary(notification.note)}`;
case 'renote':
return `Renoteされました:\n${getUserName(notification.user)}${getNoteSummary(notification.note)}`;
case 'quote':
return `引用されました:\n${getUserName(notification.user)}${getNoteSummary(notification.note)}`;
case 'reaction':
return `リアクションされました:\n${getUserName(notification.user)} <${getReactionEmoji(notification.reaction)}>「${getNoteSummary(notification.note)}`;
case 'poll_vote':
return `投票されました:\n${getUserName(notification.user)}${getNoteSummary(notification.note)}`;
default:
return `<不明な通知タイプ: ${notification.type}>`;
}
}

View file

@ -0,0 +1,14 @@
export default function(reaction: string): string {
switch (reaction) {
case 'like': return '👍';
case 'love': return '❤️';
case 'laugh': return '😆';
case 'hmm': return '🤔';
case 'surprise': return '😮';
case 'congrats': return '🎉';
case 'angry': return '💢';
case 'confused': return '😥';
case 'pudding': return '🍮';
default: return '';
}
}

View file

@ -0,0 +1,5 @@
import { IUser } from '../models/user';
export default function(user: IUser): string {
return user.name || '名無し';
}

View file

@ -0,0 +1,18 @@
import { IUser, isLocalUser } from '../models/user';
import getAcct from '../misc/acct/render';
import getUserName from './get-user-name';
/**
*
* @param user
*/
export default function(user: IUser): string {
let string = `${getUserName(user)} (@${getAcct(user)})\n` +
`${user.notesCount}投稿、${user.followingCount}フォロー、${user.followersCount}フォロワー\n`;
if (isLocalUser(user)) {
string += `場所: ${user.profile.location}、誕生日: ${user.profile.birthday}\n`;
}
return string + `${user.description}`;
}

53
src/misc/logger.ts Normal file
View file

@ -0,0 +1,53 @@
import chalk, { Chalk } from 'chalk';
export type LogLevel = 'Error' | 'Warn' | 'Info';
function toLevelColor(level: LogLevel): Chalk {
switch (level) {
case 'Error': return chalk.red;
case 'Warn': return chalk.yellow;
case 'Info': return chalk.blue;
}
}
export default class Logger {
private domain: string;
constructor(domain: string) {
this.domain = domain;
}
public static log(level: LogLevel, message: string): void {
const color = toLevelColor(level);
const time = (new Date()).toLocaleTimeString('ja-JP');
console.log(`[${time} ${color.bold(level.toUpperCase())}]: ${message}`);
}
public static error(message: string): void {
Logger.log('Error', message);
}
public static warn(message: string): void {
Logger.log('Warn', message);
}
public static info(message: string): void {
Logger.log('Info', message);
}
public log(level: LogLevel, message: string): void {
Logger.log(level, `[${this.domain}] ${message}`);
}
public error(message: string): void {
this.log('Error', message);
}
public warn(message: string): void {
this.log('Warn', message);
}
public info(message: string): void {
this.log('Info', message);
}
}

16
src/misc/machineInfo.ts Normal file
View file

@ -0,0 +1,16 @@
import * as os from 'os';
import Logger from './logger';
export default class {
public static show(): void {
const totalmem = (os.totalmem() / 1024 / 1024 / 1024).toFixed(1);
const freemem = (os.freemem() / 1024 / 1024 / 1024).toFixed(1);
const logger = new Logger('Machine');
logger.info(`Hostname: ${os.hostname()}`);
logger.info(`Platform: ${process.platform}`);
logger.info(`Architecture: ${process.arch}`);
logger.info(`Node.js: ${process.version}`);
logger.info(`CPU: ${os.cpus().length}core`);
logger.info(`MEM: ${totalmem}GB (available: ${freemem}GB)`);
}
}