-
Notifications
You must be signed in to change notification settings - Fork 1
/
Markov.js
146 lines (134 loc) · 3.98 KB
/
Markov.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
'use strict';
/**
* Markov
* @module Markov
* @author RaceProUK
* @license MIT
*/
const fs = require('fs');
const internals = {
browser: null,
events: null,
configuration: exports.defaultConfig,
isReady: false,
dictionary: null,
corpus: null
};
const privateFns = {
loadCorpus: loadCorpus,
reply: reply
};
/**
* Default plugin configuration
*/
exports.defaultConfig = {
/**
* Corpus location
*
* @default
* @type {string}
*/
corpus: ''
};
/**
* Prepare Plugin prior to login
*
* @param {*} plugConfig Plugin specific configuration
* @param {Config} config Overall bot configuration
* @param {externals.events.SockEvents} events EventEmitter used for the bot
* @param {Browser} browser Web browser for communicating with discourse
*/
exports.prepare = function (plugConfig, config, events, browser) {
if (plugConfig === null || typeof plugConfig !== 'object') {
plugConfig = {};
}
internals.browser = browser;
internals.events = events;
internals.configuration = config.mergeObjects(true, exports.defaultConfig, plugConfig);
events.onNotification('mentioned', reply);
events.onNotification('replied', reply);
events.onNotification('private_message', reply);
setImmediate(loadCorpus); //Could take a while, so decouple corpus loading to allow bot to load quickly
};
/**
* Start the plugin after login
*/
exports.start = function () {};
/**
* Stop the plugin prior to exit or reload
*/
exports.stop = function () {};
/**
* Load the corpus and build the in-memory corpus array and state machine dictionary
*/
function loadCorpus() {
const corpusFile = internals.configuration.corpus;
if (corpusFile === null || typeof corpusFile !== 'string') {
return;
}
fs.readFile(corpusFile, function (err, data) {
if (err) {
internals.events.emit('logError', err);
return;
}
const corpus = data.toString().split(/\s/);
if (corpus.length < 3) {
internals.events.emit('logWarning', 'Corpus must contain at least three words');
return;
}
const dictionary = {};
for (let i = 0; i < corpus.length - 2; i++) {
const key = [corpus[i], corpus[i + 1]],
value = corpus[i + 2];
dictionary[key] = dictionary[key] || [];
dictionary[key].push(value);
}
internals.corpus = corpus;
internals.dictionary = dictionary;
internals.isReady = true;
});
};
/**
* Reply to summons
*
* @param {external.notifications.Notification} notification Notification recieved
* @param {external.topics.Topic} topic Topic trigger post belongs to
* @param {external.posts.CleanedPost} post Post that triggered notification
*/
function reply(notification, topic, post) {
if (!internals.isReady) {
return;
}
const dictionary = internals.dictionary,
corpus = internals.corpus,
length = random(10, 100);
const seed = random(0, corpus.length - 3),
text = [corpus[seed + 1], corpus[seed]];
while (text.length < length) {
const key = [text[1], text[0]];
const candidates = dictionary[key];
if (candidates && candidates.length) {
const index = random(0, candidates.length - 1);
text.unshift(candidates[index]);
} else {
break; //No candidates, so we can't continue
}
}
internals.browser.createPost(topic.id, post.post_number, text.reverse().join(' '), () => 0);
};
/**
* Generate a random number in the specified range.
*
* @param {number} min - The lower bound of the range
* @param {number} max - The upper bound of the range
* @returns {number} The generated number
*/
function random(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
/* istanbul ignore else */
if (typeof GLOBAL.describe === 'function') {
//test is running
exports.internals = internals;
exports.privateFns = privateFns;
}