├── .bithoundrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .jsbeautifyrc ├── .mailmap ├── .travis.yml ├── Changelog.md ├── LICENSE.md ├── README.md ├── docs ├── assets │ ├── anchor.js │ ├── bass-addons.css │ ├── bass.css │ ├── fonts │ │ ├── EOT │ │ │ ├── SourceCodePro-Bold.eot │ │ │ └── SourceCodePro-Regular.eot │ │ ├── LICENSE.txt │ │ ├── OTF │ │ │ ├── SourceCodePro-Bold.otf │ │ │ └── SourceCodePro-Regular.otf │ │ ├── TTF │ │ │ ├── SourceCodePro-Bold.ttf │ │ │ └── SourceCodePro-Regular.ttf │ │ ├── WOFF │ │ │ ├── OTF │ │ │ │ ├── SourceCodePro-Bold.otf.woff │ │ │ │ └── SourceCodePro-Regular.otf.woff │ │ │ └── TTF │ │ │ │ ├── SourceCodePro-Bold.ttf.woff │ │ │ │ └── SourceCodePro-Regular.ttf.woff │ │ ├── WOFF2 │ │ │ ├── OTF │ │ │ │ ├── SourceCodePro-Bold.otf.woff2 │ │ │ │ └── SourceCodePro-Regular.otf.woff2 │ │ │ └── TTF │ │ │ │ ├── SourceCodePro-Bold.ttf.woff2 │ │ │ │ └── SourceCodePro-Regular.ttf.woff2 │ │ └── source-code-pro.css │ ├── github.css │ ├── site.js │ ├── split.css │ ├── split.js │ └── style.css └── index.html ├── documentation.yml ├── lib ├── bufferObject.js ├── client.js ├── constants.json ├── endpoints.json ├── eventObjectTypes.js ├── events.json ├── logger.js ├── privateVars.js ├── room.js ├── socket.js └── utils.js ├── package-lock.json ├── package.json └── test ├── .eslintrc.json ├── eslint.js └── mocha.opts /.bithoundrc: -------------------------------------------------------------------------------- 1 | { 2 | "critics": { 3 | "lint": { 4 | "engine": "eslint" 5 | }, 6 | "wc": { 7 | "limit": 5000 8 | } 9 | }, 10 | "ignore": [ 11 | "**/docs/**", 12 | "**/node_modules/**" 13 | ] 14 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 4 7 | 8 | [{package.json,*.yml}] 9 | indent_style = space 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [**.js] 16 | indent_style = space 17 | indent_size = 4 18 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'thedark1337', 3 | rules: { 4 | camelcase: [2, { 5 | allow: [ 6 | 'long_description', 7 | 'short_description' 8 | ], 9 | properties: 'always' 10 | }], 11 | 'func-style': 0, 12 | 'max-lines-per-function' :0, 13 | 'newline-per-chained-call': 0, 14 | 'no-negated-condition': 0, 15 | 'no-sync': 0, 16 | 'one-var': [2, { 17 | initialized: 'never' 18 | }], 19 | 'prefer-arrow-callback': 0, 20 | 'prefer-destructuring': 0, 21 | 'prefer-rest-params': 0, 22 | 'prefer-reflect': 0, 23 | 'prefer-spread': 0, 24 | 'require-jsdoc': 2 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Coverage directory used by tools like istanbul 9 | coverage 10 | 11 | # Dependency directories 12 | node_modules/ 13 | 14 | # Optional npm cache directory 15 | .npm 16 | 17 | # Optional eslint cache 18 | .eslintcache 19 | 20 | # Optional REPL history 21 | .node_repl_history 22 | 23 | # Output of 'npm pack' 24 | *.tgz 25 | 26 | # Yarn Integrity file 27 | .yarn-integrity 28 | 29 | # dotenv environment variables file 30 | .env 31 | 32 | -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent_size": 4, 3 | "indent_char": " ", 4 | "indent_level": 0, 5 | "indent_with_tabs": false, 6 | "preserve_newlines": true, 7 | "max_preserve_newlines": 10, 8 | "jslint_happy": false, 9 | "brace_style": "collapse", 10 | "keep_array_indentation": false, 11 | "keep_function_indentation": false, 12 | "space_before_conditional": true, 13 | "break_chained_methods": false, 14 | "eval_code": false, 15 | "unescape_strings": false, 16 | "wrap_line_length": 0 17 | } -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Chikachi 2 | Chikachi 3 | Chikachi 4 | Chikachi 5 | Colgate Minuette 6 | Darryan 7 | Gabriel Huber 8 | JTBrinkmann 9 | Jordan Hawker 10 | Saevar Berg 11 | TheBanHammer 12 | Thedark1337 13 | VaiTon86 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_install: if [[ `npm -v` != 6* ]]; then npm i -g npm@6; fi 2 | sudo: false 3 | language: node_js 4 | node_js: 5 | - '10' 6 | - '8' 7 | - '6' 8 | notifications: 9 | webhooks: 10 | urls: 11 | - https://webhooks.tfle.xyz/travis 12 | on_start: always 13 | install: 14 | - npm ci 15 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | plugAPI Changelog 2 | ================= 3 | 4 | 5.1.1 5 | --- 6 | * **UPDATED:** Dependencies to latest versions. (Commit [18f9c01](https://github.com/plugCubed/plugAPI/commit/18f9c019814f261df224254ee96c77feaaf2a2fc)) 7 | * **ADDED/CHANGED:** Added in ability to change both long description and short description (Commit [7f53b32](https://github.com/plugCubed/plugAPI/commit/7f53b32d468f9b1114cb1cf2c7721be439893bed)) 8 | 9 | 5.1.0 10 | ---- 11 | * **ADDED:** Added methods to set / get badges (Commit [1ff7f73](https://github.com/plugCubed/plugAPI/commit/1ff7f7335a)) 12 | * **CHANGED:** Remove logging for some plugdj events and ignore instead (Will still be emitted) (Commit [8e8474c](https://github.com/plugcubed/plugapi/commit/8e8474c05d)) 13 | * **CHANGED:** Parse strings as numbers in API methods that require it (Commit [8e8474c](https://github.com/plugcubed/plugapi/commit/8e8474c05d)) 14 | * **CHANGED:** Also accept 0 for sendChat delete timeout (Commit [c7a28ef](https://github.com/plugcubed/plugapi/commit/c7a28efdca)) 15 | * **CHANGED:** Websocket URL has been changed to ws-prod.plug.dj (Commit [3138c43](https://github.com/plugCubed/plugAPI/commit/3138c43156caeb1dcae7b4aa62d299624a05aef4)) 16 | * **CHANGED:** Get History before setting ADVANCE object on startup (Commit [349f478](https://github.com/plugCubed/plugAPI/commit/349f4781fd0cf815b279daab460304d01ac2ba07)) 17 | * **FIXED:** HTTP Status checks for API routes (Commit [8e8474c](https://github.com/plugcubed/plugapi/commit/8e8474c05d)) 18 | * **FIXED:** Return empty array if currentDJ or waitingDJs is null for getWaitList & getDJs (Commit [8e63576](https://github.com/plugcubed/plugapi/commit/8e63576c33)) 19 | * **FIXED:** Set grabs properly (Commit [484fdb2](https://github.com/plugcubed/plugapi/commit/484fdb2a50)) 20 | * **FIXED:** Error swallowing with API requests. (Commit [6b44907](https://github.com/plugcubed/plugapi/commit/6b449073cf)) 21 | * **FIXED:** Permissions for global roles (Commit [4c2c545](https://github.com/plugcubed/plugapi/commit/4c2c545fa9)) 22 | * **FIXED:** endpoint for waitlist unban (Commit [cb6825c](https://github.com/plugcubed/plugapi/commit/cb6825cf4f)]) 23 | * **FIXED:** revert bot.mutedTriggerNormalEvents conditional change in chat event (Commit [00af49c](https://github.com/plugcubed/plugapi/commit/00af49c8ed)) 24 | * **FIXED:** Do not assume we were given a valid array for room.usersToArray (Commit [d99576b](https://github.com/plugcubed/plugapi/commit/d99576bd4e)) 25 | * **FIXED:** Command args should now convert properly for mentioned users. Also filter out empty strings and null/undefined. (Commit [a1b8bc0](https://github.com/plugcubed/plugapi/commit/a1b8bc045e)) 26 | * **UPDATED:** Message splitting & escaping now provided by [plug-message-split](https://www.npmjs.com/package/plug-message-split) (Commit [54f7077](https://github.com/plugcubed/plugapi/commit/54f70775b7)) 27 | * **UPDATED:** Throw on joining room if user is banned, room is over capacity or other error. (Commit [bead8c8](https://github.com/plugCubed/plugAPI/commit/bead8c86be16bf4fe5bd10dd22ec2394029ba5cf)) 28 | * **UPDATED:** Add URL on 403 error (Commit [dd9d4c1](https://github.com/plugCubed/plugAPI/commit/dd9d4c16f796b5897660d9ae06bf0c675d821221)) 29 | 30 | 5.0.0 31 | ---- 32 | * **ADDED:** Added support for FORCE_NAME_CHANGE. (Commit [878ed6e](https://github.com/plugCubed/plugAPI/commit/878ed6e6ca)) 33 | * **ADDED:** Experimental auto reconnect (Commit [dbc113a](https://github.com/plugCubed/plugAPI/commit/dbc113a76e)) 34 | * **BREAKING:** humanized / jsdoc more events (Commit [af3b9d7](https://github.com/plugCubed/plugAPI/commit/af3b9d7ef7)) 35 | * **BREAKING:** more humanized event types & more JSDoc (Commit [b095179](https://github.com/plugCubed/plugAPI/commit/b0951794a1)]) 36 | * **DOCS:** Upload first iteration of auto generated docs. (Commit [b6909f7](https://github.com/plugCubed/plugAPI/commit/b6909f7ad1)) 37 | * **UPDATED:** Moved constants to a JSON & updated for new roles (Commit [ae022ab](https://github.com/plugCubed/plugAPI/commit/ae022ab898)) 38 | * **UPDATED:** Role update for plug.dj. * 1000 roles (Commit [6968cf5](https://github.com/plugCubed/plugAPI/commit/6968cf5c69)) 39 | * **UPDATED:** Added new gRoles Part 2 (#137) (Commit [273e39b](https://github.com/plugCubed/plugAPI/commit/273e39b339)) 40 | * **UPDATED:** Role Update for 9/19/17 (#136) (Commit [0ac160a](https://github.com/plugCubed/plugAPI/commit/0ac160a2b6)) 41 | * **UPDATED:** Added Socket Class for Auto Reconnect. WaitList Ban event & methods added. Updated dependencies. (Commit [ba08530](https://github.com/plugCubed/plugAPI/commit/ba08530499)) 42 | * **UPDATED:** got to latest & chalk. Fixed breaking changes. (Commit [7f327bf](https://github.com/plugCubed/plugAPI/commit/7f327bf0b9)) 43 | 44 | 45 | 5.0.0-beta.3 46 | ---- 47 | * **CHANGED:** Authentication now uses endpoints instead of parsing HTML (Commit [d7fefcf](https://github.com/plugCubed/plugAPI/commit/12976f93004be233f0b1a7c76efaaca58d7fefcf)) 48 | * **CHANGED / FIXED:** Guests now don't need to parse HTML page to login, and set cookies correctly for guest mode (Commit [8023591](https://github.com/plugCubed/plugAPI/commit/d4435def7fa1ca4900864569f0be86a088023591)) 49 | * **CHANGED:** PlugAPI is now ES2015 class based. Begin working on JSDoc (Commit [4ce3a43](https://github.com/plugCubed/plugAPI/commit/644c1ce054da8b968e5a5aaab99fe42644ce3a43)) 50 | * **ADDED:** Facebook Login is now possible (Commit [ff7ddee](https://github.com/plugCubed/plugAPI/commit/68ce54e1db461763757c8ceb8f8134a17ff7ddee)) 51 | * **UPDATED:** WS to 2.1.0 (Commit [ca772be](https://github.com/plugCubed/plugAPI/commit/c990b5234886de23fcbdd7269b006d72fca772be)) 52 | * **FIXED:** updateUser should now also update bot's user data as well. (Bug since 4.2.3) (Commit [8982a70](https://github.com/plugCubed/plugAPI/commit/89636a14fca563b564e6010ad3203dc4a8982a70)) 53 | * **CHANGED:** Readme is now modernized to ES2015 with new code examples (Commit [7d2daef](https://github.com/plugCubed/plugAPI/commit/c01dcc51af8e327e2b829c0aae887b5f27d2daef)) 54 | 55 | 56 | 5.0.0-beta.2 57 | ----- 58 | * **FIXED:** Check if ws exists and is open before sending socket events. (Commit [e07cdc1](https://github.com/plugCubed/plugAPI/commit/c9debeb17461873cc88c731f2d44176b8e07cdc1)) 59 | * **UPDATED:** Chat Handler performance should be better. (Commit [e07cdc1](https://github.com/plugCubed/plugAPI/commit/c9debeb17461873cc88c731f2d44176b8e07cdc1)) 60 | * **CHANGED:** Replace request for got. Installing PlugAPI should take less time now. (Commit [27b56e8](https://github.com/plugCubed/plugAPI/commit/0b88343c34065502b91f3289df4eab6fb27b56e8)) 61 | * **FIXED:** Guest mode checks for functions it cant use. Make getPlaylists callback optional. Improved error message for logging in. (Commit [3083a43](https://github.com/plugCubed/plugAPI/commit/683cf3193f4fcb322f4b93afeb4d3aa483083a43)) 62 | 63 | 5.0.0-beta.1 64 | ----- 65 | * **ADDED:** data.rawun to the user object, decode HTML entities for username in data.username, added silver property (Commit [1fd534d](https://github.com/plugCubed/plugAPI/commit/33802506a012944cba4ca9c2c05f1ac8f1fd534d)) 66 | * **FIXED:** data.type should now be set properly for /em messages (Commit [6795d3b](https://github.com/plugCubed/plugAPI/commit/e5fbc67a8b5ad1b0ad1aca4d4338cc0c56795d3b)) 67 | * **FIXED:** Playlist management functionality (Thanks @au2001) (Commit [10e06ed](https://github.com/plugCubed/plugAPI/commit/dd1b9d541d011bfcfdc44bb21a455c24c10e06ed)) 68 | * **FIXED:** Callback of advance should now be binded properly to avoid errors (Commit [acc9bdf](https://github.com/plugCubed/plugAPI/commit/d9fa3ac448e1bf83b444dcd114a263adcacc9bdf)) 69 | * **FIXED:** Guest users should no longer say the bot has left the room (Commit [c111c3c](https://github.com/plugCubed/plugAPI/commit/4438c2e2db1b641a34257bb1ed1973988c111c3c)) 70 | * **UPDATED:** Events & Endpoints are now in separate JSON files for easy maintenance (Commit [afe3f73](https://github.com/plugCubed/plugAPI/commit/d1d3e18be232b8c34e25bcb14cf424f55afe3f73)) 71 | * **UPDATED:** Added constants to bot prototype. Now it is possible to do bot.ROOM_ROLE.RESIDENTDJ, etc. (Commit [51dd1cb](https://github.com/plugCubed/plugAPI/commit/5136b0fcb32623eb29c5afa3727424c5e51dd1cb)) 72 | * **ADDED:** Guest Mode (Thanks @Dazzuh) (Commit [cee7c4e](https://github.com/plugCubed/plugAPI/commit/e00c8fac717e2be7436c2af5deef836e9cee7c4e)) 73 | * **FIXED:** getHistory should now be the correct data, Fixed empty strings in messageData.args for command event. (Commit [b29e55b](https://github.com/plugCubed/plugAPI/commit/f1d8167771628b9e6e0dfcf0d28f98043b29e55b)) 74 | * **BREAKING:** PlugAPI now does not support node versions < 4 (Commit [2979234](https://github.com/plugCubed/plugAPI/commit/24e00348ee0a9db00131a50db3ff644cd2979234)) 75 | * **BREAKING:** Dropped PlugAPI logger for [Jethro](https://github.com/JethroLogger/Jethro/tree/v2/docs/v2/) (Commit [0094238](https://github.com/plugCubed/plugAPI/commit/db247ec3f7c06a268553e54f5aadaf19c0094238)) 76 | * **DEPRECATE:** CreateLogger is no longer usable (Commit [0094238](https://github.com/plugCubed/plugAPI/commit/db247ec3f7c06a268553e54f5aadaf19c0094238)) 77 | 78 | * **FIXED:** Playlist counts should now be correct for playlist functions (Commit[ca3f8b8](https://github.com/plugCubed/plugAPI/commit/38c7c263083288f7015c6b300fa8bad20ca3f8b8)) 79 | * **FIXED:** Logger to be less strict on type checks and dropped cookie handler for request.jar (Commit[30a65da](https://github.com/plugCubed/plugAPI/commit/3e2fa9e20707d62341e3c1d3c7046f66730a65da)) 80 | 81 | 4.2.3 82 | ----- 83 | * **UPDATED:** Eventemitter3 to 1.2.0 and mocha-eslint to 2.0.2 (Commit [2f1d6b4](https://github.com/plugCubed/plugAPI/commit/dafe0eb48544d88b41ee5fd8bb666b7862f1d6b4)) 84 | * **FIXED:** Moved deleteAllChat to correct location (Commit [58fe475](https://github.com/plugCubed/plugAPI/commit/5dad3fc2ef9fc343c849224dfa0a24e4458fe475)) 85 | 86 | 4.2.2 87 | ----- 88 | * **UPDATED:** All dependencies to latest versions. (Commit [a4615d8](https://github.com/plugCubed/plugAPI/commit/c4d8cbe933a92664062d6f845dd6e4938a4615d8)) 89 | * **FIXED:** moderateDeleteChat should check for bouncer permissions (Commit [c0d0e33](https://github.com/plugCubed/plugAPI/commit/b4f03f842a9d38ce44f98519cb5e5dd7ac0d0e33)) 90 | 91 | 4.2.1 92 | ----- 93 | * **ADDED:** Option to delete all chat regardless of role (Commit [3b6ccc4](https://github.com/plugCubed/plugAPI/commit/37de0ccc2356c378d2a10532fe20e72b13b6ccc4)) 94 | * **UPDATED:** All dependencies to latest versions. (Commit [7aed7be](https://github.com/plugCubed/plugAPI/commit/70c74e18c851b2120f9cf12c38ace2dbe7aed7be)) 95 | * **UPDATED:** Codestyle to match newest ESLint config.(Commit [fc41705](https://github.com/plugCubed/plugAPI/commit/600a708fb7681752c9356d9e7d12ff6f1fc41705)) 96 | * **ADDED:** Linting for tests. (Commit [fc41705](https://github.com/plugCubed/plugAPI/commit/600a708fb7681752c9356d9e7d12ff6f1fc41705)) 97 | 98 | 4.2.0 99 | ------ 100 | * **FIXED:** Commands should now be able to be deleted. (Commit [3aacf8c](https://github.com/plugCubed/plugAPI/commit/548b4b06d463f32a98e745aeceae415c83aacf8c)) 101 | * **FIXED:** parseInt if getUser uid is String (Commit [3aacf8c](https://github.com/plugCubed/plugAPI/commit/548b4b06d463f32a98e745aeceae415c83aacf8c)) 102 | * **ADDED:** MAINT_MODE and MAINT_MODE_ALERT events (Commit [3aacf8c](https://github.com/plugCubed/plugAPI/commit/548b4b06d463f32a98e745aeceae415c83aacf8c)) 103 | * **IMPROVE:** JSDoc to be more consistent (Commit [3aacf8c](https://github.com/plugCubed/plugAPI/commit/548b4b06d463f32a98e745aeceae415c83aacf8c)) 104 | * **IMPROVE:** Typo in utils comment (Commit [322bcbb](https://github.com/plugCubed/plugAPI/commit/44f130bf24b60274fe7fd58e5e0ca1f15322bcbb)) 105 | 4.1.2 106 | ------ 107 | * **ADDED:** `Object.assign` Ponyfill (Commit [48d3c92](https://github.com/plugCubed/plugAPI/commit/106f9eed0b89d26c74152d1e7336431ca48d3c92)) 108 | * **UPDATED:** Request to 2.61.0 , ws to 0.8.0 (Commit [48d3c92](https://github.com/plugCubed/plugAPI/commit/106f9eed0b89d26c74152d1e7336431ca48d3c92)) 109 | * **CHANGED:** Use `Array.isArray` instead of `util.isArray` due to being deprecated (Commit [ac2bdbc](https://github.com/plugCubed/plugAPI/commit/83a28892112ee12ed1d9f8cff4a4001b0ac2bdbc)) 110 | * **CHANGED:** Use `Object.assign` ponyfill instead of util._extend (Commit [1927037](https://github.com/plugCubed/plugAPI/commit/3a26d81482e2c15bc0d2c624083da7d841927037)) 111 | * **FIXED:** Null / Undefined messages should not break sendChat (Commit [ac2bdbc](https://github.com/plugCubed/plugAPI/commit/83a28892112ee12ed1d9f8cff4a4001b0ac2bdbc)) 112 | 113 | 4.1.1 114 | ------ 115 | * **FIXED:** Null issue with ws (Commit [73d9409](https://github.com/plugCubed/plugAPI/commit/5431ffa108aa4eca6f59a86b8ee2420db73d9409)) 116 | * **FIXED:** A few broken links in readme (Commit [d635862](https://github.com/plugCubed/plugAPI/commit/f56d59e93ba7b1f5dd3e7ae762d42b453d635862)) 117 | 118 | 4.1.0 119 | ------ 120 | * **ADDED:** Starting work on mocha test. Linting added for now. (Commit [33375f3](https://github.com/plugCubed/plugAPI/commit/2ca5357b9c10d9251ca44022afe37a22533375f3)) 121 | * **CHANGED:** Linted and fixed many issues with code. (Commit [33375f3](https://github.com/plugCubed/plugAPI/commit/2ca5357b9c10d9251ca44022afe37a22533375f3)) 122 | * **FIXED:** Forgot to rename function in switch case. (Commit [33375f3](https://github.com/plugCubed/plugAPI/commit/2ca5357b9c10d9251ca44022afe37a22533375f3)) 123 | * **ADDED:** Default case for mutes (sets to null if time is not defined) (Commit [33375f3](https://github.com/plugCubed/plugAPI/commit/2ca5357b9c10d9251ca44022afe37a22533375f3)) 124 | * **CHANGED:** JSDoc to be valid and descriptions where needed. (Commit [33375f3](https://github.com/plugCubed/plugAPI/commit/2ca5357b9c10d9251ca44022afe37a22533375f3)) 125 | * **CHANGED:** Use Strict mode. (Commit [33375f3](https://github.com/plugCubed/plugAPI/commit/2ca5357b9c10d9251ca44022afe37a22533375f3)) 126 | 127 | 4.0.1 128 | ------ 129 | * **FIXED:** Room.js was returning Internal Users Array and not the local variable. (Commit [07793b8](https://github.com/plugCubed/plugAPI/commit/f02a2d57afbda19f9247863fd49d2a59307793b8)) 130 | 131 | 4.0.0 132 | ------ 133 | * **CHANGED:** Room.js object checks to find both null and undefined (Commit [c54e34b](https://github.com/plugCubed/plugAPI/commit/864070e1fd2662e0254dddf801bcdff4b1c54e3b)) 134 | * **REMOVED:** cookies saving / cookies.tmp file. Was causing too many issues. (Commit [10059f7](https://github.com/plugCubed/plugAPI/commit/977925a931b89e8ba0300ced2fe7bcbb710059f7)) (Commit [30b2a13](https://github.com/plugCubed/plugAPI/commit/17014bfaf92b5532f26ab5b2c6e368b7d30b2a13)) 135 | * **CHANGED:** Most of plugAPI is now compliant to the node.js style of callback(err, data) (Commit [10059f7](https://github.com/plugCubed/plugAPI/commit/977925a931b89e8ba0300ced2fe7bcbb710059f7)) 136 | * **CHANGED:** Use IsFinite ES6 polyfill for a more accurate check of numbers. (Commit [10059f7](https://github.com/plugCubed/plugAPI/commit/977925a931b89e8ba0300ced2fe7bcbb710059f7)) 137 | * **ADDED:** Use Handle-Errors to throw if no callback is defined, else return the error. (Commit [10059f7](https://github.com/plugCubed/plugAPI/commit/977925a931b89e8ba0300ced2fe7bcbb710059f7)) 138 | * **FIXED:** Empty Arguments getting interpreted as 0. (Commit [10059f7](https://github.com/plugCubed/plugAPI/commit/977925a931b89e8ba0300ced2fe7bcbb710059f7)) 139 | * **FIXED:** moderateForceSkip skipping other users. (Commit [77baafb](https://github.com/plugCubed/plugAPI/commit/6cf13b75a7d85ba436dadff8a1fe952a377baafb)) 140 | * **CHANGED:** From Eventemitter 2 to Eventemitter 3. (Commit [bea2829](https://github.com/plugCubed/plugAPI/commit/7c5806b8da427a26e546c8c2e87d47cdabea2829)) 141 | 142 | 143 | **BREAKING CHANGES:** 144 | * OnAny and OffAny are now removed. 145 | * Async now requires end users to handle errors by plugAPI. 146 | 147 | 3.5.0 148 | ------ 149 | * **REMOVED:** Removed Bin and Minification build. Not neccesary for a node.js library. (Commit [7ddf40](https://github.com/plugCubed/plugAPI/commit/63ab9c23fbaaa6f45fc901d8c0995e89dd7ddf40)) 150 | * **ADDED:** selfSkip method to skip self (Commit [44c6b78](https://github.com/plugCubed/plugAPI/commit/58ddd2eb187ef459fe18f5d6015fc7ec744c6b78)). 151 | * **CHANGED:** moderateForceSkip will skip self if person djing is the bot (Commit [44c6b78](https://github.com/plugCubed/plugAPI/commit/58ddd2eb187ef459fe18f5d6015fc7ec744c6b78)). 152 | * **CHANGED:** Codestyle to be multiple var declarations. Fixed a duplicate var in room.js as well (Commit [44c6b78](https://github.com/plugCubed/plugAPI/commit/58ddd2eb187ef459fe18f5d6015fc7ec744c6b78)). 153 | * **CHANGED:** Added in a baseURL to request (Commit [44c6b78](https://github.com/plugCubed/plugAPI/commit/58ddd2eb187ef459fe18f5d6015fc7ec744c6b78)). 154 | * **UPDATED:** Dependencies: chalk@1.1.0, request to 2.60.0 (Commit [44c6b78](https://github.com/plugCubed/plugAPI/commit/58ddd2eb187ef459fe18f5d6015fc7ec744c6b78)). 155 | * **CHANGED:** Removed outdated node-html-encoder for [he](https://npmjs.com/package/he) (Commit [44c6b78](https://github.com/plugCubed/plugAPI/commit/58ddd2eb187ef459fe18f5d6015fc7ec744c6b78)). 156 | * **CHANGED:** Src is now Lib to fit the pattern of node.js modules. (Commit [44c6b78](https://github.com/plugCubed/plugAPI/commit/58ddd2eb187ef459fe18f5d6015fc7ec744c6b78)). 157 | * **REMOVED:** Grunt and ClosureCompiler. Will add testing later on. (Commit [7ddf40](https://github.com/plugCubed/plugAPI/commit/63ab9c23fbaaa6f45fc901d8c0995e89dd7ddf40)) 158 | 159 | 3.4.0 160 | ------- 161 | 162 | * **CHANGED:** Removed getStatus and setStatus and updated plugAPI's status object. (Commit [e2e331e](https://github.com/plugCubed/plugAPI/commit/077946781eb1a2ac72a47c3d11b7ab7c6e2e331e)). 163 | * **ADDED:** Added friendRequest, gifted, and modBan to ignore list. (Commit [f9c703d](https://github.com/plugCubed/plugAPI/commit/b07028daef2e7eba04d5176006e9e0e20f9c703d)). 164 | * **ADDED:** Added djListCycle and djListLocked event objects, Added getBoothMeta function. (Commit [0010d0b](https://github.com/plugCubed/plugAPI/commit/e3533aa8e78b667a101da562149bdb3920010d0b)). 165 | * **CHANGED:** Changed djListUpdate to emit user objects instead of ids. (Commit [0010d0b](https://github.com/plugCubed/plugAPI/commit/e3533aa8e78b667a101da562149bdb3920010d0b)). 166 | * **UPDATED:** Updated user object properties and defaults.(Commit [b5abec1](https://github.com/plugCubed/plugAPI/commit/fcb031643df302495bdd2dec1495df0f5b5abec1)). 167 | * **ADDED:** Added room description, name, and welcome update events. Added getRoomMeta function. Don't add guests to users listing. (Commit [aab71a9](https://github.com/plugCubed/plugAPI/commit/9ced439cdcbabec1b3e0a488e253a58f5aab71a9)). 168 | * **ADDED:** Added 2 more bots to readme info. QBot and Holly Refbots (Commit [9bfd810](https://github.com/plugCubed/plugAPI/commit/6b3c24c8de4c28c598945513a5b98d8ba9bfd810)). (Commit [f9aa9a8](https://github.com/plugCubed/plugAPI/commit/b3a53099804b7f3cb3c552026f6620d6bf9aa9a8)). 169 | 170 | 3.3.0 171 | ------- 172 | * **CHANGED:** Changed Package.json license to new [format](https://docs.npmjs.com/files/package.json#license) (Commit [46dc1fe](https://github.com/plugCubed/plugAPI/commit/086561bf265b040768d4a99f84c0b05cd46dc1fe)). 173 | * **UPDATED:** Dependencies. - Deasync@0.1.0, Request@2.56.0, Source Map Support@0.3.1 (Commit [46dc1fe](https://github.com/plugCubed/plugAPI/commit/086561bf265b040768d4a99f84c0b05cd46dc1fe)). 174 | * **FIXED:** Playlist functions not working properly due to callbacks. (Commit [4c1f915](https://github.com/plugCubed/plugAPI/commit/90c3816177cda33faf3263f15d644dcac4c1f915)) 175 | 176 | 3.2.1 177 | ------- 178 | * **CHANGED:** Npmignore apparently is case sensitive... actually ignore Gruntfile.js 179 | * **FIXED:** Markdown in Changelog was wrong in previous entries. 180 | 181 | 3.2.0 182 | ------- 183 | * **REMOVED:** REPL Example. Not very used anymore. 184 | * **ADDED:** Npmignore file to ignore files when publishing. 185 | * **FIXED:** Latest WS was breaking connection due to removal of Origin Header. 186 | * **CHANGED:** Pinned dependencies to avoid issues with breaking changes. 187 | * **CHANGED:** Changed TAT to contributor and Thedark1337 to Author 188 | * **ADDED:** Bugs URL in package json to link to Github issues. 189 | * **ADDED:** David Badge to check if dependencies are at latest version. 190 | 191 | 3.1.4 192 | ------- 193 | * **ADDED:** Chat Level Update Object and notificaton when it changes. 194 | * **IMPROVE:** Logging in to plug.dj via plugAPI should show more descriptive errors instead of stacktraces that were unreadable. 195 | * **CHANGED:** Code change for cookies.js, should be typeof === 'undefined' 196 | * **FIXED:** CreatePlaylist typeof check was String instead of string. Fix provided by @purechaose 197 | 198 | 3.1.3 199 | ------- 200 | * **FIXED:** Grab was always false, and users were not being removed from staff properly. Fix provided by @anjanms. 201 | * **FIXED:** ModerateBanUser was not returning true if successful.Fix provided by @chrishayesmu. 202 | 203 | 3.1.2 204 | ------- 205 | * **FIXED:** Added type back to chat messages. 206 | 207 | 3.1.1 208 | ------- 209 | * **FIXED:** Update for Go DJ web socket 210 | 211 | 3.1.0 212 | ------- 213 | * **ADDED:** Added utils 214 | * **FIXED:** Fixed being unable to lock and unlock the booth without reloading the bot. (#57) 215 | 216 | 3.0.0 217 | ------- 218 | * **FIXED:** Working with plug.dj > v1.0.0 219 | * New connection method for socket server (Chat server and socket server combined) 220 | * **ADDED:** Manage a history of chat, limited to 512 messages (Same limit as browser clients) 221 | * **ADDED:** Documentation in JSDoc format (If you're using an IDE, this should give you in-editor documentation) 222 | * **CHANGED:** Uses e-mail and password as authentication 223 | * **CHANGED:** `bot.ROLE` split up into `bot.ROOM_ROLE` and `bot.GLOBAL_ROLE` 224 | * **REMOVED:** All deprecated functions are removed 225 | * **REMOVED:** `bot.isUsernameAvailable(name, callback)` and `bot.changeUsername(name, callback)` removed 226 | * A lot more, can't remember everything I have done... 227 | 228 | 2.4.1 229 | ------- 230 | * **FIXED:** Compatibility with pre-2.4.0 command eventlisteners using chatID (Commit [b9afcba](https://github.com/TATDK/plugapi/commit/b9afcba861d7eb85e05df3e47506db2de4f950e2)) 231 | 232 | 2.4.0 233 | ------- 234 | * **ADDED:** Added deleteCommands (Commit [bf8acd8](https://github.com/TATDK/plugapi/commit/bf8acd87927f05caeed737b8162621a13740a0e1)) 235 | * **IMPROVE:** Improvements for plug.dj v0.9.833 (Commit [5fa24ad](https://github.com/TATDK/plugapi/commit/5fa24adfafca678dbb60f86b1a072c38e3c63714)) 236 | 237 | 2.3.2 238 | ------- 239 | * **FIXED:** Temporary fix for plug.dj v0.9.833 (Commit [69e2f51](https://github.com/TATDK/plugapi/commit/69e2f51310dabbeda9dc6017eb522d05089ce5de)) 240 | 241 | 2.3.1 242 | ------- 243 | * **FIXED:** Fix detecting numbers (Commit [0319ad7](https://github.com/TATDK/plugapi/commit/0319ad7f173a2bae92212744053109801203ce1c)) 244 | 245 | 2.3.0 246 | ------- 247 | * **ADDED:** Set status via `setStatus` (Commit [7bd4390](https://github.com/TATDK/plugapi/commit/7bd4390d15d8946e66ce2680b88336a32ab10dfc)) 248 | * **IMPROVE:** Convert numbers in args to actual numbers (Commit [629a655](https://github.com/TATDK/plugapi/commit/629a6553358b2dd0ee001b269154067b67cc4331)) 249 | * **FIXED:** Fix error with commandPrefix longer than 1 character (Commit [c259fe8](https://github.com/TATDK/plugapi/commit/c259fe81b26de6f97c4291c0617e0bc853d9c35f)) 250 | 251 | 2.2.8 252 | ------- 253 | * **IMPROVE:** Include all user data on `USER_LEAVE` (Commit [b198ddc](https://github.com/TATDK/plugapi/commit/b198ddc33143e68943d794f66d1ac2b5acbf62cb)) 254 | 255 | 2.2.7 256 | ------- 257 | * **FIXED:** Gateway error requesting captcha (Commit [f5e4590](https://github.com/TATDK/plugapi/commit/f5e4590dd7189dc6b9125f30034fd93eca09d51d)) 258 | 259 | 2.2.6 260 | ------- 261 | * **FIXED:** Fix dependency for `sockjs-client-node` (Commit [a1650eb](https://github.com/TATDK/plugapi/commit/a1650ebdbc14e102991e93e0fe4f650e39f34d40)) 262 | 263 | 2.2.5 264 | ------- 265 | * **FIXED:** Be sure the user exists while handling MODERATE_STAFF messages - Fixes #14 (Commit [7883859](https://github.com/TATDK/plugapi/commit/788385993247e01eb9633e65bf524edaf868eecf)) 266 | 267 | 2.2.4 268 | ------- 269 | * **IMPROVE:** Allow command prefixes longer than 1 character (Commit [56374ed](https://github.com/TATDK/plugapi/commit/56374ed0bb970416bb825918ae20b7c7c78fee41)) 270 | * **IMPROVE:** Added sourcemap for easier tracking of issues and bugs (Commit [56374ed](https://github.com/TATDK/plugapi/commit/56374ed0bb970416bb825918ae20b7c7c78fee41)) 271 | 272 | 2.2.3 273 | ------- 274 | * **IMPROVE:** Improve invalid roomname detection (Commit [3d0dbee](https://github.com/TATDK/plugapi/commit/3d0dbeeb28fa6073ef53dd530517d86dea928a90)) 275 | 276 | 2.2.2 277 | ------- 278 | * **IMPROVE:** Allow banning people not in the room (Commit [78e7945](https://github.com/TATDK/plugapi/commit/78e794527174635a57adbd11a3f9be0ac0fabd2c)) 279 | 280 | 2.2.1 281 | ------- 282 | * **FIXED:** Fixed coding error (Commit [ba52159](https://github.com/TATDK/plugapi/commit/ba5215962360a48300e1ddb2b2a3aeb7d2df0c4e)) 283 | 284 | 2.2.0 285 | ------- 286 | * **ADDED:** Added `havePermission` (Commit [a323aa8](https://github.com/TATDK/plugapi/commit/a323aa8f0860ee5e0b2444b5fded4fa80174cac0)) 287 | 288 | 2.1.0 289 | ------- 290 | * **ADDED:** Added `close` (Commit [ffa6f65](https://github.com/TATDK/plugapi/commit/ffa6f65efc1a9f57a8417637f5978185a95f6fe6)) 291 | * **ADDED:** Ability to set other command prefix (Commit [a122f47](https://github.com/TATDK/plugapi/commit/a122f473b4934b49d09a8f73c1263433eb6f18ab)) 292 | * **ADDED:** Ability to have bot's own messages processed (Commit [ea226c8](https://github.com/TATDK/plugapi/commit/ea226c8fdaab117560d9e235c0d3e05bb6129cca)) 293 | * **ADDED:** Add function to move a user in waitlist through user object (Commit [303d5fc](https://github.com/TATDK/plugapi/commit/303d5fced045d0e6e06aea54fa912347122349ac)) 294 | * **IMPROVE:** Improve code for joining room (Commit [47335b2](https://github.com/TATDK/plugapi/commit/47335b257ff4bbdb62231e014e9818ed374ca5ae)) 295 | * **IMPROVE:** Added checks to prevent unnecessary requests (Commit [c1691ca](https://github.com/TATDK/plugapi/commit/c1691cac98c557b65a91bc9b5ef3993eb2e41d4a)) 296 | * **FIXED:** Connect to p3's socket server via HTTPS (Commit [d5b32d3](https://github.com/TATDK/plugapi/commit/d5b32d3da5bcaf89e54c65f704b39d71ee05248e)) 297 | * **DEPRECATE:** Deprecate `setLogObject` (Commit [3e8c690](https://github.com/TATDK/plugapi/commit/3e8c690caba5946e198b361c0693419a84ff3054)) 298 | * **DEPRECATE:** Deprecate old parameters for `getTwitterAuth` (Commit [b7e2579](https://github.com/TATDK/plugapi/commit/b7e2579064f438ba94f63e82d269583320798d32)) 299 | * **DEPRECATE:** Deprecate `moderateUnBanUser` (Commit [5bb2fda](https://github.com/TATDK/plugapi/commit/5bb2fda5fd1900a2a64ad9de106f10e9a42a8de6)) 300 | * **DEPRECATE:** Deprecate `addToWaitlist` and `removeFromWaitlist` on user objects (Commit [b1cf712](b1cf712bd77374f5f8471e51fccbf66ead2de225)) 301 | 302 | 2.0.3 303 | ------- 304 | * **FIXED:** Fix broken `getHistory` (Commit [645f03a](https://github.com/TATDK/plugapi/commit/645f03a30504183ce24d7034c66fe08579657281)) 305 | 306 | 2.0.2 307 | ------- 308 | * **FIXED:** More inteligent way of running the `queueTicker` (Commit [80e314c](https://github.com/TATDK/plugapi/commit/80e314c6bc4a18a109d3bf14f3602c6d8ccbc3de)) 309 | 310 | 2.0.1 311 | ------- 312 | * **FIXED:** Changed required node version from 0.6 to 0.10 (Commit [78ec90f](https://github.com/TATDK/plugapi/commit/78ec90fca5d2fa1404e85e8df49b14ea76bc3372)) 313 | 314 | 2.0.0 315 | ------- 316 | * **ADDED:** Settings files for formatting in .jsbeautifyrc and .editorconfig format (Commit [5123fbc](https://github.com/TATDK/plugapi/commit/5123fbce7206c15d0e985cc73c0e3ba82c8bb258)) 317 | * **ADDED:** Prevent spamming plug.dj's servers (Commit [e52a3ef](https://github.com/TATDK/plugapi/commit/e52a3eff27df94914911f0cbdcd67d4bb2888062)) 318 | * **ADDED:** Support to use plugCubed's socket server for PMs (Commit [e52a3ef](https://github.com/TATDK/plugapi/commit/e52a3eff27df94914911f0cbdcd67d4bb2888062)) 319 | * **FIXED:** plug.dj backend changes (Commit [e52a3ef](https://github.com/TATDK/plugapi/commit/e52a3eff27df94914911f0cbdcd67d4bb2888062)) 320 | * **FIXED:** `plug-dj-login` is now an optional dependency as it was supposed to be (Commit [9003025](https://github.com/TATDK/plugapi/commit/90030251ac2120c81634d8fad6604678a6c348c6)) 321 | * Using Grunt to run Closure Compiler on the api (Commit [7719cdc](https://github.com/TATDK/plugapi/commit/7719cdc760e21cf1a61dac1fb6485e11f9fc89f7)) 322 | * Fix order of readme (Commit [b7b66a6](https://github.com/TATDK/plugapi/commit/b7b66a695de892cc218d973032ccb7e1ab249183)) 323 | * Facepalm, bin folder needs to be included (Commit [7719cdc](https://github.com/TATDK/plugapi/commit/7719cdc760e21cf1a61dac1fb6485e11f9fc89f7)) 324 | 325 | 1.1.4 326 | ------- 327 | * **FIXED:** Ensure gateway requests sends an array (Commit [d000f9d](https://github.com/TATDK/plugapi/commit/d000f9d8b77c92594773f5808546ef39a442e0bb)) 328 | 329 | 1.1.3 330 | ------- 331 | * **FIXED:** Uncompress the updatecode from the server (Commit [963235a](https://github.com/TATDK/plugapi/commit/963235af51a80e160f1a07a7ca162203c59720ea)) 332 | * **REMOVE:** Removed `cheerio` as a dependency as it wasn't used (Commit [52731a6](https://github.com/TATDK/plugapi/commit/52731a6af870120888731e6b559eb0b82b65cdd8)) 333 | 334 | 1.1.2 335 | ------- 336 | * **CHANGED:** Now getting the updateCode from server (Commit [b32bc48](https://github.com/TATDK/plugapi/commit/b32bc4801c1208236f81fa46441a7e72a845786b)) 337 | 338 | 1.1.1 339 | ------- 340 | * **FIXED:** Hopefully give an error when gateway returns invalid JSON (Commit [dfb9c6c](https://github.com/TATDK/plugapi/commit/dfb9c6c19ed19bac5c6f318d349b97963d0ee0bf)) 341 | * **FIXED:** Actually remove the user when the user leaves the room (Commit [dfb9c6c](https://github.com/TATDK/plugapi/commit/dfb9c6c19ed19bac5c6f318d349b97963d0ee0bf)) 342 | 343 | 1.1.0 344 | ------- 345 | * **ADDED:** Added optional autodelete of message (Commit [af6f879](https://github.com/TATDK/plugapi/commit/af6f879f52239bc0c67d46c84c9cb464f17e57c6)) 346 | 1. Added respondTimeout for commands 347 | 1. Added timeout parameter for `intChat`, `chat` and `sendChat` 348 | * **CHANGED:** Now using MIT license (Commit [93da87d](https://github.com/TATDK/plugapi/commit/93da87d3ad0655a5cb265ee50a80c6189f92004d)) 349 | 350 | 1.0.0 351 | ------- 352 | * Initial Release 353 | * TAT is added as maintainer of the official npm package 354 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | =============== 3 | 4 | Copyright (c) 2014 Chris Vickery, Thomas "TAT" Andresen and other contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## plugAPI [![Build Status](https://img.shields.io/travis/plugCubed/plugAPI.svg)](https://travis-ci.org/plugCubed/plugAPI) [![npm version](http://img.shields.io/npm/v/plugapi.svg)](https://npmjs.org/package/plugapi) [![npm downloads](https://img.shields.io/npm/dm/plugapi.svg)](https://npmjs.org/package/plugapi) [![NPM](https://img.shields.io/npm/l/plugapi.svg)](https://github.com/plugCubed/plugAPI/blob/master/LICENSE.md) [![David](https://img.shields.io/david/plugcubed/plugapi.svg)](https://david-dm.org/plugcubed/plugapi) [![Discord Server](https://img.shields.io/discord/249303078493224960.svg)](https://plugcubed.net/discord) 2 | 3 | 4 | ## About 5 | 6 | A generic NodeJS API for creating plug.dj bots. 7 | 8 | Originally by [Chris Vickery](https://github.com/chrisinajar), now maintained by [TAT](https://github.com/TATDK) and [The plug³ Team](https://github.com/plugCubed). 9 | 10 | ## How to use 11 | Run the following: 12 | 13 | ``` javascript 14 | npm install plugapi 15 | ``` 16 | 17 | You can choose to instantiate plugAPI with either Sync or Async: 18 | 19 | **Sync:** 20 | 21 | ```javascript 22 | const PlugAPI = require('plugapi'); 23 | const bot = new PlugAPI({ 24 | email: '', 25 | password: '' 26 | }); 27 | 28 | bot.connect('roomslug'); // The part after https://plug.dj 29 | 30 | bot.on(PlugAPI.events.ROOM_JOIN, (room) => { 31 | console.log(`Joined ${room}`); 32 | }); 33 | ``` 34 | 35 | **Async:** 36 | 37 | ```javascript 38 | const PlugAPI = require('plugapi'); 39 | 40 | new PlugAPI({ 41 | email: '', 42 | password: '' 43 | }, (err, bot) => { 44 | if (!err) { 45 | bot.connect('roomslug'); // The part after https://plug.dj 46 | 47 | bot.on(PlugAPI.events.ROOM_JOIN, (room) => { 48 | console.log(`Joined ${room}`); 49 | }); 50 | } else { 51 | console.log(`Error initializing plugAPI: ${err}`); 52 | } 53 | }); 54 | ``` 55 | --- 56 | **New features in V5.0.0** 57 | 58 | Guest login is now possible if no userdata is passed into plugAPI or guest is set to true 59 | 60 | **Guest:** 61 | ```javascript 62 | const PlugAPI = require('plugapi'); 63 | const bot = new PlugAPI(); 64 | // OR 65 | const bot = new PlugAPI({ 66 | guest: true 67 | }); 68 | ``` 69 | 70 | Facebook login is now possible. Easiest way to obtain the Access Token and user ID is to login via fb on plug and view the request data. 71 | 72 | **Facebook:** 73 | ```javascript 74 | const PlugAPI = require('plugapi'); 75 | const bot = new PlugAPI({ 76 | facebook: { 77 | accessToken: 'xxxxxxxx', 78 | userID: 'xxxxxxxx' 79 | } 80 | }); 81 | ``` 82 | 83 | PlugAPI now uses tough-cookie to store cookies. Refer to the wiki for more information. 84 | 85 | --- 86 | ## Examples 87 | Here are some bots that are using this API. 88 | 89 | | Botname | Room | 90 | | ---------------------------------------------------- | --------------------------------------------------------------- | 91 | | AuntJackie | [Mix-N-Mash](https://plug.dj/mix-n-mash-2) | 92 | | BotX | [NightCore Fanclub](https://plug.dj/nightcore-fanclub) | 93 | | [BeavisBot](https://github.com/AvatarKava/BeavisBot) | [I <3 the 80's and 90's](https://plug.dj/i-the-80-s-and-90-s-1) | 94 | | brainbot | [5M4R7](https://plug.dj/5m4r7) | 95 | | Charlotte | [Youtunes](https://plug.dj/youtunes) | 96 | | -DnB- | [Drum & Bass](https://plug.dj/drum-bass) | 97 | | DRPG | [Discord Dungeons](https://plug.dj/discorddungeons) | 98 | | Ekko | [EDT](https://plug.dj/edtentertainment) | 99 | | [F!shtank](https://github.com/botnation/fishtank) | [SWaQ Hanger](https://plug.dj/swaq-hanger/) | 100 | | FlavorBar | [Flavorz](https://plug.dj/flavorz) | 101 | | FoxBot | [Approaching Nirvana](https://plug.dj/approachingnirvana) | 102 | | Holly Refbots | [Connect The Songs (Read Info!)](https://plug.dj/connect-the-songs-read-info/) | 103 | | KawaiiBot | [AnimeMusic](https://plug.dj/hummingbird-me) | 104 | | prehibicja | [[PL] Prohibicja.xyz ANY GENRE](https://plug.dj/prohibicja) | 105 | | QBot | [EDM Qluster](https://plug.dj/qluster) | 106 | | Skynet Cubed | [PlugCubed](https://plug.dj/plugcubed) | 107 | | TFLBot | [The F**k Off Lounge \| TFL](https://plug.dj/thedark1337) | 108 | | [Toaster-chan](https://git.io/vDTfR) | [☆ ♥ Nightcore-331 ♥ ☆](https://plug.dj/nightcore) | 109 | Have a bot that uses the API? [**Let us know!**](https://github.com/plugCubed/plugAPI/issues/new) 110 | --- 111 | ## EventListener 112 | You can listen on essentially any event that plug emits. 113 | ```javascript 114 | // basic chat handler to show incoming chats formatted nicely 115 | bot.on(PlugAPI.events.CHAT, (data) => { 116 | if (data.type === 'emote') { 117 | console.log(data.from + data.message); 118 | } else {` 119 | console.log(`${data.from} > ${ data.message}`); 120 | } 121 | }); 122 | ``` 123 | 124 | Here's an example for automatic reconnecting on errors / close events 125 | ```javascript 126 | const reconnect = () => { bot.connect(ROOM); }; 127 | 128 | bot.on('close', reconnect); 129 | bot.on('error', reconnect); 130 | ``` 131 | --- 132 | 133 | ## API Documentation 134 | 135 | For V4 documentation, the [Wiki](https://github.com/plugcubed/plugapi/wiki) is the best resource. 136 | 137 | For V5 documentation, please refer to the [Docs](https://plugcubed.github.io/plugAPI) for documentation on methods and events. 138 | The documentation is written in JSDoc in the respective files found in the lib/ folder. 139 | If there are changes to be made, edit the JSDoc and run the followng command: 140 | ```javascript 141 | npm run docs 142 | ``` 143 | Submit a pull request and wait for review 144 | 145 | --- 146 | ## Contribute 147 | 1. Clone repository to an empty folder. 148 | 2. CD to the folder containing the repository. 149 | 3. Run `npm install` to set up the environment. 150 | 4. Edit your changes in the code, and make sure it follows our codestyle. 151 | 5. Run `npm test` to make sure all tests pass. 152 | 6. After it's bug free, you may submit it as a Pull Request to this repository. 153 | --- 154 | ## Misc Options 155 | 156 | **Multi line chat** 157 | 158 | Since plug.dj cuts off chat messages at 250 characters, you can choose to have your bot split up chat messages into multiple lines. 159 | 160 | **Delete Message Blocks** 161 | 162 | With how plug currently works, deleting messages deletes the entire group of messages from the same user. Set this option to disallow that. 163 | 164 | **Delete All Chat** 165 | 166 | PlugAPI mimics plug's behavior in disallowing deletion of chat of users above the bot's rank. Setting this option to true will bypass that check. 167 | 168 | ```javascript 169 | var bot = new PlugAPI(auth); 170 | bot.deleteMessageBlocks = false; //set to true if you want the bot to not delete grouped messages. Default is false. 171 | bot.deleteAllChat = false; // Set to true to enable deletion of chat regardless of role . Default is false 172 | bot.multiLine = true; // Set to true to enable multi line chat. Default is false 173 | bot.multiLineLimit = 5; // Set to the maximum number of lines the bot should split messages up into. Any text beyond this number will just be omitted. Default is 5. 174 | ``` 175 | -------------------------------------------------------------------------------- /docs/assets/anchor.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * AnchorJS - v4.0.0 - 2017-06-02 3 | * https://github.com/bryanbraun/anchorjs 4 | * Copyright (c) 2017 Bryan Braun; Licensed MIT 5 | */ 6 | /* eslint-env amd, node */ 7 | 8 | // https://github.com/umdjs/umd/blob/master/templates/returnExports.js 9 | (function(root, factory) { 10 | 'use strict'; 11 | if (typeof define === 'function' && define.amd) { 12 | // AMD. Register as an anonymous module. 13 | define([], factory); 14 | } else if (typeof module === 'object' && module.exports) { 15 | // Node. Does not work with strict CommonJS, but 16 | // only CommonJS-like environments that support module.exports, 17 | // like Node. 18 | module.exports = factory(); 19 | } else { 20 | // Browser globals (root is window) 21 | root.AnchorJS = factory(); 22 | root.anchors = new root.AnchorJS(); 23 | } 24 | })(this, function() { 25 | 'use strict'; 26 | function AnchorJS(options) { 27 | this.options = options || {}; 28 | this.elements = []; 29 | 30 | /** 31 | * Assigns options to the internal options object, and provides defaults. 32 | * @param {Object} opts - Options object 33 | */ 34 | function _applyRemainingDefaultOptions(opts) { 35 | opts.icon = opts.hasOwnProperty('icon') ? opts.icon : '\ue9cb'; // Accepts characters (and also URLs?), like '#', '¶', '❡', or '§'. 36 | opts.visible = opts.hasOwnProperty('visible') ? opts.visible : 'hover'; // Also accepts 'always' & 'touch' 37 | opts.placement = opts.hasOwnProperty('placement') 38 | ? opts.placement 39 | : 'right'; // Also accepts 'left' 40 | opts.class = opts.hasOwnProperty('class') ? opts.class : ''; // Accepts any class name. 41 | // Using Math.floor here will ensure the value is Number-cast and an integer. 42 | opts.truncate = opts.hasOwnProperty('truncate') 43 | ? Math.floor(opts.truncate) 44 | : 64; // Accepts any value that can be typecast to a number. 45 | } 46 | 47 | _applyRemainingDefaultOptions(this.options); 48 | 49 | /** 50 | * Checks to see if this device supports touch. Uses criteria pulled from Modernizr: 51 | * https://github.com/Modernizr/Modernizr/blob/da22eb27631fc4957f67607fe6042e85c0a84656/feature-detects/touchevents.js#L40 52 | * @returns {Boolean} - true if the current device supports touch. 53 | */ 54 | this.isTouchDevice = function() { 55 | return !!( 56 | 'ontouchstart' in window || 57 | (window.DocumentTouch && document instanceof DocumentTouch) 58 | ); 59 | }; 60 | 61 | /** 62 | * Add anchor links to page elements. 63 | * @param {String|Array|Nodelist} selector - A CSS selector for targeting the elements you wish to add anchor links 64 | * to. Also accepts an array or nodeList containing the relavant elements. 65 | * @returns {this} - The AnchorJS object 66 | */ 67 | this.add = function(selector) { 68 | var elements, 69 | elsWithIds, 70 | idList, 71 | elementID, 72 | i, 73 | index, 74 | count, 75 | tidyText, 76 | newTidyText, 77 | readableID, 78 | anchor, 79 | visibleOptionToUse, 80 | indexesToDrop = []; 81 | 82 | // We reapply options here because somebody may have overwritten the default options object when setting options. 83 | // For example, this overwrites all options but visible: 84 | // 85 | // anchors.options = { visible: 'always'; } 86 | _applyRemainingDefaultOptions(this.options); 87 | 88 | visibleOptionToUse = this.options.visible; 89 | if (visibleOptionToUse === 'touch') { 90 | visibleOptionToUse = this.isTouchDevice() ? 'always' : 'hover'; 91 | } 92 | 93 | // Provide a sensible default selector, if none is given. 94 | if (!selector) { 95 | selector = 'h2, h3, h4, h5, h6'; 96 | } 97 | 98 | elements = _getElements(selector); 99 | 100 | if (elements.length === 0) { 101 | return this; 102 | } 103 | 104 | _addBaselineStyles(); 105 | 106 | // We produce a list of existing IDs so we don't generate a duplicate. 107 | elsWithIds = document.querySelectorAll('[id]'); 108 | idList = [].map.call(elsWithIds, function assign(el) { 109 | return el.id; 110 | }); 111 | 112 | for (i = 0; i < elements.length; i++) { 113 | if (this.hasAnchorJSLink(elements[i])) { 114 | indexesToDrop.push(i); 115 | continue; 116 | } 117 | 118 | if (elements[i].hasAttribute('id')) { 119 | elementID = elements[i].getAttribute('id'); 120 | } else if (elements[i].hasAttribute('data-anchor-id')) { 121 | elementID = elements[i].getAttribute('data-anchor-id'); 122 | } else { 123 | tidyText = this.urlify(elements[i].textContent); 124 | 125 | // Compare our generated ID to existing IDs (and increment it if needed) 126 | // before we add it to the page. 127 | newTidyText = tidyText; 128 | count = 0; 129 | do { 130 | if (index !== undefined) { 131 | newTidyText = tidyText + '-' + count; 132 | } 133 | 134 | index = idList.indexOf(newTidyText); 135 | count += 1; 136 | } while (index !== -1); 137 | index = undefined; 138 | idList.push(newTidyText); 139 | 140 | elements[i].setAttribute('id', newTidyText); 141 | elementID = newTidyText; 142 | } 143 | 144 | readableID = elementID.replace(/-/g, ' '); 145 | 146 | // The following code builds the following DOM structure in a more effiecient (albeit opaque) way. 147 | // ''; 148 | anchor = document.createElement('a'); 149 | anchor.className = 'anchorjs-link ' + this.options.class; 150 | anchor.href = '#' + elementID; 151 | anchor.setAttribute('aria-label', 'Anchor link for: ' + readableID); 152 | anchor.setAttribute('data-anchorjs-icon', this.options.icon); 153 | 154 | if (visibleOptionToUse === 'always') { 155 | anchor.style.opacity = '1'; 156 | } 157 | 158 | if (this.options.icon === '\ue9cb') { 159 | anchor.style.font = '1em/1 anchorjs-icons'; 160 | 161 | // We set lineHeight = 1 here because the `anchorjs-icons` font family could otherwise affect the 162 | // height of the heading. This isn't the case for icons with `placement: left`, so we restore 163 | // line-height: inherit in that case, ensuring they remain positioned correctly. For more info, 164 | // see https://github.com/bryanbraun/anchorjs/issues/39. 165 | if (this.options.placement === 'left') { 166 | anchor.style.lineHeight = 'inherit'; 167 | } 168 | } 169 | 170 | if (this.options.placement === 'left') { 171 | anchor.style.position = 'absolute'; 172 | anchor.style.marginLeft = '-1em'; 173 | anchor.style.paddingRight = '0.5em'; 174 | elements[i].insertBefore(anchor, elements[i].firstChild); 175 | } else { 176 | // if the option provided is `right` (or anything else). 177 | anchor.style.paddingLeft = '0.375em'; 178 | elements[i].appendChild(anchor); 179 | } 180 | } 181 | 182 | for (i = 0; i < indexesToDrop.length; i++) { 183 | elements.splice(indexesToDrop[i] - i, 1); 184 | } 185 | this.elements = this.elements.concat(elements); 186 | 187 | return this; 188 | }; 189 | 190 | /** 191 | * Removes all anchorjs-links from elements targed by the selector. 192 | * @param {String|Array|Nodelist} selector - A CSS selector string targeting elements with anchor links, 193 | * OR a nodeList / array containing the DOM elements. 194 | * @returns {this} - The AnchorJS object 195 | */ 196 | this.remove = function(selector) { 197 | var index, 198 | domAnchor, 199 | elements = _getElements(selector); 200 | 201 | for (var i = 0; i < elements.length; i++) { 202 | domAnchor = elements[i].querySelector('.anchorjs-link'); 203 | if (domAnchor) { 204 | // Drop the element from our main list, if it's in there. 205 | index = this.elements.indexOf(elements[i]); 206 | if (index !== -1) { 207 | this.elements.splice(index, 1); 208 | } 209 | // Remove the anchor from the DOM. 210 | elements[i].removeChild(domAnchor); 211 | } 212 | } 213 | return this; 214 | }; 215 | 216 | /** 217 | * Removes all anchorjs links. Mostly used for tests. 218 | */ 219 | this.removeAll = function() { 220 | this.remove(this.elements); 221 | }; 222 | 223 | /** 224 | * Urlify - Refine text so it makes a good ID. 225 | * 226 | * To do this, we remove apostrophes, replace nonsafe characters with hyphens, 227 | * remove extra hyphens, truncate, trim hyphens, and make lowercase. 228 | * 229 | * @param {String} text - Any text. Usually pulled from the webpage element we are linking to. 230 | * @returns {String} - hyphen-delimited text for use in IDs and URLs. 231 | */ 232 | this.urlify = function(text) { 233 | // Regex for finding the nonsafe URL characters (many need escaping): & +$,:;=?@"#{}|^~[`%!'<>]./()*\ 234 | var nonsafeChars = /[& +$,:;=?@"#{}|^~[`%!'<>\]\.\/\(\)\*\\]/g, 235 | urlText; 236 | 237 | // The reason we include this _applyRemainingDefaultOptions is so urlify can be called independently, 238 | // even after setting options. This can be useful for tests or other applications. 239 | if (!this.options.truncate) { 240 | _applyRemainingDefaultOptions(this.options); 241 | } 242 | 243 | // Note: we trim hyphens after truncating because truncating can cause dangling hyphens. 244 | // Example string: // " ⚡⚡ Don't forget: URL fragments should be i18n-friendly, hyphenated, short, and clean." 245 | urlText = text 246 | .trim() // "⚡⚡ Don't forget: URL fragments should be i18n-friendly, hyphenated, short, and clean." 247 | .replace(/\'/gi, '') // "⚡⚡ Dont forget: URL fragments should be i18n-friendly, hyphenated, short, and clean." 248 | .replace(nonsafeChars, '-') // "⚡⚡-Dont-forget--URL-fragments-should-be-i18n-friendly--hyphenated--short--and-clean-" 249 | .replace(/-{2,}/g, '-') // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated-short-and-clean-" 250 | .substring(0, this.options.truncate) // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated-" 251 | .replace(/^-+|-+$/gm, '') // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated" 252 | .toLowerCase(); // "⚡⚡-dont-forget-url-fragments-should-be-i18n-friendly-hyphenated" 253 | 254 | return urlText; 255 | }; 256 | 257 | /** 258 | * Determines if this element already has an AnchorJS link on it. 259 | * Uses this technique: http://stackoverflow.com/a/5898748/1154642 260 | * @param {HTMLElemnt} el - a DOM node 261 | * @returns {Boolean} true/false 262 | */ 263 | this.hasAnchorJSLink = function(el) { 264 | var hasLeftAnchor = 265 | el.firstChild && 266 | (' ' + el.firstChild.className + ' ').indexOf(' anchorjs-link ') > -1, 267 | hasRightAnchor = 268 | el.lastChild && 269 | (' ' + el.lastChild.className + ' ').indexOf(' anchorjs-link ') > -1; 270 | 271 | return hasLeftAnchor || hasRightAnchor || false; 272 | }; 273 | 274 | /** 275 | * Turns a selector, nodeList, or array of elements into an array of elements (so we can use array methods). 276 | * It also throws errors on any other inputs. Used to handle inputs to .add and .remove. 277 | * @param {String|Array|Nodelist} input - A CSS selector string targeting elements with anchor links, 278 | * OR a nodeList / array containing the DOM elements. 279 | * @returns {Array} - An array containing the elements we want. 280 | */ 281 | function _getElements(input) { 282 | var elements; 283 | if (typeof input === 'string' || input instanceof String) { 284 | // See https://davidwalsh.name/nodelist-array for the technique transforming nodeList -> Array. 285 | elements = [].slice.call(document.querySelectorAll(input)); 286 | // I checked the 'input instanceof NodeList' test in IE9 and modern browsers and it worked for me. 287 | } else if (Array.isArray(input) || input instanceof NodeList) { 288 | elements = [].slice.call(input); 289 | } else { 290 | throw new Error('The selector provided to AnchorJS was invalid.'); 291 | } 292 | return elements; 293 | } 294 | 295 | /** 296 | * _addBaselineStyles 297 | * Adds baseline styles to the page, used by all AnchorJS links irregardless of configuration. 298 | */ 299 | function _addBaselineStyles() { 300 | // We don't want to add global baseline styles if they've been added before. 301 | if (document.head.querySelector('style.anchorjs') !== null) { 302 | return; 303 | } 304 | 305 | var style = document.createElement('style'), 306 | linkRule = 307 | ' .anchorjs-link {' + 308 | ' opacity: 0;' + 309 | ' text-decoration: none;' + 310 | ' -webkit-font-smoothing: antialiased;' + 311 | ' -moz-osx-font-smoothing: grayscale;' + 312 | ' }', 313 | hoverRule = 314 | ' *:hover > .anchorjs-link,' + 315 | ' .anchorjs-link:focus {' + 316 | ' opacity: 1;' + 317 | ' }', 318 | anchorjsLinkFontFace = 319 | ' @font-face {' + 320 | ' font-family: "anchorjs-icons";' + // Icon from icomoon; 10px wide & 10px tall; 2 empty below & 4 above 321 | ' src: url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype");' + 322 | ' }', 323 | pseudoElContent = 324 | ' [data-anchorjs-icon]::after {' + 325 | ' content: attr(data-anchorjs-icon);' + 326 | ' }', 327 | firstStyleEl; 328 | 329 | style.className = 'anchorjs'; 330 | style.appendChild(document.createTextNode('')); // Necessary for Webkit. 331 | 332 | // We place it in the head with the other style tags, if possible, so as to 333 | // not look out of place. We insert before the others so these styles can be 334 | // overridden if necessary. 335 | firstStyleEl = document.head.querySelector('[rel="stylesheet"], style'); 336 | if (firstStyleEl === undefined) { 337 | document.head.appendChild(style); 338 | } else { 339 | document.head.insertBefore(style, firstStyleEl); 340 | } 341 | 342 | style.sheet.insertRule(linkRule, style.sheet.cssRules.length); 343 | style.sheet.insertRule(hoverRule, style.sheet.cssRules.length); 344 | style.sheet.insertRule(pseudoElContent, style.sheet.cssRules.length); 345 | style.sheet.insertRule(anchorjsLinkFontFace, style.sheet.cssRules.length); 346 | } 347 | } 348 | 349 | return AnchorJS; 350 | }); 351 | -------------------------------------------------------------------------------- /docs/assets/bass-addons.css: -------------------------------------------------------------------------------- 1 | .input { 2 | font-family: inherit; 3 | display: block; 4 | width: 100%; 5 | height: 2rem; 6 | padding: .5rem; 7 | margin-bottom: 1rem; 8 | border: 1px solid #ccc; 9 | font-size: .875rem; 10 | border-radius: 3px; 11 | box-sizing: border-box; 12 | } 13 | -------------------------------------------------------------------------------- /docs/assets/bass.css: -------------------------------------------------------------------------------- 1 | /*! Basscss | http://basscss.com | MIT License */ 2 | 3 | .h1{ font-size: 2rem } 4 | .h2{ font-size: 1.5rem } 5 | .h3{ font-size: 1.25rem } 6 | .h4{ font-size: 1rem } 7 | .h5{ font-size: .875rem } 8 | .h6{ font-size: .75rem } 9 | 10 | .font-family-inherit{ font-family:inherit } 11 | .font-size-inherit{ font-size:inherit } 12 | .text-decoration-none{ text-decoration:none } 13 | 14 | .bold{ font-weight: bold; font-weight: bold } 15 | .regular{ font-weight:normal } 16 | .italic{ font-style:italic } 17 | .caps{ text-transform:uppercase; letter-spacing: .2em; } 18 | 19 | .left-align{ text-align:left } 20 | .center{ text-align:center } 21 | .right-align{ text-align:right } 22 | .justify{ text-align:justify } 23 | 24 | .nowrap{ white-space:nowrap } 25 | .break-word{ word-wrap:break-word } 26 | 27 | .line-height-1{ line-height: 1 } 28 | .line-height-2{ line-height: 1.125 } 29 | .line-height-3{ line-height: 1.25 } 30 | .line-height-4{ line-height: 1.5 } 31 | 32 | .list-style-none{ list-style:none } 33 | .underline{ text-decoration:underline } 34 | 35 | .truncate{ 36 | max-width:100%; 37 | overflow:hidden; 38 | text-overflow:ellipsis; 39 | white-space:nowrap; 40 | } 41 | 42 | .list-reset{ 43 | list-style:none; 44 | padding-left:0; 45 | } 46 | 47 | .inline{ display:inline } 48 | .block{ display:block } 49 | .inline-block{ display:inline-block } 50 | .table{ display:table } 51 | .table-cell{ display:table-cell } 52 | 53 | .overflow-hidden{ overflow:hidden } 54 | .overflow-scroll{ overflow:scroll } 55 | .overflow-auto{ overflow:auto } 56 | 57 | .clearfix:before, 58 | .clearfix:after{ 59 | content:" "; 60 | display:table 61 | } 62 | .clearfix:after{ clear:both } 63 | 64 | .left{ float:left } 65 | .right{ float:right } 66 | 67 | .fit{ max-width:100% } 68 | 69 | .max-width-1{ max-width: 24rem } 70 | .max-width-2{ max-width: 32rem } 71 | .max-width-3{ max-width: 48rem } 72 | .max-width-4{ max-width: 64rem } 73 | 74 | .border-box{ box-sizing:border-box } 75 | 76 | .align-baseline{ vertical-align:baseline } 77 | .align-top{ vertical-align:top } 78 | .align-middle{ vertical-align:middle } 79 | .align-bottom{ vertical-align:bottom } 80 | 81 | .m0{ margin:0 } 82 | .mt0{ margin-top:0 } 83 | .mr0{ margin-right:0 } 84 | .mb0{ margin-bottom:0 } 85 | .ml0{ margin-left:0 } 86 | .mx0{ margin-left:0; margin-right:0 } 87 | .my0{ margin-top:0; margin-bottom:0 } 88 | 89 | .m1{ margin: .5rem } 90 | .mt1{ margin-top: .5rem } 91 | .mr1{ margin-right: .5rem } 92 | .mb1{ margin-bottom: .5rem } 93 | .ml1{ margin-left: .5rem } 94 | .mx1{ margin-left: .5rem; margin-right: .5rem } 95 | .my1{ margin-top: .5rem; margin-bottom: .5rem } 96 | 97 | .m2{ margin: 1rem } 98 | .mt2{ margin-top: 1rem } 99 | .mr2{ margin-right: 1rem } 100 | .mb2{ margin-bottom: 1rem } 101 | .ml2{ margin-left: 1rem } 102 | .mx2{ margin-left: 1rem; margin-right: 1rem } 103 | .my2{ margin-top: 1rem; margin-bottom: 1rem } 104 | 105 | .m3{ margin: 2rem } 106 | .mt3{ margin-top: 2rem } 107 | .mr3{ margin-right: 2rem } 108 | .mb3{ margin-bottom: 2rem } 109 | .ml3{ margin-left: 2rem } 110 | .mx3{ margin-left: 2rem; margin-right: 2rem } 111 | .my3{ margin-top: 2rem; margin-bottom: 2rem } 112 | 113 | .m4{ margin: 4rem } 114 | .mt4{ margin-top: 4rem } 115 | .mr4{ margin-right: 4rem } 116 | .mb4{ margin-bottom: 4rem } 117 | .ml4{ margin-left: 4rem } 118 | .mx4{ margin-left: 4rem; margin-right: 4rem } 119 | .my4{ margin-top: 4rem; margin-bottom: 4rem } 120 | 121 | .mxn1{ margin-left: -.5rem; margin-right: -.5rem; } 122 | .mxn2{ margin-left: -1rem; margin-right: -1rem; } 123 | .mxn3{ margin-left: -2rem; margin-right: -2rem; } 124 | .mxn4{ margin-left: -4rem; margin-right: -4rem; } 125 | 126 | .ml-auto{ margin-left:auto } 127 | .mr-auto{ margin-right:auto } 128 | .mx-auto{ margin-left:auto; margin-right:auto; } 129 | 130 | .p0{ padding:0 } 131 | .pt0{ padding-top:0 } 132 | .pr0{ padding-right:0 } 133 | .pb0{ padding-bottom:0 } 134 | .pl0{ padding-left:0 } 135 | .px0{ padding-left:0; padding-right:0 } 136 | .py0{ padding-top:0; padding-bottom:0 } 137 | 138 | .p1{ padding: .5rem } 139 | .pt1{ padding-top: .5rem } 140 | .pr1{ padding-right: .5rem } 141 | .pb1{ padding-bottom: .5rem } 142 | .pl1{ padding-left: .5rem } 143 | .py1{ padding-top: .5rem; padding-bottom: .5rem } 144 | .px1{ padding-left: .5rem; padding-right: .5rem } 145 | 146 | .p2{ padding: 1rem } 147 | .pt2{ padding-top: 1rem } 148 | .pr2{ padding-right: 1rem } 149 | .pb2{ padding-bottom: 1rem } 150 | .pl2{ padding-left: 1rem } 151 | .py2{ padding-top: 1rem; padding-bottom: 1rem } 152 | .px2{ padding-left: 1rem; padding-right: 1rem } 153 | 154 | .p3{ padding: 2rem } 155 | .pt3{ padding-top: 2rem } 156 | .pr3{ padding-right: 2rem } 157 | .pb3{ padding-bottom: 2rem } 158 | .pl3{ padding-left: 2rem } 159 | .py3{ padding-top: 2rem; padding-bottom: 2rem } 160 | .px3{ padding-left: 2rem; padding-right: 2rem } 161 | 162 | .p4{ padding: 4rem } 163 | .pt4{ padding-top: 4rem } 164 | .pr4{ padding-right: 4rem } 165 | .pb4{ padding-bottom: 4rem } 166 | .pl4{ padding-left: 4rem } 167 | .py4{ padding-top: 4rem; padding-bottom: 4rem } 168 | .px4{ padding-left: 4rem; padding-right: 4rem } 169 | 170 | .col{ 171 | float:left; 172 | box-sizing:border-box; 173 | } 174 | 175 | .col-right{ 176 | float:right; 177 | box-sizing:border-box; 178 | } 179 | 180 | .col-1{ 181 | width:8.33333%; 182 | } 183 | 184 | .col-2{ 185 | width:16.66667%; 186 | } 187 | 188 | .col-3{ 189 | width:25%; 190 | } 191 | 192 | .col-4{ 193 | width:33.33333%; 194 | } 195 | 196 | .col-5{ 197 | width:41.66667%; 198 | } 199 | 200 | .col-6{ 201 | width:50%; 202 | } 203 | 204 | .col-7{ 205 | width:58.33333%; 206 | } 207 | 208 | .col-8{ 209 | width:66.66667%; 210 | } 211 | 212 | .col-9{ 213 | width:75%; 214 | } 215 | 216 | .col-10{ 217 | width:83.33333%; 218 | } 219 | 220 | .col-11{ 221 | width:91.66667%; 222 | } 223 | 224 | .col-12{ 225 | width:100%; 226 | } 227 | @media (min-width: 40em){ 228 | 229 | .sm-col{ 230 | float:left; 231 | box-sizing:border-box; 232 | } 233 | 234 | .sm-col-right{ 235 | float:right; 236 | box-sizing:border-box; 237 | } 238 | 239 | .sm-col-1{ 240 | width:8.33333%; 241 | } 242 | 243 | .sm-col-2{ 244 | width:16.66667%; 245 | } 246 | 247 | .sm-col-3{ 248 | width:25%; 249 | } 250 | 251 | .sm-col-4{ 252 | width:33.33333%; 253 | } 254 | 255 | .sm-col-5{ 256 | width:41.66667%; 257 | } 258 | 259 | .sm-col-6{ 260 | width:50%; 261 | } 262 | 263 | .sm-col-7{ 264 | width:58.33333%; 265 | } 266 | 267 | .sm-col-8{ 268 | width:66.66667%; 269 | } 270 | 271 | .sm-col-9{ 272 | width:75%; 273 | } 274 | 275 | .sm-col-10{ 276 | width:83.33333%; 277 | } 278 | 279 | .sm-col-11{ 280 | width:91.66667%; 281 | } 282 | 283 | .sm-col-12{ 284 | width:100%; 285 | } 286 | 287 | } 288 | @media (min-width: 52em){ 289 | 290 | .md-col{ 291 | float:left; 292 | box-sizing:border-box; 293 | } 294 | 295 | .md-col-right{ 296 | float:right; 297 | box-sizing:border-box; 298 | } 299 | 300 | .md-col-1{ 301 | width:8.33333%; 302 | } 303 | 304 | .md-col-2{ 305 | width:16.66667%; 306 | } 307 | 308 | .md-col-3{ 309 | width:25%; 310 | } 311 | 312 | .md-col-4{ 313 | width:33.33333%; 314 | } 315 | 316 | .md-col-5{ 317 | width:41.66667%; 318 | } 319 | 320 | .md-col-6{ 321 | width:50%; 322 | } 323 | 324 | .md-col-7{ 325 | width:58.33333%; 326 | } 327 | 328 | .md-col-8{ 329 | width:66.66667%; 330 | } 331 | 332 | .md-col-9{ 333 | width:75%; 334 | } 335 | 336 | .md-col-10{ 337 | width:83.33333%; 338 | } 339 | 340 | .md-col-11{ 341 | width:91.66667%; 342 | } 343 | 344 | .md-col-12{ 345 | width:100%; 346 | } 347 | 348 | } 349 | @media (min-width: 64em){ 350 | 351 | .lg-col{ 352 | float:left; 353 | box-sizing:border-box; 354 | } 355 | 356 | .lg-col-right{ 357 | float:right; 358 | box-sizing:border-box; 359 | } 360 | 361 | .lg-col-1{ 362 | width:8.33333%; 363 | } 364 | 365 | .lg-col-2{ 366 | width:16.66667%; 367 | } 368 | 369 | .lg-col-3{ 370 | width:25%; 371 | } 372 | 373 | .lg-col-4{ 374 | width:33.33333%; 375 | } 376 | 377 | .lg-col-5{ 378 | width:41.66667%; 379 | } 380 | 381 | .lg-col-6{ 382 | width:50%; 383 | } 384 | 385 | .lg-col-7{ 386 | width:58.33333%; 387 | } 388 | 389 | .lg-col-8{ 390 | width:66.66667%; 391 | } 392 | 393 | .lg-col-9{ 394 | width:75%; 395 | } 396 | 397 | .lg-col-10{ 398 | width:83.33333%; 399 | } 400 | 401 | .lg-col-11{ 402 | width:91.66667%; 403 | } 404 | 405 | .lg-col-12{ 406 | width:100%; 407 | } 408 | 409 | } 410 | .flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } 411 | 412 | @media (min-width: 40em){ 413 | .sm-flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } 414 | } 415 | 416 | @media (min-width: 52em){ 417 | .md-flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } 418 | } 419 | 420 | @media (min-width: 64em){ 421 | .lg-flex{ display:-webkit-box; display:-webkit-flex; display:-ms-flexbox; display:flex } 422 | } 423 | 424 | .flex-column{ -webkit-box-orient:vertical; -webkit-box-direction:normal; -webkit-flex-direction:column; -ms-flex-direction:column; flex-direction:column } 425 | .flex-wrap{ -webkit-flex-wrap:wrap; -ms-flex-wrap:wrap; flex-wrap:wrap } 426 | 427 | .items-start{ -webkit-box-align:start; -webkit-align-items:flex-start; -ms-flex-align:start; -ms-grid-row-align:flex-start; align-items:flex-start } 428 | .items-end{ -webkit-box-align:end; -webkit-align-items:flex-end; -ms-flex-align:end; -ms-grid-row-align:flex-end; align-items:flex-end } 429 | .items-center{ -webkit-box-align:center; -webkit-align-items:center; -ms-flex-align:center; -ms-grid-row-align:center; align-items:center } 430 | .items-baseline{ -webkit-box-align:baseline; -webkit-align-items:baseline; -ms-flex-align:baseline; -ms-grid-row-align:baseline; align-items:baseline } 431 | .items-stretch{ -webkit-box-align:stretch; -webkit-align-items:stretch; -ms-flex-align:stretch; -ms-grid-row-align:stretch; align-items:stretch } 432 | 433 | .self-start{ -webkit-align-self:flex-start; -ms-flex-item-align:start; align-self:flex-start } 434 | .self-end{ -webkit-align-self:flex-end; -ms-flex-item-align:end; align-self:flex-end } 435 | .self-center{ -webkit-align-self:center; -ms-flex-item-align:center; align-self:center } 436 | .self-baseline{ -webkit-align-self:baseline; -ms-flex-item-align:baseline; align-self:baseline } 437 | .self-stretch{ -webkit-align-self:stretch; -ms-flex-item-align:stretch; align-self:stretch } 438 | 439 | .justify-start{ -webkit-box-pack:start; -webkit-justify-content:flex-start; -ms-flex-pack:start; justify-content:flex-start } 440 | .justify-end{ -webkit-box-pack:end; -webkit-justify-content:flex-end; -ms-flex-pack:end; justify-content:flex-end } 441 | .justify-center{ -webkit-box-pack:center; -webkit-justify-content:center; -ms-flex-pack:center; justify-content:center } 442 | .justify-between{ -webkit-box-pack:justify; -webkit-justify-content:space-between; -ms-flex-pack:justify; justify-content:space-between } 443 | .justify-around{ -webkit-justify-content:space-around; -ms-flex-pack:distribute; justify-content:space-around } 444 | 445 | .content-start{ -webkit-align-content:flex-start; -ms-flex-line-pack:start; align-content:flex-start } 446 | .content-end{ -webkit-align-content:flex-end; -ms-flex-line-pack:end; align-content:flex-end } 447 | .content-center{ -webkit-align-content:center; -ms-flex-line-pack:center; align-content:center } 448 | .content-between{ -webkit-align-content:space-between; -ms-flex-line-pack:justify; align-content:space-between } 449 | .content-around{ -webkit-align-content:space-around; -ms-flex-line-pack:distribute; align-content:space-around } 450 | .content-stretch{ -webkit-align-content:stretch; -ms-flex-line-pack:stretch; align-content:stretch } 451 | .flex-auto{ 452 | -webkit-box-flex:1; 453 | -webkit-flex:1 1 auto; 454 | -ms-flex:1 1 auto; 455 | flex:1 1 auto; 456 | min-width:0; 457 | min-height:0; 458 | } 459 | .flex-none{ -webkit-box-flex:0; -webkit-flex:none; -ms-flex:none; flex:none } 460 | .fs0{ flex-shrink: 0 } 461 | 462 | .order-0{ -webkit-box-ordinal-group:1; -webkit-order:0; -ms-flex-order:0; order:0 } 463 | .order-1{ -webkit-box-ordinal-group:2; -webkit-order:1; -ms-flex-order:1; order:1 } 464 | .order-2{ -webkit-box-ordinal-group:3; -webkit-order:2; -ms-flex-order:2; order:2 } 465 | .order-3{ -webkit-box-ordinal-group:4; -webkit-order:3; -ms-flex-order:3; order:3 } 466 | .order-last{ -webkit-box-ordinal-group:100000; -webkit-order:99999; -ms-flex-order:99999; order:99999 } 467 | 468 | .relative{ position:relative } 469 | .absolute{ position:absolute } 470 | .fixed{ position:fixed } 471 | 472 | .top-0{ top:0 } 473 | .right-0{ right:0 } 474 | .bottom-0{ bottom:0 } 475 | .left-0{ left:0 } 476 | 477 | .z1{ z-index: 1 } 478 | .z2{ z-index: 2 } 479 | .z3{ z-index: 3 } 480 | .z4{ z-index: 4 } 481 | 482 | .border{ 483 | border-style:solid; 484 | border-width: 1px; 485 | } 486 | 487 | .border-top{ 488 | border-top-style:solid; 489 | border-top-width: 1px; 490 | } 491 | 492 | .border-right{ 493 | border-right-style:solid; 494 | border-right-width: 1px; 495 | } 496 | 497 | .border-bottom{ 498 | border-bottom-style:solid; 499 | border-bottom-width: 1px; 500 | } 501 | 502 | .border-left{ 503 | border-left-style:solid; 504 | border-left-width: 1px; 505 | } 506 | 507 | .border-none{ border:0 } 508 | 509 | .rounded{ border-radius: 3px } 510 | .circle{ border-radius:50% } 511 | 512 | .rounded-top{ border-radius: 3px 3px 0 0 } 513 | .rounded-right{ border-radius: 0 3px 3px 0 } 514 | .rounded-bottom{ border-radius: 0 0 3px 3px } 515 | .rounded-left{ border-radius: 3px 0 0 3px } 516 | 517 | .not-rounded{ border-radius:0 } 518 | 519 | .hide{ 520 | position:absolute !important; 521 | height:1px; 522 | width:1px; 523 | overflow:hidden; 524 | clip:rect(1px, 1px, 1px, 1px); 525 | } 526 | 527 | @media (max-width: 40em){ 528 | .xs-hide{ display:none !important } 529 | } 530 | 531 | @media (min-width: 40em) and (max-width: 52em){ 532 | .sm-hide{ display:none !important } 533 | } 534 | 535 | @media (min-width: 52em) and (max-width: 64em){ 536 | .md-hide{ display:none !important } 537 | } 538 | 539 | @media (min-width: 64em){ 540 | .lg-hide{ display:none !important } 541 | } 542 | 543 | .display-none{ display:none !important } 544 | 545 | -------------------------------------------------------------------------------- /docs/assets/fonts/EOT/SourceCodePro-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/EOT/SourceCodePro-Bold.eot -------------------------------------------------------------------------------- /docs/assets/fonts/EOT/SourceCodePro-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/EOT/SourceCodePro-Regular.eot -------------------------------------------------------------------------------- /docs/assets/fonts/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | 5 | This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /docs/assets/fonts/OTF/SourceCodePro-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/OTF/SourceCodePro-Bold.otf -------------------------------------------------------------------------------- /docs/assets/fonts/OTF/SourceCodePro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/OTF/SourceCodePro-Regular.otf -------------------------------------------------------------------------------- /docs/assets/fonts/TTF/SourceCodePro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/TTF/SourceCodePro-Bold.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/TTF/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/TTF/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/WOFF/OTF/SourceCodePro-Bold.otf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/WOFF/OTF/SourceCodePro-Regular.otf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/WOFF/TTF/SourceCodePro-Bold.ttf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/WOFF/TTF/SourceCodePro-Regular.ttf.woff -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Bold.otf.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/WOFF2/OTF/SourceCodePro-Regular.otf.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Bold.ttf.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plugCubed/plugAPI/5b67f51413e370c0ac881f53b7320d885543170b/docs/assets/fonts/WOFF2/TTF/SourceCodePro-Regular.ttf.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/source-code-pro.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'Source Code Pro'; 3 | font-weight: 400; 4 | font-style: normal; 5 | font-stretch: normal; 6 | src: url('EOT/SourceCodePro-Regular.eot') format('embedded-opentype'), 7 | url('WOFF2/TTF/SourceCodePro-Regular.ttf.woff2') format('woff2'), 8 | url('WOFF/OTF/SourceCodePro-Regular.otf.woff') format('woff'), 9 | url('OTF/SourceCodePro-Regular.otf') format('opentype'), 10 | url('TTF/SourceCodePro-Regular.ttf') format('truetype'); 11 | } 12 | 13 | @font-face{ 14 | font-family: 'Source Code Pro'; 15 | font-weight: 700; 16 | font-style: normal; 17 | font-stretch: normal; 18 | src: url('EOT/SourceCodePro-Bold.eot') format('embedded-opentype'), 19 | url('WOFF2/TTF/SourceCodePro-Bold.ttf.woff2') format('woff2'), 20 | url('WOFF/OTF/SourceCodePro-Bold.otf.woff') format('woff'), 21 | url('OTF/SourceCodePro-Bold.otf') format('opentype'), 22 | url('TTF/SourceCodePro-Bold.ttf') format('truetype'); 23 | } 24 | -------------------------------------------------------------------------------- /docs/assets/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | -webkit-text-size-adjust: none; 14 | } 15 | 16 | .hljs-comment, 17 | .diff .hljs-header, 18 | .hljs-javadoc { 19 | color: #998; 20 | font-style: italic; 21 | } 22 | 23 | .hljs-keyword, 24 | .css .rule .hljs-keyword, 25 | .hljs-winutils, 26 | .nginx .hljs-title, 27 | .hljs-subst, 28 | .hljs-request, 29 | .hljs-status { 30 | color: #1184CE; 31 | } 32 | 33 | .hljs-number, 34 | .hljs-hexcolor, 35 | .ruby .hljs-constant { 36 | color: #ed225d; 37 | } 38 | 39 | .hljs-string, 40 | .hljs-tag .hljs-value, 41 | .hljs-phpdoc, 42 | .hljs-dartdoc, 43 | .tex .hljs-formula { 44 | color: #ed225d; 45 | } 46 | 47 | .hljs-title, 48 | .hljs-id, 49 | .scss .hljs-preprocessor { 50 | color: #900; 51 | font-weight: bold; 52 | } 53 | 54 | .hljs-list .hljs-keyword, 55 | .hljs-subst { 56 | font-weight: normal; 57 | } 58 | 59 | .hljs-class .hljs-title, 60 | .hljs-type, 61 | .vhdl .hljs-literal, 62 | .tex .hljs-command { 63 | color: #458; 64 | font-weight: bold; 65 | } 66 | 67 | .hljs-tag, 68 | .hljs-tag .hljs-title, 69 | .hljs-rules .hljs-property, 70 | .django .hljs-tag .hljs-keyword { 71 | color: #000080; 72 | font-weight: normal; 73 | } 74 | 75 | .hljs-attribute, 76 | .hljs-variable, 77 | .lisp .hljs-body { 78 | color: #008080; 79 | } 80 | 81 | .hljs-regexp { 82 | color: #009926; 83 | } 84 | 85 | .hljs-symbol, 86 | .ruby .hljs-symbol .hljs-string, 87 | .lisp .hljs-keyword, 88 | .clojure .hljs-keyword, 89 | .scheme .hljs-keyword, 90 | .tex .hljs-special, 91 | .hljs-prompt { 92 | color: #990073; 93 | } 94 | 95 | .hljs-built_in { 96 | color: #0086b3; 97 | } 98 | 99 | .hljs-preprocessor, 100 | .hljs-pragma, 101 | .hljs-pi, 102 | .hljs-doctype, 103 | .hljs-shebang, 104 | .hljs-cdata { 105 | color: #999; 106 | font-weight: bold; 107 | } 108 | 109 | .hljs-deletion { 110 | background: #fdd; 111 | } 112 | 113 | .hljs-addition { 114 | background: #dfd; 115 | } 116 | 117 | .diff .hljs-change { 118 | background: #0086b3; 119 | } 120 | 121 | .hljs-chunk { 122 | color: #aaa; 123 | } 124 | -------------------------------------------------------------------------------- /docs/assets/site.js: -------------------------------------------------------------------------------- 1 | /* global anchors */ 2 | 3 | // add anchor links to headers 4 | anchors.options.placement = 'left'; 5 | anchors.add('h3'); 6 | 7 | // Filter UI 8 | var tocElements = document.getElementById('toc').getElementsByTagName('li'); 9 | 10 | document.getElementById('filter-input').addEventListener('keyup', function(e) { 11 | var i, element, children; 12 | 13 | // enter key 14 | if (e.keyCode === 13) { 15 | // go to the first displayed item in the toc 16 | for (i = 0; i < tocElements.length; i++) { 17 | element = tocElements[i]; 18 | if (!element.classList.contains('display-none')) { 19 | location.replace(element.firstChild.href); 20 | return e.preventDefault(); 21 | } 22 | } 23 | } 24 | 25 | var match = function() { 26 | return true; 27 | }; 28 | 29 | var value = this.value.toLowerCase(); 30 | 31 | if (!value.match(/^\s*$/)) { 32 | match = function(element) { 33 | var html = element.firstChild.innerHTML; 34 | return html && html.toLowerCase().indexOf(value) !== -1; 35 | }; 36 | } 37 | 38 | for (i = 0; i < tocElements.length; i++) { 39 | element = tocElements[i]; 40 | children = Array.from(element.getElementsByTagName('li')); 41 | if (match(element) || children.some(match)) { 42 | element.classList.remove('display-none'); 43 | } else { 44 | element.classList.add('display-none'); 45 | } 46 | } 47 | }); 48 | 49 | var items = document.getElementsByClassName('toggle-sibling'); 50 | for (var j = 0; j < items.length; j++) { 51 | items[j].addEventListener('click', toggleSibling); 52 | } 53 | 54 | function toggleSibling() { 55 | var stepSibling = this.parentNode.getElementsByClassName('toggle-target')[0]; 56 | var icon = this.getElementsByClassName('icon')[0]; 57 | var klass = 'display-none'; 58 | if (stepSibling.classList.contains(klass)) { 59 | stepSibling.classList.remove(klass); 60 | icon.innerHTML = '▾'; 61 | } else { 62 | stepSibling.classList.add(klass); 63 | icon.innerHTML = '▸'; 64 | } 65 | } 66 | 67 | function showHashTarget(targetId) { 68 | if (targetId) { 69 | var hashTarget = document.getElementById(targetId); 70 | // new target is hidden 71 | if ( 72 | hashTarget && 73 | hashTarget.offsetHeight === 0 && 74 | hashTarget.parentNode.parentNode.classList.contains('display-none') 75 | ) { 76 | hashTarget.parentNode.parentNode.classList.remove('display-none'); 77 | } 78 | } 79 | } 80 | 81 | function scrollIntoView(targetId) { 82 | // Only scroll to element if we don't have a stored scroll position. 83 | if (targetId && !history.state) { 84 | var hashTarget = document.getElementById(targetId); 85 | if (hashTarget) { 86 | hashTarget.scrollIntoView(); 87 | } 88 | } 89 | } 90 | 91 | function gotoCurrentTarget() { 92 | showHashTarget(location.hash.substring(1)); 93 | scrollIntoView(location.hash.substring(1)); 94 | } 95 | 96 | window.addEventListener('hashchange', gotoCurrentTarget); 97 | gotoCurrentTarget(); 98 | 99 | var toclinks = document.getElementsByClassName('pre-open'); 100 | for (var k = 0; k < toclinks.length; k++) { 101 | toclinks[k].addEventListener('mousedown', preOpen, false); 102 | } 103 | 104 | function preOpen() { 105 | showHashTarget(this.hash.substring(1)); 106 | } 107 | 108 | var split_left = document.querySelector('#split-left'); 109 | var split_right = document.querySelector('#split-right'); 110 | var split_parent = split_left.parentNode; 111 | var cw_with_sb = split_left.clientWidth; 112 | split_left.style.overflow = 'hidden'; 113 | var cw_without_sb = split_left.clientWidth; 114 | split_left.style.overflow = ''; 115 | 116 | Split(['#split-left', '#split-right'], { 117 | elementStyle: function(dimension, size, gutterSize) { 118 | return { 119 | 'flex-basis': 'calc(' + size + '% - ' + gutterSize + 'px)' 120 | }; 121 | }, 122 | gutterStyle: function(dimension, gutterSize) { 123 | return { 124 | 'flex-basis': gutterSize + 'px' 125 | }; 126 | }, 127 | gutterSize: 20, 128 | sizes: [33, 67] 129 | }); 130 | 131 | // Chrome doesn't remember scroll position properly so do it ourselves. 132 | // Also works on Firefox and Edge. 133 | 134 | function updateState() { 135 | history.replaceState( 136 | { 137 | left_top: split_left.scrollTop, 138 | right_top: split_right.scrollTop 139 | }, 140 | document.title 141 | ); 142 | } 143 | 144 | function loadState(ev) { 145 | if (ev) { 146 | // Edge doesn't replace change history.state on popstate. 147 | history.replaceState(ev.state, document.title); 148 | } 149 | if (history.state) { 150 | split_left.scrollTop = history.state.left_top; 151 | split_right.scrollTop = history.state.right_top; 152 | } 153 | } 154 | 155 | window.addEventListener('load', function() { 156 | // Restore after Firefox scrolls to hash. 157 | setTimeout(function() { 158 | loadState(); 159 | // Update with initial scroll position. 160 | updateState(); 161 | // Update scroll positions only after we've loaded because Firefox 162 | // emits an initial scroll event with 0. 163 | split_left.addEventListener('scroll', updateState); 164 | split_right.addEventListener('scroll', updateState); 165 | }, 1); 166 | }); 167 | 168 | window.addEventListener('popstate', loadState); 169 | -------------------------------------------------------------------------------- /docs/assets/split.css: -------------------------------------------------------------------------------- 1 | .gutter { 2 | background-color: #f5f5f5; 3 | background-repeat: no-repeat; 4 | background-position: 50%; 5 | } 6 | 7 | .gutter.gutter-vertical { 8 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII='); 9 | cursor: ns-resize; 10 | } 11 | 12 | .gutter.gutter-horizontal { 13 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); 14 | cursor: ew-resize; 15 | } 16 | -------------------------------------------------------------------------------- /docs/assets/split.js: -------------------------------------------------------------------------------- 1 | /*! Split.js - v1.3.5 */ 2 | // https://github.com/nathancahill/Split.js 3 | // Copyright (c) 2017 Nathan Cahill; Licensed MIT 4 | 5 | (function(global, factory) { 6 | typeof exports === 'object' && typeof module !== 'undefined' 7 | ? (module.exports = factory()) 8 | : typeof define === 'function' && define.amd 9 | ? define(factory) 10 | : (global.Split = factory()); 11 | })(this, function() { 12 | 'use strict'; 13 | // The programming goals of Split.js are to deliver readable, understandable and 14 | // maintainable code, while at the same time manually optimizing for tiny minified file size, 15 | // browser compatibility without additional requirements, graceful fallback (IE8 is supported) 16 | // and very few assumptions about the user's page layout. 17 | var global = window; 18 | var document = global.document; 19 | 20 | // Save a couple long function names that are used frequently. 21 | // This optimization saves around 400 bytes. 22 | var addEventListener = 'addEventListener'; 23 | var removeEventListener = 'removeEventListener'; 24 | var getBoundingClientRect = 'getBoundingClientRect'; 25 | var NOOP = function() { 26 | return false; 27 | }; 28 | 29 | // Figure out if we're in IE8 or not. IE8 will still render correctly, 30 | // but will be static instead of draggable. 31 | var isIE8 = global.attachEvent && !global[addEventListener]; 32 | 33 | // This library only needs two helper functions: 34 | // 35 | // The first determines which prefixes of CSS calc we need. 36 | // We only need to do this once on startup, when this anonymous function is called. 37 | // 38 | // Tests -webkit, -moz and -o prefixes. Modified from StackOverflow: 39 | // http://stackoverflow.com/questions/16625140/js-feature-detection-to-detect-the-usage-of-webkit-calc-over-calc/16625167#16625167 40 | var calc = 41 | ['', '-webkit-', '-moz-', '-o-'] 42 | .filter(function(prefix) { 43 | var el = document.createElement('div'); 44 | el.style.cssText = 'width:' + prefix + 'calc(9px)'; 45 | 46 | return !!el.style.length; 47 | }) 48 | .shift() + 'calc'; 49 | 50 | // The second helper function allows elements and string selectors to be used 51 | // interchangeably. In either case an element is returned. This allows us to 52 | // do `Split([elem1, elem2])` as well as `Split(['#id1', '#id2'])`. 53 | var elementOrSelector = function(el) { 54 | if (typeof el === 'string' || el instanceof String) { 55 | return document.querySelector(el); 56 | } 57 | 58 | return el; 59 | }; 60 | 61 | // The main function to initialize a split. Split.js thinks about each pair 62 | // of elements as an independant pair. Dragging the gutter between two elements 63 | // only changes the dimensions of elements in that pair. This is key to understanding 64 | // how the following functions operate, since each function is bound to a pair. 65 | // 66 | // A pair object is shaped like this: 67 | // 68 | // { 69 | // a: DOM element, 70 | // b: DOM element, 71 | // aMin: Number, 72 | // bMin: Number, 73 | // dragging: Boolean, 74 | // parent: DOM element, 75 | // isFirst: Boolean, 76 | // isLast: Boolean, 77 | // direction: 'horizontal' | 'vertical' 78 | // } 79 | // 80 | // The basic sequence: 81 | // 82 | // 1. Set defaults to something sane. `options` doesn't have to be passed at all. 83 | // 2. Initialize a bunch of strings based on the direction we're splitting. 84 | // A lot of the behavior in the rest of the library is paramatized down to 85 | // rely on CSS strings and classes. 86 | // 3. Define the dragging helper functions, and a few helpers to go with them. 87 | // 4. Loop through the elements while pairing them off. Every pair gets an 88 | // `pair` object, a gutter, and special isFirst/isLast properties. 89 | // 5. Actually size the pair elements, insert gutters and attach event listeners. 90 | var Split = function(ids, options) { 91 | if (options === void 0) options = {}; 92 | 93 | var dimension; 94 | var clientDimension; 95 | var clientAxis; 96 | var position; 97 | var paddingA; 98 | var paddingB; 99 | var elements; 100 | 101 | // All DOM elements in the split should have a common parent. We can grab 102 | // the first elements parent and hope users read the docs because the 103 | // behavior will be whacky otherwise. 104 | var parent = elementOrSelector(ids[0]).parentNode; 105 | var parentFlexDirection = global.getComputedStyle(parent).flexDirection; 106 | 107 | // Set default options.sizes to equal percentages of the parent element. 108 | var sizes = 109 | options.sizes || 110 | ids.map(function() { 111 | return 100 / ids.length; 112 | }); 113 | 114 | // Standardize minSize to an array if it isn't already. This allows minSize 115 | // to be passed as a number. 116 | var minSize = options.minSize !== undefined ? options.minSize : 100; 117 | var minSizes = Array.isArray(minSize) 118 | ? minSize 119 | : ids.map(function() { 120 | return minSize; 121 | }); 122 | var gutterSize = options.gutterSize !== undefined ? options.gutterSize : 10; 123 | var snapOffset = options.snapOffset !== undefined ? options.snapOffset : 30; 124 | var direction = options.direction || 'horizontal'; 125 | var cursor = 126 | options.cursor || 127 | (direction === 'horizontal' ? 'ew-resize' : 'ns-resize'); 128 | var gutter = 129 | options.gutter || 130 | function(i, gutterDirection) { 131 | var gut = document.createElement('div'); 132 | gut.className = 'gutter gutter-' + gutterDirection; 133 | return gut; 134 | }; 135 | var elementStyle = 136 | options.elementStyle || 137 | function(dim, size, gutSize) { 138 | var style = {}; 139 | 140 | if (typeof size !== 'string' && !(size instanceof String)) { 141 | if (!isIE8) { 142 | style[dim] = calc + '(' + size + '% - ' + gutSize + 'px)'; 143 | } else { 144 | style[dim] = size + '%'; 145 | } 146 | } else { 147 | style[dim] = size; 148 | } 149 | 150 | return style; 151 | }; 152 | var gutterStyle = 153 | options.gutterStyle || 154 | function(dim, gutSize) { 155 | return (obj = {}), (obj[dim] = gutSize + 'px'), obj; 156 | var obj; 157 | }; 158 | 159 | // 2. Initialize a bunch of strings based on the direction we're splitting. 160 | // A lot of the behavior in the rest of the library is paramatized down to 161 | // rely on CSS strings and classes. 162 | if (direction === 'horizontal') { 163 | dimension = 'width'; 164 | clientDimension = 'clientWidth'; 165 | clientAxis = 'clientX'; 166 | position = 'left'; 167 | paddingA = 'paddingLeft'; 168 | paddingB = 'paddingRight'; 169 | } else if (direction === 'vertical') { 170 | dimension = 'height'; 171 | clientDimension = 'clientHeight'; 172 | clientAxis = 'clientY'; 173 | position = 'top'; 174 | paddingA = 'paddingTop'; 175 | paddingB = 'paddingBottom'; 176 | } 177 | 178 | // 3. Define the dragging helper functions, and a few helpers to go with them. 179 | // Each helper is bound to a pair object that contains it's metadata. This 180 | // also makes it easy to store references to listeners that that will be 181 | // added and removed. 182 | // 183 | // Even though there are no other functions contained in them, aliasing 184 | // this to self saves 50 bytes or so since it's used so frequently. 185 | // 186 | // The pair object saves metadata like dragging state, position and 187 | // event listener references. 188 | 189 | function setElementSize(el, size, gutSize) { 190 | // Split.js allows setting sizes via numbers (ideally), or if you must, 191 | // by string, like '300px'. This is less than ideal, because it breaks 192 | // the fluid layout that `calc(% - px)` provides. You're on your own if you do that, 193 | // make sure you calculate the gutter size by hand. 194 | var style = elementStyle(dimension, size, gutSize); 195 | 196 | // eslint-disable-next-line no-param-reassign 197 | Object.keys(style).forEach(function(prop) { 198 | return (el.style[prop] = style[prop]); 199 | }); 200 | } 201 | 202 | function setGutterSize(gutterElement, gutSize) { 203 | var style = gutterStyle(dimension, gutSize); 204 | 205 | // eslint-disable-next-line no-param-reassign 206 | Object.keys(style).forEach(function(prop) { 207 | return (gutterElement.style[prop] = style[prop]); 208 | }); 209 | } 210 | 211 | // Actually adjust the size of elements `a` and `b` to `offset` while dragging. 212 | // calc is used to allow calc(percentage + gutterpx) on the whole split instance, 213 | // which allows the viewport to be resized without additional logic. 214 | // Element a's size is the same as offset. b's size is total size - a size. 215 | // Both sizes are calculated from the initial parent percentage, 216 | // then the gutter size is subtracted. 217 | function adjust(offset) { 218 | var a = elements[this.a]; 219 | var b = elements[this.b]; 220 | var percentage = a.size + b.size; 221 | 222 | a.size = offset / this.size * percentage; 223 | b.size = percentage - offset / this.size * percentage; 224 | 225 | setElementSize(a.element, a.size, this.aGutterSize); 226 | setElementSize(b.element, b.size, this.bGutterSize); 227 | } 228 | 229 | // drag, where all the magic happens. The logic is really quite simple: 230 | // 231 | // 1. Ignore if the pair is not dragging. 232 | // 2. Get the offset of the event. 233 | // 3. Snap offset to min if within snappable range (within min + snapOffset). 234 | // 4. Actually adjust each element in the pair to offset. 235 | // 236 | // --------------------------------------------------------------------- 237 | // | | <- a.minSize || b.minSize -> | | 238 | // | | | <- this.snapOffset || this.snapOffset -> | | | 239 | // | | | || | | | 240 | // | | | || | | | 241 | // --------------------------------------------------------------------- 242 | // | <- this.start this.size -> | 243 | function drag(e) { 244 | var offset; 245 | 246 | if (!this.dragging) { 247 | return; 248 | } 249 | 250 | // Get the offset of the event from the first side of the 251 | // pair `this.start`. Supports touch events, but not multitouch, so only the first 252 | // finger `touches[0]` is counted. 253 | if ('touches' in e) { 254 | offset = e.touches[0][clientAxis] - this.start; 255 | } else { 256 | offset = e[clientAxis] - this.start; 257 | } 258 | 259 | // If within snapOffset of min or max, set offset to min or max. 260 | // snapOffset buffers a.minSize and b.minSize, so logic is opposite for both. 261 | // Include the appropriate gutter sizes to prevent overflows. 262 | if (offset <= elements[this.a].minSize + snapOffset + this.aGutterSize) { 263 | offset = elements[this.a].minSize + this.aGutterSize; 264 | } else if ( 265 | offset >= 266 | this.size - (elements[this.b].minSize + snapOffset + this.bGutterSize) 267 | ) { 268 | offset = this.size - (elements[this.b].minSize + this.bGutterSize); 269 | } 270 | 271 | // Actually adjust the size. 272 | adjust.call(this, offset); 273 | 274 | // Call the drag callback continously. Don't do anything too intensive 275 | // in this callback. 276 | if (options.onDrag) { 277 | options.onDrag(); 278 | } 279 | } 280 | 281 | // Cache some important sizes when drag starts, so we don't have to do that 282 | // continously: 283 | // 284 | // `size`: The total size of the pair. First + second + first gutter + second gutter. 285 | // `start`: The leading side of the first element. 286 | // 287 | // ------------------------------------------------ 288 | // | aGutterSize -> ||| | 289 | // | ||| | 290 | // | ||| | 291 | // | ||| <- bGutterSize | 292 | // ------------------------------------------------ 293 | // | <- start size -> | 294 | function calculateSizes() { 295 | // Figure out the parent size minus padding. 296 | var a = elements[this.a].element; 297 | var b = elements[this.b].element; 298 | 299 | this.size = 300 | a[getBoundingClientRect]()[dimension] + 301 | b[getBoundingClientRect]()[dimension] + 302 | this.aGutterSize + 303 | this.bGutterSize; 304 | this.start = a[getBoundingClientRect]()[position]; 305 | } 306 | 307 | // stopDragging is very similar to startDragging in reverse. 308 | function stopDragging() { 309 | var self = this; 310 | var a = elements[self.a].element; 311 | var b = elements[self.b].element; 312 | 313 | if (self.dragging && options.onDragEnd) { 314 | options.onDragEnd(); 315 | } 316 | 317 | self.dragging = false; 318 | 319 | // Remove the stored event listeners. This is why we store them. 320 | global[removeEventListener]('mouseup', self.stop); 321 | global[removeEventListener]('touchend', self.stop); 322 | global[removeEventListener]('touchcancel', self.stop); 323 | 324 | self.parent[removeEventListener]('mousemove', self.move); 325 | self.parent[removeEventListener]('touchmove', self.move); 326 | 327 | // Delete them once they are removed. I think this makes a difference 328 | // in memory usage with a lot of splits on one page. But I don't know for sure. 329 | delete self.stop; 330 | delete self.move; 331 | 332 | a[removeEventListener]('selectstart', NOOP); 333 | a[removeEventListener]('dragstart', NOOP); 334 | b[removeEventListener]('selectstart', NOOP); 335 | b[removeEventListener]('dragstart', NOOP); 336 | 337 | a.style.userSelect = ''; 338 | a.style.webkitUserSelect = ''; 339 | a.style.MozUserSelect = ''; 340 | a.style.pointerEvents = ''; 341 | 342 | b.style.userSelect = ''; 343 | b.style.webkitUserSelect = ''; 344 | b.style.MozUserSelect = ''; 345 | b.style.pointerEvents = ''; 346 | 347 | self.gutter.style.cursor = ''; 348 | self.parent.style.cursor = ''; 349 | } 350 | 351 | // startDragging calls `calculateSizes` to store the inital size in the pair object. 352 | // It also adds event listeners for mouse/touch events, 353 | // and prevents selection while dragging so avoid the selecting text. 354 | function startDragging(e) { 355 | // Alias frequently used variables to save space. 200 bytes. 356 | var self = this; 357 | var a = elements[self.a].element; 358 | var b = elements[self.b].element; 359 | 360 | // Call the onDragStart callback. 361 | if (!self.dragging && options.onDragStart) { 362 | options.onDragStart(); 363 | } 364 | 365 | // Don't actually drag the element. We emulate that in the drag function. 366 | e.preventDefault(); 367 | 368 | // Set the dragging property of the pair object. 369 | self.dragging = true; 370 | 371 | // Create two event listeners bound to the same pair object and store 372 | // them in the pair object. 373 | self.move = drag.bind(self); 374 | self.stop = stopDragging.bind(self); 375 | 376 | // All the binding. `window` gets the stop events in case we drag out of the elements. 377 | global[addEventListener]('mouseup', self.stop); 378 | global[addEventListener]('touchend', self.stop); 379 | global[addEventListener]('touchcancel', self.stop); 380 | 381 | self.parent[addEventListener]('mousemove', self.move); 382 | self.parent[addEventListener]('touchmove', self.move); 383 | 384 | // Disable selection. Disable! 385 | a[addEventListener]('selectstart', NOOP); 386 | a[addEventListener]('dragstart', NOOP); 387 | b[addEventListener]('selectstart', NOOP); 388 | b[addEventListener]('dragstart', NOOP); 389 | 390 | a.style.userSelect = 'none'; 391 | a.style.webkitUserSelect = 'none'; 392 | a.style.MozUserSelect = 'none'; 393 | a.style.pointerEvents = 'none'; 394 | 395 | b.style.userSelect = 'none'; 396 | b.style.webkitUserSelect = 'none'; 397 | b.style.MozUserSelect = 'none'; 398 | b.style.pointerEvents = 'none'; 399 | 400 | // Set the cursor, both on the gutter and the parent element. 401 | // Doing only a, b and gutter causes flickering. 402 | self.gutter.style.cursor = cursor; 403 | self.parent.style.cursor = cursor; 404 | 405 | // Cache the initial sizes of the pair. 406 | calculateSizes.call(self); 407 | } 408 | 409 | // 5. Create pair and element objects. Each pair has an index reference to 410 | // elements `a` and `b` of the pair (first and second elements). 411 | // Loop through the elements while pairing them off. Every pair gets a 412 | // `pair` object, a gutter, and isFirst/isLast properties. 413 | // 414 | // Basic logic: 415 | // 416 | // - Starting with the second element `i > 0`, create `pair` objects with 417 | // `a = i - 1` and `b = i` 418 | // - Set gutter sizes based on the _pair_ being first/last. The first and last 419 | // pair have gutterSize / 2, since they only have one half gutter, and not two. 420 | // - Create gutter elements and add event listeners. 421 | // - Set the size of the elements, minus the gutter sizes. 422 | // 423 | // ----------------------------------------------------------------------- 424 | // | i=0 | i=1 | i=2 | i=3 | 425 | // | | isFirst | | isLast | 426 | // | pair 0 pair 1 pair 2 | 427 | // | | | | | 428 | // ----------------------------------------------------------------------- 429 | var pairs = []; 430 | elements = ids.map(function(id, i) { 431 | // Create the element object. 432 | var element = { 433 | element: elementOrSelector(id), 434 | size: sizes[i], 435 | minSize: minSizes[i] 436 | }; 437 | 438 | var pair; 439 | 440 | if (i > 0) { 441 | // Create the pair object with it's metadata. 442 | pair = { 443 | a: i - 1, 444 | b: i, 445 | dragging: false, 446 | isFirst: i === 1, 447 | isLast: i === ids.length - 1, 448 | direction: direction, 449 | parent: parent 450 | }; 451 | 452 | // For first and last pairs, first and last gutter width is half. 453 | pair.aGutterSize = gutterSize; 454 | pair.bGutterSize = gutterSize; 455 | 456 | if (pair.isFirst) { 457 | pair.aGutterSize = gutterSize / 2; 458 | } 459 | 460 | if (pair.isLast) { 461 | pair.bGutterSize = gutterSize / 2; 462 | } 463 | 464 | // if the parent has a reverse flex-direction, switch the pair elements. 465 | if ( 466 | parentFlexDirection === 'row-reverse' || 467 | parentFlexDirection === 'column-reverse' 468 | ) { 469 | var temp = pair.a; 470 | pair.a = pair.b; 471 | pair.b = temp; 472 | } 473 | } 474 | 475 | // Determine the size of the current element. IE8 is supported by 476 | // staticly assigning sizes without draggable gutters. Assigns a string 477 | // to `size`. 478 | // 479 | // IE9 and above 480 | if (!isIE8) { 481 | // Create gutter elements for each pair. 482 | if (i > 0) { 483 | var gutterElement = gutter(i, direction); 484 | setGutterSize(gutterElement, gutterSize); 485 | 486 | gutterElement[addEventListener]( 487 | 'mousedown', 488 | startDragging.bind(pair) 489 | ); 490 | gutterElement[addEventListener]( 491 | 'touchstart', 492 | startDragging.bind(pair) 493 | ); 494 | 495 | parent.insertBefore(gutterElement, element.element); 496 | 497 | pair.gutter = gutterElement; 498 | } 499 | } 500 | 501 | // Set the element size to our determined size. 502 | // Half-size gutters for first and last elements. 503 | if (i === 0 || i === ids.length - 1) { 504 | setElementSize(element.element, element.size, gutterSize / 2); 505 | } else { 506 | setElementSize(element.element, element.size, gutterSize); 507 | } 508 | 509 | var computedSize = element.element[getBoundingClientRect]()[dimension]; 510 | 511 | if (computedSize < element.minSize) { 512 | element.minSize = computedSize; 513 | } 514 | 515 | // After the first iteration, and we have a pair object, append it to the 516 | // list of pairs. 517 | if (i > 0) { 518 | pairs.push(pair); 519 | } 520 | 521 | return element; 522 | }); 523 | 524 | function setSizes(newSizes) { 525 | newSizes.forEach(function(newSize, i) { 526 | if (i > 0) { 527 | var pair = pairs[i - 1]; 528 | var a = elements[pair.a]; 529 | var b = elements[pair.b]; 530 | 531 | a.size = newSizes[i - 1]; 532 | b.size = newSize; 533 | 534 | setElementSize(a.element, a.size, pair.aGutterSize); 535 | setElementSize(b.element, b.size, pair.bGutterSize); 536 | } 537 | }); 538 | } 539 | 540 | function destroy() { 541 | pairs.forEach(function(pair) { 542 | pair.parent.removeChild(pair.gutter); 543 | elements[pair.a].element.style[dimension] = ''; 544 | elements[pair.b].element.style[dimension] = ''; 545 | }); 546 | } 547 | 548 | if (isIE8) { 549 | return { 550 | setSizes: setSizes, 551 | destroy: destroy 552 | }; 553 | } 554 | 555 | return { 556 | setSizes: setSizes, 557 | getSizes: function getSizes() { 558 | return elements.map(function(element) { 559 | return element.size; 560 | }); 561 | }, 562 | collapse: function collapse(i) { 563 | if (i === pairs.length) { 564 | var pair = pairs[i - 1]; 565 | 566 | calculateSizes.call(pair); 567 | 568 | if (!isIE8) { 569 | adjust.call(pair, pair.size - pair.bGutterSize); 570 | } 571 | } else { 572 | var pair$1 = pairs[i]; 573 | 574 | calculateSizes.call(pair$1); 575 | 576 | if (!isIE8) { 577 | adjust.call(pair$1, pair$1.aGutterSize); 578 | } 579 | } 580 | }, 581 | destroy: destroy 582 | }; 583 | }; 584 | 585 | return Split; 586 | }); 587 | -------------------------------------------------------------------------------- /docs/assets/style.css: -------------------------------------------------------------------------------- 1 | .documentation { 2 | font-family: Helvetica, sans-serif; 3 | color: #666; 4 | line-height: 1.5; 5 | background: #f5f5f5; 6 | } 7 | 8 | .black { 9 | color: #666; 10 | } 11 | 12 | .bg-white { 13 | background-color: #fff; 14 | } 15 | 16 | h4 { 17 | margin: 20px 0 10px 0; 18 | } 19 | 20 | .documentation h3 { 21 | color: #000; 22 | } 23 | 24 | .border-bottom { 25 | border-color: #ddd; 26 | } 27 | 28 | a { 29 | color: #1184CE; 30 | text-decoration: none; 31 | } 32 | 33 | .documentation a[href]:hover { 34 | text-decoration: underline; 35 | } 36 | 37 | a:hover { 38 | cursor: pointer; 39 | } 40 | 41 | .py1-ul li { 42 | padding: 5px 0; 43 | } 44 | 45 | .max-height-100 { 46 | max-height: 100%; 47 | } 48 | 49 | .height-viewport-100 { 50 | height: 100vh; 51 | } 52 | 53 | section:target h3 { 54 | font-weight:700; 55 | } 56 | 57 | .documentation td, 58 | .documentation th { 59 | padding: .25rem .25rem; 60 | } 61 | 62 | h1:hover .anchorjs-link, 63 | h2:hover .anchorjs-link, 64 | h3:hover .anchorjs-link, 65 | h4:hover .anchorjs-link { 66 | opacity: 1; 67 | } 68 | 69 | .fix-3 { 70 | width: 25%; 71 | max-width: 244px; 72 | } 73 | 74 | .fix-3 { 75 | width: 25%; 76 | max-width: 244px; 77 | } 78 | 79 | @media (min-width: 52em) { 80 | .fix-margin-3 { 81 | margin-left: 25%; 82 | } 83 | } 84 | 85 | .pre, pre, code, .code { 86 | font-family: Source Code Pro,Menlo,Consolas,Liberation Mono,monospace; 87 | font-size: 14px; 88 | } 89 | 90 | .fill-light { 91 | background: #F9F9F9; 92 | } 93 | 94 | .width2 { 95 | width: 1rem; 96 | } 97 | 98 | .input { 99 | font-family: inherit; 100 | display: block; 101 | width: 100%; 102 | height: 2rem; 103 | padding: .5rem; 104 | margin-bottom: 1rem; 105 | border: 1px solid #ccc; 106 | font-size: .875rem; 107 | border-radius: 3px; 108 | box-sizing: border-box; 109 | } 110 | 111 | table { 112 | border-collapse: collapse; 113 | } 114 | 115 | .prose table th, 116 | .prose table td { 117 | text-align: left; 118 | padding:8px; 119 | border:1px solid #ddd; 120 | } 121 | 122 | .prose table th:nth-child(1) { border-right: none; } 123 | .prose table th:nth-child(2) { border-left: none; } 124 | 125 | .prose table { 126 | border:1px solid #ddd; 127 | } 128 | 129 | .prose-big { 130 | font-size: 18px; 131 | line-height: 30px; 132 | } 133 | 134 | .quiet { 135 | opacity: 0.7; 136 | } 137 | 138 | .minishadow { 139 | box-shadow: 2px 2px 10px #f3f3f3; 140 | } 141 | -------------------------------------------------------------------------------- /documentation.yml: -------------------------------------------------------------------------------- 1 | project-name: 2 | - plugAPI 3 | -------------------------------------------------------------------------------- /lib/bufferObject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * A buffer to cache data 5 | * @method BufferObject 6 | * @param {Object} data The data to cache. 7 | * @param {Function} getUpdate Function that is called when data needs to be updated. 8 | * @param {Number} maxAge the maximum age that items will be stored for. 9 | * @returns {Object} Returns an object of functions and the data that is stored. 10 | * @private 11 | */ 12 | function BufferObject(data, getUpdate, maxAge) { 13 | if (!Object.is(typeof getUpdate, 'function')) { 14 | throw new Error('BufferObject requires an update function'); 15 | } 16 | 17 | maxAge = maxAge || 6e4; 18 | 19 | return { 20 | lastUpdate: data ? Date.now() : 0, 21 | data: data || null, 22 | set(setData) { 23 | this.data = setData; 24 | this.lastUpdate = Date.now(); 25 | }, 26 | get(callback) { 27 | if (this.data != null) { 28 | if (maxAge < 0 || this.lastUpdate >= Date.now() - maxAge) { 29 | if (Object.is(typeof callback, 'function')) { 30 | return callback(this.data); 31 | } 32 | 33 | return this.data; 34 | } 35 | } 36 | 37 | const that = this; 38 | 39 | getUpdate(function(err, updateData) { 40 | if (err) { 41 | that.get(); 42 | 43 | return; 44 | } 45 | that.set(updateData); 46 | if (Object.is(typeof callback, 'function')) { 47 | return callback(updateData); 48 | } 49 | 50 | return updateData; 51 | }); 52 | }, 53 | push(pushData) { 54 | 55 | // Be sure the data is loaded 56 | this.get(); 57 | 58 | if (Array.isArray(this.data)) { 59 | this.data.push(pushData); 60 | } 61 | }, 62 | remove(removeData) { 63 | this.get(); 64 | 65 | for (const i in this.data) { 66 | if (!this.data.hasOwnProperty(i)) continue; 67 | if (Object.is(this.data[i], removeData)) { 68 | this.data.splice(i, 1); 69 | 70 | return; 71 | } 72 | } 73 | }, 74 | removeAt(index) { 75 | 76 | // Be sure the data is loaded 77 | this.get(); 78 | 79 | if (Array.isArray(this.data) && index < this.data.length) { 80 | this.data.splice(index, 1); 81 | } 82 | } 83 | }; 84 | } 85 | 86 | module.exports = BufferObject; 87 | -------------------------------------------------------------------------------- /lib/constants.json: -------------------------------------------------------------------------------- 1 | { 2 | "BAN": { 3 | "HOUR": "h", 4 | "DAY": "d", 5 | "PERMA": "f" 6 | }, 7 | "BAN_REASON": { 8 | "SPAMMING_TROLLING": 1, 9 | "VERBAL_ABUSE": 2, 10 | "OFFENSIVE_MEDIA": 3, 11 | "INAPPROPRIATE_GENRE": 4, 12 | "NEGATIVE_ATTITUDE": 5 13 | }, 14 | "GLOBAL_ROLES": { 15 | "NONE": 0, 16 | "PROMOTER": 500, 17 | "PLOT": 750, 18 | "VOLUNTEER": 2000, 19 | "MODERATOR": 2500, 20 | "AMBASSADOR": 3000, 21 | "LEADER": 4000, 22 | "ADMIN": 5000 23 | }, 24 | "MUTE": { 25 | "SHORT": "s", 26 | "MEDIUM": "m", 27 | "LONG": "l" 28 | }, 29 | "MUTE_REASON": { 30 | "VIOLATING_COMMUNITY_RULES": 1, 31 | "VERBAL_ABUSE": 2, 32 | "SPAMMING_TROLLING": 3, 33 | "OFFENSIVE_LANGUAGE": 4, 34 | "NEGATIVE_ATTITUDE": 5 35 | }, 36 | "ROOM_ROLE": { 37 | "NONE": 0, 38 | "RESIDENTDJ": 1000, 39 | "BOUNCER": 2000, 40 | "MANAGER": 3000, 41 | "COHOST": 4000, 42 | "HOST": 5000 43 | }, 44 | "STATUS": { 45 | "OFFLINE": 0, 46 | "ONLINE": 1 47 | }, 48 | "WLBAN": { 49 | "SHORT": "s", 50 | "MEDIUM": "m", 51 | "LONG": "l", 52 | "PERMA": "f" 53 | }, 54 | "WLBAN_REASON": { 55 | "SPAMMING_TROLLING": 1, 56 | "VERBAL_ABUSE": 2, 57 | "OFFENSIVE_MEDIA": 3, 58 | "INAPPROPRIATE_GENRE": 4, 59 | "NEGATIVE_ATTITUDE": 5 60 | } 61 | } -------------------------------------------------------------------------------- /lib/endpoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "CHAT_DELETE": "chat/", 3 | "HISTORY": "rooms/history", 4 | "MODERATE_ADD_DJ": "booth/add", 5 | "MODERATE_BAN": "bans/add", 6 | "MODERATE_BOOTH": "booth", 7 | "MODERATE_MOVE_DJ": "booth/move", 8 | "MODERATE_MUTE": "mutes", 9 | "MODERATE_PERMISSIONS": "staff/update", 10 | "MODERATE_REMOVE_DJ": "booth/remove/", 11 | "MODERATE_SKIP": "booth/skip", 12 | "MODERATE_STAFF": "staff/", 13 | "MODERATE_UNBAN": "bans/", 14 | "MODERATE_UNMUTE": "mutes/", 15 | "MODERATE_WLBAN": "booth/waitlistban", 16 | "PLAYLIST": "playlists", 17 | "ROOM_CYCLE_BOOTH": "booth/cycle", 18 | "ROOM_INFO": "rooms/update", 19 | "ROOM_LOCK_BOOTH": "booth/lock", 20 | "SKIP_ME": "booth/skip/me", 21 | "USER_INFO": "users/me", 22 | "USER_GET_AVATARS": "store/inventory/avatars", 23 | "USER_GET_BADGES": "store/inventory/badges", 24 | "USER_SET_AVATAR": "users/avatar", 25 | "USER_SET_BADGE": "users/badge" 26 | } 27 | -------------------------------------------------------------------------------- /lib/eventObjectTypes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file contains functions for converting plug.dj socket objects to plugAPI objects (easier to read and extend). 3 | * When adding new functions, be sure to call it the event type name and then change the messageHandler to use the new object. 4 | */ 5 | 6 | 'use strict'; 7 | 8 | const plugMessageSplit = require('plug-message-split'); 9 | 10 | module.exports = { 11 | 12 | /** 13 | * @param {Object} data The raw socket data of the advance event. 14 | * @param {Object} data.m The media data 15 | * @param {String} data.t The time the media started 16 | * @param {Array} data.d The current djs in waitList 17 | * @param {Number} data.c The current user DJing 18 | * @param {Number} data.p Playlist ID that is currently active 19 | * @returns {{media: Object, startTime: String, historyID: Number, djs: Array, currentDJ: Number, playlistID: Number}} A humanized form of the advance event sent in from plug.dj. 20 | * @private 21 | */ 22 | advance(data) { 23 | return { 24 | media: data.m, 25 | startTime: data.t, 26 | historyID: data.h, 27 | djs: data.d, 28 | currentDJ: data.c, 29 | playlistID: data.p 30 | }; 31 | }, 32 | 33 | /** 34 | * @param {Object} data The raw socket data of the chat event sent in from plug.dj 35 | * @param {String} data.message The chat message 36 | * @param {String} data.un The user's username that sent the message 37 | * @param {Number} data.uid The user's ID that that sent the message 38 | * @param {String} data.cid The message's unique ID. 39 | * @param {Room} room PlugAPI's internal room.js functions 40 | * @returns {{raw: Object, id: (playback.media.cid|*), from: (User|null), message: String, mentions: User[], muted: Boolean}} A humanized form of the chat object sent in from plug.dj 41 | * @private 42 | */ 43 | chat(data, room) { 44 | return { 45 | raw: data, 46 | id: data.cid, 47 | from: room.getUser(data.uid), 48 | message: plugMessageSplit.unescape(data.message), 49 | mentions: [], 50 | muted: room.isMuted(data.uid), 51 | type: (function() { 52 | if (data.message.startsWith('/me') || data.message.startsWith('/em')) { 53 | return 'emote'; 54 | } 55 | if (data.message.indexOf(`@${room.getSelf().username}`) > -1) { 56 | return 'mention'; 57 | } 58 | 59 | return 'message'; 60 | })() 61 | }; 62 | }, 63 | 64 | /** 65 | * @param {{m: String, mi: Number, f: Boolean}} data The raw socket data of the DJ Cycle event sent in from plug.dj 66 | * @param {Room} room PlugAPI's internal room.js functions 67 | * @returns {{raw: Object, user: Object, cycle: Boolean}} A humanized form of the DJ Cycle event sent in from plug.dj 68 | * @private 69 | */ 70 | djListCycle(data, room) { 71 | return { 72 | raw: data, 73 | user: room.getUser(data.mi), 74 | cycle: data.f 75 | }; 76 | }, 77 | 78 | /** 79 | * @param {{m: String, mi: Number, c: Boolean, f: Boolean}} data The raw socket data of the DJ List Locked event sent in from plug.dj 80 | * @param {Room} room PlugAPI's internal room.js functions 81 | * @returns {{raw: Object, user: Object, clear: Boolean, locked: Boolean}} A humanized form of the DJ List Locked event sent in from plug.dj 82 | * @private 83 | */ 84 | djListLocked(data, room) { 85 | return { 86 | raw: data, 87 | user: room.getUser(data.mi), 88 | clear: data.c, 89 | locked: data.f 90 | }; 91 | }, 92 | 93 | /** 94 | * @param {Number} data the id of the user that grabbed the song 95 | * @param {Room} room PlugAPI's internal room.js functions 96 | * @returns {{raw: Object, user: Object}} A humanized form of the grab event sent infrom plug.dj 97 | * @private 98 | */ 99 | grab(data, room) { 100 | return { 101 | raw: data, 102 | user: room.getUser(data) 103 | }; 104 | }, 105 | 106 | /** 107 | * @param {{m: String, mi: Number, t: Number, d: String}} data The raw socket data of the modBan event 108 | * @param {Room} room PlugAPI's internal room.js functions 109 | * @returns {{raw: Object, duration: String, moderator: Object, user: Object}} A humanized form of the modBan event sent in from plug.dj 110 | * @private 111 | */ 112 | modBan(data, room) { 113 | return { 114 | raw: data, 115 | duration: Object.is(data.d, 'f') ? 'Forever' : Object.is(data.d, 'd') ? 'Day' : Object.is(data.d, 'h') ? 'Hour' : null, 116 | moderator: room.getUser(data.mi), 117 | user: data.t 118 | }; 119 | }, 120 | 121 | /** 122 | * @param {{m: String, mi: Number, i: Number, d: String, r: Number}} data The raw socket data of the modMute event 123 | * @param {Room} room PlugAPI's internal room.js functions 124 | * @returns {{raw: Object, duration: String, reason: Number, moderator: Object, user: Object}} A humanized form of the modMute event sent in from plug.dj 125 | * @private 126 | */ 127 | modMute(data, room) { 128 | return { 129 | raw: data, 130 | duration: Object.is(data.d, 's') ? 'Short' : Object.is(data.d, 'm') ? 'Medium' : Object.is(data.d, 'l') ? 'Long' : Object.is(data.d, 'o') ? 'Unmuted' : null, 131 | moderator: room.getUser(data.mi), 132 | reason: data.r, 133 | user: room.getUser(data.i) 134 | }; 135 | }, 136 | 137 | /** 138 | * @param {{mi: Number, m: String}} data The raw socket data of the modSkip event. 139 | * @param {Room} room PlugAPI's internal room.js functions 140 | * @returns {{raw: Object, user: Object}} A humanized form of the modSkip event sent in from plug.dj 141 | * @private 142 | */ 143 | modSkip(data, room) { 144 | return { 145 | raw: data, 146 | user: room.getUser(data.mi) 147 | }; 148 | }, 149 | 150 | /** 151 | * @param {{mi: Number, m: String, u: Array}} data The raw socket data of the modStaff event. 152 | * @param {Room} room PlugAPI's internal room.js functions 153 | * @returns {{raw: Object, moderator: Object, users: Array}} A humanized form of the modStaff event sent in from plug.dj 154 | * @private 155 | */ 156 | modStaff(data, room) { 157 | return { 158 | raw: data, 159 | moderator: room.getUser(data.mi), 160 | users: data.u.map((user) => { 161 | return { 162 | role: user.p, 163 | user: room.getUser(user.i) 164 | }; 165 | }) 166 | }; 167 | }, 168 | 169 | /** 170 | * @param {{m: String, mi: Number, i: Number, d: String, t: String, ti: Number}} data The raw socket data of the modMute event 171 | * @param {Room} room PlugAPI's internal room.js functions 172 | * @returns {{raw: Object, duration: String, reason: Number, moderator: Object, user: Object}} A humanized form of the modWaitListBanevent sent in from plug.dj 173 | * @private 174 | */ 175 | modWaitlistBan(data, room) { 176 | return { 177 | raw: data, 178 | duration: Object.is(data.d, 's') ? 'Short' : Object.is(data.d, 'm') ? 'Medium' : Object.is(data.d, 'l') ? 'Long' : Object.is(data.d, 'f') ? 'Forever' : Object.is(data.d, 'o') ? 'Unbanned' : null, 179 | moderator: room.getUser(data.mi), 180 | user: room.getUser(data.ti) 181 | }; 182 | }, 183 | 184 | /** 185 | * @param {{i: Number, v: Number}} data the raw socket data of the Vote event. 186 | * @param {Room} room PlugAPI's internal room.js functions 187 | * @returns {{raw: Object, user: Object, vote: Number}} A humanized form of the vote event sent in from plug.dj 188 | * @private 189 | */ 190 | vote(data, room) { 191 | return { 192 | raw: data, 193 | user: room.getUser(data.i), 194 | vote: data.v 195 | }; 196 | }, 197 | 198 | /** 199 | * @param {{m: Number, u: Number}} data The raw socket data of the Chat Level Update event sent in from plug.dj 200 | * @param {Room} room PlugAPI's internal room.js functions 201 | * @returns {{raw: Object, id: Number, user: Object, level: Number}} A humanized form of the Chat Level Update event sent in from plug.dj 202 | * @private 203 | */ 204 | roomMinChatLevelUpdate(data, room) { 205 | return { 206 | raw: data, 207 | id: data.u, 208 | user: room.getUser(data.u), 209 | level: data.m 210 | }; 211 | }, 212 | 213 | /** 214 | * @param {{u: Number, d: String}} data The raw socket data of the Room Description Update event sent in from plug.dj 215 | * @param {Room} room PlugAPI's internal room.js functions 216 | * @returns {{raw: Object, user: Object, description: String}} A humanized form of the Room Description Update event sent in from plug.dj 217 | * @private 218 | */ 219 | roomDescriptionUpdate(data, room) { 220 | return { 221 | raw: data, 222 | user: room.getUser(data.u), 223 | description: data.d 224 | }; 225 | }, 226 | 227 | /** 228 | * @param {{u: Number, n: String}} data The raw socket data of the Room Name Update event sent in from plug.dj 229 | * @param {Room} room PlugAPI's internal room.js functions 230 | * @returns {{raw: Object, user: Object, name: String}} A humanized form of the Room Name Update event sent in from plug.dj 231 | * @private 232 | */ 233 | roomNameUpdate(data, room) { 234 | return { 235 | raw: data, 236 | user: room.getUser(data.u), 237 | name: data.n 238 | }; 239 | }, 240 | 241 | /** 242 | * @param {{u: Number, w: String}} data The raw socket data of the Room Welcome Update event sent in from plug.dj 243 | * @param {Room} room PlugAPI's internal room.js functions 244 | * @returns {{raw: Object, user: Object, name: String}} A humanized form of the Room Welcome Update event sent in from plug.dj 245 | * @private 246 | */ 247 | roomWelcomeUpdate(data, room) { 248 | return { 249 | raw: data, 250 | user: room.getUser(data.u), 251 | welcome: data.w 252 | }; 253 | } 254 | }; 255 | -------------------------------------------------------------------------------- /lib/events.json: -------------------------------------------------------------------------------- 1 | { 2 | "ADVANCE": "advance", 3 | "BAN": "ban", 4 | "CHAT": "chat", 5 | "CHAT_COMMAND": "command", 6 | "CHAT_DELETE": "chatDelete", 7 | "CHAT_LEVEL_UPDATE": "roomMinChatLevelUpdate", 8 | "COMMAND": "command", 9 | "DJ_LIST_CYCLE": "djListCycle", 10 | "DJ_LIST_LOCKED": "djListLocked", 11 | "DJ_LIST_UPDATE": "djListUpdate", 12 | "EARN": "earn", 13 | "FRIEND_REQUEST": "friendRequest", 14 | "FORCE_NAME_CHANGE": "nameChangedRoom", 15 | "GIFTED": "gifted", 16 | "GIFTED_EARN": "gift", 17 | "GRAB": "grab", 18 | "KILL_SESSION": "killSession", 19 | "MAINT_MODE": "plugMaintenance", 20 | "MAINT_MODE_ALERT": "plugMaintenanceAlert", 21 | "MODERATE_ADD_DJ": "modAddDJ", 22 | "MODERATE_AMBASSADOR": "modAmbassador", 23 | "MODERATE_BAN": "modBan", 24 | "MODERATE_MOVE_DJ": "modMoveDJ", 25 | "MODERATE_MUTE": "modMute", 26 | "MODERATE_REMOVE_DJ": "modRemoveDJ", 27 | "MODERATE_SKIP": "modSkip", 28 | "MODERATE_STAFF": "modStaff", 29 | "MODERATE_WLBAN": "modWaitlistBan", 30 | "NOTIFY": "notify", 31 | "PDJ_MESSAGE": "plugMessage", 32 | "PDJ_UPDATE": "plugUpdate", 33 | "PING": "ping", 34 | "PLAYLIST_CYCLE": "playlistCycle", 35 | "ROOM_DESCRIPTION_UPDATE": "roomDescriptionUpdate", 36 | "ROOM_JOIN": "roomJoin", 37 | "ROOM_NAME_UPDATE": "roomNameUpdate", 38 | "ROOM_WELCOME_UPDATE": "roomWelcomeUpdate", 39 | "ROOM_THUMBNAIL_UPDATE": "roomThumbnailUpdate", 40 | "ROOM_BANNER_UPDATE": "roomBannerUpdate", 41 | "ROOM_BACKGROUND_UPDATE": "roomBackgroundUpdate", 42 | "ROOM_LANGUAGE_UPDATE": "roomLanguageUpdate", 43 | "ROOM_GENRES_UPDATE": "roomGenresUpdate", 44 | "ROOM_SUB_GENRES_UPDATE": "roomSubGenresUpdate", 45 | "ROOM_SHORT_DESCRIPTION_UPDATE": "roomShortDescriptionUpdate", 46 | "ROOM_LONG_DESCRIPTION_UPDATE": "roomLongDescriptionUpdate", 47 | "ROOM_LINK_ADD": "roomLinkAdd", 48 | "ROOM_LINK_DELETE": "roomLinkDelete", 49 | "SKIP": "skip", 50 | "USER_JOIN": "userJoin", 51 | "USER_LEAVE": "userLeave", 52 | "USER_UPDATE": "userUpdate", 53 | "VOTE": "vote", 54 | "API_FLOOD": "floodAPI", 55 | "IP_BANNED": "banIP" 56 | } 57 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Jethro = require('jethro'); 4 | const logger = new Jethro(); 5 | const handleLogMessage = (channel, source, message) => { 6 | if (typeof source === 'string' && message != null && Object.is(typeof logger[channel], 'function')) { 7 | logger[channel](source, message); 8 | } else { 9 | logger.warning('PlugAPI', `An invalid channel (${typeof source}) , invalid channel (${typeof logger[channel]}) or message (${typeof message}) was specified`); 10 | } 11 | }; 12 | 13 | module.exports = { 14 | debug(source, message) { 15 | handleLogMessage('debug', source, message); 16 | }, 17 | error(source, message) { 18 | handleLogMessage('error', source, message); 19 | }, 20 | info(source, message) { 21 | handleLogMessage('info', source, message); 22 | }, 23 | success(source, message) { 24 | handleLogMessage('success', source, message); 25 | }, 26 | warn(source, message) { 27 | handleLogMessage('warn', source, message); 28 | }, 29 | warning(source, message) { 30 | handleLogMessage('warning', source, message); 31 | }, 32 | logger 33 | }; 34 | -------------------------------------------------------------------------------- /lib/privateVars.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const store = new WeakMap(); 4 | 5 | module.exports = function(obj) { 6 | if (!store.has(obj)) { 7 | store.set(obj, {}); 8 | } 9 | 10 | return store.get(obj); 11 | }; 12 | -------------------------------------------------------------------------------- /lib/room.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const plugMessageSplit = require('plug-message-split'); 4 | const constants = require('./constants.json'); 5 | 6 | /** 7 | * Represents a User. Note that some properties are only shown for the bot only and not other users. 8 | * 9 | * @param {Object} data Represents a user. 10 | * @param {String|null} [data.avatarID=null] The user's avatar ID. 11 | * @param {String|null} [data.badge=null] The user's badge ID. 12 | * @param {String|null} [data.blurb=undefined] The user's blurb from their profile page. 13 | * @param {Number} [data.gRole=0] The user's global role (0 if not a BA / Admin) 14 | * @param {Boolean} [data.grab=false] If the user has grabbed a song 15 | * @param {Number} [data.id=-1] The user's ID. 16 | * @param {Array} [data.ignores=[]] An array of users that are ignored. Only shown for the bot user. 17 | * @param {String} [data.joined=''] The time a user joined plug.dj as as string. Empty if not provided. 18 | * @param {String} [data.language=null] The user's language. 19 | * @param {Number} [data.level=1] The user's level. Only shown for the bot user. 20 | * @param {Array} [data.notifications=[]] The user's notifications. Only shown for the bot user. 21 | * @param {Number|null} [data.pp=undefined] The user's Plug Points. Only shown for the bot user. 22 | * @param {Boolean|null} [data.pw=undefined] Whether the bot user is signed in via password or not. 23 | * @param {String} [data.rawun=''] The user's username. 24 | * @param {Number} [data.role=0] The user's Role. See {@link PlugAPI.ROOM_ROLE} for more info. 25 | * @param {Boolean} [data.silver=false] Whether the user is a silver subscriber or not. 26 | * @param {String|null} [data.slug=null] The user's slug to be used in a profile. https://plug.dj/@/slug (only exists for level > 5) 27 | * @param {Number} [data.status=1] The user's status. 28 | * @param {Number} [data.sub=0] Whether the user is a gold subscriber or not. 29 | * @param {String} [data.username=''] The user's username with HTML entities decoded. 30 | * @param {Number} [data.vote=0] The user's current vote. 31 | * @param {Number|null} [data.xp=undefined] The user's XP. Only shown for the bot user. 32 | * @param {Room} instance The specific instance of the Room Class. 33 | * 34 | * @constructor 35 | */ 36 | class User { 37 | constructor(data, instance) { 38 | this.avatarID = data.avatarID ? data.avatarID : null; 39 | this.badge = data.badge ? data.badge : null; 40 | this.blurb = data.blurb ? data.blurb : undefined; 41 | this.gRole = data.gRole != null ? data.gRole : constants.GLOBAL_ROLES.NONE; 42 | this.grab = Object.is(instance.grabs[data.id], 1); 43 | this.id = data.id ? data.id : -1; 44 | this.ignores = data.ignores ? data.ignores : undefined; 45 | this.joined = data.joined ? data.joined : ''; 46 | this.language = data.language ? data.language : null; 47 | this.level = data.level ? data.level : 1; 48 | this.notifications = data.notifications ? data.notifications : undefined; 49 | this.pp = data.pp != null ? data.pp : undefined; 50 | this.pw = data.pw != null ? data.pw : undefined; 51 | this.rawun = data.username ? data.username : ''; 52 | this.role = data.role ? data.role : constants.ROOM_ROLE.NONE; 53 | this.silver = data.silver ? data.silver : false; 54 | this.slug = data.slug ? data.slug : null; 55 | this.status = data.status != null ? data.status : 1; 56 | this.sub = data.sub ? data.sub : 0; 57 | this.username = plugMessageSplit.unescape(data.username ? data.username : ''); 58 | this.vote = instance.votes[data.id] != null ? Object.is(instance.votes[data.id], -1) ? -1 : 1 : 0; 59 | this.xp = data.xp != null ? data.xp : undefined; 60 | 61 | } 62 | 63 | mention() { 64 | return `@${this.username}`; 65 | } 66 | 67 | toString() { 68 | return this.username; 69 | } 70 | } 71 | 72 | /** 73 | * Room data container 74 | * SHOULD NOT BE USED/ACCESSED DIRECTLY 75 | * @constructor 76 | * @private 77 | */ 78 | class Room { 79 | constructor() { 80 | this.songHistory = []; 81 | this.cacheUsers = {}; 82 | 83 | /** 84 | * @private 85 | * @type {{currentDJ: number, isLocked: boolean, shouldCycle: boolean, waitingDJs: Array}} 86 | */ 87 | this.booth = { 88 | currentDJ: -1, 89 | isLocked: false, 90 | shouldCycle: true, 91 | waitingDJs: [] 92 | }; 93 | 94 | /** 95 | * @private 96 | * @type {Array} 97 | */ 98 | this.fx = []; // eslint-disable-line no-unused-vars 99 | 100 | /** 101 | * @private 102 | * @type {{}} 103 | */ 104 | this.grabs = {}; 105 | 106 | /** 107 | * @private 108 | * @type {{shortDescription: string, longDescription: string, favorite: boolean, guests: number, hostID: number, hostName: string, id: number, minChatLevel: number, name: string, population: number, slug: string, welcome: string}} 109 | */ 110 | this.meta = { 111 | shortDescription: '', 112 | longDescription: '', 113 | favorite: false, 114 | guests: 0, 115 | hostID: -1, 116 | hostName: '', 117 | id: -1, 118 | minChatLevel: 1, 119 | name: '', 120 | population: 0, 121 | slug: '', 122 | welcome: '' 123 | }; 124 | 125 | /** 126 | * @private 127 | * @type {{}} 128 | */ 129 | this.mutes = {}; 130 | 131 | if (this.clearMuteInterval != null) clearInterval(this.clearMuteInterval); 132 | 133 | this.clearMuteInterval = setInterval(() => { 134 | const mutes = Object.keys(this.mutes); 135 | 136 | if (mutes.length > 0) { 137 | for (let i = 0; i < mutes.length; i++) { 138 | if (this.mutes[i] > 0) { 139 | this.mutes[i]--; 140 | } 141 | } 142 | } 143 | }, 1e3); 144 | 145 | /** 146 | * @private 147 | * @type {{historyID: string, media: {author: string, cid: string, duration: number, format: number, id: number, image: string, title: string}, playlistID: number, startTime: string}} 148 | */ 149 | this.playback = { 150 | historyID: '', 151 | media: { 152 | author: '', 153 | cid: '', 154 | duration: -1, 155 | format: -1, 156 | id: -1, 157 | image: '', 158 | title: '' 159 | }, 160 | playlistID: -1, 161 | startTime: '' 162 | }; 163 | 164 | /** 165 | * @private 166 | * @type {User} 167 | */ 168 | this.mySelf = {}; 169 | 170 | /** 171 | * @private 172 | * @type {number} 173 | */ 174 | this.role = constants.ROOM_ROLE.NONE; // eslint-disable-line no-unused-vars 175 | 176 | /** 177 | * @private 178 | * @type {Object[]} 179 | */ 180 | this.users = []; 181 | 182 | /** 183 | * @private 184 | * @type {{}} 185 | */ 186 | this.votes = {}; 187 | } 188 | 189 | /** 190 | * Make an array of userIDs to an array of user objects 191 | * @param {Number[]} ids User IDs to convert to array. 192 | * @returns {User[]} An array of user objects 193 | */ 194 | usersToArray(ids) { 195 | let user; 196 | const usersArray = []; 197 | 198 | if (ids) { 199 | for (let i = 0; i < ids.length; i++) { 200 | user = this.getUser(ids[i]); 201 | if (user != null) usersArray.push(user); 202 | } 203 | } 204 | 205 | return usersArray; 206 | } 207 | 208 | /** 209 | * Implementation of plug.dj methods 210 | * Gets the permissions over another user by user object 211 | * @param {User} other The Other user to compare to 212 | * @returns {{canModChat: boolean, canModMute: boolean, canModBan: boolean, canModStaff: boolean}} an object of booleans for permissions 213 | */ 214 | getPermissions(other) { 215 | const me = this.getSelf(); 216 | const permissions = { 217 | canModChat: false, 218 | canModMute: false, 219 | canModBan: false, 220 | canModStaff: false 221 | }; 222 | 223 | if (Object.is(other, null) || Object.is(me, null)) return permissions; 224 | const isAdmin = Object.is(me.gRole, constants.GLOBAL_ROLES.ADMIN); 225 | 226 | permissions.canModBan = other.gRole < constants.GLOBAL_ROLES.VOLUNTEER && ((me.gRole > constants.GLOBAL_ROLES.VOLUNTEER) || me.role > other.role); 227 | permissions.canModChat = isAdmin || ((other.gRole < constants.GLOBAL_ROLES.MODERATOR) && ((me.role > constants.ROOM_ROLE.RESIDENTDJ) || me.gRole > constants.GLOBAL_ROLES.PLOT)); 228 | permissions.canModMute = other.role < constants.ROOM_ROLE.RESIDENTDJ && other.gRole < constants.GLOBAL_ROLES.VOLUNTEER; 229 | permissions.canModStaff = isAdmin || (!Object.is(other.gRole, constants.GLOBAL_ROLES.ADMIN) && (!(me.gRole < constants.GLOBAL_ROLES.MODERATOR && me.role < constants.ROOM_ROLE.BOUNCER) && Math.max(me.role, me.gRole) > Math.max(other.role, other.gRole))); 230 | 231 | return permissions; 232 | } 233 | 234 | /** 235 | * Implementation of plug.dj methods 236 | * Gets the permissions over another user by userID 237 | * @param {Number} [uid] Other User's ID. 238 | * @returns {{canModChat: boolean, canModMute: boolean, canModBan: boolean, canModStaff: boolean}} Object of booleans of permissions 239 | */ 240 | getPermissionsID(uid) { 241 | return this.getPermissions(this.getUser(uid)); 242 | } 243 | 244 | /** 245 | * Check if a user have a certain permission in the room staff 246 | * @param {Number|undefined} uid The User's ID. If not specified, use's bot's ID. 247 | * @param {Number} permission The Room Permission to check. 248 | * @returns {boolean} Returns true if user has permission specified. False if not. 249 | */ 250 | haveRoomPermission(uid, permission) { 251 | const user = this.getUser(uid); 252 | 253 | return !(user == null || user.role < permission); 254 | } 255 | 256 | /** 257 | * Check if a user have a certain permission in the global staff (admins, ambassadors) 258 | * @param {Number|undefined} uid The User's ID. If not specified, use's bot's ID. 259 | * @param {Number} permission The Global Permission to check. 260 | * @returns {boolean} Returns true if user has global permission specified. False if not. 261 | */ 262 | haveGlobalPermission(uid, permission) { 263 | const user = this.getUser(uid); 264 | 265 | return !(user == null || user.gRole < permission); 266 | } 267 | 268 | /** 269 | * Is user an ambassador? 270 | * Can only check users in the room 271 | * @param {Number} [uid] The User's ID. If not specified, use's bot's ID. 272 | * @returns {Boolean} returns true if user is an ambassador. False if not. 273 | */ 274 | isAmbassador(uid) { 275 | if (!uid) { 276 | uid = this.mySelf.id; 277 | } 278 | 279 | return this.haveGlobalPermission(uid, constants.GLOBAL_ROLES.AMBASSADOR) && !this.isAdmin(uid); 280 | } 281 | 282 | /** 283 | * Is user an admin? 284 | * Can only check users in the room 285 | * @param {Number} [uid] The User's ID. If not specified, use's bot's ID. 286 | * @returns {Boolean} Returns true if user is an admin. False if not. 287 | */ 288 | isAdmin(uid) { 289 | if (!uid) { 290 | uid = this.mySelf.id; 291 | } 292 | 293 | return this.haveGlobalPermission(uid, constants.GLOBAL_ROLES.ADMIN); 294 | } 295 | 296 | /** 297 | * Is user staff? 298 | * Does not include global staff 299 | * Can only check users in the room 300 | * @param {Number} [uid] The User's ID. If not specified, use's bot's ID. 301 | * @returns {Boolean} Returns true if user is staff. False if not. 302 | */ 303 | isStaff(uid) { 304 | if (!uid) { 305 | uid = this.mySelf.id; 306 | } 307 | 308 | return this.haveRoomPermission(uid, constants.ROOM_ROLE.RESIDENTDJ); 309 | } 310 | 311 | /** 312 | * Is user current DJ? 313 | * @param {Number} [uid] The User's ID. If not specified, use's bot's ID. 314 | * @returns {boolean} Returns true if user is DJing. False if not. 315 | */ 316 | isDJ(uid) { 317 | if (!uid) { 318 | uid = this.mySelf.id; 319 | } 320 | 321 | return Object.is(this.booth.currentDJ, uid); 322 | } 323 | 324 | /** 325 | * Is user in waitlist? 326 | * @param {Number} [uid] The User's ID. If not specified, use's bot's ID. 327 | * @returns {boolean} Returns true if the user is in waitlist. False if not. 328 | */ 329 | isInWaitList(uid) { 330 | if (!uid) { 331 | uid = this.mySelf.id; 332 | } 333 | 334 | return this.getWaitListPosition(uid) > 0; 335 | } 336 | 337 | /** 338 | * Reset all room variables 339 | */ 340 | reset() { 341 | this.booth = { 342 | currentDJ: -1, 343 | isLocked: false, 344 | shouldCycle: true, 345 | waitingDJs: [] 346 | }; 347 | this.fx = []; 348 | this.grabs = {}; 349 | this.meta = { 350 | shortDescription: '', 351 | longDescription: '', 352 | favorite: false, 353 | guests: 0, 354 | hostID: -1, 355 | hostName: '', 356 | id: -1, 357 | minChatLevel: 1, 358 | name: '', 359 | population: 0, 360 | slug: '', 361 | welcome: '' 362 | }; 363 | this.mutes = {}; 364 | this.playback = { 365 | historyID: '', 366 | media: { 367 | author: '', 368 | cid: '', 369 | duration: -1, 370 | format: -1, 371 | id: -1, 372 | image: '', 373 | title: '' 374 | }, 375 | playlistID: -1, 376 | startTime: '' 377 | }; 378 | this.role = 0; 379 | this.users = []; 380 | this.votes = {}; 381 | } 382 | 383 | /** 384 | * Add a new user 385 | * @param {Object} user User data 386 | */ 387 | addUser(user) { 388 | 389 | // Don't add yourself 390 | if (Object.is(user.id, this.mySelf.id)) return; 391 | 392 | // Don't add guests 393 | if (user.guest) { 394 | this.meta.guests += 1; 395 | 396 | return; 397 | } 398 | 399 | // Only add if the user doesn't exist 400 | if (Object.is(this.getUser(user.id), null)) { 401 | this.users.push(user); 402 | this.meta.population = this.users.length + 1; 403 | } 404 | 405 | // Remove user from cache 406 | delete this.cacheUsers[this.booth.currentDJ]; 407 | } 408 | 409 | /** 410 | * Remove an user 411 | * @param {Number} uid UserID 412 | */ 413 | removeUser(uid) { 414 | 415 | // Remove guests 416 | if (Object.is(uid, 0)) { 417 | this.meta.guests = Math.max(0, this.meta.guests - 1); 418 | 419 | return; 420 | } 421 | 422 | const users = Object.keys(this.users); 423 | 424 | for (let i = 0; i < users.length; i++) { 425 | if (Object.is(this.users[i].id, uid)) { 426 | 427 | // User found 428 | this.cacheUsers[uid] = this.users.splice(i, 1); 429 | this.meta.population = this.users.length + 1; 430 | 431 | if (this.votes[uid]) delete this.votes[uid]; 432 | if (this.grabs[uid]) delete this.grabs[uid]; 433 | 434 | return; 435 | } 436 | } 437 | } 438 | 439 | /** 440 | * Update an user 441 | * @param {Object} data User data changes 442 | */ 443 | updateUser(data) { 444 | const users = Object.keys(this.users); 445 | const dataLoop = (i, userData, isSelf) => { 446 | for (const j in userData) { 447 | if (!userData.hasOwnProperty(j)) continue; 448 | if (!Object.is(j, 'i')) { 449 | if (isSelf) { 450 | this.mySelf[j] = userData[j]; 451 | } else { 452 | this.users[i][j] = userData[j]; 453 | } 454 | } 455 | } 456 | }; 457 | 458 | for (let i = 0; i < users.length; i++) { 459 | if (Object.is(this.users[i].id, data.i)) { 460 | dataLoop(i, data); 461 | 462 | return; 463 | } else if (Object.is(this.mySelf.id, data.i)) { 464 | dataLoop(i, data, true); 465 | 466 | return; 467 | } 468 | } 469 | } 470 | 471 | /** 472 | * Is user muted? 473 | * @param {Number} uid The user's id to check if they are muted 474 | * @returns {Boolean} True if muted, false if not. 475 | */ 476 | 477 | isMuted(uid) { 478 | return this.mutes[uid] != null && this.mutes[uid] > 0; 479 | } 480 | 481 | /** 482 | * Set mySelf object 483 | * @param {Object} data Self data 484 | */ 485 | setSelf(data) { 486 | this.mySelf = data; 487 | } 488 | 489 | /** 490 | * Set room data 491 | * @param {Object} data Room data 492 | */ 493 | setRoomData(data) { 494 | this.booth = data.booth; 495 | this.fx = data.fx; 496 | this.grabs = data.grabs; 497 | this.meta = data.meta; 498 | this.mutes = data.mutes; 499 | this.playback = data.playback; 500 | this.mySelf.role = data.role; 501 | this.users = data.users; 502 | this.votes = data.votes; 503 | } 504 | 505 | setBoothLocked(data) { 506 | this.booth.isLocked = data; 507 | } 508 | 509 | setBoothCycle(cycle) { 510 | this.booth.shouldCycle = cycle; 511 | } 512 | 513 | setDJs(djs) { 514 | this.booth.waitingDJs = djs; 515 | } 516 | 517 | setMinChatLevel(level) { 518 | this.meta.minChatLevel = level; 519 | } 520 | 521 | setRoomDescription(desc) { 522 | this.meta.shortDescription = desc; 523 | } 524 | 525 | setRoomLongDescription(desc) { 526 | this.meta.longDescription = desc; 527 | } 528 | 529 | setRoomName(name) { 530 | this.meta.name = name; 531 | } 532 | 533 | setRoomWelcome(welcome) { 534 | this.meta.welcome = welcome; 535 | } 536 | 537 | /** 538 | * Set the media object. 539 | * @param {Object} mediaInfo The Media Info 540 | * @param {String} mediaStartTime The Media starting time. 541 | */ 542 | setMedia(mediaInfo, mediaStartTime) { 543 | this.votes = {}; 544 | this.grabs = {}; 545 | 546 | this.playback.media = mediaInfo; 547 | this.playback.startTime = mediaStartTime; 548 | } 549 | 550 | /** 551 | * @param {AdvanceEventObject} data Advance Event Object data 552 | */ 553 | advance(data) { 554 | if (this.songHistory.length < 1) { 555 | setImmediate(this.advance.bind(this), data); 556 | 557 | return; 558 | } 559 | 560 | this.songHistory[0].room = this.getRoomScore(); 561 | 562 | this.setMedia(data.media, data.startTime); 563 | this.setDJs(data.djs); 564 | 565 | this.booth.currentDJ = data.currentDJ; 566 | this.playback.historyID = data.historyID; 567 | this.playback.playlistID = data.playlistID; 568 | 569 | // Clear cache of users 570 | this.cacheUsers = {}; 571 | } 572 | 573 | muteUser(data) { 574 | switch (data.d) { 575 | 576 | // Unmute 577 | case 'o': 578 | this.mutes[data.i] = 0; 579 | break; 580 | 581 | // Short (15 minutes) 582 | case 's': 583 | this.mutes[data.i] = 900; 584 | break; 585 | 586 | // Medium (30 minutes) 587 | case 'm': 588 | this.mutes[data.i] = 1800; 589 | break; 590 | 591 | // Long (45 minutes) 592 | case 'l': 593 | this.mutes[data.i] = 2700; 594 | break; 595 | default: 596 | this.mutes[data.i] = null; 597 | break; 598 | } 599 | } 600 | 601 | setGrab(uid) { 602 | this.grabs[uid] = 1; 603 | } 604 | 605 | setVote(uid, vote) { 606 | this.votes[uid] = vote; 607 | } 608 | 609 | setEarn(data) { 610 | if (isFinite(data.xp) && data.xp > 0) { 611 | this.mySelf.xp = data.xp; 612 | } 613 | if ((isFinite(data.pp) && data.pp > 0) || (isFinite(data) && data > 0)) { 614 | this.mySelf.pp = data.pp || data; 615 | } 616 | if (isFinite(data.level) && data.level > 0) { 617 | this.mySelf.level = data.level; 618 | } 619 | } 620 | 621 | /** 622 | * Get the user object for yourself 623 | * @returns {User} A User Object 624 | */ 625 | getSelf() { 626 | return this.mySelf != null ? new User(this.mySelf, this) : null; 627 | } 628 | 629 | /** 630 | * Get specific user in the community 631 | * @param {Number} [uid] The User ID to lookup 632 | * @returns {User|null} A User Object or Null if can't be found 633 | */ 634 | getUser(uid) { 635 | if (!uid || Object.is(uid, this.mySelf.id)) return this.getSelf(); 636 | if (Object.is(typeof uid, 'string')) { 637 | uid = parseInt(uid, 10); 638 | } 639 | const users = Object.keys(this.users); 640 | 641 | for (let i = 0; i < users.length; i++) { 642 | if (Object.is(this.users[i].id, uid)) return new User(this.users[i], this); 643 | } 644 | 645 | return null; 646 | } 647 | 648 | /** 649 | * Get all users in the community 650 | * @returns {User[]} An array of users in room 651 | */ 652 | getUsers() { 653 | return this.usersToArray([this.mySelf.id].concat(((() => { 654 | const ids = []; 655 | const users = Object.keys(this.users); 656 | 657 | for (let i = 0; i < users.length; i++) { 658 | ids.push(this.users[i].id); 659 | } 660 | 661 | return ids; 662 | }))())); 663 | } 664 | 665 | /** 666 | * Get the current DJ 667 | * @returns {User|null} Current DJ or {null} if no one is currently DJing 668 | */ 669 | getDJ() { 670 | if (this.booth.currentDJ > 0) { 671 | const user = this.getUser(this.booth.currentDJ); 672 | 673 | if (!Object.is(user, null)) { 674 | return user; 675 | } 676 | 677 | if (this.cacheUsers[this.booth.currentDJ] != null) { 678 | return new User(this.cacheUsers[this.booth.currentDJ], this); 679 | } 680 | } 681 | 682 | return null; 683 | } 684 | 685 | /** 686 | * Get all DJs (including current DJ) 687 | * @returns {User[]} An Array of all DJs in the room and the current DJ. 688 | */ 689 | getDJs() { 690 | if (this.booth.currentDJ == null || this.booth.waitingDJs == null) { 691 | return []; 692 | } 693 | 694 | return this.usersToArray([this.booth.currentDJ].concat(this.booth.waitingDJs)); 695 | } 696 | 697 | /** 698 | * Get all DJs in waitlist 699 | * @returns {User[]} An Array of all DJs in the room that are in waitlist 700 | */ 701 | getWaitList() { 702 | if (this.booth.waitingDJs == null) { 703 | return []; 704 | } 705 | 706 | return this.usersToArray(this.booth.waitingDJs); 707 | } 708 | 709 | /** 710 | * Get a user's position in waitlist 711 | * @param {Number} uid User ID 712 | * @returns {Number} 713 | * Position in waitlist. 714 | * If current DJ, it returns 0. 715 | * If not in waitlist, it returns -1 716 | */ 717 | getWaitListPosition(uid) { 718 | if (Object.is(this.booth.currentDJ, uid)) { 719 | return 0; 720 | } 721 | const pos = this.booth.waitingDJs == null ? -1 : this.booth.waitingDJs.indexOf(uid); 722 | 723 | return pos < 0 ? -1 : pos + 1; 724 | } 725 | 726 | /** 727 | * Get admins in the room 728 | * @returns {Array} An Array of all admins in the room 729 | */ 730 | getAdmins() { 731 | const admins = []; 732 | const reference = [this.mySelf].concat(this.users); 733 | const references = Object.keys(reference); 734 | 735 | for (let i = 0; i < references.Length; i++) { 736 | if (Object.is(reference[i].gRole, constants.GLOBAL_ROLES.ADMIN)) { 737 | admins.push(reference[i].id); 738 | } 739 | } 740 | 741 | return this.usersToArray(admins); 742 | } 743 | 744 | /** 745 | * Get all ambassadors in the community 746 | * @returns {Array} An Array of all ambassadors in the room 747 | */ 748 | getAmbassadors() { 749 | const ambassadors = []; 750 | const reference = [this.mySelf].concat(this.users); 751 | const references = Object.keys(reference); 752 | 753 | for (let i = 0; i < references.Length; i++) { 754 | if (Object.is(reference[i].gRole, constants.GLOBAL_ROLES.AMBASSADOR)) { 755 | ambassadors.push(reference[i].id); 756 | } 757 | } 758 | 759 | return this.usersToArray(ambassadors); 760 | } 761 | 762 | /** 763 | * Get users in the community that aren't DJing nor in the waitlist 764 | * @returns {Array} An array of all the users in room that are not DJing and not in the waitlist. 765 | */ 766 | getAudience() { 767 | const audience = []; 768 | const reference = [this.mySelf].concat(this.users); 769 | const references = Object.keys(reference); 770 | 771 | for (let i = 0; i < references.length; i++) { 772 | const uid = reference[i].id; 773 | 774 | if (this.getWaitListPosition(uid) < 0) { 775 | audience.push(uid); 776 | } 777 | } 778 | 779 | return this.usersToArray(audience); 780 | } 781 | 782 | getStaff() { 783 | const staff = []; 784 | const reference = [this.mySelf].concat(this.users); 785 | const references = Object.keys(reference); 786 | 787 | for (let i = 0; i < references.length; i++) { 788 | const user = reference[i]; 789 | 790 | if (user.role > constants.ROOM_ROLE.NONE) { 791 | staff.push(user.id); 792 | } 793 | } 794 | 795 | return this.usersToArray(staff); 796 | } 797 | 798 | /** 799 | * Host if in community, otherwise null 800 | * @returns {User|null} Returns the host user object if they are in room, or null if there is none. 801 | */ 802 | getHost() { 803 | return this.getUser(this.meta.hostID); 804 | } 805 | 806 | getMedia() { 807 | return this.playback.media; 808 | } 809 | 810 | getStartTime() { 811 | return this.playback.startTime; 812 | } 813 | 814 | getHistoryID() { 815 | return this.playback.historyID; 816 | } 817 | 818 | getHistory() { 819 | return this.songHistory; 820 | } 821 | 822 | setHistory(err, data) { 823 | if (!err) { 824 | this.songHistory = data; 825 | } 826 | } 827 | 828 | /** 829 | * Get the booth meta 830 | * @returns {booth} The booth meta in an object 831 | */ 832 | getBoothMeta() { 833 | const result = Object.assign({}, this.booth); 834 | 835 | // Override ids with full user objects 836 | result.currentDJ = this.getDJ(); 837 | result.waitingDJs = this.getWaitList(); 838 | 839 | return result; 840 | } 841 | 842 | /** 843 | * Get the room meta 844 | * @returns {meta} The room meta in an object 845 | */ 846 | getRoomMeta() { 847 | return Object.assign({}, this.meta); 848 | } 849 | 850 | /** 851 | * Get the room score 852 | * @returns {{positive: number, listeners: number, grabs: number, negative: number, skipped: number}} An object representing the room score. 853 | */ 854 | getRoomScore() { 855 | const result = { 856 | positive: 0, 857 | listeners: Math.max(this.getUsers().length - 1, 0), 858 | grabs: 0, 859 | negative: 0, 860 | skipped: 0 861 | }; 862 | let i; 863 | const votesData = Object.keys(this.votes); 864 | const grabsData = Object.keys(this.grabs); 865 | 866 | for (i = 0; i < votesData.length; i++) { 867 | if (this.votes[votesData[i]] > 0) { 868 | result.positive++; 869 | } else if (this.votes[votesData[i]] < 0) { 870 | result.negative++; 871 | } 872 | } 873 | 874 | for (i = 0; i < grabsData.length; i++) { 875 | if (this.grabs[grabsData[i]] > 0) { 876 | result.grabs++; 877 | } 878 | } 879 | 880 | return result; 881 | } 882 | 883 | registerUserExtensions(instance) { 884 | 885 | /** 886 | * Add the user to the waitlist 887 | * @memberof User 888 | */ 889 | User.prototype.addToWaitList = function() { 890 | instance.moderateAddDJ(this.id); 891 | }; 892 | 893 | /** 894 | * Remove the user from the waitlist 895 | * @memberof User 896 | */ 897 | User.prototype.removeFromWaitList = function() { 898 | instance.moderateRemoveDJ(this.id); 899 | }; 900 | 901 | /** 902 | * Move the user to a new position in the waitlist 903 | * @memberof User 904 | * @param {Number} pos New position 905 | */ 906 | User.prototype.moveInWaitList = function(pos) { 907 | instance.moderateMoveDJ(this.id, pos); 908 | }; 909 | } 910 | } 911 | module.exports = Room; 912 | -------------------------------------------------------------------------------- /lib/socket.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const WebSocket = require('ws'); 4 | const autoBind = require('auto-bind'); 5 | const EventEmitter3 = require('eventemitter3'); 6 | 7 | const timerDuration = 15 * 1000; // Average Heartbeat is approximately 15 seconds 8 | 9 | /** 10 | * Socket Class with auto reconnect. 11 | * @param {String} socketUrl The URL for WebSocket to connect to. 12 | * @extends {EventEmitter3} 13 | * @author Henchman 14 | * @constructor 15 | * @private 16 | */ 17 | class Socket extends EventEmitter3 { 18 | constructor(socketUrl) { 19 | super(); 20 | autoBind(this); 21 | this.url = socketUrl; 22 | this.alive = false; 23 | this.manualExit = false; 24 | this.downTimeout = undefined; 25 | this.connectionAttempts = 0; 26 | this.reconnectTimeout = undefined; 27 | } 28 | 29 | connect() { 30 | clearTimeout(this.downTimeout); 31 | clearTimeout(this.reconnectTimeout); 32 | this.downTimeout = this.reconnectTimeout = undefined; 33 | if (this.connection && Object.is(this.connection.readyState, WebSocket.OPEN)) { 34 | this.connection.removeAllListeners(); 35 | this.connection.on('error', () => {}); 36 | this.connection.close(); 37 | } 38 | this.connection = new WebSocket(this.url, { 39 | origin: 'https://plug.dj' 40 | }); 41 | 42 | this.connection.on('open', (data) => { 43 | this.resetDownTimer(); 44 | this.emit('open', data); 45 | }); 46 | this.connection.on('close', (data) => { 47 | if (this.manualExit) { 48 | clearTimeout(this.downTimeout); 49 | clearTimeout(this.reconnectTimeout); 50 | this.downTimeout = this.reconnectTimeout = undefined; 51 | } else { 52 | this.reconnect(); 53 | } 54 | this.manualExit = false; 55 | this.emit('close', data); 56 | }); 57 | this.connection.on('error', (data) => { 58 | this.reconnect(); 59 | this.emit('error', data); 60 | }); 61 | this.connection.on('message', (data) => { 62 | this.heartbeat(); 63 | this.emit('message', data); 64 | }); 65 | } 66 | 67 | reconnect() { 68 | if (Object.is(this.reconnectTimeout, undefined)) { 69 | clearTimeout(this.downTimeout); 70 | this.downTimeout = undefined; 71 | this.reconnectTimeout = setTimeout(() => { 72 | this.connect(); 73 | }, 5000); 74 | } 75 | } 76 | 77 | heartbeat() { 78 | this.resetDownTimer(); 79 | } 80 | 81 | resetDownTimer() { 82 | clearTimeout(this.downTimeout); 83 | this.downTimeout = undefined; 84 | if (Object.is(this.reconnectTimeout, undefined)) { 85 | this.downTimeout = setTimeout(() => { 86 | this.reconnect(); 87 | }, timerDuration); 88 | } 89 | } 90 | 91 | send(data) { 92 | this.connection.send(data); 93 | } 94 | 95 | close(isManualExit) { 96 | if (isManualExit) { 97 | this.manualExit = true; 98 | } 99 | 100 | return this.connection.close(); 101 | } 102 | 103 | } 104 | 105 | module.exports = Socket; 106 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * PlugAPI Util Functions 5 | * @memberof PlugAPI 6 | */ 7 | const Utils = { 8 | 9 | /** 10 | * Calculates the levenshtein distance between two strings. 11 | * Code obtained from http://rosettacode.org/wiki/Levenshtein_distance#JavaScript 12 | * @param {String} a String A 13 | * @param {String} b String B 14 | * @memberof PlugAPI.Utils 15 | * @returns {Number} Levenshtein distance between string A and B 16 | */ 17 | levenshtein(a, b) { 18 | const aLen = a.length; 19 | const bLen = b.length; 20 | const d = []; 21 | let i; 22 | let j; 23 | 24 | if (!aLen) return bLen; 25 | if (!bLen) return aLen; 26 | 27 | for (i = 0; i <= aLen; i++) { 28 | d[i] = [i]; 29 | } 30 | for (j = 0; j <= bLen; j++) { 31 | d[0][j] = j; 32 | } 33 | 34 | for (j = 1; j <= bLen; j++) { 35 | for (i = 1; i <= aLen; i++) { 36 | if (a[i - 1] === b[j - 1]) { 37 | d[i][j] = d[i - 1][j - 1]; 38 | } else { 39 | d[i][j] = Math.min(d[i - 1][j], d[i][j - 1], d[i - 1][j - 1]) + 1; 40 | } 41 | } 42 | } 43 | 44 | return d[aLen][bLen]; 45 | }, 46 | 47 | /** 48 | * Calculates the percentage of uppercase letters in a string 49 | * @param {String} str The string to calculate 50 | * @memberof PlugAPI.Utils 51 | * 52 | * @example 53 | * 54 | * PlugAPI.Utils.uppercase('HAI') // returns 100 55 | * @returns {Number} Percentage of uppercase letters 56 | */ 57 | uppercase(str) { 58 | const chars = str.length; 59 | const uLet = str.match(/[A-Z]/g); 60 | 61 | if (uLet != null) { 62 | return (uLet.length / chars) * 100; 63 | } 64 | 65 | return 0; 66 | } 67 | }; 68 | 69 | module.exports = Utils; 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plugapi", 3 | "description": "Generic API for building plug.dj bots", 4 | "version": "5.1.1", 5 | "author": "Alex Pham (https://thedark1337.com)", 6 | "bugs": "https://github.com/plugCubed/plugAPI/issues", 7 | "contributors": [ 8 | { 9 | "name": "Chris Vickery", 10 | "email": "chrisinajar@gmail.com" 11 | }, 12 | { 13 | "name": "ging" 14 | }, 15 | { 16 | "name": "Nicolas Jeker", 17 | "email": "n.jeker@gmx.net" 18 | }, 19 | { 20 | "name": "Chris Bellew", 21 | "email": "atomjack@gmail.com" 22 | }, 23 | { 24 | "name": "Chikachi", 25 | "email": "TAT@plugcubed.net" 26 | }, 27 | { 28 | "name": "Dazzuh", 29 | "email": "dazzuh@radiant.dj" 30 | } 31 | ], 32 | "dependencies": { 33 | "auto-bind": "^2.1.1", 34 | "chalk": "^2.3.2", 35 | "deasync": "^0.1.15", 36 | "eventemitter3": "^4.0.0", 37 | "got": "^9.6.0", 38 | "handle-errors": "^1.0.0", 39 | "jethro": "^4.4.3", 40 | "p-retry": "^4.1.0", 41 | "plug-message-split": "^1.0.1", 42 | "tough-cookie": "^3.0.1", 43 | "ws": "^7.1.2" 44 | }, 45 | "devDependencies": { 46 | "documentation": "^12.1.2", 47 | "eslint-config-thedark1337": "^5.0.0", 48 | "mocha": "^6.2.1", 49 | "mocha-eslint": "^5.0.0" 50 | }, 51 | "engines": { 52 | "node": ">= 4.0.0" 53 | }, 54 | "keywords": [ 55 | "api", 56 | "bot", 57 | "plug", 58 | "plug.dj" 59 | ], 60 | "license": "MIT", 61 | "main": "./lib/client.js", 62 | "optionalDependencies": { 63 | "bufferutil": "2.x || 3.x", 64 | "utf-8-validate": "2.x || 4.x" 65 | }, 66 | "repository": "plugcubed/plugapi", 67 | "scripts": { 68 | "docs": "documentation build lib/*.js -c documentation.yml -f html -o docs", 69 | "test": "mocha" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/eslint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const lint = require('mocha-eslint'); 4 | const paths = [ 5 | 'lib/**/*.js', 6 | 'test/*.js' 7 | ]; 8 | const options = {}; 9 | 10 | lint(paths, options); 11 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --timeout 5000 2 | --------------------------------------------------------------------------------