diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..38e3d72 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,10 @@ +env: + browser: true + commonjs: true + es6: true +extends: 'eslint:recommended' +globals: + registerPlugin: readonly +parserOptions: + ecmaVersion: 2018 +rules: {} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..a5f23f8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.js eol=lf \ No newline at end of file diff --git a/.gitignore b/.gitignore index b512c09..ad13fa8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +node_modules +default-attached/command.js \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eaa84a0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2019 Michael Friese, Max Schmitt, Jonas Bögle + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 11407f9..d742bd8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# sinusbot scripts +# SinusBot scripts -This repository contains a few scripts and snippets which demonstrate how the scripting engine of the [sinusbot](https://sinusbot.com) works. Scripts written in JavaScript. +This repository contains a few scripts and snippets which demonstrates how the scripting engine of the [sinusbot](https://sinusbot.com) works. Scripts are written in JavaScript. -The new scripting engine is documentated [here](https://www.sinusbot.com/docs/scripting/) and more details can be found in the [wiki](https://wiki.sinusbot.com/en:guides:features:scripts). +The new scripting engine is documentated [here](https://sinusbot.github.io/scripting-docs) and more details can be found in the [docs](https://sinusbot.github.io/docs). diff --git a/ported/README.md b/default-attached/README.md similarity index 72% rename from ported/README.md rename to default-attached/README.md index fef258e..c6c651d 100644 --- a/ported/README.md +++ b/default-attached/README.md @@ -1 +1 @@ -### Default attached scripts of the SinusBot which are ported to the script engine v2 \ No newline at end of file +### Default attached scripts of the SinusBot which are ported to the v8 scripting engine \ No newline at end of file diff --git a/default-attached/advertising.js b/default-attached/advertising.js new file mode 100644 index 0000000..a77bb00 --- /dev/null +++ b/default-attached/advertising.js @@ -0,0 +1,74 @@ +registerPlugin({ + name: 'Advertising (Text)', + version: '3.0.0', + backends: ['ts3'], + description: 'This script will announce one of the configured lines every x seconds.', + author: 'SinusBot Team', // Michael Friese, Max Schmitt, Jonas Bögle + vars: [{ + name: 'ads', + title: 'Ads (supports bbcode)', + type: 'multiline', + placeholder: 'Welcome to the best TS3-Server!' + }, { + name: 'interval', + title: 'Interval (in seconds)', + type: 'number', + placeholder: '5', + default: 5 + }, { + name: 'order', + title: 'Order', + type: 'select', + options: [ + 'line by line (default)', + 'random' + ], + default: '0' + }, { + name: 'type', + title: 'Broadcast-Type', + type: 'select', + options: [ + 'Channel', + 'Server' + ], + default: '0' + }] +}, (_, {ads, order, type, interval}) => { + const backend = require('backend') + const engine = require('engine') + + ads = ads ? ads.split('\n').map(line => line.trim().replace(/\r/g, '')) : [] + + if (ads.length === 0) { + engine.log('There are no ads configured.') + return + } + if (interval <= 3) { + engine.log('The interval is too small, use a value bigger than 3 seconds.') + return + } + + const RANDOM = '1'; + const SERVER = '1'; + + let index = -1 + + setInterval(() => { + switch (order) { + case RANDOM: + index = Math.floor(Math.random() * ads.length) + break + default: + index = (++index % ads.length) + } + + switch (type) { + case SERVER: + backend.chat(ads[index]) + break + default: + backend.getCurrentChannel().chat(ads[index]) + } + }, interval * 1000) +}) \ No newline at end of file diff --git a/default-attached/alonemode.js b/default-attached/alonemode.js new file mode 100644 index 0000000..f50f39a --- /dev/null +++ b/default-attached/alonemode.js @@ -0,0 +1,62 @@ +registerPlugin({ + name: 'AloneMode', + version: '3.2.0', + backends: ['ts3', 'discord'], + description: 'This script will save CPU and bandwidth by stopping or muting the bot when nobody is listening anyways.', + author: 'SinusBot Team', // Michael Friese, Max Schmitt, Jonas Bögle, Fabian "fabm3n" + vars: [{ + name: 'mode', + title: 'Mode', + type: 'select', + options: [ + 'mute only', + 'stop playback' + ] + }] +}, (_, {mode}) => { + const engine = require('engine') + const backend = require('backend') + const event = require('event') + const audio = require('audio') + const media = require('media') + + const MUTE_ONLY = '0' + + let isMuted = false + let lastPosition = 0 + let lastTrack + + audio.setMute(false) + + event.on('clientMove', () => { + let currentChannel = backend.getCurrentChannel() + let clients = currentChannel ? currentChannel.getClientCount() : 0 + + if (clients > 1 && isMuted) { + isMuted = false + engine.log('Ending AloneMode...') + + if (mode == MUTE_ONLY) { + audio.setMute(false) + } else { + if (lastTrack) { + lastTrack.play() + audio.seek(lastPosition) + engine.log(`Seeking to ${lastPosition} of track '${lastTrack.title()}'`) + } + } + } else if (clients <= 1 && audio.isPlaying() && isMuted == false) { + isMuted = true + engine.log('Starting AloneMode...') + + if (mode == MUTE_ONLY) { + audio.setMute(true) + } else { + lastPosition = audio.getTrackPosition() + lastTrack = media.getCurrentTrack() + engine.log(`Position ${lastPosition} saved for track '${lastTrack.title()}'`) + media.stop() + } + } + }) +}) diff --git a/default-attached/bookmark.js b/default-attached/bookmark.js new file mode 100644 index 0000000..60e047d --- /dev/null +++ b/default-attached/bookmark.js @@ -0,0 +1,51 @@ +registerPlugin({ + name: 'Bookmarks!', + version: '3.0.0', + backends: ['ts3', 'discord'], + description: 'Enter .bookmark to save the current position, enter .resume to seek to the bookmarked position.', + author: 'SinusBot Team', // Michael Friese, Max Schmitt, Jonas Bögle + vars: [] +}, () => { + const store = require('store') + const media = require('media') + const audio = require('audio') + const event = require('event') + + event.on('load', () => { + //try to load the library + const Command = require('command') + //check if the library has been loaded successfully + if (!Command) throw new Error('Command.js library not found! Please download Command.js and enable it to be able use this script!') + + Command.createCommand('bookmark') + .help('saves the current position') + .manual('saves the current position') + .manual('can be resumed by the \'resume\' command. (Seeks to the bookmarked position of the track)') + .exec((client, args, reply) => { + const track = media.getCurrentTrack() + if (!track) { + return + } + const pos = audio.getTrackPosition() + store.set(track.id(), pos) + reply(`Position saved for track '${track.title()}' at ${pos} ms`) + }) + + Command.createCommand('resume') + .help('resumes to the bookmarked position') + .manual('resumes to the bookmarked position (use bookmark command to set)') + .exec((client, args, reply) => { + const track = media.getCurrentTrack() + if (!track) { + return + } + const pos = store.get(track.id()) + if (!pos) { + reply('No position found, sorry.') + return + } + audio.seek(pos) + reply(`Resumed at ${pos} ms of track '${track.title()}'`) + }) + }) +}) \ No newline at end of file diff --git a/default-attached/followme.js b/default-attached/followme.js new file mode 100644 index 0000000..d238245 --- /dev/null +++ b/default-attached/followme.js @@ -0,0 +1,28 @@ +registerPlugin({ + name: 'Follow Me', + version: '3.0.0', + backends: ['ts3', 'discord'], + description: 'The bot will follow the movements of any of the clients given', + author: 'SinusBot Team', // Michael Friese, Max Schmitt, Jonas Bögle + vars: [{ + name: 'clientUids', + title: 'Comma-separated list of client-UIDs that the bot should follow', + type: 'string' + }] +}, (_, {clientUids}) => { + const engine = require('engine') + const backend = require('backend') + const event = require('event') + + if (!clientUids) { + engine.log('No client-UIDs set.') + return + } + + const uids = clientUids.trim().split(',') + event.on('clientMove', ({ client, toChannel }) => { + if (toChannel && uids.includes(client.uid())) { + backend.getBotClient().moveTo(toChannel) + } + }) +}) \ No newline at end of file diff --git a/default-attached/norecording.js b/default-attached/norecording.js new file mode 100644 index 0000000..3c6e587 --- /dev/null +++ b/default-attached/norecording.js @@ -0,0 +1,21 @@ +registerPlugin({ + name: 'No Recording!', + version: '3.0.0', + backends: ['ts3'], + description: 'This script will kick anyone who attempts to record.', + author: 'SinusBot Team', // Michael Friese, Max Schmitt, Jonas Bögle + vars: [{ + name: 'kickMessage', + title: 'The optional kick message.', + type: 'string', + placeholder: 'No recording on our server!' + }] +}, (_, config) => { + const event = require('event') + + const kickMessage = config.kickMessage || 'No recording on our server!' + + event.on('clientRecord', client => { + client.kickFromServer(kickMessage) + }) +}) \ No newline at end of file diff --git a/default-attached/rememberChannel.js b/default-attached/rememberChannel.js new file mode 100644 index 0000000..0060282 --- /dev/null +++ b/default-attached/rememberChannel.js @@ -0,0 +1,17 @@ +registerPlugin({ + name: 'Remember Last Channel', + version: '3.0.0', + backends: ['ts3', 'discord'], + description: 'This script will remember, which channel the bot was last moved to and will set it as default channel on join.', + author: 'SinusBot Team', // Michael Friese, Max Schmitt, Jonas Bögle + vars: [] +}, () => { + const event = require('event') + const engine = require('engine') + + event.on('clientMove', ({ client, toChannel }) => { + if (toChannel && client.isSelf()) { + engine.setDefaultChannelID(toChannel.id()) + } + }) +}) \ No newline at end of file diff --git a/default-attached/sinusbot-commands.js b/default-attached/sinusbot-commands.js new file mode 100644 index 0000000..c7fd451 --- /dev/null +++ b/default-attached/sinusbot-commands.js @@ -0,0 +1,1589 @@ +/** + * @author Jonas Bögle + * @license MIT + * + * MIT License + * + * Copyright (c) 2019-2020 Jonas Bögle, Michael Friese + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Thanks to the following GitHub sponsors for supporting my work: + * - Michael Friese + * - Jay Vasallo + * + * https://github.com/sponsors/irgendwr + * + */ +registerPlugin({ + name: 'SinusBot Commands', + version: '1.1.2', + description: 'Enables the default commands.', + author: 'Jonas Bögle (@irgendwr)', + engine: '>= 1.0.0', + backends: ['ts3', 'discord'], + // the next line is not required since beta.7 + //requiredModules: ['discord-dangerous'], + autorun: true, + vars: [ + { + /* + * Note: Normally you should **not** add something like this, + * because you can already disable scripts by unchecking them. + * However in this case it makes sense as `autorun: true` + * no longer allows users to disable it otherwise. + */ + name: 'disable', + title: 'Disable the default SinusBot commands.', + type: 'checkbox', + default: false + }, + { + name: 'createSuccessReaction', + title: 'Add a reaction to each command if it was successfull.', + type: 'checkbox', + default: false, + /* conditions: [ + { field: 'disable', value: false } + ], */ // conditions checking for "false" are currently buggy. @flyth needs to fix this. + }, + { + name: 'discord', + title: 'Show discord settings', + type: 'checkbox', + default: true, + /* conditions: [ + { field: 'disable', value: false } + ], */ // conditions checking for "false" are currently buggy. @flyth needs to fix this. + }, + { + name: 'url', + title: 'URL to Webinterface (optional, for album covers in discord)', + type: 'string', + placeholder: 'i.e. https://sinusbot.example.com', + conditions: [ + { field: 'discord', value: true }, + /*{ field: 'disable', value: false }*/ + ], // conditions checking for "false" are currently buggy. @flyth needs to fix this. + }, + { + name: 'songInStatus', + title: 'Show playing song in status.', + type: 'checkbox', + default: true, + conditions: [ + { field: 'discord', value: true } + ], + }, + { + name: 'deleteOldMessages', + title: 'Delete previous responses if !playing command is used again', + type: 'checkbox', + default: true, + conditions: [ + { field: 'discord', value: true }, + /*{ field: 'disable', value: false }*/ + ], // conditions checking for "false" are currently buggy. @flyth needs to fix this. + } + ] +}, (_, config, meta) => { + const event = require('event') + const engine = require('engine') + const backend = require('backend') + const format = require('format') + const audio = require('audio') + const media = require('media') + + engine.log(`Loaded ${meta.name} v${meta.version} by ${meta.author}.`) + engine.log(`SinusBot v${engine.version()} on ${engine.os()}`) + + /********* privileges *********/ + const ENQUEUE = 1 << 13; + const SKIP_QUEUE = 1 << 14; + const ADMIN_QUEUE = 1 << 15; + const PLAYBACK = 1 << 12; + const START_STOP = 1 << 8; + const EDIT_BOT_SETTINGS = 1 << 16; + const LOGIN = 1 << 0; + const UPLOAD_FILES = 1 << 2; + const DELETE_FILES = 1 << 3; + const EDIT_FILES = 1 << 4; + const CREATE_AND_DELETE_PLAYLISTS = 1 << 5; + const EDIT_PLAYLISTS = 1 << 7; + const EDIT_INSTANCES = 1 << 17; + const EDIT_USERS = 1 << 9; + + const ERROR_PREFIX = '❌ '; + const WARNING_PREFIX = '⚠ '; + const SUCCESS_PREFIX = '✔ '; + const USAGE_PREFIX = ERROR_PREFIX + 'Usage: '; + + const sinusbotURL = config.url; + const REACTION_PREV = '⏮'; + const REACTION_PLAYPAUSE = '⏯'; + const REACTION_NEXT = '⏭'; + const REACTION_SUCCESS = '✅'; + const HIDE_REACTIONS_IF_PRIVATE = false; + + const PATTERN_URL = /^https?:\/\/\S+/i; + const PATTERN_YT_DOMAIN = /^https?:\/\/(?:www\.)?(?:youtube\.com|youtu\.be)\//i; + + // for join/leave + const ERROR_BOT_NULL = ERROR_PREFIX+'Unable to change channel.\nTry to set a *Default Channel* in the webinterface and click save.' + + /** @type {object[]} */ + let lastEmbeds = []; + + if (config.discord && engine.getBackend() != 'discord') { + // hide discord-only settings if backend is not discord + config.discord = false; + engine.saveConfig(config); + } else if (!config.discord && engine.getBackend() == 'discord') { + // show discord-only settings if backend is discord + config.discord = true; + engine.saveConfig(config); + } + + const ytCallbacks = {}; + event.on("ytdl.success", ev => { + engine.log(`Downloaded YouTube Video: ${ev.url}`); + const jobId = ev.jobId; + if (typeof ytCallbacks[jobId] === 'function') { + ytCallbacks[jobId](ev); + delete ytCallbacks[jobId]; + } + }); + + event.on("ytdl.error", (ev, message) => { + engine.log(`Error while downloading YouTube Video: ${ev.url}; ${message}`); + if (message.startsWith("exit status")) { + engine.log('Please see "Upload" page in web-interface for more details and read the documentation for troubleshooting advice: https://sinusbot.github.io/docs/youtube-dl/'); + } + + const jobId = ev.jobId; + if (typeof ytCallbacks[jobId] === 'function') { + ytCallbacks[jobId](ev, message); + delete ytCallbacks[jobId]; + } + }); + + /** + * Registers a callback for success/error events of a given jobId. + * @param {string} jobId Job ID + * @param {(ev: {url: string, jobId: string, trackId?: string}, message: string) => void} callback Callback + */ + function ytCallback(jobId, callback) { + ytCallbacks[jobId] = callback; + } + + /** + * + * @param {string} jobId Job ID + * @param {MessageEvent} ev Event + * @param {(msg: string) => void} reply + */ + function handleYT(jobId, ev, reply) { + ytCallback(jobId, (ytev, err) => { + if (err) { + if (err.startsWith("exit status")) { + reply(ERROR_PREFIX + `Error: ${err}; Please see "Upload" page in web-interface for more details.`); + } else { + reply(ERROR_PREFIX + `Error: ${err}`); + } + return; + } + successReaction(ev, reply); + }); + } + + if (config.disable) { + engine.log('SinusBot commands are DISABLED.'); + } else { // BEGIN COMMANDS-ENABLED + event.on('load', () => { + const command = require('command'); + if (!command) { + engine.log('command.js library not found! Please download command.js to your scripts folder and restart the SinusBot, otherwise this script will not work.'); + engine.log('command.js can be found here: https://github.com/Multivit4min/Sinusbot-Command/blob/master/command.js'); + return; + } + const {createCommand} = command; + + createCommand('register') + .addArgument(args => args.string.setName('username')) + .help('Register a new user') + .manual('Registers a new user bound to the Account you are using. This account has no privileges by default but can be edited by the bot administrators.') + .exec((client, args, reply, ev) => { + if (!engine.registrationEnabled()) { + reply('Registration is disabled.'); + return; + } + + // print syntax if no username given + if (!args.username) { + reply(USAGE_PREFIX + 'register '); + return; + } + + if (engine.getUserByName(args.username)) { + reply(ERROR_PREFIX + 'This username already exists.'); + return; + } + + // check if client already has a user + let user = getUserByUid(client.uid()); + if (user) { + reply(ERROR_PREFIX + `You already have a user with the name "${user.name()}".`); + return; + } + + // create user + let newUser = engine.addUser(args.username); + if (!newUser) { + reply(ERROR_PREFIX + 'Unable to create user, try another username.'); + return; + } + + if (!newUser.setUid(client.uid())) { + newUser.delete() + reply(ERROR_PREFIX + 'Unable to assign uid to user.'); + return; + } + reply(SUCCESS_PREFIX + 'Registered a user with the given name.\nThis account has no privileges by default but can be edited by the bot administrators.'); + successReaction(ev, reply); + }); + + createCommand('password') + .alias('pass') + .addArgument(args => args.rest.setName('value')) + .help('Change your password') + .manual('Changes your password to .') + .checkPermission(client => getUserByUid(client.uid())) + .exec((client, args, reply, ev) => { + // print syntax if no value given + if (!args.value) { + reply(USAGE_PREFIX + 'password \n'+ WARNING_PREFIX + 'Don\'t use this command in a public channel.'); + return; + } + + if (ev.mode !== 1) { + reply(WARNING_PREFIX + 'Don\'t use this command in a public channel.'); + return; + } + + let user = getUserByUid(client.uid()); + if (!user) { + reply(ERROR_PREFIX + `You don't have a user-account. Use ${format.bold('!register')} to create one.`); + return; + } + + // set password + if (!user.setPassword(args.value)) { + reply(ERROR_PREFIX + 'Unable to set password.'); + return; + } + reply(SUCCESS_PREFIX + 'Changed your password.'); + successReaction(ev, reply); + }); + + createCommand('whoami') + .help('Show user identities') + .manual('Shows user identities matching your ID/groups.') + .exec((client, args, reply, ev) => { + let users = getUsersByClient(client); + if (users && users.length != 0) { + const usersStr = users.map(user => user.name()).join(", "); + if (users.length == 1) { + reply(`You match the following user: ${usersStr}.`); + } else { + reply(`You match the following ${users.length} users: ${usersStr}.`); + } + } else { + reply("You don't match any users."); + } + successReaction(ev, reply); + }); + + createCommand('privileges') + .alias('priv', 'privs') + .help('Show user privileges') + .manual('Shows user privileges.') + .exec((client, args, reply, ev) => { + let privs = [ + { n: ENQUEUE, s: 'Enqueue' }, + { n: SKIP_QUEUE, s: 'Skip Queue' }, + { n: ADMIN_QUEUE, s: 'Admin Queue' }, + { n: PLAYBACK, s: 'Playback' }, + { n: START_STOP, s: 'Start/Stop' }, + { n: EDIT_BOT_SETTINGS, s: 'Edit Bot Settings' }, + { n: LOGIN, s: 'Login' }, + { n: UPLOAD_FILES, s: 'Upload Files' }, + { n: DELETE_FILES, s: 'Delete Files' }, + { n: EDIT_FILES, s: 'Edit Files' }, + { n: CREATE_AND_DELETE_PLAYLISTS, s: 'Create/Delete Playlists' }, + { n: EDIT_PLAYLISTS, s: 'Edit Playlists' }, + { n: EDIT_INSTANCES, s: 'Edit Instances' }, + { n: EDIT_USERS, s: 'Edit Users' }, + ]; + let uprivs = []; + privs.forEach(p => { + if (requirePrivileges(p.n)(client)) { + uprivs.push(p.s); + } + }) + + if (uprivs.length >= 1) { + reply(`You have the following privileges: ${uprivs.join(", ")}.`); + } else { + reply(`You have no privileges.`); + } + //reply(getUsersByClient(client).map(u => u.privileges().toString(2)).join(", ")); + + successReaction(ev, reply); + }); + + createCommand('playing') + .help('Show what\'s currently playing') + .manual('Show what\'s currently playing') + .exec((client, args, reply, ev) => { + if (engine.getBackend() !== 'discord' || !ev.message) { + if (!audio.isPlaying()) { + successReaction(ev, reply); + return reply('There is nothing playing at the moment.'); + } + + reply(formatTrack(media.getCurrentTrack())); + successReaction(ev, reply); + return; + } + + let msg = getPlayingEmbed(); + if (!audio.isPlaying()) { + msg.content = 'There is nothing playing at the moment.'; + } + + backend.extended().createMessage(ev.message.channelID(), msg, (err, res) => { + if (err) return engine.log(err); + if (!res) return engine.log('Error: empty response'); + + const {id, channel_id} = JSON.parse(res); + + // messages that should be deleted + let deleteMsg = []; + const msgId = ev.message ? ev.message.ID() : null; + const index = lastEmbeds.findIndex(embed => embed.channelId == channel_id); + if (index !== -1) { + if (config.deleteOldMessages) { + // delete previous embed + deleteMsg.push(lastEmbeds[index].messageId); + // delete previous command from user + if (lastEmbeds[index].messageId) { + deleteMsg.push(lastEmbeds[index].invokeMessageId); + } + } + // save new embed + lastEmbeds[index].messageId = id; + lastEmbeds[index].invokeMessageId = msgId; + } else { + // save new embed + lastEmbeds.push({ + channelId: channel_id, + messageId: id, + invokeMessageId: msgId + }); + } + + deleteMessages(channel_id, deleteMsg); + + if (HIDE_REACTIONS_IF_PRIVATE && ev.mode === 1) return; + + wait(1000) + // create reaction controls + .then(() => createReaction(channel_id, id, REACTION_PREV)) + .then(() => wait(150)) + .then(() => createReaction(channel_id, id, REACTION_PLAYPAUSE)) + .then(() => wait(150)) + .then(() => createReaction(channel_id, id, REACTION_NEXT)); + }); + successReaction(ev, reply); + }); + + createCommand('next') + .help('Play the next track') + .manual('Plays the next track (only when a playlist or queue is active).') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + media.playNext(); + successReaction(ev, reply); + }); + + createCommand('prev') + .alias('previous') + .help('Play the previous track') + .manual('Plays the previous track (only when a playlistis active).') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + media.playPrevious(); + successReaction(ev, reply); + }); + + createCommand('search') + .alias('s') + .addArgument(args => args.rest.setName('searchstring')) + .help('Search for tracks') + .manual('Searches for tracks, returns 20 results at most.') + .checkPermission(requirePrivileges(PLAYBACK, ENQUEUE)) + .exec((client, args, reply, ev) => { + // print syntax if no searchstring given + if (!args.searchstring) { + reply(USAGE_PREFIX + 'search '); + return; + } + + const tracks = media.search(args.searchstring); + if (tracks.length == 0) { + reply('Sorry, nothing found.'); + successReaction(ev, reply); + return; + } + + const response = tracks.map(formatTrack).join("\n") + reply(response); + successReaction(ev, reply); + }); + + createCommand('play') + .alias('p') + .addArgument(args => args.rest.setName('idORsearchstring', 'searchstring / uuid')) + .help('Play a track by its id or name') + .manual('Plays a track by its id or searches for a track and plays the first match.') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + let query = args.idORsearchstring; + // print syntax if no idORsearchstring given + if (!query) { + reply(USAGE_PREFIX + 'play '); + return; + } + + let track = media.getTrackByID(query); + if (!track) { + let tracks = media.search(query); + if (tracks.length > 0) { + track = tracks[0]; + } else { + query = stripURL(query); + if (query.match(PATTERN_URL)) { + if (!query.match(PATTERN_YT_DOMAIN)) { + if (media.playURL(query)) { + successReaction(ev, reply); + return; + } + } + if (media.ytStream(query)) { + successReaction(ev, reply); + return; + } + const jobId = media.yt(query); + handleYT(jobId, ev, reply); + return; + } + return reply('Sorry, nothing found.'); + } + } + + track.play(); + reply(`Playing ${formatTrack(track)}`); + successReaction(ev, reply); + }); + + createCommand('playlist') + .addArgument(args => args.rest.setName('playlistname')) + .help('Start playing back the playlist ') + .manual('starts playing back the playlist .') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + // print syntax if no playlistname given + if (!args.playlistname) { + reply(USAGE_PREFIX + 'playlist '); + return; + } + + const match = media.getPlaylists().find(playlist => { + // case insensitive equals + return playlist.name() == args.playlistname || playlist.name().localeCompare(args.playlistname, undefined, { sensitivity: 'accent' }) === 0 + }); + + if (!match) { + reply('Sorry, no matching playlist found.'); + successReaction(ev, reply); + return; + } + + media.playlistPlayByID(match, 0); + + successReaction(ev, reply); + }); + + createCommand('queue') + .alias('q') + .addArgument(args => args.rest.setName('idORsearchstring', 'searchstring / uuid').optional(true)) + .help('Enqueue a track or resume queue') + .manual('Enqueue a track by its id or search for a track and enqueue the first match. When no track is provided it wil resume the queue.') + .checkPermission(requirePrivileges(PLAYBACK, ENQUEUE)) + .exec((client, args, reply, ev) => { + if (!args.idORsearchstring) { + if (!audio.isPlaying()) { + media.playQueueNext(); + } + return; + } + + let track = media.getTrackByID(args.idORsearchstring); + if (!track) { + const tracks = media.search(args.idORsearchstring); + if (tracks.length > 0) { + track = tracks[0]; + } else { + reply('Sorry, nothing found.'); + return; + } + } + + track.enqueue(); + reply(`Added ${formatTrack(track)} to the queue`); + successReaction(ev, reply); + }); + + createCommand('queuenext') + .alias('qnext', 'qn') + .addArgument(args => args.rest.setName('idORsearchstring', 'searchstring / uuid')) + .help('Prepends a track to the queue') + .manual('Prepends a track by its id or searches for a track and prepends the first match to the queue.') + .checkPermission(requirePrivileges(SKIP_QUEUE)) + .exec((client, args, reply, ev) => { + // print syntax if no idORsearchstring given + if (!args.idORsearchstring) { + reply(USAGE_PREFIX + 'queuenext '); + return; + } + + let track = media.getTrackByID(args.idORsearchstring); + if (!track) { + const tracks = media.search(args.idORsearchstring); + if (tracks.length > 0) { + track = tracks[0]; + } else { + reply('Sorry, nothing found.'); + return; + } + } + + track.enqueue(); + reply(`Added ${formatTrack(track)} to the queue`); + successReaction(ev, reply); + }); + + createCommand('stop') + .help('Stop playback') + .manual('Stops playback.') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + media.stop(); + successReaction(ev, reply); + }); + + createCommand('!stop') + .help('Stop playback and remove idle-track') + .manual('Stops playback and removes idle-track.') + .checkPermission(requirePrivileges(PLAYBACK|EDIT_BOT_SETTINGS)) + .exec((client, args, reply, ev) => { + media.stop(); + media.clearIdleTrack(); + successReaction(ev, reply); + }); + + createCommand('volume') + .alias('vol') + .addArgument(args => args.string.setName('value')) + .help('Change the volume') + .manual('Changes the volume.') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + let value = args.value; + let volume = audio.getVolume(); + + switch (value) { + case 'up': + volume += 10; + break; + case 'dn': + case 'down': + volume -= 10; + break; + default: + value = parseInt(value, 10); + if (value >= 0 && value <= 100) { + volume = value; + } else { + reply(USAGE_PREFIX + 'volume '); + return; + } + } + + if (volume < 0) { + volume = 0; + } else if (volume > 100) { + volume = 100; + } + + audio.setVolume(volume); + successReaction(ev, reply); + }); + + createCommand('stream') + .addArgument(args => args.string.setName('url')) + .help('Stream a url') + .manual('Streams from ; this may be http-streams like shoutcast / icecast or just remote soundfiles.') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + // print syntax if no url given + if (!args.url) { + reply(USAGE_PREFIX + 'stream '); + return; + } + + const url = stripURL(args.url); + + if (url.match(PATTERN_YT_DOMAIN)) { + if (!media.ytStream(url)) { + reply(ERROR_PREFIX + 'Unable to stream this YouTube URL.'); + return; + } + successReaction(ev, reply); + return; + } + + if (!media.playURL(url)) { + reply(ERROR_PREFIX + 'Unable to stream this URL.'); + return; + } + successReaction(ev, reply); + }); + + createCommand('say') + .addArgument(args => args.rest.setName('text')) + .help('Say a text via TTS') + .manual('Uses text-to-speech (if configured) to say the given text.') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + // print syntax if no text given + if (!args.text) { + reply(USAGE_PREFIX + 'say '); + return; + } + + audio.say(args.text); + successReaction(ev, reply); + }); + + createCommand('sayex') + .addArgument(args => args.string.setName('locale')) + .addArgument(args => args.rest.setName('text')) + .help('Say a text via TTS with given locale') + .manual('Uses text-to-speech (if configured) to say the given text with a given locale.') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + // print syntax if no locale/text given + if (!args.locale || !args.text) { + reply(USAGE_PREFIX + 'sayex '); + return; + } + + audio.say(args.text, args.locale); + successReaction(ev, reply); + }); + + createCommand('ttsurl') + .addArgument(args => args.string.setName('url')) + .help('Set the TTS url.') + .manual('Sets the TTS url.') + .checkPermission(requirePrivileges(EDIT_BOT_SETTINGS)) + .exec((client, args, reply, ev) => { + // print syntax if no url given + if (!args.url) { + reply(USAGE_PREFIX + 'ttsurl '); + return; + } + + audio.setTTSURL(stripURL(args.url)); + successReaction(ev, reply); + }); + + createCommand('ttslocale') + .addArgument(args => args.string.setName('locale')) + .help('Set the TTS locale.') + .manual('Sets the TTS locale.') + .checkPermission(requirePrivileges(EDIT_BOT_SETTINGS)) + .exec((client, args, reply, ev) => { + // print syntax if no locale given + if (!args.locale) { + reply(USAGE_PREFIX + 'ttslocale '); + return; + } + + audio.setTTSDefaultLocale(args.locale); + successReaction(ev, reply); + }); + + createCommand('yt') + .addArgument(args => args.string.setName('url')) + .help('Play via youtube-dl') + .manual('Plays via external youtube-dl (if enabled); beware: the file will be downloaded first and played back afterwards, so there might be a slight delay before playback starts.') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + const url = stripURL(args.url); + if (!url) return reply(USAGE_PREFIX + 'yt '); + if (!url.match(PATTERN_URL)) return reply(ERROR_PREFIX + 'Invalid URL.'); + + const jobId = media.yt(url); + ytCallback(jobId, (ytev, err) => { + if (err) { + // try to stream + if (!media.ytStream(url)) { + if (err.startsWith("exit status")) { + return reply(ERROR_PREFIX + `Error: ${err}; Please see "Upload" page in web-interface for more details.`); + } + return reply(ERROR_PREFIX + `Error: ${err}`); + } + } + successReaction(ev, reply); + }); + }); + + createCommand('ytstream') + .alias('streamyt') + .addArgument(args => args.string.setName('url')) + .help('Stream via youtube-dl') + .manual('Streams via external youtube-dl (if enabled)') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + const url = stripURL(args.url); + if (!url) return reply(USAGE_PREFIX + 'ytstream '); + if (!url.match(PATTERN_URL)) return reply(ERROR_PREFIX + 'Invalid URL.'); + + if (!media.ytStream(url)) { + return reply(ERROR_PREFIX + 'Unable to stream this URL.'); + } + successReaction(ev, reply); + }); + + createCommand('ytdl') + .addArgument(args => args.string.setName('url')) + .help('Download and play via youtube-dl') + .manual('Plays via external youtube-dl (if enabled); beware: the file will be downloaded first and played back afterwards, so there might be a slight delay before playback starts; additionally, the file will be stored.') + .checkPermission(requirePrivileges(PLAYBACK|UPLOAD_FILES)) + .exec((client, args, reply, ev) => { + const url = stripURL(args.url); + if (!url) return reply(USAGE_PREFIX + 'ytdl '); + if (!url.match(PATTERN_URL)) return reply(ERROR_PREFIX + 'Invalid URL.'); + + const jobId = media.ytdl(url, true); + handleYT(jobId, ev, reply); + }); + + createCommand('qyt') + .addArgument(args => args.string.setName('url')) + .help('Enqueue via youtube-dl') + .manual('Enqueues via external youtube-dl (if enabled); beware: the file will be downloaded first and played back afterwards, so there might be a slight delay before playback starts.') + .checkPermission(requirePrivileges(PLAYBACK, ENQUEUE)) + .exec((client, args, reply, ev) => { + const url = stripURL(args.url); + if (!url) return reply(USAGE_PREFIX + 'qyt '); + if (!url.match(PATTERN_URL)) return reply(ERROR_PREFIX + 'Invalid URL.'); + + const jobId = media.enqueueYt(url); + handleYT(jobId, ev, reply); + }); + + createCommand('qytdl') + .addArgument(args => args.string.setName('url')) + .help('Download and enqueue via youtube-dl') + .manual('Enqueues via external youtube-dl (if enabled); beware: the file will be downloaded first and played back afterwards, so there might be a slight delay before playback starts; additionally, the file will be stored.') + .checkPermission(requirePrivileges(PLAYBACK|UPLOAD_FILES, ENQUEUE|UPLOAD_FILES)) + .exec((client, args, reply, ev) => { + const url = stripURL(args.url); + if (!url) return reply(USAGE_PREFIX + 'qytdl '); + if (!url.match(PATTERN_URL)) return reply(ERROR_PREFIX + 'Invalid URL.'); + + const jobId = media.enqueueYtdl(url); + handleYT(jobId, ev, reply); + }); + + createCommand('shuffle') + .help('Toggle shuffle') + .manual('Toggles shuffle.') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + audio.setShuffle(!audio.isShuffle()); + reply(SUCCESS_PREFIX + `Shuffle is now ${audio.isShuffle() ? 'en' : 'dis'}abled.`); + successReaction(ev, reply); + }); + + createCommand('repeat') + .help('Toggle repeat') + .manual('Toggles repeat.') + .checkPermission(requirePrivileges(PLAYBACK)) + .exec((client, args, reply, ev) => { + audio.setRepeat(!audio.isRepeat()); + reply(SUCCESS_PREFIX + `Repeat is now ${audio.isRepeat() ? 'en' : 'dis'}abled.`); + successReaction(ev, reply); + }); + + if (engine.getBackend() == 'ts3') { + createCommand('sub') + .help('Subscribe to bot') + .manual('Subscribes to the bot. (subscription transfer-mode only)') + .checkPermission(() => engine.isSubscriptionMode()) + .exec((client, args, reply, ev) => { + if (!engine.isSubscriptionMode()) { + reply(ERROR_PREFIX + 'This command only works if Transmit-Mode is set to Subscription.'); + return; + } + client.subscribe(true); + successReaction(ev, reply); + }); + + createCommand('unsub') + .help('Unsubscribe from bot') + .manual('Unsubscribes from the bot. (subscription transfer-mode only)') + .checkPermission(() => engine.isSubscriptionMode()) + .exec((client, args, reply, ev) => { + if (!engine.isSubscriptionMode()) { + reply(ERROR_PREFIX + 'This command only works if Transmit-Mode is set to Subscription.'); + return; + } + client.subscribe(false); + successReaction(ev, reply); + }); + + createCommand('subchan') + .help('Add subscription for channel') + .manual('Adds subscription for the channel the user is currently in. (subscription transfer-mode only)') + .checkPermission(client => requirePrivileges(EDIT_BOT_SETTINGS)(client) && engine.isSubscriptionMode()) + .exec((client, args, reply, ev) => { + if (!engine.isSubscriptionMode()) { + reply(ERROR_PREFIX + 'This command only works if Transmit-Mode is set to Subscription.'); + return; + } + client.getChannels()[0].subscribe(true); + successReaction(ev, reply); + }); + + createCommand('unsubchan') + .help('Remove subscription for channel') + .manual('Removes subscription for the channel the user is currently in. (subscription transfer-mode only)') + .checkPermission(client => requirePrivileges(EDIT_BOT_SETTINGS)(client) && engine.isSubscriptionMode()) + .exec((client, args, reply, ev) => { + if (!engine.isSubscriptionMode()) { + reply(ERROR_PREFIX + 'This command only works if Transmit-Mode is set to Subscription.'); + return; + } + client.getChannels()[0].subscribe(false); + successReaction(ev, reply); + }); + + createCommand('mode') + .addArgument(args => args.string.setName('mode')) + .help('Change Transmit-Mode') + .manual('Changes Transmit-Mode; 0 = to channel, 1 = subscription mode') + .checkPermission(requirePrivileges(EDIT_BOT_SETTINGS)) + .exec((client, args, reply, ev) => { + let mode = args.mode; + if (typeof mode === 'string') { + mode = mode.toLowerCase(); + } + + switch (mode) { + case "0": + case "chan": + case "channel": + engine.setSubscriptionMode(false); + reply(SUCCESS_PREFIX + 'Transmit-Mode is now set to Channel (default).'); + successReaction(ev, reply); + break; + case "1": + case "sub": + case "subscription": + engine.setSubscriptionMode(true); + reply(SUCCESS_PREFIX + 'Transmit-Mode is now set to Subscription.'); + successReaction(ev, reply); + break; + default: + reply(`Transmit-Mode is currently set to ${engine.isSubscriptionMode() ? 'Subscription' : 'Channel (default)'}.\n` + USAGE_PREFIX + 'mode <0|chan(nel)|1|sub(scription)>'); + } + }); + } + + createCommand('registration') + .addArgument(args => args.string.setName('value')) + .help('Enable / disable user registration via chat') + .manual('Enables / disables user registration via chat. Value should be either `enable` or `disable`.') + .checkPermission(requirePrivileges(EDIT_BOT_SETTINGS)) + .exec((client, args, reply, ev) => { + switch (args.value) { + case "enable": + engine.enableRegistration(); + reply(SUCCESS_PREFIX + 'Registration is now enabled.'); + successReaction(ev, reply); + break; + case "disable": + engine.disableRegistration(); + reply(SUCCESS_PREFIX + 'Registration is now disabled.'); + successReaction(ev, reply); + break; + default: + reply(`Registration is currently ${engine.registrationEnabled() ? 'en' : 'dis'}abled.\n` + USAGE_PREFIX + 'registration '); + } + }); + + createCommand('prefix') + .addArgument(args => args.string.setName('prefix')) + .help('Change command prefix') + .manual('Changes the prefix for all core commands to , default is "!".') + .checkPermission(requirePrivileges(EDIT_BOT_SETTINGS)) + .exec((client, args, reply, ev) => { + // print syntax if no prefix given + if (!args.prefix) { + reply(USAGE_PREFIX + 'prefix '); + return; + } + + engine.setCommandPrefix(args.prefix); + reply(SUCCESS_PREFIX + 'New prefix: ' + args.prefix); + successReaction(ev, reply); + }); + + createCommand('ping') + .help('responds with "PONG"') + .exec((client, args, reply, ev) => { + reply(`PONG`); + successReaction(ev, reply); + }); + + createCommand('version') + .help('Show version') + .manual('Shows the SinusBot version.') + .checkPermission(requirePrivileges(EDIT_BOT_SETTINGS)) + .exec((client, args, reply, ev) => { + reply(`SinusBot v${engine.version()} on ${engine.os()}\nsinusbot-commands.js v${meta.version}\ncommand.js v${command.getVersion()}`); + successReaction(ev, reply); + }); + + createCommand('reload') + .help('Reload scripts') + .manual('Reloads scripts.\nNote: Adding new scripts requires a complete sinusbot restart.') + .checkPermission(requirePrivileges(EDIT_BOT_SETTINGS)) + .exec((client, args, reply, ev) => { + reply('reloading...'); + let success = engine.reloadScripts(); + if (success) { + reply(SUCCESS_PREFIX + `Scripts reloaded.\n*Please note: adding new scripts requires a complete sinusbot restart.*`); + successReaction(ev, reply); + } else { + reply('Unable to reload scripts. Did you allow it in your `config.ini`?'); + } + }); + + createCommand('join') + .help('Move the SinusBot to your channel') + .manual('Moves the SinusBot into your channel.') + .checkPermission(requirePrivileges(START_STOP)) + .exec((client, args, reply, ev) => { + var channel = client.getChannels()[0] + if (!channel) { + return reply(ERROR_PREFIX+'I\'m unable to join your channel :('); + } + + if (!getBotClient()) { + return reply(ERROR_BOT_NULL); + } + bot.moveTo(channel); + engine.setDefaultChannelID(channel.id()) + successReaction(ev, reply); + }); + + /* // currently not working due to a bug. + createCommand('disconnect') + .help('Disconnect the SinusBot') + .manual('Disconnects the SinusBot.') + .checkPermission(requirePrivileges(START_STOP)) + .exec((client, args, reply, ev) => { + backend.disconnect() + }); + // */ + + if (engine.getBackend() == 'discord') { + createCommand('leave') + .help('Disconnect the SinusBot') + .manual('Disconnects the SinusBot from the current voice channel.') + .checkPermission(requirePrivileges(START_STOP)) + .exec((client, args, reply, ev) => { + if (!getBotClient()) { + return reply(ERROR_BOT_NULL); + } + + bot.moveTo(''); + engine.setDefaultChannelID(''); + successReaction(ev, reply); + }); + } + }); + } // END COMMANDS-ENABLED + + // stores last bot client object for `getBotClient()` + let bot = backend.getBotClient(); + + /** + * Wrapper for `backend.getBotClient()` because it sometimes returns `null` -_- + * @returns {Client} Bot client + */ + function getBotClient() { + bot = backend.getBotClient() || bot; + return bot; + } + + /********** !playing stuff for discord **********/ + if (engine.getBackend() == 'discord') { + event.on('discord:MESSAGE_REACTION_ADD', ev => { + let ename = ev.emoji.name; + // remove 0xefb88f aka. "VARIATION SELECTOR-16" (no idea why discord puts that at the end) + if (ename.endsWith('\ufe0f')) { + ename = ename.slice(0, -1); + } + const emoji = ev.emoji.id ? `${ename}:${ev.emoji.id}` : ename; + + // ignore reactions that are not controls + if (![REACTION_PREV, REACTION_PLAYPAUSE, REACTION_NEXT].includes(emoji)) return; + + // ignore reactions from the bot itself + if (backend.getBotClientID().endsWith(ev.user_id)) return; + + // get user via id + let client = backend.getClientByID(`${ev.guild_id || backend.getBotClientID().split('/')[0]}/${ev.user_id}`); + + if (!client) { + engine.log(`playing controls: using workaround for client '${ev.user_id}'`); + // @ts-ignore: workaround + client = fakeClient(ev.guild_id || backend.getBotClientID().split('/')[0], ev.user_id, ev.channel_id); + } + + // ignore if no matching user found or reaction from the bot itself + if (!client) return engine.log(`playing controls: could not find client '${ev.user_id}'`); + + // ignore if reaction is from the bot itself + if (client.isSelf()) return; + + let callback = () => { + // delete the rection + deleteUserReaction(ev.channel_id, ev.message_id, ev.user_id, emoji); + + // check if user has the 'playback' permission + if (!requirePrivileges(PLAYBACK)(client)) { + engine.log(`${client.nick()} is missing playback permissions for reaction controls`); + client.chat(ERROR_PREFIX + 'You need the playback permission to use reaction controls'); + return; + } + + const track = media.getCurrentTrack(); + + switch (emoji) { + case REACTION_PREV: + // ignore if nothing is playing + if (!audio.isPlaying()) return; + + if (media.getQueue().length !== 0) { + // start from beginning if we're playing queue + audio.seek(0); + } else { + // try prev (doesn't work for queue or folder) + media.playPrevious(); + + // fallback: start from beginning if there is no previous track + if (!audio.isPlaying()) { + if (track) track.play(); + } + } + break; + case REACTION_PLAYPAUSE: + if (audio.isPlaying()) { + media.stop(); + } else { + if (!track) return; + const pos = audio.getTrackPosition(); + + if (pos && pos < (track.duration() - 1000 /* milliseconds */)) { + // continue playing at last pos + audio.setMute(true); + track.play(); + audio.seek(pos); + audio.setMute(false); + } else { + // or start from beginning if it already ended + track.play(); + } + } + break; + case REACTION_NEXT: + if (audio.isPlaying()) { + media.playNext(); + } else { + // is something in queue? + if (media.getQueue().length !== 0) { + // resume queue + media.playQueueNext(); + } + } + } + }; + + // was reaction added to previous response? + if (lastEmbeds.some(embed => embed.messageId == ev.message_id)) { + callback(); + return; + } + + getMessage(ev.channel_id, ev.message_id).then(msg => { + // was reaction added to previous response of the bot? + if (backend.getBotClientID().endsWith(msg.author.id)) { + callback(); + } + }) + }); + + /** + * Called when track or it's info changes + * @param {Track} track + */ + const onChange = track => { + if (config.songInStatus) { + const prefix = '🎵 '; + const suffix = ' 🎵'; + + // set track info as status + backend.extended().setStatus({ + game: { + name: prefix + formatTrack(track) + suffix, + type: 2, // => 0 (game), 1 (streaming), 2 (listening) + }, + status: "online", + afk: false + }); + } + + // update embeds + lastEmbeds.forEach(async embed => { + await editMessage(embed.channelId, embed.messageId, getPlayingEmbed()).then(() => wait(100)); + }); + }; + + event.on('track', onChange); + event.on('trackInfo', onChange); + event.on('trackEnd', () => { + if (!config.songInStatus) { + return; + } + if (getBotClient()) { + bot.setDescription(''); + } + }); + } + + /** + * Returns embed for current track + */ + function getPlayingEmbed() { + let track = media.getCurrentTrack(); + + if (!track) { + return { + content: "", + embed: {}, + }; + } + + let album = track.album(); + let duration = track.duration(); + + let fields = []; + fields.push({ + name: "Duration", + value: duration ? timestamp(duration) : 'stream', + inline: true + }); + if (album) { + fields.push({ + name: "Album", + value: album, + inline: true + }); + } + + return { + content: formatTrack(track), + embed: { + title: formatTrack(track), + url: sinusbotURL || null, + color: 0xe13438, + thumbnail: { + url: sinusbotURL && track.thumbnail() ? `${sinusbotURL}/cache/${track.thumbnail()}` : null + }, + fields: fields, + footer: { + icon_url: "https://sinusbot.github.io/logo.png", + text: "SinusBot" + } + }, + }; + } + + /** + * Returns a fake client object from IDs. (Discord only) + * @param {string} guild Guild ID + * @param {string} id User ID + * @param {string} channelid Channel ID + */ + function fakeClient(guild, id, channelid) { + const clid = `${guild}/${id}` + return { + chat: (/** @type {string} */ str) => { + backend.extended().createMessage(channelid, { + content: str + }); + }, + isSelf: () => false, + id: () => clid, + uid: () => clid, + uniqueId: () => clid, + uniqueID: () => clid, + DBID: () => clid, + databaseID: () => clid, + databaseId: () => clid, + type: () => 1, + getURL: () => `<@${id}>`, + name: () => `unknown (ID: ${id})`, + nick: () => `unknown (ID: ${id})`, + phoneticName: () => '', + description: () => '', + getServerGroups: () => [], + getChannelGroup: () => null, + getChannels: () => [], + getAudioChannel: () => null, + equals: (/** @type {Client} */ client) => { + const uid = client.uid().split("/") + if (uid.length === 2) { + return uid[2] === id + } else { + return client.uid() === clid + } + } + } + } + + /********** helper functions **********/ + + /** + * Returns the first user with a given UID. + * + * @param {string} uid UID of the client + * @returns {User} first user with given uid + */ + function getUserByUid(uid) { + return engine.getUsers().find(user => user.tsUid() === uid) + } + + /** + * Returns alls users that match the clients UID and ServerGroups. + * + * @param {Client} client + * @returns {User[]} Users that match the clients UID and ServerGroups. + */ + function getUsersByClient(client) { + return engine.getUsers().filter(user => + // does the UID match? + client.uid() == user.uid() || + // does a group ID match? + client.getServerGroups().map(group => group.id()).includes(user.groupId()) || + // group ID '-1' matches everyone + user.groupId() == '-1' + ); + } + + /** + * Returns a function that checks if a given user has all of the required privileges. + * @param {...number} privileges If at least one privilege matches the returned function will return true. + */ + function requirePrivileges(...privileges) { + return (/** @type {Client} */ client) => { + // check if at least one user has the required privileges + return getUsersByClient(client).some(user => { + // check if at least one privilege is found + return privileges.some(priv => { + return ((user.privileges()|user.instancePrivileges()) & priv) === priv; + }); + }); + }; + } + + // /** + // * Returns a formatted string from a track. + // * + // * @param {Track} track + // * @returns {string} formatted string + // */ + // function formatTrackWithID(track) { + // return `${format.code(track.id())} ${formatTrack(track)}`; + // } + + /** + * Returns a formatted string from a track. + * + * @param {Track} track + * @returns {string} formatted string + */ + function formatTrack(track) { + let title = track.tempTitle() || track.title(); + let artist = track.tempArtist() || track.artist(); + return artist ? `${artist} - ${title}` : title; + } + + /** + * Removes TeamSpeaks URL bb-code or Discords < > from a given string. + * + * @param {string} str + * @returns {string} str without [URL] [/URL] and < > + */ + function stripURL(str) { + // don't handle non-strings, return as provided + if (typeof str !== 'string') return str; + + // remove surrounding [URL] [/URL] tags + let match = str.match(/\[URL\](.+)\[\/URL\]/i); + if (match && match.length >= 2) { + return match[1]; + } + + // remove surrounding < > + match = str.match(/<(.+)>/); + if (match && match.length >= 2) { + return match[1]; + } + + // if nothing matches just return str + return str; + } + + /** + * Returns a more human readable timestamp (hours:minutes:secods) + * @param {number} milliseconds + */ + function timestamp(milliseconds) { + const SECOND = 1000; //milliseconds + const MINUTE = 60 * SECOND; + const HOUR = 60 * MINUTE; + + let seconds = Math.floor(milliseconds / SECOND); + let minutes = Math.floor(milliseconds / MINUTE); + let hours = Math.floor(milliseconds / HOUR); + + minutes = minutes % (HOUR/MINUTE); + seconds = seconds % (MINUTE/SECOND); + + let str = ''; + + if (hours !== 0) { + str += hours + ':'; + if (minutes <= 9) { + str += '0'; + } + } + str += minutes + ':'; + if (seconds <= 9) { + str += '0'; + } + str += seconds; + + return str; + } + + /** + * @ignore + * @typedef MessageEvent + * @type {object} + * @property {Client} client + * @property {Channel} channel + * @property {string} text + * @property {number} mode + * @property {DiscordMessage} [message] + */ + + /** + * Gives the user feedback if a command was successfull. + * + * @param {MessageEvent} ev + * @param {(msg: string) => void} reply + */ + function successReaction(ev, reply) { + if (!config.createSuccessReaction) { + return; + } + switch (engine.getBackend()) { + case "discord": + if (ev.message) ev.message.createReaction(REACTION_SUCCESS) + return + case "ts3": + return reply(`${REACTION_SUCCESS} done!`) + } + } + + /** + * Waits for given milliseconds. + * @param {number} ms Time to wait for in milliseconds. + * @return {Promise} + */ + function wait(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + /** + * Adds a reaction to a message. + * @param {string} channelID Channel ID + * @param {string} messageID Message ID + * @param {string} emoji Emoji + * @return {Promise} + * @author Jonas Bögle + * @license MIT + */ + function createReaction(channelID, messageID, emoji) { + return discord('PUT', `/channels/${channelID}/messages/${messageID}/reactions/${emoji}/@me`, null, false); + } + + /** + * Removes a reaction from a message. + * @param {string} channelID Channel ID + * @param {string} messageID Message ID + * @param {string} userID User ID + * @param {string} emoji Emoji + * @return {Promise} + * @author Jonas Bögle + * @license MIT + */ + function deleteUserReaction(channelID, messageID, userID, emoji) { + return discord('DELETE', `/channels/${channelID}/messages/${messageID}/reactions/${emoji}/${userID}`, null, false); + } + + /** + * Gets a message. + * @param {string} channelID Channel ID + * @param {string} messageID Message ID + * @return {Promise} + * @author Jonas Bögle + * @license MIT + */ + function getMessage(channelID, messageID) { + return discord('GET', `/channels/${channelID}/messages/${messageID}`, null, true); + } + + /** + * Edits a message. + * @param {string} channelID Channel ID + * @param {string} messageID Message ID + * @param {object} message New message + * @return {Promise} + * @author Jonas Bögle + * @license MIT + */ + function editMessage(channelID, messageID, message) { + return discord('PATCH', `/channels/${channelID}/messages/${messageID}`, message, true); + } + + /** + * Deletes a message. + * @param {string} channelID Channel ID + * @param {string} messageID Message ID + * @return {Promise} + * @author Jonas Bögle + * @license MIT + */ + function deleteMessage(channelID, messageID) { + return discord('DELETE', `/channels/${channelID}/messages/${messageID}`, null, false); + } + + /** + * Deletes multiple messages. + * @param {string} channelID Channel ID + * @param {string[]} messageIDs Message IDs + * @return {Promise} + * @author Jonas Bögle + * @license MIT + */ + function deleteMessages(channelID, messageIDs) { + switch (messageIDs.length) { + case 0: return Promise.resolve(); + case 1: return deleteMessage(channelID, messageIDs[0]); + default: return discord('POST', `/channels/${channelID}/messages/bulk-delete`, {messages: messageIDs}, false); + } + } + + /** + * Executes a discord API call + * @param {string} method http method + * @param {string} path path + * @param {object} [data] json data + * @param {boolean} [repsonse] `true` if you're expecting a json response, `false` otherwise + * @return {Promise} + * @author Jonas Bögle + * @license MIT + */ + function discord(method, path, data=null, repsonse=true) { + return new Promise((resolve, reject) => { + backend.extended().rawCommand(method, path, data, (err, data) => { + if (err) return reject(err); + if (repsonse) { + let res; + try { + res = JSON.parse(data); + } catch (err) { + engine.log(`${method} ${path} failed`) + engine.log(`${data}`) + return reject(err); + } + + if (res === undefined) { + engine.log(`${method} ${path} failed`) + engine.log(`${data}`) + return reject('Invalid Response'); + } + + return resolve(res); + } + resolve(); + }); + }); + } +}) diff --git a/default-attached/welcome.js b/default-attached/welcome.js new file mode 100644 index 0000000..aa2e874 --- /dev/null +++ b/default-attached/welcome.js @@ -0,0 +1,33 @@ +registerPlugin({ + name: 'Welcome!', + version: '3.0.0', + backends: ['ts3'], + description: 'This plugin will let the bot greet everyone.', + author: 'SinusBot Team', // Michael Friese, Max Schmitt, Jonas Bögle + vars: [{ + name: 'message', + title: 'The message that should be displayed. (%n = nickname)', + type: 'string' + }, { + name: 'type', + title: 'Message-Type', + type: 'select', + options: [ + 'Private chat', + 'Poke' + ] + }] +}, (_, { message, type }) => { + const event = require('event') + + event.on('clientMove', ({ client, fromChannel }) => { + let msg = message.replace('%n', client.name()) + if (!fromChannel) { + if (type == '0') { + client.chat(msg) + } else { + client.poke(msg) + } + } + }) +}) \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json index c14023d..d248292 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,6 +1,12 @@ { "compilerOptions": { - "target": "es5", - "module": "commonjs" - } + "checkJs": true, + "target": "es2018", + "types" : ["sinusbot-scripting-engine"] + }, + "typeAcquisition": {"enable": false, "include": ["sinusbot-scripting-engine"]}, + "exclude": [ + "node_modules", + "**/node_modules/*" + ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3b91017 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,14 @@ +{ + "name": "scripts", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "sinusbot-scripting-engine": { + "version": "1.0.18", + "resolved": "https://registry.npmjs.org/sinusbot-scripting-engine/-/sinusbot-scripting-engine-1.0.18.tgz", + "integrity": "sha512-tRJDIAN8carWE4LCOKM15elemn7zLAkg2RB9KjGPTfhwMxePsZJs+Xl1+i2MrF5T0qmUpbMxzEg3941Kud3hKg==", + "dev": true + } + } +} diff --git a/package.json b/package.json index f3ced24..4aaacdc 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,6 @@ "author": "SinusBot", "license": "MIT", "devDependencies": { - "sinusbot-scripting-engine": "^1.0.0" + "sinusbot-scripting-engine": "^1.0.18" } } diff --git a/ported/01-advertising.js b/ported/01-advertising.js deleted file mode 100644 index a78df56..0000000 --- a/ported/01-advertising.js +++ /dev/null @@ -1,52 +0,0 @@ -registerPlugin({ - name: 'Advertising (Text)', - version: '2.0', - description: 'This script will announce one of the configured lines every x seconds.', - author: 'Michael Friese & mxschmitt ', - vars: [{ - name: 'ads', - title: 'Ads (supports bbcode)', - type: 'multiline', - placeholder: 'Welcome to the best TS3-Server!' - }, { - name: 'interval', - title: 'Interval (in seconds)', - type: 'number', - placeholder: '5' - }, { - name: 'order', - title: 'Order', - type: 'select', - options: [ - 'default (line by line)', - 'random' - ] - }, { - name: 'type', - title: 'Broadcast-Type', - type: 'select', - options: [ - 'Channel', - 'Server' - ] - }] -}, function (sinusbot, config) { - var backend = require('backend'); - var ads = (config && config.ads) ? config.ads.split('\n').map(function (e) { - return e.trim().replace(/\r/g, ''); - }) : []; - var ctr = -1; - setInterval(function () { - ctr++; - if (ads.length == 0 || config.Interval < 5) return; - var ad = ctr % ads.length; - if (config.order == 1 && ads.length > 1) { - ad = sinusbot.getRand(ads.length - 1); - } - if (config.type == 0) { - backend.getCurrentChannel().chat(ads[ad]); - } else { - backend.chat(ads[ad]); - } - }, config.interval * 1000); -}); \ No newline at end of file diff --git a/ported/02-alonemode.js b/ported/02-alonemode.js deleted file mode 100644 index 0fafeba..0000000 --- a/ported/02-alonemode.js +++ /dev/null @@ -1,53 +0,0 @@ -registerPlugin({ - name: 'AloneMode', - version: '2.0', - description: 'This script will save CPU and bandwidth by stopping or muting the bot when nobody is listening anyways.', - author: 'Michael Friese & mxschmitt ', - vars: [{ - name: 'mode', - title: 'Mode', - type: 'select', - options: [ - 'mute only', - 'stop playback' - ] - }] -}, function (sinusbot, config) { - var engine = require('engine'), - backend = require('backend'), - event = require('event'), - audio = require('audio'), - media = require('media'); - - var isMuted = false, - LastPosition = 0, - LastTitle; - - audio.setMute(false); - event.on('clientMove', function (ev) { - if (backend.getCurrentChannel().getClientCount() > 1 && isMuted) { - isMuted = false; - engine.log('Ending AloneMode...'); - if (config.mode == 0) { - audio.setMute(false); - } else { - LastTitle.play(); - audio.seek(LastPosition); - engine.log('Seeking to ' + LastPosition); - } - return; - } - if (backend.getCurrentChannel().getClientCount() <= 1 && audio.isPlaying()) { - isMuted = true; - engine.log('Starting AloneMode...'); - if (config.mode == 0) { - audio.setMute(true); - } else { - LastPosition = getPos(); - engine.log('Pos is ' + LastPosition); - LastTitle = media.getCurrentTrack(); - media.stop(); - } - } - }); -}); \ No newline at end of file diff --git a/ported/03-bookmark.js b/ported/03-bookmark.js deleted file mode 100644 index a03c35e..0000000 --- a/ported/03-bookmark.js +++ /dev/null @@ -1,39 +0,0 @@ -registerPlugin({ - name: 'Bookmarks!', - version: '2.0.1', - description: 'Enter .bookmark to save the current position, enter .resume to seek to the bookmarked position.', - author: 'Michael Friese & mxschmitt ', - vars: [] -}, function (sinusbot, config) { - - var store = require('store'), - media = require('media'), - backend = require('backend'), - audio = require('audio'); - - var event = require('event'); - - event.on('chat', function (ev) { - engine.log(ev.text); - if (ev.text == '.bookmark') { - engine.log("bookmark"); - var track = media.getCurrentTrack(); - if (!track) return; - var pos = audio.getTrackPosition(); - store.set(track.ID(), pos); - backend.getCurrentChannel().chat('Position saved for track ' + track.uuid + ' at ' + pos + 'ms.'); - } - if (ev.text == '.resume') { - engine.log("resume"); - var track = media.getCurrentTrack(); - if (!track) return; - var pos = store.get(track.ID()); - if (!pos) { - backend.getCurrentChannel().chat('No position found, sorry.'); - return; - } - audio.seek(pos); - backend.getCurrentChannel().chat('Resumed at ' + pos + 'ms.'); - } - }); -}); diff --git a/ported/04-no-recording.js b/ported/04-no-recording.js deleted file mode 100644 index 0b799a0..0000000 --- a/ported/04-no-recording.js +++ /dev/null @@ -1,18 +0,0 @@ -registerPlugin({ - name: 'No Recording!', - version: '2.0', - description: 'This script will kick anyone who attempts to record.', - author: 'Michael Friese & mxschmitt ', - vars: [{ - name: 'kickMessage', - title: 'The optional kick message.', - type: 'string' - }] -}, function (sinusbot, config) { - var engine = require('engine'); - var event = require('event'), - kickMessage = config.kickMessage ? config.kickMessage : 'No recording on our server!'; - event.on('clientRecord', function (ev) { - ev.kickFromServer(kickMessage) - }); -}); \ No newline at end of file diff --git a/ported/05-follow-me.js b/ported/05-follow-me.js deleted file mode 100644 index 932df98..0000000 --- a/ported/05-follow-me.js +++ /dev/null @@ -1,33 +0,0 @@ -registerPlugin({ - name: 'Follow Me', - version: '2.0', - description: 'The bot will follow the movements of any of the clients given', - author: 'Michael Friese & mxschmitt ', - vars: [{ - name: 'clientUids', - title: 'Comma-separated list of client-ids that the bot should follow', - type: 'string' - }] -}, function (sinusbot, config) { - - var engine = require('engine'), - backend = require('backend'), - event = require('event'); - - - if (!config.clientUids) { - engine.log('Invalid clientUids'); - return; - } - - var uids = config.clientUids.split(','); - event.on('clientMove', function (ev) { - if (uids.indexOf(ev.client.uniqueID()) >= 0 && ev.toChannel != null) { - engine.log('Following ' + ev.client.name()); - backend.getBotClient().moveTo(ev.toChannel); - return; - } - }); - - engine.log('Follow Me initialized...'); -}); \ No newline at end of file diff --git a/ported/06-remember-channel.js b/ported/06-remember-channel.js deleted file mode 100644 index d5267d6..0000000 --- a/ported/06-remember-channel.js +++ /dev/null @@ -1,16 +0,0 @@ -registerPlugin({ - name: 'Remember Last Channel', - version: '2.0', - description: 'This script will remember, which channel the bot was last moved to and will set it as default channel on join.', - author: 'Michael Friese & mxschmitt ', - vars: [] -}, function (sinusbot, config) { - var event = require('event'); - var backend = require('backend'); - var engine = require('engine'); - event.on('clientMove', function (ev) { - if (ev.client.uniqueID() == backend.getBotClient().uniqueID()) { - engine.setDefaultChannelID(ev.toChannel.ID()); - } - }); -}); \ No newline at end of file diff --git a/ported/07-welcome.js b/ported/07-welcome.js deleted file mode 100644 index ae7cfbe..0000000 --- a/ported/07-welcome.js +++ /dev/null @@ -1,58 +0,0 @@ -registerPlugin({ - name: 'Welcome!', - version: '2.1', - description: 'This plugin will let the bot greet everyone.', - author: 'Michael Friese & mxschmitt ', - vars: [{ - name: 'message', - title: 'The message that should be displayed. (%n = nickname)', - type: 'string' - }, { - name: 'type', - title: 'Message-Type', - type: 'select', - options: [ - 'Private chat', - 'Poke' - ] - }, { - name: 'newline', - title: 'How new lines should be handled', - type: 'select', - options: [ - 'normal (one message with multiple lines)', - 'multiple messages (a new message for each line)' - ], - // only show this when private chat is selected - conditions: [{ - field: 'type', - value: 0 - }] - }] -}, function (sinusbot, config) { - var engine = require('engine'); - var event = require('event'); - - config.type = config.type || 0; - config.newline = config.newline || 0; - engine.saveConfig(config); - - event.on('clientMove', function (ev) { - var msg = config.message; - msg = msg.replace(/%n/g, ev.client.name()); - - if (ev.fromChannel == undefined) { - if (config.type == 0) { - if (config.newline == 0) { - ev.client.chat(msg); - } else { - msg.split('\n').forEach(function (line) { - ev.client.chat(line) - }); - } - } else { - ev.client.poke(msg); - } - } - }); -}); diff --git a/scripts/00-basic.js b/scripts/00-basic.js index 1dec174..77ded48 100644 --- a/scripts/00-basic.js +++ b/scripts/00-basic.js @@ -1,4 +1,3 @@ -// @ts-check registerPlugin({ name: 'Example script', version: '1.0.0', @@ -10,9 +9,9 @@ registerPlugin({ // import modules const engine = require('engine'); const event = require('event'); - + // listen for chat event - event.on('chat', ({client, text}) => { + event.on('chat', ({ client, text }) => { engine.log(`${client.name()} wrote ${text}`); client.chat(`Hi ${client.name()}, you just wrote: ${text}`); }); diff --git a/scripts/01-speech-recognition.js b/scripts/01-speech-recognition.js index d0c5cc9..c17d939 100644 --- a/scripts/01-speech-recognition.js +++ b/scripts/01-speech-recognition.js @@ -1,61 +1,24 @@ registerPlugin({ - name: 'Chuck Norris Speech API', - version: '1.1', - backends: ["ts3"], - description: 'This is a simple script that will write you Chuck Norris jokes when you say "joke"', - author: 'mxschmitt & irgendwer ', - vars: [{ - name: 'msgtype', - title: 'message type', - type: 'select', - options: ['Poke', 'Private message', 'Channel message', 'Say it'] - }], - voiceCommands: ['joke'] -}, function (sinusbot, config) { - var engine = require('engine') - var event = require('event') - var audio = require('audio') - var backend = require('backend') + name: 'Speech Recognition Demo', + version: '1.0', + description: 'This is a simple script that will stop playback when you say "stop"', + author: 'Michael Friese ', + vars: [], + voiceCommands: ['stop'] +}, function(_, config) { + var event = require('event'); + var engine = require('engine'); + var audio = require('audio'); + var media = require('media'); - audio.setAudioReturnChannel(0x2); + audio.setAudioReturnChannel(2); - event.on('speech', function (ev) { - if (ev.text == 'joke') { - engine.log('Writes a joke to ' + ev.clientId); - sinusbot.http({ - "method": "GET", - "url": "http://api.icndb.com/jokes/random", - "timeout": 6000, - "headers": [{ - "Content-Type": "application/json" - },] - }, function (error, response) { - if (response.statusCode != 200) { - engine.log(error); - return; - } - var joke = unescape(JSON.parse(response.data).value.joke).replace('"', '\"'); - engine.log("received joke: " + joke); - switch (config.msgtype) { - case 0: - if (joke.length > 100) { - engine.log("over 100"); - var joke = joke.substring(0, 96) + '...'; - } - ev.client.poke(joke); - break; - case 1: - ev.client.chat(joke); - case 2: - backend.getCurrentChannel().chat(joke); - break; - default: - audio.say(joke); - break; - } - }); + event.on('speech', function(ev) { + if (ev.text == 'stop') { + engine.log('Stopping playback on behalf of client ' + ev.client.nick()); + media.stop(); } else { - engine.log('Client ' + ev.client.name() + ' just said ' + ev.text); + engine.log(ev.client.nick() + ' just said ' + ev.text); } }); -}); +}); \ No newline at end of file diff --git a/snippets/00-http.js b/snippets/00-http.js index 036680c..7e0e420 100644 --- a/snippets/00-http.js +++ b/snippets/00-http.js @@ -1,46 +1,54 @@ -// import modules -var engine = require('engine'); -var http = require('http'); +registerPlugin({ + name: 'Demo http json Script', + version: '1.0.0', + description: 'This example actually does nothing', + author: 'Author ', + requiredModules: ['http'], // <-- don't forget this! + vars: [] +}, function(_, config, meta) { + // import modules + var engine = require('engine'); + var http = require('http'); -// define data that should be sent -var sendData = JSON.stringify({ foo: 'bar' }); + // define data that should be sent + var sendData = JSON.stringify({ foo: 'bar' }); -// send request -http.simpleRequest({ - 'method': 'POST', - 'url': 'https://example.com', - 'timeout': 6000, - 'maxSize': 1024 * 1024 * 5, - 'body': sendData, - 'headers': { - 'Content-Type': 'application/json', - 'Content-Length': sendData.length - } -}, function (error, response) { - if (error) { - engine.log("Error: " + error); - return; - } - - if (response.statusCode != 200) { - engine.log("HTTP Error: " + response.status); - return; - } - - // parse JSON response - var res; - try { - res = JSON.parse(response.data.toString()); - } catch (err) { - engine.log(err.message); - } - - // check if parsing was successfull - if (res === undefined) { - engine.log("Invalid JSON."); - return; - } - - // success! - engine.log(res); -}); + // send request + http.simpleRequest({ + 'method': 'POST', + 'url': 'https://example.com', + 'timeout': 6000, + 'body': sendData, + 'headers': { + 'Content-Type': 'application/json', + 'Content-Length': sendData.length + } + }, function (error, response) { + if (error) { + engine.log("Error: " + error); + return; + } + + if (response.statusCode != 200) { + engine.log("HTTP Error: " + response.status); + return; + } + + // parse JSON response + var res; + try { + res = JSON.parse(response.data.toString()); + } catch (err) { + engine.log(err.message); + } + + // check if parsing was successfull + if (res === undefined) { + engine.log("Invalid JSON."); + return; + } + + // success! + engine.log(res); + }); +}); \ No newline at end of file diff --git a/snippets/01-basic-http.js b/snippets/01-basic-http.js index d68566a..179f3e1 100644 --- a/snippets/01-basic-http.js +++ b/snippets/01-basic-http.js @@ -1,23 +1,32 @@ -// import modules -var engine = require('engine'); -var http = require('http'); +registerPlugin({ + name: 'Demo http basic Script', + version: '1.0.0', + description: 'This example actually does nothing', + author: 'Author ', + requiredModules: ['http'], // <-- don't forget this! + vars: [] +}, function(_, config, meta) { + // import modules + var engine = require('engine'); + var http = require('http'); -// send request -http.simpleRequest({ - 'method': 'GET', - 'url': 'https://example.com', - 'timeout': 6000, -}, function (error, response) { - if (error) { - engine.log("Error: " + error); - return; - } - - if (response.statusCode != 200) { - engine.log("HTTP Error: " + response.status); - return; - } - - // success! - engine.log("Response: " + response.data.toString()); -}); + // send request + http.simpleRequest({ + 'method': 'GET', + 'url': 'https://example.com', + 'timeout': 6000, + }, function (error, response) { + if (error) { + engine.log("Error: " + error); + return; + } + + if (response.statusCode != 200) { + engine.log("HTTP Error: " + response.status); + return; + } + + // success! + engine.log("Response: " + response.data.toString()); + }); +}); \ No newline at end of file