mirror of
https://github.com/syuilo/ai.git
synced 2024-11-23 21:47:58 +00:00
Compare commits
No commits in common. "b2ab6778b04c8546672a242d934ac2399c041d2f" and "2f40fd4aa0aaf9a71949d86f093349c9409ea0b8" have entirely different histories.
b2ab6778b0
...
2f40fd4aa0
115
src/ai.ts
115
src/ai.ts
|
@ -46,21 +46,32 @@ export type ModuleDataDoc<Data = any> = {
|
||||||
/**
|
/**
|
||||||
* 藍
|
* 藍
|
||||||
*/
|
*/
|
||||||
export default class 藍 {
|
export default interface 藍 extends Ai {
|
||||||
|
connection: Stream;
|
||||||
|
lastSleepedAt: number;
|
||||||
|
|
||||||
|
friends: loki.Collection<FriendDoc>;
|
||||||
|
moduleData: loki.Collection<ModuleDataDoc>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 起動中の藍
|
||||||
|
*/
|
||||||
|
export class Ai {
|
||||||
public readonly version = pkg._v;
|
public readonly version = pkg._v;
|
||||||
public account: User;
|
public account: User;
|
||||||
public connection: Stream;
|
public connection?: Stream;
|
||||||
public modules: Module[] = [];
|
public modules: Module[] = [];
|
||||||
private mentionHooks: MentionHook[] = [];
|
private mentionHooks: MentionHook[] = [];
|
||||||
private contextHooks: { [moduleName: string]: ContextHook } = {};
|
private contextHooks: { [moduleName: string]: ContextHook } = {};
|
||||||
private timeoutCallbacks: { [moduleName: string]: TimeoutCallback } = {};
|
private timeoutCallbacks: { [moduleName: string]: TimeoutCallback } = {};
|
||||||
public installedModules: { [moduleName: string]: InstalledModule } = {};
|
public installedModules: { [moduleName: string]: InstalledModule } = {};
|
||||||
public db: loki;
|
public db: loki;
|
||||||
public lastSleepedAt: number;
|
public lastSleepedAt?: number;
|
||||||
|
|
||||||
private meta: loki.Collection<Meta>;
|
private meta?: loki.Collection<Meta>;
|
||||||
|
|
||||||
private contexts: loki.Collection<{
|
private contexts?: loki.Collection<{
|
||||||
noteId?: string;
|
noteId?: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
module: string;
|
module: string;
|
||||||
|
@ -68,7 +79,7 @@ export default class 藍 {
|
||||||
data?: any;
|
data?: any;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
private timers: loki.Collection<{
|
private timers?: loki.Collection<{
|
||||||
id: string;
|
id: string;
|
||||||
module: string;
|
module: string;
|
||||||
insertedAt: number;
|
insertedAt: number;
|
||||||
|
@ -76,16 +87,20 @@ export default class 藍 {
|
||||||
data?: any;
|
data?: any;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
public friends: loki.Collection<FriendDoc>;
|
public friends?: loki.Collection<FriendDoc>;
|
||||||
public moduleData: loki.Collection<any>;
|
public moduleData?: loki.Collection<any>;
|
||||||
|
|
||||||
|
private ready: boolean = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 藍インスタンスを生成します
|
* 藍インスタンスを生成します
|
||||||
* @param account 藍として使うアカウント
|
* @param account 藍として使うアカウント
|
||||||
* @param modules モジュール。先頭のモジュールほど高優先度
|
* @param modules モジュール。先頭のモジュールほど高優先度
|
||||||
*/
|
*/
|
||||||
@bindThis
|
constructor(account: User, modules: Module[]) {
|
||||||
public static start(account: User, modules: Module[]) {
|
this.account = account;
|
||||||
|
this.modules = modules;
|
||||||
|
|
||||||
let memoryDir = '.';
|
let memoryDir = '.';
|
||||||
if (config.memoryDir) {
|
if (config.memoryDir) {
|
||||||
memoryDir = config.memoryDir;
|
memoryDir = config.memoryDir;
|
||||||
|
@ -94,7 +109,7 @@ export default class 藍 {
|
||||||
|
|
||||||
this.log(`Lodaing the memory from ${file}...`);
|
this.log(`Lodaing the memory from ${file}...`);
|
||||||
|
|
||||||
const db = new loki(file, {
|
this.db = new loki(file, {
|
||||||
autoload: true,
|
autoload: true,
|
||||||
autosave: true,
|
autosave: true,
|
||||||
autosaveInterval: 1000,
|
autosaveInterval: 1000,
|
||||||
|
@ -103,7 +118,7 @@ export default class 藍 {
|
||||||
this.log(chalk.red(`Failed to load the memory: ${err}`));
|
this.log(chalk.red(`Failed to load the memory: ${err}`));
|
||||||
} else {
|
} else {
|
||||||
this.log(chalk.green('The memory loaded successfully'));
|
this.log(chalk.green('The memory loaded successfully'));
|
||||||
new 藍(account, modules, db);
|
this.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -111,19 +126,11 @@ export default class 藍 {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public log(msg: string) {
|
public log(msg: string) {
|
||||||
藍.log(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
|
||||||
private static log(msg: string) {
|
|
||||||
log(`[${chalk.magenta('AiOS')}]: ${msg}`);
|
log(`[${chalk.magenta('AiOS')}]: ${msg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor(account: User, modules: Module[], db: loki) {
|
@bindThis
|
||||||
this.account = account;
|
private run() {
|
||||||
this.modules = modules;
|
|
||||||
this.db = db;
|
|
||||||
|
|
||||||
//#region Init DB
|
//#region Init DB
|
||||||
this.meta = this.getCollection('meta', {});
|
this.meta = this.getCollection('meta', {});
|
||||||
|
|
||||||
|
@ -150,6 +157,9 @@ export default class 藍 {
|
||||||
// Init stream
|
// Init stream
|
||||||
this.connection = new Stream();
|
this.connection = new Stream();
|
||||||
|
|
||||||
|
// この時点から藍インスタンスに
|
||||||
|
this.setReady();
|
||||||
|
|
||||||
//#region Main stream
|
//#region Main stream
|
||||||
const mainStream = this.connection.useSharedConnection('main');
|
const mainStream = this.connection.useSharedConnection('main');
|
||||||
|
|
||||||
|
@ -217,12 +227,40 @@ export default class 藍 {
|
||||||
this.log(chalk.green.bold('Ai am now running!'));
|
this.log(chalk.green.bold('Ai am now running!'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 準備が完了したフラグを立てる。
|
||||||
|
*/
|
||||||
|
private setReady(): asserts this is 藍 {
|
||||||
|
// 呼び出すタイミングが正しいか検証
|
||||||
|
if (
|
||||||
|
this.connection == null ||
|
||||||
|
this.lastSleepedAt == null ||
|
||||||
|
this.meta == null ||
|
||||||
|
this.contexts == null ||
|
||||||
|
this.timers == null ||
|
||||||
|
this.friends == null ||
|
||||||
|
this.moduleData == null
|
||||||
|
) {
|
||||||
|
throw new TypeError('Cannot set ready');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public requireReady(): asserts this is 藍 {
|
||||||
|
if (!this.ready) {
|
||||||
|
throw new TypeError('Ai am not ready!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ユーザーから話しかけられたとき
|
* ユーザーから話しかけられたとき
|
||||||
* (メンション、リプライ、トークのメッセージ)
|
* (メンション、リプライ、トークのメッセージ)
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
private async onReceiveMessage(msg: Message): Promise<void> {
|
private async onReceiveMessage(msg: Message): Promise<void> {
|
||||||
|
this.requireReady();
|
||||||
|
|
||||||
this.log(chalk.gray(`<<< An message received: ${chalk.underline(msg.id)}`));
|
this.log(chalk.gray(`<<< An message received: ${chalk.underline(msg.id)}`));
|
||||||
|
|
||||||
// Ignore message if the user is a bot
|
// Ignore message if the user is a bot
|
||||||
|
@ -234,7 +272,7 @@ export default class 藍 {
|
||||||
const isNoContext = msg.replyId == null;
|
const isNoContext = msg.replyId == null;
|
||||||
|
|
||||||
// Look up the context
|
// Look up the context
|
||||||
const context = isNoContext ? null : this.contexts.findOne({
|
const context = isNoContext ? null : this.contexts!.findOne({
|
||||||
noteId: msg.replyId
|
noteId: msg.replyId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -290,6 +328,8 @@ export default class 藍 {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private onNotification(notification: any) {
|
private onNotification(notification: any) {
|
||||||
|
this.requireReady();
|
||||||
|
|
||||||
switch (notification.type) {
|
switch (notification.type) {
|
||||||
// リアクションされたら親愛度を少し上げる
|
// リアクションされたら親愛度を少し上げる
|
||||||
// TODO: リアクション取り消しをよしなにハンドリングする
|
// TODO: リアクション取り消しをよしなにハンドリングする
|
||||||
|
@ -306,12 +346,14 @@ export default class 藍 {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private crawleTimer() {
|
private crawleTimer() {
|
||||||
const timers = this.timers.find();
|
this.requireReady();
|
||||||
|
|
||||||
|
const timers = this.timers!.find();
|
||||||
for (const timer of timers) {
|
for (const timer of timers) {
|
||||||
// タイマーが時間切れかどうか
|
// タイマーが時間切れかどうか
|
||||||
if (Date.now() - (timer.insertedAt + timer.delay) >= 0) {
|
if (Date.now() - (timer.insertedAt + timer.delay) >= 0) {
|
||||||
this.log(`Timer expired: ${timer.module} ${timer.id}`);
|
this.log(`Timer expired: ${timer.module} ${timer.id}`);
|
||||||
this.timers.remove(timer);
|
this.timers!.remove(timer);
|
||||||
this.timeoutCallbacks[timer.module](timer.data);
|
this.timeoutCallbacks[timer.module](timer.data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -342,6 +384,8 @@ export default class 藍 {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public lookupFriend(userId: User['id']): Friend | null {
|
public lookupFriend(userId: User['id']): Friend | null {
|
||||||
|
this.requireReady();
|
||||||
|
|
||||||
const doc = this.friends.findOne({
|
const doc = this.friends.findOne({
|
||||||
userId: userId
|
userId: userId
|
||||||
});
|
});
|
||||||
|
@ -411,7 +455,8 @@ export default class 藍 {
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public subscribeReply(module: Module, key: string | null, id: string, data?: any) {
|
public subscribeReply(module: Module, key: string | null, id: string, data?: any) {
|
||||||
this.contexts.insertOne({
|
this.requireReady();
|
||||||
|
this.contexts!.insertOne({
|
||||||
noteId: id,
|
noteId: id,
|
||||||
module: module.name,
|
module: module.name,
|
||||||
key: key,
|
key: key,
|
||||||
|
@ -426,7 +471,8 @@ export default class 藍 {
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public unsubscribeReply(module: Module, key: string | null) {
|
public unsubscribeReply(module: Module, key: string | null) {
|
||||||
this.contexts.findAndRemove({
|
this.requireReady();
|
||||||
|
this.contexts!.findAndRemove({
|
||||||
key: key,
|
key: key,
|
||||||
module: module.name
|
module: module.name
|
||||||
});
|
});
|
||||||
|
@ -441,8 +487,10 @@ export default class 藍 {
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public setTimeoutWithPersistence(module: Module, delay: number, data?: any) {
|
public setTimeoutWithPersistence(module: Module, delay: number, data?: any) {
|
||||||
|
this.requireReady();
|
||||||
|
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
this.timers.insertOne({
|
this.timers!.insertOne({
|
||||||
id: id,
|
id: id,
|
||||||
module: module.name,
|
module: module.name,
|
||||||
insertedAt: Date.now(),
|
insertedAt: Date.now(),
|
||||||
|
@ -455,6 +503,10 @@ export default class 藍 {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public getMeta() {
|
public getMeta() {
|
||||||
|
if (this.meta == null) {
|
||||||
|
throw new TypeError('meta has not been set');
|
||||||
|
}
|
||||||
|
|
||||||
const rec = this.meta.findOne();
|
const rec = this.meta.findOne();
|
||||||
|
|
||||||
if (rec) {
|
if (rec) {
|
||||||
|
@ -477,6 +529,11 @@ export default class 藍 {
|
||||||
rec[k] = v;
|
rec[k] = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.meta.update(rec);
|
this.meta!.update(rec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME:
|
||||||
|
// JS にコンパイルされたコードでインターフェイスであるはずの藍がインポートされてしまうので、
|
||||||
|
// 同名のクラスを定義することで実行時エラーが出ないようにしている
|
||||||
|
export default class 藍 {}
|
||||||
|
|
|
@ -17,13 +17,8 @@ type Config = {
|
||||||
memoryDir?: string;
|
memoryDir?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
import chalk from 'chalk';
|
|
||||||
import uncheckedConfig from '../config.json' assert { type: 'json' };
|
import uncheckedConfig from '../config.json' assert { type: 'json' };
|
||||||
import { warn } from '@/utils/log.js';
|
import log from '@/utils/log.js';
|
||||||
|
|
||||||
function warnWithPrefix(msg: string): void {
|
|
||||||
warn(`[Config]: ${chalk.red(msg)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Type<T> {
|
class Type<T> {
|
||||||
public static readonly string = new Type<string>('string');
|
public static readonly string = new Type<string>('string');
|
||||||
|
@ -40,44 +35,23 @@ class Type<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OptionalProperty<K extends keyof Config> {
|
function checkProperty<K extends keyof Config>(config: Object, key: K, type: Type<Config[K]>): config is { [J in K]: Config[K] } {
|
||||||
protected readonly key: K;
|
const result = key in config && type.check(config[key as string]);
|
||||||
protected readonly type: Type<Config[K]>
|
if (!result) {
|
||||||
|
log(`config.json: Property ${key}: ${type.name} required`);
|
||||||
public constructor(key: K, type: Type<Config[K]>) {
|
|
||||||
this.key = key;
|
|
||||||
this.type = type;
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
check(config: Object): config is { [J in K]?: Config[K] } {
|
function checkOptionalProperty<K extends keyof Config>(config: Object, key: K, type: Type<Config[K]>): config is { [J in K]?: Config[K] } {
|
||||||
const key = this.key;
|
|
||||||
if (!(key in config)) {
|
if (!(key in config)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const result = this.type.check((config as { [J in K]?: unknown})[key]);
|
const result = type.check(config[key as string]);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
warnWithPrefix(`config.json: The type of property '${key}' must be ${this.type.name}`);
|
log(`config.json: The type of property ${key} must be ${type.name}`);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Property<K extends keyof Config> extends OptionalProperty<K> {
|
|
||||||
check(config: Object): config is { [J in K]: Config[K] } {
|
|
||||||
const result = this.key in config && this.type.check((config as { [J in K]?: unknown })[this.key]);
|
|
||||||
if (!result) {
|
|
||||||
warnWithPrefix(`config.json: Property '${this.key}': ${this.type.name} required`);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Intersection<P extends unknown[]> = P extends [infer Q, ...infer R] ? Q & Intersection<R> : unknown;
|
|
||||||
|
|
||||||
function checkProperties<P extends OptionalProperty<keyof Config>[]>(config: Object, ...properties: P):
|
|
||||||
config is object & Intersection<{ [I in keyof P]: P[I] extends OptionalProperty<infer K> ? { [J in K]: Config[K] } : never }> {
|
|
||||||
// メッセージを表示するためすべてのプロパティをチェックしてから結果を返す
|
|
||||||
return properties.map(p => p.check(config)).every(c => c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setProperty<K extends keyof Config>(config: Object, key: K, value: Config[K]): asserts config is { [L in K]: Config[K] } {
|
function setProperty<K extends keyof Config>(config: Object, key: K, value: Config[K]): asserts config is { [L in K]: Config[K] } {
|
||||||
|
@ -86,25 +60,22 @@ function setProperty<K extends keyof Config>(config: Object, key: K, value: Conf
|
||||||
|
|
||||||
function validate(config: unknown): Config {
|
function validate(config: unknown): Config {
|
||||||
if (!(config instanceof Object)) {
|
if (!(config instanceof Object)) {
|
||||||
warnWithPrefix('config.json: Root object required');
|
log('config.json: Root object required');
|
||||||
} else if (
|
} else if (
|
||||||
checkProperties(
|
checkProperty(config, 'host', Type.string) &&
|
||||||
config,
|
checkOptionalProperty(config, 'serverName', Type.string) &&
|
||||||
new Property('host', Type.string),
|
checkProperty(config, 'i', Type.string) &&
|
||||||
new OptionalProperty('serverName', Type.string),
|
checkOptionalProperty(config, 'master', Type.string) &&
|
||||||
new Property('i', Type.string),
|
checkProperty(config, 'keywordEnabled', Type.boolean) &&
|
||||||
new OptionalProperty('master', Type.string),
|
checkProperty(config, 'reversiEnabled', Type.boolean) &&
|
||||||
new Property('keywordEnabled', Type.boolean),
|
checkProperty(config, 'notingEnabled', Type.boolean) &&
|
||||||
new Property('reversiEnabled', Type.boolean),
|
checkProperty(config, 'chartEnabled', Type.boolean) &&
|
||||||
new Property('notingEnabled', Type.boolean),
|
checkProperty(config, 'serverMonitoring', Type.boolean) &&
|
||||||
new Property('chartEnabled', Type.boolean),
|
checkOptionalProperty(config, 'checkEmojisEnabled', Type.boolean) &&
|
||||||
new Property('serverMonitoring', Type.boolean),
|
checkOptionalProperty(config, 'checkEmojisAtOnce', Type.boolean) &&
|
||||||
new OptionalProperty('checkEmojisEnabled', Type.boolean),
|
checkOptionalProperty(config, 'mecab', Type.string) &&
|
||||||
new OptionalProperty('checkEmojisAtOnce', Type.boolean),
|
checkOptionalProperty(config, 'mecabDic', Type.string) &&
|
||||||
new OptionalProperty('mecab', Type.string),
|
checkOptionalProperty(config, 'memoryDir', Type.string)
|
||||||
new OptionalProperty('mecabDic', Type.string),
|
|
||||||
new OptionalProperty('memoryDir', Type.string)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
setProperty(config, 'wsUrl', config.host.replace('http', 'ws'));
|
setProperty(config, 'wsUrl', config.host.replace('http', 'ws'));
|
||||||
setProperty(config, 'apiUrl', config.host + '/api');
|
setProperty(config, 'apiUrl', config.host + '/api');
|
||||||
|
|
|
@ -5,7 +5,7 @@ import chalk from 'chalk';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
import promiseRetry from 'promise-retry';
|
import promiseRetry from 'promise-retry';
|
||||||
|
|
||||||
import 藍 from './ai.js';
|
import { Ai } from './ai.js';
|
||||||
import config from './config.js';
|
import config from './config.js';
|
||||||
import _log from './utils/log.js';
|
import _log from './utils/log.js';
|
||||||
import pkg from '../package.json' assert { type: 'json' };
|
import pkg from '../package.json' assert { type: 'json' };
|
||||||
|
@ -72,7 +72,7 @@ promiseRetry(retry => {
|
||||||
log('Starting AiOS...');
|
log('Starting AiOS...');
|
||||||
|
|
||||||
// 藍起動
|
// 藍起動
|
||||||
藍.start(account, [
|
new Ai(account, [
|
||||||
new CoreModule(),
|
new CoreModule(),
|
||||||
new EmojiModule(),
|
new EmojiModule(),
|
||||||
new EmojiReactModule(),
|
new EmojiReactModule(),
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
export type Note = {
|
export type Note = {
|
||||||
id: string;
|
id: string;
|
||||||
text: string | null;
|
text: string | null;
|
||||||
cw: string | null;
|
|
||||||
userId: string;
|
|
||||||
reply: any | null;
|
reply: any | null;
|
||||||
poll?: {
|
poll?: {
|
||||||
choices: {
|
choices: {
|
||||||
|
|
|
@ -5,7 +5,12 @@ import config from '@/config.js';
|
||||||
import serifs from '@/serifs.js';
|
import serifs from '@/serifs.js';
|
||||||
import { mecab } from './mecab.js';
|
import { mecab } from './mecab.js';
|
||||||
import 藍 from '@/ai.js';
|
import 藍 from '@/ai.js';
|
||||||
import { Note } from '@/misskey/note.js';
|
|
||||||
|
type LocalTimeline = {
|
||||||
|
userId: string;
|
||||||
|
text: string | null;
|
||||||
|
cw: string | null;
|
||||||
|
}[];
|
||||||
|
|
||||||
function kanaToHira(str: string) {
|
function kanaToHira(str: string) {
|
||||||
return str.replace(/[\u30a1-\u30f6]/g, match => {
|
return str.replace(/[\u30a1-\u30f6]/g, match => {
|
||||||
|
@ -43,11 +48,11 @@ class Installed extends InstalledModule {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async learn() {
|
private async learn() {
|
||||||
const tl = await this.ai.api<Note[]>('notes/local-timeline', {
|
const tl = await this.ai.api<LocalTimeline>('notes/local-timeline', {
|
||||||
limit: 30
|
limit: 30
|
||||||
});
|
});
|
||||||
|
|
||||||
const interestedNotes = tl.filter((note): note is Note & { text: string } =>
|
const interestedNotes = tl.filter(note =>
|
||||||
note.userId !== this.ai.account.id &&
|
note.userId !== this.ai.account.id &&
|
||||||
note.text != null &&
|
note.text != null &&
|
||||||
note.cw == null);
|
note.cw == null);
|
||||||
|
@ -55,7 +60,8 @@ class Installed extends InstalledModule {
|
||||||
let keywords: string[][] = [];
|
let keywords: string[][] = [];
|
||||||
|
|
||||||
for (const note of interestedNotes) {
|
for (const note of interestedNotes) {
|
||||||
const tokens = await mecab(note.text, config.mecab, config.mecabDic);
|
// TODO: note.text に null チェックが必要?
|
||||||
|
const tokens = await mecab(note.text as string, config.mecab, config.mecabDic);
|
||||||
const keywordsInThisNote = tokens.filter(token => token[2] == '固有名詞' && token[8] != null);
|
const keywordsInThisNote = tokens.filter(token => token[2] == '固有名詞' && token[8] != null);
|
||||||
keywords = keywords.concat(keywordsInThisNote);
|
keywords = keywords.concat(keywordsInThisNote);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,9 @@
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
|
|
||||||
export default function(msg: string) {
|
export default function(msg: string) {
|
||||||
console.log(createMessage(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function warn(msg: string) {
|
|
||||||
console.warn(createMessage(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMessage(msg: string) {
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const date = `${zeroPad(now.getHours())}:${zeroPad(now.getMinutes())}:${zeroPad(now.getSeconds())}`;
|
const date = `${zeroPad(now.getHours())}:${zeroPad(now.getMinutes())}:${zeroPad(now.getSeconds())}`;
|
||||||
return `${chalk.gray(date)} ${msg}`;
|
console.log(`${chalk.gray(date)} ${msg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function zeroPad(num: number, length: number = 2): string {
|
function zeroPad(num: number, length: number = 2): string {
|
||||||
|
|
Loading…
Reference in a new issue