diff --git a/docs/scripting.md b/docs/scripting.md index b9432264c..964ce77db 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -623,6 +623,21 @@ robot.respond /sleep it off/i, (res) -> res.reply 'zzzzz' ``` +The datastore also allows setting and getting values which are scoped to individual users: + +```coffeescript +module.exports = (robot) -> + + robot.respond /who is @?([\w .\-]+)\?*$/i, (res) -> + name = res.match[1].trim() + + users = robot.brain.usersForFuzzyName(name) + if users.length is 1 + user = users[0] + user.get('roles').then (roles) -> + res.send "#{name} is #{roles.join(', ')}" +``` + ## Script Loading There are three main sources to load scripts from: diff --git a/src/brain.js b/src/brain.js index 7e7b14363..b0694a975 100644 --- a/src/brain.js +++ b/src/brain.js @@ -10,7 +10,7 @@ const User = require('./user') // 2. If the original object was a User object, the original object // 3. If the original object was a plain JavaScript object, return // a User object with all of the original object's properties. -let reconstructUserIfNecessary = function (user) { +let reconstructUserIfNecessary = function (user, robot) { if (!user) { return null } @@ -20,6 +20,9 @@ let reconstructUserIfNecessary = function (user) { delete user.id // Use the old user as the "options" object, // populating the new user with its values. + // Also add the `robot` field so it gets a reference. + user.robot = robot + return new User(id, user) } else { return user @@ -36,6 +39,7 @@ class Brain extends EventEmitter { users: {}, _private: {} } + this.robot = robot this.autoSave = true @@ -142,7 +146,7 @@ class Brain extends EventEmitter { if (data && data.users) { for (let k in data.users) { let user = this.data.users[k] - this.data.users[k] = reconstructUserIfNecessary(user) + this.data.users[k] = reconstructUserIfNecessary(user, this.robot) } } @@ -161,6 +165,10 @@ class Brain extends EventEmitter { // Returns a User instance of the specified user. userForId (id, options) { let user = this.data.users[id] + if (!options) { + options = {} + } + options.robot = this.robot if (!user) { user = new User(id, options) diff --git a/src/user.js b/src/user.js index d071f2dd7..877fd8ed4 100644 --- a/src/user.js +++ b/src/user.js @@ -1,5 +1,7 @@ 'use strict' +const DataStoreUnavailable = require('./datastore').DataStoreUnavailable + class User { // Represents a participating user in the chat. // @@ -12,6 +14,17 @@ class User { options = {} } + // Define a getter method so we don't actually store the + // robot itself on the user object, preventing it from + // being serialized into the brain. + if (options.robot) { + let robot = options.robot + delete options.robot + this._getRobot = function () { return robot } + } else { + this._getRobot = function () { } + } + Object.keys(options).forEach((key) => { this[key] = options[key] }) @@ -20,6 +33,33 @@ class User { this.name = this.id.toString() } } + + set (key, value) { + this._checkDatastoreAvailable() + return this._getDatastore()._set(this._constructKey(key), value, 'users') + } + + get (key) { + this._checkDatastoreAvailable() + return this._getDatastore()._get(this._constructKey(key), 'users') + } + + _constructKey (key) { + return `${this.id}+${key}` + } + + _checkDatastoreAvailable () { + if (!this._getDatastore()) { + throw new DataStoreUnavailable('datastore is not initialized') + } + } + + _getDatastore () { + let robot = this._getRobot() + if (robot) { + return robot.datastore + } + } } module.exports = User diff --git a/test/datastore_test.js b/test/datastore_test.js index 2cb1ae5b8..036fb8626 100644 --- a/test/datastore_test.js +++ b/test/datastore_test.js @@ -113,4 +113,38 @@ describe('Datastore', function () { }) }) }) + + describe('User scope', function () { + it('has access to the robot object', function () { + let user = this.robot.brain.userForId('1') + expect(user._getRobot()).to.equal(this.robot) + }) + + it('can store user data which is separate from global data', function () { + let user = this.robot.brain.userForId('1') + return user.set('blah', 'blah').then(() => { + return user.get('blah').then((userBlah) => { + return this.robot.datastore.get('blah').then((datastoreBlah) => { + expect(userBlah).to.not.equal(datastoreBlah) + expect(userBlah).to.equal('blah') + expect(datastoreBlah).to.be.an('undefined') + }) + }) + }) + }) + + it('stores user data separate per-user', function () { + let userOne = this.robot.brain.userForId('1') + let userTwo = this.robot.brain.userForId('2') + return userOne.set('blah', 'blah').then(() => { + return userOne.get('blah').then((valueOne) => { + return userTwo.get('blah').then((valueTwo) => { + expect(valueOne).to.not.equal(valueTwo) + expect(valueOne).to.equal('blah') + expect(valueTwo).to.be.an('undefined') + }) + }) + }) + }) + }) })