From fc5b6e988357ba0e6e96dfc93ad693a61a8ccd5c Mon Sep 17 00:00:00 2001 From: ItsOnlyBinary Date: Thu, 9 Apr 2020 16:51:49 +0100 Subject: [PATCH 01/11] Complete Rewrite --- .eslintignore | 2 + .eslintrc.js | 99 +- .gitignore | 25 +- .stylelintignore | 2 + .stylelintrc.js | 8 + README.md | 44 +- babel.config.js | 5 + package.json | 78 +- plugin-conference.js | 436 -- src/components/HeaderButton.vue | 56 + src/components/JitsiMediaView.vue | 230 + src/components/MessageTemplate.vue | 93 + src/config.js | 76 + src/plugin.js | 94 + webpack.config.js | 51 +- yarn.lock | 9351 +++++++++++++--------------- 16 files changed, 5010 insertions(+), 5640 deletions(-) create mode 100644 .eslintignore create mode 100644 .stylelintignore create mode 100644 .stylelintrc.js create mode 100644 babel.config.js delete mode 100644 plugin-conference.js create mode 100644 src/components/HeaderButton.vue create mode 100644 src/components/JitsiMediaView.vue create mode 100644 src/components/MessageTemplate.vue create mode 100644 src/config.js create mode 100644 src/plugin.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..9464cfa --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +webpack.config.js +/dist/ \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 028d66e..b8df550 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,46 +1,53 @@ -module.exports = { - root: true, - parserOptions: { - parser: 'babel-eslint', - sourceType: 'module' - }, - extends: [ - 'airbnb-base', - 'standard' - ], - env: { - 'browser': true, - }, - // add your custom rules here - 'rules': { - 'class-methods-use-this': 0, - 'comma-dangle': ['error', { - 'arrays': 'always-multiline', - 'objects': 'always-multiline', - 'imports': 'never', - 'exports': 'never', - 'functions': 'ignore' - }], - 'import/extensions': 0, - 'import/no-extraneous-dependencies': 0, - 'import/no-unresolved': 0, - 'import/prefer-default-export': 0, - 'indent': ['error', 4], - 'no-continue': 0, - 'no-multi-assign': 0, - 'no-param-reassign': ['error', { 'props': false }], - 'no-plusplus': 0, - 'no-prototype-builtins': 0, - 'prefer-promise-reject-errors': 0, - 'no-control-regex': 0, - 'object-shorthand': 0, - 'operator-linebreak': 0, - 'prefer-const': 0, - 'prefer-destructuring': 0, - 'prefer-template': 0, - 'semi': ['error', 'always'], - 'space-before-function-paren': ['error', 'never'], - 'vue/html-indent': ['error', 4], - 'vue/max-attributes-per-line': 0, - } -} +module.exports = { + root: true, + parserOptions: { + parser: 'babel-eslint', + sourceType: 'module' + }, + extends: [ + 'airbnb-base', + 'plugin:vue/recommended', + 'standard' + ], + env: { + 'browser': true, + }, + plugins: [ + 'vue', + ], + // add your custom rules here + rules: { + 'class-methods-use-this': 0, + 'comma-dangle': ['error', { + 'arrays': 'always-multiline', + 'objects': 'always-multiline', + 'imports': 'never', + 'exports': 'never', + 'functions': 'ignore' + }], + 'import/extensions': 0, + 'import/no-extraneous-dependencies': 0, + 'import/no-unresolved': 0, + 'import/prefer-default-export': 0, + 'indent': ['error', 4], + 'no-continue': 0, + 'no-multi-assign': 0, + 'no-param-reassign': ['error', { 'props': false }], + 'no-plusplus': 0, + 'no-prototype-builtins': 0, + 'prefer-promise-reject-errors': 0, + 'no-control-regex': 0, + 'object-shorthand': 0, + 'operator-linebreak': 0, + 'prefer-const': 0, + 'prefer-destructuring': 0, + 'prefer-object-spread': 0, + 'prefer-template': 0, + 'semi': ['error', 'always'], + 'space-before-function-paren': ['error', 'never'], + 'vue/html-indent': ['error', 4], + 'vue/max-attributes-per-line': 0, + 'vue/require-prop-types': 0, + 'vue/require-default-prop': 0, + } +} diff --git a/.gitignore b/.gitignore index 62aa73f..4d0f2a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,21 @@ -/node_modules/ -/dist/ -/.cache/ -/yarn-error.log +.DS_Store +dist/ +node_modules/ + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw* diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000..3d2bc62 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,2 @@ +/dist/ +/node_modules/ diff --git a/.stylelintrc.js b/.stylelintrc.js new file mode 100644 index 0000000..b10bb66 --- /dev/null +++ b/.stylelintrc.js @@ -0,0 +1,8 @@ +module.exports = { + extends: 'stylelint-config-standard', + rules: { + indentation: 4, + 'no-descending-specificity': null, + 'declaration-no-important': true, + } +}; diff --git a/README.md b/README.md index abc9a4b..fc2fc56 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # KiwiIRC - Audio / Video conferencing -This plugin integrates the [Jitsi Meet](https://jitsi.org/jitsi-meet/) conference software into KiwiIRC. +This plugin integrates the [Jitsi Meet](https://jitsi.org/jitsi-meet/) conference software into KiwiIRC. Features - * Individual conference rooms for channels and private messages @@ -13,7 +13,7 @@ Features - yarn && yarn build ~~~ -Copy `dist/plugin-conference.min.js` to your Kiwi plugins folder +Copy `dist/plugin-conference.js` to your Kiwi plugins folder ### Loading the plugin into Kiwi IRC Add the plugin javascript file to your kiwiirc `config.json` and configure the settings: @@ -23,7 +23,7 @@ Add the plugin javascript file to your kiwiirc `config.json` and configure the s "plugins": [ { "name": "conference", - "url": "static/plugins/plugin-conference/dist/plugin-conference.min.js" + "url": "static/plugins/plugin-conference/dist/plugin-conference.js" } ], "conference": { @@ -43,29 +43,35 @@ Jitsi Meet supports extra configuration to customise its interface and functions The defaults are: ~~~json -"conference":{ - "server": "meet.jit.si", +"plugin-conference": { "secure": false, - "enabledInChannels": [ "*" ], - "joinText": "has joined the conference", - "inviteText": "is inviting you to a private call.", + "server": "meet.jit.si", + "queries": true, + "channels": true, + "enabledInChannels": ["*"], + "groupInvitesTTL": 30000, + "maxParticipantsLength": 60, + "participantsMore": "more...", + "inviteText": "{{ nick }} is inviting you to a private call.", + "joinText": "{{ nick }} has joined the conference.", "joinButtonText": "Join now!", - "disabledText": "Sorry. The sysop has not enabled conferences in this channel.", - "showLink": true, + "showLink": false, "useLinkShortener": false, + "linkShortenerURL": "https://x0.no/api/?{{ link }}", "linkShortenerAPIToken": "API_KEY_HERE", - "linkShortenerURL": "", "interfaceConfigOverwrite": { "SHOW_JITSI_WATERMARK": false, "SHOW_WATERMARK_FOR_GUESTS": false, "TOOLBAR_BUTTONS": [ - "microphone", "camera", "fullscreen", "fodeviceselection", "hangup", - "settings", "videoquality", "filmstrip", - "stats", "shortcuts" - ] + "microphone", "camera", "fullscreen", "hangup", + "settings", "videoquality", "filmstrip", "fodeviceselection", + "stats", "shortcuts", + ], }, "configOverwrite": { - } + "startWithVideoMuted": true, + "startWithAudioMuted": true, + }, } ~~~ @@ -76,11 +82,13 @@ Examples of linkShortenerURL data are: If using Bitly: - https://api-ssl.bitly.com/v3/shorten + `https://api-ssl.bitly.com/v3/shorten` Alternative shortener that doesn't require an API token: - https://x0.no/api/ + `https://x0.no/api/?{{ link }}` + +note: for link shorteners other than Bitly `{{ link }}` is replaced with the conference url and the response is read from the body More info about Jitsi's options can be found in these files: * https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..45f7299 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + '@babel/env', + ], +}; diff --git a/package.json b/package.json index 80d037b..7ddf0e1 100755 --- a/package.json +++ b/package.json @@ -1,39 +1,43 @@ { - "name": "conference-kiwiirc-plugin", - "version": "1.0.0", - "main": "plugin-conference.js", - "license": "Apache", - "private": true, - "scripts": { - "build": "webpack", - "watch": "webpack --watch", - "serve": "webpack-dev-server", - "test": "xo", - "lint:eslint": "eslint --ext .js ./", - "lint": "npm run lint:eslint" - }, - "dependencies": { - "@babel/core": "^7.0.0", - "@babel/preset-env": "^7.0.0", - "babel-core": "^6.26.3", - "babel-loader": "^8.0.2", - "babel-preset-env": "^1.7.0", - "platform": "^1.3.5", - "regenerator-runtime": "^0.13.1" - }, - "devDependencies": { - "css-loader": "^0.28.11", - "eslint": "^5.4.0", - "eslint-config-airbnb-base": "^13.1.0", - "eslint-config-standard": "^11.0.0", - "eslint-plugin-import": "^2.14.0", - "eslint-plugin-node": "^7.0.1", - "eslint-plugin-promise": "^4.0.0", - "eslint-plugin-standard": "^3.1.0", - "style-loader": "^0.20.3", - "webpack": "^4.8.3", - "webpack-cli": "^2.0.13", - "webpack-dev-server": "^3.1.1", - "xo": "^0.20.3" - } + "name": "kiwiirc-plugin-conference", + "version": "1.0.0", + "main": "./src/plugin.js", + "license": "Apache-2.0", + "private": true, + "scripts": { + "build": "webpack", + "watch": "webpack --watch", + "dev": "webpack-dev-server", + "lint": "npm run lint:eslint && npm run lint:stylelint", + "lint:eslint": "eslint --ext .js,.vue ./", + "lint:stylelint": "stylelint \"src/**/*.{vue,htm,html,css,sss,less,scss}\"" + }, + "dependencies": { + "platform": "^1.3.5" + }, + "devDependencies": { + "@babel/core": "^7.9.0", + "@babel/preset-env": "^7.9.5", + "babel-eslint": "^10.0.3", + "babel-loader": "8.1.0", + "css-loader": "^3.5.1", + "eslint": "^6.8.0", + "eslint-config-airbnb-base": "^14.1.0", + "eslint-config-standard": "^14.1.1", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-standard": "^4.0.1", + "eslint-plugin-vue": "^6.2.2", + "exports-loader": "^0.7.0", + "style-loader": "^1.1.3", + "stylelint": "^13.3.1", + "stylelint-config-standard": "^20.0.0", + "stylelint-webpack-plugin": "^1.2.3", + "vue-loader": "^15.9.1", + "vue-template-compiler": "^2.6.11", + "webpack": "^4.42.1", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.1.3" + } } diff --git a/plugin-conference.js b/plugin-conference.js deleted file mode 100644 index b4e558d..0000000 --- a/plugin-conference.js +++ /dev/null @@ -1,436 +0,0 @@ -/* eslint-disable vue/html-indent */ -/* eslint-disable no-bitwise */ -/* eslint-disable no-restricted-globals */ -/* eslint-disable no-extend-native */ -import platform from 'platform'; -const regeneratorRuntime = require("regenerator-runtime"); - -if (platform.name === 'IE') { - // polyfill for includes - // https://tc39.github.io/ecma262/#sec-array.prototype.includes - if (!Array.prototype.includes) { - Object.defineProperty(Array.prototype, 'includes', { - value: function(searchElement, fromIndex) { // eslint-disable-line - if (this == null) { - throw new TypeError('"this" is null or not defined'); - } - // 1. Let O be ? ToObject(this value). - let o = Object(this); - // 2. Let len be ? ToLength(? Get(O, "length")). - let len = o.length >>> 0; - // 3. If len is 0, return false. - if (len === 0) { - return false; - } - // 4. Let n be ? ToInteger(fromIndex). - // (If fromIndex is undefined, this step produces the value 0.) - let n = fromIndex | 0; - - // 5. If n ≥ 0, then - // a. Let k be n. - // 6. Else n < 0, - // a. Let k be len + n. - // b. If k < 0, let k be 0. - let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); - let sameValueZero = (x, y) => x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y)); - // 7. Repeat, while k < len - while (k < len) { - // a. Let elementK be the result of ? Get(O, ! ToString(k)). - // b. If SameValueZero(searchElement, elementK) is true, return true. - if (sameValueZero(o[k], searchElement)) { - return true; - } - // c. Increase k by 1. - k++; - } - // 8. Return false - return false; - }, - }); - } -} - -kiwi.plugin('conferencePlugin', (kiwi, log) => { /* eslint-disable-line no-undef */ - let api = null; - let token = null; - // captionTimer holds a per-buffer value to control grouping of join messages - let captionTimer = []; - // captions holds the actual message data that is displayed upon conference joins - let captions = []; - let kiwiConferenceTag = '1'; - let sharedData = { isOpen: false }; - let enabledInChannels = ['*']; - let inviteText = ''; - let joinText = ''; - let joinButtonText = ''; - let disabledText = ''; - let showLink = ''; - let useLinkShortener = ''; - let linkShortenerURL = ''; - let linkShortenerAPIToken = ''; - - const groupedNoticesTTL = 30000; - - if (kiwi.state.setting('conference.enabledInChannels')) { - enabledInChannels = kiwi.state.setting('conference.enabledInChannels'); - } - - if (kiwi.state.setting('conference.inviteText')) { - inviteText = ' ' + kiwi.state.setting('conference.inviteText'); - } else { - inviteText = ' is inviting you to a private call.'; - } - - if (kiwi.state.setting('conference.joinText')) { - joinText = kiwi.state.setting('conference.joinText'); - } else { - joinText = 'has joined the conference.'; - } - - if (kiwi.state.setting('conference.joinButtonText')) { - joinButtonText = kiwi.state.setting('conference.joinButtonText'); - } else { - joinButtonText = 'Join now!'; - } - - if (kiwi.state.setting('conference.disabledText')) { - disabledText = kiwi.state.setting('conference.disabledText'); - } else { - disabledText = 'Sorry. The sysop has not enabled conferences in this channel.'; - } - - if (kiwi.state.setting('conference.showLink')) { - showLink = kiwi.state.setting('conference.showLink'); - } else { - showLink = false; - } - - if (kiwi.state.setting('conference.useLinkShortener')) { - useLinkShortener = kiwi.state.setting('conference.useLinkShortener'); - } else { - useLinkShortener = false; - } - - if (kiwi.state.setting('conference.linkShortenerURL')) { - linkShortenerURL = kiwi.state.setting('conference.linkShortenerURL'); - } else { - linkShortenerURL = ''; - } - - if (kiwi.state.setting('conference.linkShortenerAPIToken')) { - linkShortenerAPIToken = kiwi.state.setting('conference.linkShortenerAPIToken'); - } else { - linkShortenerAPIToken = ''; - } - - // Load any jitsi UI config settings - let interfaceConfigOverwriteFromConfig = kiwi.state.setting('conference.interfaceConfigOverwrite') || {}; - let interfaceConfigOverwrite = { - SHOW_JITSI_WATERMARK: false, - SHOW_WATERMARK_FOR_GUESTS: false, - TOOLBAR_BUTTONS: [ - 'microphone', 'camera', 'fullscreen', 'hangup', - 'settings', 'videoquality', 'filmstrip', 'fodeviceselection', - 'stats', 'shortcuts', - ], - }; - Object.keys(interfaceConfigOverwriteFromConfig).forEach((key) => { - interfaceConfigOverwrite[key] = interfaceConfigOverwriteFromConfig[key]; - }); - - // Load any jitsi general config settings - let configOverwriteFromConfig = kiwi.state.setting('conference.configOverwrite') || {}; - let configOverwrite = { - startWithVideoMuted: true, - startWithAudioMuted: true, - }; - Object.keys(configOverwriteFromConfig).forEach((key) => { - configOverwrite[key] = configOverwriteFromConfig[key]; - }); - - // Add the call button to the channel+query headers - const conferencingTool = document.createElement('div'); - conferencingTool.style.cursor = 'pointer'; - conferencingTool.innerHTML = ''; - if (kiwi.state.setting('conference.channels') !== false) { - kiwi.addUi('header_channel', conferencingTool); - } - if (kiwi.state.setting('conference.queries') !== false) { - kiwi.addUi('header_query', conferencingTool); - } - conferencingTool.onclick = (e) => { - e.preventDefault(); - if (api) { - hideCams(true); - } else { - showCams(); - } - }; - - // The component that gets shown in the messagelist when somebody joins a conference call - const joinCallMessageComponent = kiwi.Vue.extend({ - template: ` -
-
- {{caption}} - ${joinText} -
-
${joinButtonText}
-
- `, - methods: { - showCams, - }, - data() { - return { - captions: null, - sharedData, - } - } - }); - - kiwi.on('message.new', (newMessage, buffer) => { - let messageTemplate = null; - let message = ''; - let nick = ''; - if (newMessage.tags && newMessage.tags['+kiwiirc.com/conference'] === kiwiConferenceTag) { - nick = newMessage.nick; - if (buffer.isChannel()) { - let bufferMessages = buffer.getMessages(); - for (let i = bufferMessages.length; i--;) { - if (bufferMessages[i].tags && bufferMessages[i].tags['+kiwiirc.com/conference'] === kiwiConferenceTag) { - messageTemplate = bufferMessages[i]; - break; - } - } - } - let timerKey = buffer.networkid + buffer.name; - // if this is the first join message in groupedNoticesTTL milliseconds, - // or is a private message, inject a new in-call component - if (typeof captionTimer[timerKey] === 'undefined' || Date.now() - captionTimer[timerKey] > groupedNoticesTTL || buffer.isQuery()) { - // if this is the first notice received or groupeNoticesTTL (time to live) has expired... - messageTemplate = newMessage; - captions[timerKey] = []; - } else { - // else eliminate the incomming message. it does not need to be displayed. - newMessage.template = new kiwi.Vue({ template: null }); - newMessage.template.$mount(); - } - captionTimer[timerKey] = Date.now(); - // if this is a channel join (not a PM), then just display the nick. - // Otherwise show the message below. - if (buffer.isChannel()) { - message = ''; - } else { - message = inviteText; - } - if (!captions[timerKey].includes(nick + message)) { - captions[timerKey].push(nick + message); - // only inject a new vue component if this is the first - // join message in groupedNoticesTTL milliseconds - if (captions[timerKey].length === 1) { - messageTemplate.template = new joinCallMessageComponent({ - name: 'custom', - data() { - return { - captions: captions[timerKey], - sharedData: sharedData, - }; - }, - }); - messageTemplate.template.$mount(); - } - } - } - }); - - function showCams() { - sharedData.isOpen = true; - kiwi.emit('mediaviewer.show', { iframe: true, url: 'about:blank' }); - - // Give some time for the mediaviewer to show up in the DOM - setTimeout(loadJitsi, 10); - } - - const getBitlyShortLink = async (url) => await (await (await fetch(url)).json()).data.url; - - const getShortLink = async (url) => await (await (await fetch('https://cors-anywhere.herokuapp.com/' + url, {headers: new Headers({'origin': url.split('//')[1].split('/')[0]})})).text()); - - async function shareLink() { - if (!showLink) return ''; - let network = window.kiwi.state.getActiveNetwork(); - let buffer = window.kiwi.state.getActiveBuffer(); - let roomName = ''; - if (buffer.isQuery()) { // cam is being invoked in PM, not a channel - let nicks = []; - nicks.push(network.nick); - nicks.push(buffer.name); - nicks.sort(); - nicks[0] = 'query-' + nicks[0] + '#'; - roomName = nicks.join(''); - } else { - roomName = buffer.name; - } - let link = location.protocol + '//' + kiwi.state.setting('conference.server') + '/' + encodeRoomName(network.connection.server, roomName); - if (useLinkShortener) { - let req; - let shortLink; - if (linkShortenerURL.indexOf('api-ssl.bitly.com') !== -1) { - req = `${linkShortenerURL}?access_token=${linkShortenerAPIToken}&longUrl=${link}`; - shortLink = await getBitlyShortLink(req); - } else { - req = `${linkShortenerURL}/?${link}`; - shortLink = await getShortLink(req); - } - return await shortLink; - } else { - return link; - } - } - - async function loadJitsi() { - let iframe = prepareJitsiIframe(); - let innerDoc = iframe.contentDocument || iframe.contentWindow.document; - let jitsiBody = innerDoc.getElementsByTagName('body')[0]; - let innerHead = innerDoc.getElementsByTagName('head')[0]; - let network = window.kiwi.state.getActiveNetwork(); - let buffer = window.kiwi.state.getActiveBuffer(); - - let loadingAnimation = document.createElement('div'); - loadingAnimation.style.position = 'absolute'; - loadingAnimation.style.top = '34%'; - loadingAnimation.style.marginLeft = '45%'; - loadingAnimation.innerHTML = '