Failed Conditions
Push — master ( f5e7b3...711045 )
by Yo
01:30
created

Alfred.js ➔ ... ➔ ???   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 10
Bugs 0 Features 0
Metric Value
cc 1
c 10
b 0
f 0
nc 1
nop 0
dl 0
loc 1
rs 10
1
"use strict";
2
3
const sarahClient = require('./client/sarahClient');
4
const pMapNormalizeAnyway = require('./plugins/promise/mapNormalizeAnyway');
5
const pAllAnyway = require('./plugins/promise/allAnyway');
6
const logger = require('./logger');
7
const DecoratorPlugin = require('./model/DecoratorPlugin');
8
const NestedError = require('nested-error-stacks');
9
const ActorPlugin = require('./model/ActorPlugin');
10
11
12
/**
13
 * Represent the bot which can speak and listen to you
14
 */
15
class Alfred {
16
    /**
17
     * @public
18
     *
19
     * @param {string} textToSpeech
20
     *
21
     * @returns {Promise<null|Error>}
22
     */
23
    speak(textToSpeech) {
24
25
        return this.decorateTts(textToSpeech)
26
            .then(textToSpeech => {
27
                if (!textToSpeech) {
28
                    return Promise.resolve();
29
                }
30
31
                this.logger.debug(`Alfred will say "${textToSpeech}"`);
32
33
                return sarahClient({
34
                    tts: textToSpeech,
35
                    sync: true
36
                })
37
                    .then(() => null)
38
                    .catch(error => {
39
                        const newError = new NestedError('Alfred::speak error', error);
40
                        this.logError(newError);
41
42
                        return Promise.reject(newError);
43
                    });
44
            })
45
        ;
46
    }
47
48
    /**
49
     * @public
50
     *
51
     * @returns {Promise<null|Error>}
52
     */
53
    listen() {
54
        return sarahClient({'listen': true})
55
            .then(() => null)
56
            .catch(error => {
57
                const newError = new NestedError('Alfred::listen error', error);
58
                this.logError(newError);
59
60
                const throwError = () => {
61
                    return Promise.reject(newError);
62
                };
63
64
                const tts = 'Impossible de vous écouter';
65
                return this.speak(tts)
66
                    .catch(error => {
0 ignored issues
show
Unused Code introduced by
The parameter error is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
67
                        logger.warning(`skip previous speak("${tts}") error !`);
68
69
                        return throwError();
70
                    })
71
                    .then(throwError);
72
            })
73
        ;
74
    }
75
76
    /**
77
     * @public
78
     *
79
     * @returns {Promise<boolean>} true if action have been taken in account else false
80
     */
81
    stopListening() {
82
        return sarahClient({'listen': false})
83
            .then(() => true)
84
            .catch(error => {
85
                const newError = new NestedError('Alfred::stopListening error', error);
86
                this.logError(newError);
87
88
                const throwError = () => {
89
                    return Promise.reject(newError);
90
                };
91
                const tts = 'je n\'arrive pas à ne rien faire ! J\'ai besoin de votre aide !';
92
93
                return this.speak(tts)
94
                    .catch(error => {
0 ignored issues
show
Unused Code introduced by
The parameter error is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
95
                        logger.warning(`skip previous speak("${tts}") error !`);
96
97
                        return throwError();
98
                    })
99
                    .then(throwError);
100
            })
101
            .then(processed => {
102
                if (processed) {
103
                    return this.speak('A votre service !').then(() => processed);
104
                }
105
106
                return processed;
107
            })
108
        ;
109
    }
110
111
    /**
112
     * @public
113
     * Politely wake up Alfred before asking him something.
114
     *
115
     * He will wake up each of his leprechauns
116
     *
117
     * @return {Promise}
118
     */
119
    init() {
120
        let haveInvalidPlugins = false;
121
        return pAllAnyway(
122
            this.pluginList.map(plugin => plugin.init(this))
123
        )
124
            .then(({resolvedList, rejectedList}) => {
125
                haveInvalidPlugins = rejectedList.length > 0;
126
127
                return this.cleanPluginList(resolvedList.length ? resolvedList.keys() : []);
128
            })
129
            .then(() => this.splitPluginListByRole())
130
            .then(() => {this.initialized = true;})
131
            .then(() => {
132
                if (haveInvalidPlugins) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if haveInvalidPlugins is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
133
                    return this.speak('Certain lutins semble malades. Ils sont au repos pour le moment.');
134
                }
135
            })
136
            .then(() => this.listen())
137
        ;
138
    }
139
140
    /**
141
     * @public
142
     *
143
     * @param {Plugin[]} pluginList
144
     */
145
    constructor(pluginList = []) {
146
        this.logger = logger;
147
        /** @type {Plugin[]} */
148
        this.pluginList = pluginList;
149
        /** @type {DecoratorPlugin[]} */
150
        this.decoratorList = [];
151
        /** @type {ActorPlugin[]} */
152
        this.actorList = [];
153
        /** @type {Plugin[]} */
154
        this.invalidPluginList = [];
155
    }
156
157
    /**
158
     * @private
159
     *
160
     * @param {Promise<string>} textToSpeech
161
     */
162
    decorateTts(textToSpeech) {
163
        return pMapNormalizeAnyway(
164
            textToSpeech,
165
            this.decoratorList
166
                // Return a callback that accept the value to normalize
167
                .map(decorator => decorator.normalizeTts)
168
        )
169
    }
170
171
    /**
172
     * @private
173
     *
174
     * @return {Promise<undefined|Error>}
175
     */
176
    cleanPluginList(validPluginIdList) {
177
        // Override plugins list with only valid ones
178
        const backupPluginList = this.pluginList;
179
        this.pluginList = [];
180
181
        return new Promise((resolve, reject) => {
182
            try {
183
                this.pluginList = validPluginIdList.map(index => {
184
                    return backupPluginList[index];
185
                });
186
                resolve();
187
            } catch (e) {
188
                reject(new NestedError('Error during plugins validity split', error));
0 ignored issues
show
Bug introduced by
The variable error seems to be never declared. If this is a global, consider adding a /** global: error */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
189
            }
190
        });
191
    }
192
193
    /**
194
     * @private
195
     *
196
     * @return {Promise<undefined|Error>}
197
     */
198
    splitPluginListByRole() {
199
        return Promise.all([
200
            () => {
201
                this.decoratorList = this.pluginList
202
                    .map(plugin => plugin instanceof DecoratorPlugin);
203
            },
204
            () => {
205
                this.actorList = this.pluginList
206
                    .map(plugin => plugin instanceof ActorPlugin);
207
            }
208
        ])
209
            .catch(error => Promise.reject(new NestedError('Error during plugins roles split', error)));
210
    }
211
212
    /**
213
     * @private
214
     * @param {Error} error
215
     *
216
     * @returns {Error}
217
     */
218
    logError(error) {
219
        this.logger.error(`Alfred error => ${error.message}`);
220
221
        return error;
222
    }
223
}
224
225
module.exports = Alfred;
226