akala.injectWithNameAsync(['$agent.api/lifttt'], async (lifttt: Client<Connection>) => { var cl = akala.api.jsonrpcws(channel).createClient(lifttt, { executeAction: function (action) { switch (action.name) { case 'log': console.log(action.params.message); break; } }, executeCondition: function (condition) { }, stopTrigger: function (trigger) { }, executeTrigger: async function (trigger) { return null; } }); var server = cl.$proxy(); await server.registerChannel({ name: 'console', view: '@domojs/lifttt/fs.html', icon: 'terminal' }); await server.registerAction({ name: 'log', fields: [{ name: 'message', type: 'string' }] }) })
akala.injectWithNameAsync(['$agent.api/lifttt'], async (lifttt: Client<Connection>) => { var registeredTriggers: { [triggerId: string]: fs.FSWatcher } = {}; var writeFile = promisify(fs.writeFile); var cl = akala.api.jsonrpcws(channel).createClient(lifttt, { executeAction: function (action) { switch (action.name) { case 'write': return writeFile(action.params.file as string, action.params.data); } }, executeCondition: function (condition) { }, stopTrigger: async function (trigger) { if (registeredTriggers[trigger.id]) { registeredTriggers[trigger.id].close(); delete registeredTriggers[trigger.id]; } }, executeTrigger: async function (trigger) { switch (trigger.name) { case 'watch': var stat = await promisify(fs.stat)(trigger.params['path'] as string); if (stat.isDirectory() || stat.isFile()) { var id = uuid(); var watcher = fs.watch(trigger.params['path'] as string, function (event, fileName) { if (!trigger.params['event'] || trigger.params['event'] == event) server.trigger({ id: id, data: { path: fileName, mtime: new Date().toJSON() } }); }); registeredTriggers[id] = watcher; } break; } return null; } }); var server = cl.$proxy(); await server.registerChannel({ name: 'fs', view: '@domojs/lifttt/fs.html', icon: 'file' }); await server.registerTrigger({ name: 'watch', icon: 'file-medical-alt', view: '@domojs/lifttt/fs-watch.html', fields: [{ name: 'path', type: 'string' }] }) await server.registerAction({ name: 'write', icon: 'file-medical-alt', view: '@domojs/lifttt/fs-watch.html', fields: [{ name: 'path', type: 'string' }, { name: 'data', type: 'string' }] }) })
akala.injectWithNameAsync(['$agent.api/media'], function (client: Client<Connection>) { var s = akala.api.jsonrpcws(media.scrapper).createClient(client, { scrap: function (media: media.TVShow | media.Movie) { switch (media.subType) { case 'movie': fs.writeFile(media.path + '.nfo', `<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <movie> <title>${media.name}</title> <sorttitle>${media.displayName}</sorttitle> <thumb aspect="poster">${media.cover}</thumb> </movie>`, 'utf8', function (err) { if (err) akala.logger.error(err); }); break; case 'tvshow': fs.writeFile(media.path + '.nfo', `<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <episodedetails> <title>${media.name}</title> <sorttitle>${media.displayName}</sorttitle> <thumb aspect="poster">${media.cover}</thumb> <season>${media.season}</season> <episode>${media.episode}</episode> </episodedetails>`, 'utf8', function (err) { if (err) akala.logger.error(err); }); break; } return media; } }).$proxy(); s.register({ type: 'video', priority: 20 }); });
akala.injectWithNameAsync(['$agent.api/lifttt'], async (lifttt: Client<Connection>) => { var cl = akala.api.jsonrpcws(channel).createClient(lifttt, { executeAction: function (action) { switch (action.name) { case 'call': var http: akala.Http = akala.resolve('$http'); return http.call(action.params as any).then((response) => { return; }); break; } }, executeCondition: function (condition) { }, stopTrigger: function (trigger) { }, executeTrigger: async function (trigger) { return null; } }); var server = cl.$proxy(); await server.registerChannel({ name: 'http', icon: 'html5' }); await server.registerAction({ name: 'call', fields: [ { name: 'url', type: 'string' }, { name: 'method', type: 'string' }, { name: 'queryString', type: 'string' }, { name: 'body', type: 'string' }, { name: 'headers', type: 'string' }, { name: 'contentType', type: 'string' }, { name: 'type', type: 'string' }, ] }) })
akala.injectWithNameAsync(['$agent.api/zeroconf', '$agent.api/media'], function (zeroconfClient, mediaClient) { var kodis: { [id: string]: PromiseLike<{ Player: KodiPlayerProxy, Playlist: KodiPlaylistProxy }> } = {}; var timers: { [id: string]: NodeJS.Timer } = {}; function startTimer(id: string) { if (timers[id]) return; timers[id] = setInterval(function () { client.status({ target: id }); }, 1000) } function stopTimer(id: string) { if (timers[id]) clearInterval(timers[id]); timers[id] = null; } var client = akala.api.jsonrpcws(media.player).createClient(mediaClient, { mute(p) { }, async status(p) { var kodi = await kodis[p.identity]; var players = await kodi.Player.GetActivePlayers(null) if (players.length > 0) { debug.verbose(players); var player = await kodi.Player.GetProperties({ playerid: players[0].playerid, properties: ['position', 'percentage', 'repeat', 'canseek', 'time', 'totaltime', 'speed'] }); debug.verbose(player); return client.$proxy().status({ identity: p.identity, state: player.speed ? 'playing' : 'paused', position: player.percentage / 100, time: player.time.seconds + 60 * player.time.minutes + 3600 * player.time.hours, length: player.totaltime.seconds + 60 * player.totaltime.minutes + 3600 * player.totaltime.hours, }); } else { client.$proxy().status({ identity: p.identity, state: 'stopped', }); stopTimer(p.identity); } }, async playlist(p: { target: string }) { var kodi = await kodis[p.target]; var players = await kodi.Player.GetActivePlayers(null); if (players.length > 0) { debug.log(players); var properties = await kodi.Player.GetProperties({ playerid: players[0].playerid, properties: ['playlistid', 'speed'] }); if (typeof (properties.playlistid) != 'undefined') { var item: { item: Item & { current?: boolean } } = await kodi.Player.GetItem({ playerid: players[0].playerid, properties: ['title', 'artist', 'albumartist', 'fanart', 'plot', 'season', 'episode', 'thumbnail', 'file', 'art'] }) { akala.extend(item.item, { current: true }); var playlist = await kodi.Playlist.GetItems({ playlistid: properties.playlistid, properties: ['title', 'artist', 'albumartist', 'fanart', 'plot', 'season', 'episode', 'thumbnail', 'file', 'art'] }) debug.verbose(playlist); if (playlist && !playlist.items) playlist.items = [item.item] else playlist.items.unshift(item.item); debug.verbose(playlist.items); if (properties.speed > 0) startTimer(p.target); if (playlist && playlist.items) client.$proxy().playlist({ identity: p.target, playlist: akala.map(playlist.items, function (media: Item & { current?: boolean }) { return { url: media.file.replace(/smb:\/\//, 'file://///'), current: media.current }; }) }); } } } }, async next(p) { var kodi = await kodis[p.target]; var players = await kodi.Player.GetActivePlayers(null); if (players.length > 0) { debug.verbose(players); kodi.Player.GoNext({ playerid: players[0].playerid }) } }, async previous(p) { var kodi = await kodis[p.target]; var players = await kodi.Player.GetActivePlayers(null); if (players.length > 0) { debug.verbose(players); kodi.Player.GoPrevious({ playerid: players[0].playerid }) } }, async pause(p) { var kodi = await kodis[p.target]; var players = await kodi.Player.GetActivePlayers(null); if (players.length > 0) { debug.verbose(players); var status = await kodi.Player.PlayPause({ playerid: players[0].playerid }); if (status.speed) startTimer(p.target); else stopTimer(p.target); } }, async stop(p) { var kodi = await kodis[p.target]; var players = await kodi.Player.GetActivePlayers(null); if (players.length > 0) { debug.verbose(players); await kodi.Player.Stop({ playerid: players[0].playerid }); stopTimer(p.target); } }, async play(p) { var kodi = await kodis[p.target]; var media = p.media; debug.info(media); if (typeof (media) != 'undefined') { if (!media || isNaN(Number(media.path))) { media.path = decodeURIComponent(media.path); media.path = media.path.replace(/file:\/\/\/\/\//, 'smb://'); debug.verbose(media); var result = await kodi.Playlist.GetPlaylists(null); debug.verbose(result); var mediaType = media.id.substring('media:'.length, media.id.indexOf(':', 'media:'.length)); debug.verbose(mediaType); if (result && result.items) { var playlist = akala.grep(result.items, function (e) { return e.type == mediaType; })[0]; if (typeof (playlist) != 'undefined') { await kodi.Playlist.Add({ playlistid: playlist.playlistid, item: { file: media.path } }); startTimer(p.target); } } else { await kodi.Player.Open({ item: { file: media.path } }); startTimer(p.target); } } else { throw new Error('Not implemented') } } else this.pause(); } }); akala.api.jsonrpcws(sd.meta).createClient(zeroconfClient, { add: function (kodiService: KodiService) { kodis[kodiService.id] = new Promise((resolve, reject) => { var kodi = new Client(); kodi.connect('ws://' + kodiService.referer.address + ':' + kodiService.port + '/jsonrpc', function connected(err) { if (err) { debug.error(err); reject(err); } debug.log('connected to ' + kodiService.name); kodi.send('JSONRPC.Introspect', [], async function (error, reply) { if (error) { debug.error(error); reject(error); } else { akala.each(reply['methods'], function (m, i) { var ns = (i as string).split('.'); if (typeof (kodi[ns[0]]) == 'undefined') kodi[ns[0]] = {}; kodi[ns[0]][ns[1]] = function (params) { return new Promise<any>((resolve, reject) => { if (!kodi.isConnected()) { client.$proxy().unregisterPlayer({ identity: kodiService.id, name: kodiService.name }) reject(new Error('Not connected')); return; } akala.each(m.params, function (value) { if (value.required && typeof (params[value.name]) == 'undefined') { throw JSON.stringify(params) + ' is missing the required param ' + value.name + ' for ' + ns[1]; } }); akala.logger.verbose('calling ' + i, JSON.stringify(params)); kodi.send(i as string, params, (error, result) => { if (error) reject(error); else resolve(result); }); }); } }) akala.each(reply['notifications'], function (i, m) { var ns = i.split('.'); if (typeof (kodi[ns[0]]) == 'undefined') kodi[ns[0]] = {}; kodi[ns[0]][ns[1]] = function (callback) { if (typeof (callback) == 'undefined') { throw 'callback is missing for ' + ns[1]; } akala.logger.info('monitoring ' + i, JSON.stringify(i)); kodi.expose(i, callback); } }) debug('kodi client built'); await kodi['JSONRPC'].SetConfiguration({ notifications: { gui: false, system: true, player: true, audiolibrary: false, other: false, videolibrary: false } }); kodi['Player'].OnPause(function () { debug('OnPause') client.status({ target: kodiService.id }); stopTimer(kodiService.id); }); kodi['Player'].OnPlay(function () { debug('OnPlay') client.status({ target: kodiService.id }); client.playlist({ target: kodiService.id }); startTimer(kodiService.id); }); kodi['Player'].OnSeek(function () { debug('OnSeek') client.status({ target: kodiService.id }); }); kodi['Player'].OnPropertyChanged(function () { debug('OnPropertyChanged') client.status({ target: kodiService.id }); }); kodi['Player'].OnStop(function () { debug('OnStop') client.status({ target: kodiService.id }); stopTimer(kodiService.id); }); resolve(kodi as any); } }); }); }) }, delete(service: KodiService) { client.$proxy().unregisterPlayer({ identity: service.id, name: service.name }); } }).$proxy().notify({ type: 'xmbc-jsonrpc' }); });
akala.injectWithNameAsync(['$agent.api/lifttt', '$worker'], function (client: Client<Connection>, worker: EventEmitter) { var recipes: { [name: string]: Recipe & { triggerId?: string } } = {}; var triggerMap: { [id: string]: Recipe } = {}; var init: boolean; var recipeFile = path.resolve(process.cwd(), '../../../recipes.json'); exists(recipeFile).then(async (exists) => { if (exists) { logger.verbose(recipeFile + ' exists') var recipeStore: { [id: string]: Recipe } = JSON.parse(await readFile(recipeFile, { encoding: 'utf8' })); logger.verbose(recipeStore); init = true; worker.on('ready', function () { setTimeout(function () { logger.verbose('initializing recipes') akala.eachAsync(recipeStore, async function (recipe, name, next) { delete recipe.triggerId; try { await cl.insert(recipe, init); } catch { setTimeout(cl.insert, 60000, recipe, true); } next(); }, function () { init = false; }); }, 30000) }) } else logger.info(recipeFile + ' does not exist'); }); var server = akala.api.jsonrpcws(organizer).createServerProxy(client); var cl = akala.api.jsonrpcws(organizer).createClient(client, { trigger: async (param) => { var triggerData = akala.extend({ $date: new Date() }, param.data); var conditionsData: PayloadDataType = null; akala.logger.verbose(`trigger ${param.id} received`); if (triggerMap[param.id].condition) { var result = interpolate(triggerMap[param.id].condition.params, triggerData); conditionsData = await server.executeCondition({ name: triggerMap[param.id].condition.name, channel: triggerMap[param.id].condition.channel, params: { $triggerData: triggerData, ...result } }); } akala.logger.verbose(`interpolating: ${JSON.stringify(triggerMap[param.id].action.params)}`); akala.logger.verbose(`triggerData: ${JSON.stringify(triggerData)}`); akala.logger.verbose(`conditionsData: ${JSON.stringify(conditionsData)}`); await server.executeAction({ name: triggerMap[param.id].action.name, channel: triggerMap[param.id].action.channel, params: interpolate(triggerMap[param.id].action.params, { $triggerData: triggerData, $conditionsData: conditionsData }) }); }, async update(param) { if (!(param.name in recipes)) return Promise.reject({ status: 404, message: 'recipe does not exist' }); if (recipes[param.recipe.name].triggerId) { await server.stopTrigger({ id: recipes[param.recipe.name].triggerId }); delete triggerMap[recipes[param.recipe.name].triggerId]; } if (param.name != param.recipe.name) { delete recipes[param.name]; param.recipe.name = param.name; } recipes[param.name] = param.recipe; await writeFile(recipeFile, JSON.stringify(recipes)); recipes[param.recipe.name].triggerId = await server.executeTrigger(param.recipe.trigger); triggerMap[recipes[param.recipe.name].triggerId] = param.recipe; }, async insert(recipe, init?: boolean) { if (recipe.name in recipes) return Promise.reject({ status: 403, message: 'recipe already exists' }); recipes[recipe.name] = recipe; if (!init) await writeFile(recipeFile, JSON.stringify(recipes)); logger.verbose(`requesting trigger ${recipe.trigger}`); recipes[recipe.name].triggerId = await server.executeTrigger(recipe.trigger); triggerMap[recipes[recipe.name].triggerId] = recipe; }, get(param) { return recipes[param.name]; }, list() { return akala.map(recipes, function (recipe) { return recipe; }, true); } }); server.registerOrganizer({ id:os.hostname() }); });
akala.injectWithName(['$worker'], function (worker) { function getMainDevice(name) { var indexOfDot = name.indexOf('.'); if (indexOfDot > 0) var mainDevice = name.substr(0, indexOfDot); else var mainDevice = name; return deviceCollection[mainDevice]; } var client = akala.api.jsonrpcws(devices.deviceType).createClient(c, { exec: function (p) { var cmd = p.command; var mainDevice = getMainDevice(p.device); if (p.device != mainDevice.name) { switch (p.device.substring(mainDevice.name.length + 1)) { case 'power': if (p.command == 'off') cmd = 'PF' else cmd = 'PO' break; case 'mute': if (p.command == 'off') cmd = 'MF'; else cmd = 'MO'; break; case 'volume': switch (p.command) { case 'up': cmd = 'VU'; break; case 'down': cmd = 'VD'; break; case 'set': cmd = 'VL'; break; } break; case 'input': switch (p.command) { case 'Game': cmd = '49FN'; break; case 'Dvd': cmd = '04FN'; break; case 'Sat/Cbl': cmd = '06FN'; break; case 'Dvr/Bdr': cmd = '15FN'; break; case 'iPod': cmd = '17FN'; break; case 'Video': cmd = '10FN'; break; case 'BD': cmd = '25FN'; break; } break; } } return api.send(cmd, mainDevice['address']).then((result) => undefined); }, getStatus: function (device) { var mainDevice = getMainDevice(device.device); if (mainDevice.name == device.device) return Promise.resolve(mainDevice.status()); else return Promise.resolve(mainDevice.subdevices[device.device.substring(mainDevice.name.length + 1)].status()); }, save: (p) => { if (p.device.name.indexOf('.') > -1) return p.device; deviceCollection[p.device.name] = p.device; p.device['address'] = p.body.IP || p.device.name; p.device.statusMethod = 'pull'; p.device.subdevices = [ { name: "power", type: 'pioneer', category: 'switch', classes: ['power', 'switch'], statusMethod: 'pull', status: function () { return api.send('?P', p.device.name).then(function (result) { console.log('result:' + result); if (result) result = result.trim(); return { state: result == 'PWR0', color: result == 'PWR0' ? 'green' : 'red' }; }); }, commands: ['on', 'off'] }, { name: "mute", type: 'pioneer', category: 'switch', statusMethod: 'pull', status: function () { console.log('mute status'); return api.send('?M', p.device.name).then((result) => { console.log(result); return { state: result == 'MUT0', color: result == 'MUT0' ? 'green' : 'red' }; }); }, commands: ['on', 'off'] }, { name: "volume", type: 'pioneer', category: 'input', statusMethod: 'pull', status: function () { var status = {}; return api.send('?V', p.device.name).then(function (result) { return { state: Number(/\d+/.exec(result)) * 100 / 185 }; }); }, commands: ['up', 'down', 'set'] }, { name: "input", type: 'pioneer', category: 'values', statusMethod: 'pull', status: function () { return api.send('?FN', p.device.name).then(function (result) { console.log(result); switch (Number(/[0-9]+/.exec(result))) { case 49: return { state: 'Game' }; case 25: return { state: 'BD' }; case 4: return { state: 'Dvd' }; case 6: return { state: 'Sat/Cbl' }; case 15: return { state: 'Dvr/Bdr' }; case 17: return { state: 'iPod' }; case 10: return { state: 'Video' }; default: return null; } }); }, commands: ['Game', 'Dvd', 'Sat/Cbl', 'Dvr/Bdr', 'iPod', 'Video', 'BD'] } ] return p.device; } }); worker.on('ready', function () { client.$proxy().register({ commandMode: 'dynamic', name: 'pioneer', view: '/@domojs/pioneer/device.html' }); }); })();