├── .gitignore ├── LICENSE ├── README.md └── calendar ├── RandomUtil.js ├── RoomnameGenerator.js ├── css └── all.css ├── jitsi-logo-128x128.png ├── jitsi-logo-16x16.png ├── jitsi-logo-48x48.png ├── jitsi-logo-blue.svg ├── jitsi-logo-grey.svg ├── jitsi-logo-white-48x48.png ├── jquery.js ├── manifest.json ├── meet-calendar.js ├── popup.html └── popup.js /.gitignore: -------------------------------------------------------------------------------- 1 | .*swp 2 | /calendar/target 3 | 4 | .DS_STORE 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jidesha 2 | ======= 3 | 4 | A Chrome extension for calendar integration (Google Calendar and Office 365). 5 | 6 | ## How to create your own extension for your Jitsi Meet installation 7 | 8 | Each Jitsi Meet installation needs a customised extension. 9 | There is only one small JSON file to adapt. You have 10 | to create the extension and distribute it, either through 11 | Google Chrome's Web Store or by telling your users how to 12 | install the CRX file. 13 | 14 | ### Create the extension 15 | 16 | Edit the `manifest.json` file. You must adapt the `externally_connectable` 17 | URL: 18 | 19 | "matches": [ 20 | "*://your.server.com/*" 21 | ] 22 | 23 | Do not include any port information. 24 | 25 | You might also want to edit the name, the description, the version or 26 | to replace the icons. 27 | 28 | Then, according to https://developer.chrome.com/extensions/packaging , 29 | go inside Chrome to "chrome://extensions", click on the Developer Mode, 30 | and "Pack extension". The result is a CRX file and, if you do this for 31 | the first time, a private key used for later updates. 32 | 33 | ### Install your own extension 34 | 35 | Install your own extension into your Chrome. One way is to drag the 36 | CRX file into the "chrome://extensions" window. 37 | 38 | When Chrome shows it among your installed extensions, 39 | you will also see its *hash ID*. 40 | 41 | ### Distribute your extension manually to your users 42 | 43 | You can send the CRX file to your users and tell them how to 44 | install it. For example, you might want to put it 45 | directly onto your Jitsi Meet server (webroot in `/usr/share/jitsi-meet`). 46 | This would only be helpful for downloading the extension, as 47 | Chrome will not allow a direct installation from your site. 48 | -------------------------------------------------------------------------------- /calendar/RandomUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Digits. 3 | * @const 4 | */ 5 | var DIGITS = '0123456789'; 6 | 7 | /** 8 | * Generates random int within the range [min, max] 9 | * @param min the minimum value for the generated number 10 | * @param max the maximum value for the generated number 11 | * @returns random int number 12 | */ 13 | function randomInt(min, max) { 14 | return Math.floor(Math.random() * (max - min + 1)) + min; 15 | } 16 | 17 | /** 18 | * Get random element from array or string. 19 | * @param {Array|string} arr source 20 | * @returns array element or string character 21 | */ 22 | function randomElement(arr) { 23 | return arr[randomInt(0, arr.length -1)]; 24 | } 25 | 26 | /** 27 | * Returns a random string of digits with length 'len'. 28 | * The string will be with random numbers of count 'len' - 2, and the last 29 | * two digits will be a check sum using the "ISO 7064 Mod 97,10" algorithm. 30 | * In order to verify it the formula is: 31 | * (num_to_check % 97) == 1 32 | * @param len the length. 33 | */ 34 | function randomDigitString(len) { 35 | var ret = ''; 36 | var randomLen = len - 2; 37 | while (randomLen--) { 38 | ret += this.randomElement(DIGITS); 39 | } 40 | var num = parseInt(ret); 41 | var verifyNumber = (98 - (num * 100) % 97) % 97; 42 | return num.toString() 43 | + (verifyNumber < 10 ? '0' : '') // adds leading zero if single digit 44 | + verifyNumber.toString(); 45 | } 46 | -------------------------------------------------------------------------------- /calendar/RoomnameGenerator.js: -------------------------------------------------------------------------------- 1 | //var nouns = [ 2 | //]; 3 | var pluralNouns = [ 4 | "Aliens", "Animals", "Antelopes", "Ants", "Apes", "Apples", "Baboons", 5 | "Bacteria", "Badgers", "Bananas", "Bats", "Bears", "Birds", "Bonobos", 6 | "Brides", "Bugs", "Bulls", "Butterflies", "Cheetahs", "Cherries", "Chicken", 7 | "Children", "Chimps", "Clowns", "Cows", "Creatures", "Dinosaurs", "Dogs", 8 | "Dolphins", "Donkeys", "Dragons", "Ducks", "Dwarfs", "Eagles", "Elephants", 9 | "Elves", "Fathers", "Fish", "Flowers", "Frogs", "Fruit", "Fungi", 10 | "Galaxies", "Geese", "Goats", "Gorillas", "Hedgehogs", "Hippos", "Horses", 11 | //"Hunters", "Insects", "Kids", "Knights", "Lemons", "Lemurs", "Leopards", 12 | "LifeForms", "Lions", "Lizards", "Mice", "Monkeys", "Monsters", "Mushrooms", 13 | "Octopodes", "Oranges", "Orangutans", "Organisms", "Pants", "Parrots", 14 | "Penguins", "People", "Pigeons", "Pigs", "Pineapples", "Plants", "Potatoes", 15 | "Priests", "Rats", "Reptiles", "Reptilians", "Rhinos", "Seagulls", "Sheep", 16 | "Siblings", "Snakes", "Spaghetti", "Spiders", "Squid", "Squirrels", 17 | "Stars", "Students", "Teachers", "Tigers", "Tomatoes", "Trees", "Vampires", 18 | "Vegetables", "Viruses", "Vulcans", "Weasels", "Werewolves", "Whales", 19 | "Witches", "Wizards", "Wolves", "Workers", "Worms", "Zebras" 20 | ]; 21 | var verbs = [ 22 | "Abandon", "Adapt", "Advertise", "Answer", "Anticipate", "Appreciate", 23 | "Approach", "Argue", "Ask", "Bite", "Blossom", "Blush", "Breathe", "Breed", 24 | "Bribe", "Burn", "Calculate", "Clean", "Code", "Communicate", "Compute", 25 | "Confess", "Confiscate", "Conjugate", "Conjure", "Consume", "Contemplate", 26 | "Crawl", "Dance", "Delegate", "Devour", "Develop", "Differ", "Discuss", 27 | "Dissolve", "Drink", "Eat", "Elaborate", "Emancipate", "Estimate", "Expire", 28 | "Extinguish", "Extract", "Facilitate", "Fall", "Feed", "Finish", "Floss", 29 | "Fly", "Follow", "Fragment", "Freeze", "Gather", "Glow", "Grow", "Hex", 30 | "Hide", "Hug", "Hurry", "Improve", "Intersect", "Investigate", "Jinx", 31 | "Joke", "Jubilate", "Kiss", "Laugh", "Manage", "Meet", "Merge", "Move", 32 | "Object", "Observe", "Offer", "Paint", "Participate", "Party", "Perform", 33 | "Plan", "Pursue", "Pierce", "Play", "Postpone", "Pray", "Proclaim", 34 | "Question", "Read", "Reckon", "Rejoice", "Represent", "Resize", "Rhyme", 35 | "Scream", "Search", "Select", "Share", "Shoot", "Shout", "Signal", "Sing", 36 | "Skate", "Sleep", "Smile", "Smoke", "Solve", "Spell", "Steer", "Stink", 37 | "Substitute", "Swim", "Taste", "Teach", "Terminate", "Think", "Type", 38 | "Unite", "Vanish", "Worship" 39 | ]; 40 | var adverbs = [ 41 | "Absently", "Accurately", "Accusingly", "Adorably", "AllTheTime", "Alone", 42 | "Always", "Amazingly", "Angrily", "Anxiously", "Anywhere", "Appallingly", 43 | "Apparently", "Articulately", "Badly", "Barely", 44 | "Beautifully", "Blindly", "Bravely", "Brightly", "Briskly", "Brutally", 45 | "Calmly", "Carefully", "Casually", "Cautiously", "Cleverly", "Constantly", 46 | "Correctly", "Crazily", "Curiously", "Cynically", "Daily", "Dangerously", 47 | //"Deliberately", "Delicately", "Desperately", "Discreetly", "Eagerly", 48 | "Easily", "Evenly", "Everywhere", "Exactly", "Expectantly", 49 | "Extensively", "Ferociously", "Fiercely", "Finely", "Flatly", "Frequently", 50 | //"Frighteningly", "Gently", "Gloriously", "Grimly", "Guiltily", "Happily", 51 | "Hard", "Hastily", "Heroically", "High", "Highly", "Hourly", 52 | //"Hysterically", "Immensely", "Impartially", "Impolitely", "Indifferently", 53 | "Intensely", "Jealously", "Jovially", "Kindly", "Lazily", "Lightly", 54 | "Loudly", "Lovingly", "Loyally", "Magnificently", "Malevolently", "Merrily", 55 | //"Mightily", "Miserably", "Mysteriously", "NOT", "Nervously", "Nicely", 56 | "Nowhere", "Objectively", "Obnoxiously", "Obsessively", "Obviously", 57 | //"Often", "Painfully", "Patiently", "Playfully", "Politely", "Poorly", 58 | //"Precisely", "Promptly", "Quickly", "Quietly", "Randomly", "Rapidly", 59 | "Rarely", "Recklessly", "Regularly", "Responsibly", 60 | "Rudely", "Ruthlessly", "Sadly", "Scornfully", "Seamlessly", "Seldom", 61 | "Selfishly", "Seriously", "Shakily", "Sharply", "Sideways", "Silently", 62 | "Sleepily", "Slightly", "Slowly", "Smoothly", "Softly", "Solemnly", 63 | //"Steadily", "Sternly", "Strangely", "Strongly", "Stunningly", "Surely", 64 | "Tenderly", "Thoughtfully", "Tightly", "Uneasily", "Vanishingly", 65 | "Violently", "Warmly", "Weakly", "Wearily", "Weekly", "Weirdly", "Well", 66 | "Well", "Wildly", "Wisely", "Wonderfully", "Yearly" 67 | ]; 68 | var adjectives = [ 69 | "Abominable", "Accurate", "Adorable", "All", "Alleged", "Ancient", "Angry", 70 | "Anxious", "Appalling", "Apparent", "Astonishing", "Attractive", "Awesome", 71 | "Baby", "Bad", "Beautiful", "Benign", "Big", "Bitter", "Blind", "Blue", 72 | //"Bold", "Brave", "Bright", "Brisk", "Calm", "Camouflaged", "Casual", 73 | "Cautious", "Choppy", "Chosen", "Clever", "Cold", "Cool", "Crawly", 74 | "Crazy", "Creepy", "Cruel", "Curious", "Cynical", "Dangerous", "Dark", 75 | "Delicate", "Desperate", "Difficult", "Discreet", "Disguised", "Dizzy", 76 | "Dumb", "Eager", "Easy", "Edgy", "Electric", "Elegant", "Emancipated", 77 | //"Enormous", "Euphoric", "Evil", "Fast", "Ferocious", "Fierce", "Fine", 78 | "Flawed", "Flying", "Foolish", "Foxy", "Freezing", "Funny", "Furious", 79 | "Gentle", "Glorious", "Golden", "Good", "Green", "Green", "Guilty", 80 | "Hairy", "Happy", "Hard", "Hasty", "Hazy", "Heroic", "Hostile", "Hot", 81 | //"Humble", "Humongous", "Humorous", "Hysterical", "Idealistic", "Ignorant", 82 | "Immense", "Impartial", "Impolite", "Indifferent", "Infuriated", 83 | "Insightful", "Intense", "Interesting", "Intimidated", "Intriguing", 84 | "Jealous", "Jolly", "Jovial", "Jumpy", "Kind", "Laughing", "Lazy", "Liquid", 85 | "Lonely", "Longing", "Loud", "Loving", "Loyal", "Macabre", "Mad", "Magical", 86 | //"Magnificent", "Malevolent", "Medieval", "Memorable", "Mere", "Merry", 87 | //"Mighty", "Mischievous", "Miserable", "Modified", "Moody", "Most", 88 | "Mysterious", "Mystical", "Needy", "Nervous", "Nice", "Objective", 89 | "Obnoxious", "Obsessive", "Obvious", "Opinionated", "Orange", "Painful", 90 | "Passionate", "Perfect", "Pink", "Playful", "Poisonous", "Polite", "Poor", 91 | "Popular", "Powerful", "Precise", "Preserved", "Pretty", "Purple", "Quick", 92 | "Quiet", "Random", "Rapid", "Rare", "Real", "Reassuring", "Reckless", "Red", 93 | //"Regular", "Remorseful", "Responsible", "Rich", "Rude", "Ruthless", "Sad", 94 | //"Scared", "Scary", "Scornful", "Screaming", "Selfish", "Serious", "Shady", 95 | //"Shaky", "Sharp", "Shiny", "Shy", "Simple", "Sleepy", "Slow", "Sly", 96 | "Small", "Smart", "Smelly", "Smiling", "Smooth", "Smug", "Sober", "Soft", 97 | "Solemn", "Square", "Square", "Steady", "Strange", "Strong", "Stunning", 98 | "Subjective", "Successful", "Surly", "Sweet", "Tactful", "Tense", 99 | "Thoughtful", "Tight", "Tiny", "Tolerant", "Uneasy", "Unique", "Unseen", 100 | "Warm", "Weak", "Weird", "WellCooked", "Wild", "Wise", "Witty", "Wonderful", 101 | "Worried", "Yellow", "Young", "Zealous" 102 | ]; 103 | 104 | /* 105 | * Maps a string (category name) to the array of words from that category. 106 | */ 107 | var CATEGORIES = 108 | { 109 | //"_NOUN_": nouns, 110 | "_PLURALNOUN_": pluralNouns, 111 | //"_PLACE_": places, 112 | "_VERB_": verbs, 113 | "_ADVERB_": adverbs, 114 | "_ADJECTIVE_": adjectives 115 | //"_PRONOUN_": pronouns, 116 | //"_CONJUNCTION_": conjunctions, 117 | }; 118 | 119 | var PATTERNS = [ 120 | "_ADJECTIVE__PLURALNOUN__VERB__ADVERB_" 121 | 122 | // BeautifulFungiOrSpaghetti 123 | //"_ADJECTIVE__PLURALNOUN__CONJUNCTION__PLURALNOUN_", 124 | 125 | // AmazinglyScaryToy 126 | //"_ADVERB__ADJECTIVE__NOUN_", 127 | 128 | // NeitherTrashNorRifle 129 | //"Neither_NOUN_Nor_NOUN_", 130 | //"Either_NOUN_Or_NOUN_", 131 | 132 | // EitherCopulateOrInvestigate 133 | //"Either_VERB_Or_VERB_", 134 | //"Neither_VERB_Nor_VERB_", 135 | 136 | //"The_ADJECTIVE__ADJECTIVE__NOUN_", 137 | //"The_ADVERB__ADJECTIVE__NOUN_", 138 | //"The_ADVERB__ADJECTIVE__NOUN_s", 139 | //"The_ADVERB__ADJECTIVE__PLURALNOUN__VERB_", 140 | 141 | // WolvesComputeBadly 142 | //"_PLURALNOUN__VERB__ADVERB_", 143 | 144 | // UniteFacilitateAndMerge 145 | //"_VERB__VERB_And_VERB_", 146 | 147 | //NastyWitchesAtThePub 148 | //"_ADJECTIVE__PLURALNOUN_AtThe_PLACE_", 149 | ]; 150 | 151 | /* 152 | * Returns true if the string 's' contains one of the 153 | * template strings. 154 | */ 155 | function hasTemplate(s) { 156 | for (var template in CATEGORIES){ 157 | if (s.indexOf(template) >= 0){ 158 | return true; 159 | } 160 | } 161 | } 162 | 163 | /** 164 | * Generates new room name. 165 | * @param customDictionary a dictionary containing keys pluralNouns, verbs, 166 | * adverbs and adjectives, values are array of strings. 167 | */ 168 | function generateRoomWithoutSeparator(customDictionary) { 169 | // Note that if more than one pattern is available, the choice of 170 | // 'name' won't have a uniform distribution amongst all patterns (names 171 | // from patterns with fewer options will have higher probability of 172 | // being chosen that names from patterns with more options). 173 | var name = randomElement(PATTERNS); 174 | var word; 175 | var categories = CATEGORIES; 176 | if (customDictionary) { 177 | categories = { 178 | "_PLURALNOUN_": customDictionary.pluralNouns, 179 | "_VERB_": customDictionary.verbs, 180 | "_ADVERB_": customDictionary.adverbs, 181 | "_ADJECTIVE_": customDictionary.adjectives 182 | }; 183 | } 184 | while (hasTemplate(name)) { 185 | for (var template in categories) { 186 | word = randomElement(categories[template]); 187 | name = name.replace(template, word); 188 | } 189 | } 190 | 191 | return name; 192 | } -------------------------------------------------------------------------------- /calendar/css/all.css: -------------------------------------------------------------------------------- 1 | .button_container { 2 | position: relative; 3 | } 4 | 5 | .button_container.solo .hangouts_button { 6 | display: none; 7 | } 8 | 9 | .button_container.solo #jitsi_button { 10 | left: 0px; 11 | } 12 | 13 | #jitsi_button { 14 | min-width: 60px; 15 | line-height: 24px; 16 | } 17 | 18 | #jitsi_button a { 19 | display: block; 20 | text-decoration: none; 21 | background: transparent url('chrome-extension://__MSG_@@extension_id__/jitsi-logo-white-48x48.png') no-repeat; 22 | background-size: 20px 20px; 23 | background-position: left; 24 | text-indent: 20px; 25 | padding-left: 4px; 26 | } 27 | 28 | .jitsi_quick_add_icon { 29 | background: transparent url('chrome-extension://__MSG_@@extension_id__/jitsi-logo-grey.svg') no-repeat; 30 | background-size: contain; 31 | height: 16px; 32 | width: 16px; 33 | margin-left: 4px; 34 | margin-top: 8px; 35 | } 36 | 37 | .jitsi_quick_add_text_size { 38 | font-size: 14px; 39 | line-height: 24px; 40 | } 41 | 42 | .jitsi_edit_page_icon { 43 | background: transparent url('chrome-extension://__MSG_@@extension_id__/jitsi-logo-grey.svg') no-repeat; 44 | background-size: contain; 45 | height: 20px; 46 | width: 20px; 47 | margin-left: 4px; 48 | margin-top: 4px; 49 | } 50 | 51 | .jitsi_ms_button { 52 | background: transparent url('chrome-extension://__MSG_@@extension_id__/jitsi-logo-blue.svg') no-repeat; 53 | width: 20px; 54 | height: 20px; 55 | } 56 | -------------------------------------------------------------------------------- /calendar/jitsi-logo-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsi/jidesha/fcb8be45c908cfd8f66ff5836152ec75e041973c/calendar/jitsi-logo-128x128.png -------------------------------------------------------------------------------- /calendar/jitsi-logo-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsi/jidesha/fcb8be45c908cfd8f66ff5836152ec75e041973c/calendar/jitsi-logo-16x16.png -------------------------------------------------------------------------------- /calendar/jitsi-logo-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsi/jidesha/fcb8be45c908cfd8f66ff5836152ec75e041973c/calendar/jitsi-logo-48x48.png -------------------------------------------------------------------------------- /calendar/jitsi-logo-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /calendar/jitsi-logo-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /calendar/jitsi-logo-white-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jitsi/jidesha/fcb8be45c908cfd8f66ff5836152ec75e041973c/calendar/jitsi-logo-white-48x48.png -------------------------------------------------------------------------------- /calendar/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Jitsi Meetings", 4 | "description": "A simple extension that allows you to schedule Jitsi Meetings.", 5 | "version": "0.2.8", 6 | "minimum_chrome_version": "88", 7 | "icons": { 8 | "16": "jitsi-logo-16x16.png", 9 | "48": "jitsi-logo-48x48.png", 10 | "128": "jitsi-logo-128x128.png" 11 | }, 12 | "host_permissions": [ 13 | "https://calendar.google.com/*" 14 | ], 15 | "externally_connectable": { 16 | "matches": [ 17 | "*://meet.jit.si/*" 18 | ] 19 | }, 20 | "content_scripts": [ 21 | { 22 | "matches": ["https://calendar.google.com/calendar/*", "https://outlook.live.com/owa/*"], 23 | "js": ["jquery.js", "RandomUtil.js", "RoomnameGenerator.js", "meet-calendar.js"], 24 | "css": ["/css/all.css"], 25 | "all_frames" : false, 26 | "run_at" : "document_end" 27 | } 28 | ], 29 | "web_accessible_resources": [{ 30 | "matches": [ 31 | "https://calendar.google.com/*", 32 | "https://outlook.live.com/*" 33 | ], 34 | "resources": [ 35 | "jitsi-logo-48x48.png", 36 | "jitsi-logo-white-48x48.png", 37 | "jitsi-logo-blue.svg", 38 | "jitsi-logo-grey.svg" 39 | ] 40 | }], 41 | "action": { 42 | "default_title": "Create Jitsi Meetings", 43 | "default_popup": "popup.html" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /calendar/meet-calendar.js: -------------------------------------------------------------------------------- 1 | const BASE_DOMAIN = "meet.jit.si"; 2 | const BASE_URL = "https://" + BASE_DOMAIN + "/"; 3 | const APP_NAME = "Jitsi"; 4 | const NUMBER_RETRIEVE_SCRIPT = false; 5 | const CONFERENCE_MAPPER_SCRIPT = false; 6 | 7 | //A text to be used when adding info to the location field. 8 | const LOCATION_TEXT = APP_NAME + ' Meeting'; 9 | 10 | let generateRoomNameAsDigits = false; 11 | 12 | /** 13 | * The event page we will be updating. 14 | */ 15 | class EventContainer { 16 | constructor() { 17 | // Numbers used to access the service, will be listed in the 18 | // autogenerated description of the event when adding a meeting to it. 19 | // {"US": ["+1xxx", "+1xxx"], "France": ["+33xxx"]} 20 | this.numbers = {}; 21 | 22 | // used to implement autocreate meetings, this is done after all 23 | // the needed information is retrieved as numbers, upon calling update 24 | this.scheduleAutoCreateMeeting = false; 25 | } 26 | 27 | /** 28 | * @returns {EventContainer} 29 | */ 30 | static getInstance() { 31 | var eventEditPage = document.querySelector('#maincell #coverinner'); 32 | if (eventEditPage) 33 | return new GEvent(eventEditPage); 34 | else if (document.querySelector('body').dataset.viewfamily) 35 | return new G2Event(document.querySelector('body')); 36 | else 37 | return new MSLiveEvent(); 38 | } 39 | 40 | /** 41 | * The description of the event. 42 | * @abstract 43 | * @returns {Description} 44 | */ 45 | get description() {} 46 | 47 | /** 48 | * The button container where we will add the jitsi button. 49 | * @abstract 50 | */ 51 | get buttonContainer() {} 52 | 53 | /** 54 | * The location of the event. 55 | * @abstract 56 | * @returns {Location} 57 | */ 58 | get location() {} 59 | 60 | /** 61 | * The container element of the event edit page. 62 | * @returns {*} 63 | */ 64 | get container(){ 65 | return this.containerElement; 66 | }; 67 | 68 | set container(c){ 69 | this.containerElement = c; 70 | }; 71 | 72 | /** 73 | * Main entry point of the event modifictaions. 74 | * @abstract 75 | */ 76 | update() {} 77 | 78 | /** 79 | * Checks for the button on current page 80 | */ 81 | isButtonPresent() { 82 | return ($('#jitsi_button').length >= 1); 83 | } 84 | 85 | /** 86 | * Clears instances. 87 | */ 88 | reset() { 89 | this.descriptionInstance = null; 90 | this.locationInstance = null; 91 | } 92 | 93 | /** 94 | * Updates meetingId, if there is meetingId set it, if not generate it. 95 | */ 96 | updateMeetingId() { 97 | 98 | if (!this.isButtonPresent()) { 99 | // there is no button present we will add it, so we will clean 100 | // the state of the EventContainer, so we can update all values. 101 | // this clears the states between creating/editing different events 102 | // we add the button 103 | this.reset(); 104 | } 105 | 106 | var inviteText; 107 | var ix = -1; 108 | 109 | // checking location 110 | if (this.location && this.location.text) { 111 | inviteText = this.location.text; 112 | 113 | if (inviteText) 114 | ix = inviteText.indexOf(BASE_URL); 115 | } 116 | 117 | // if nothing found let's check description 118 | if (ix == -1) { 119 | inviteText = this.description.value; 120 | 121 | if (inviteText) 122 | ix = inviteText.indexOf(BASE_URL); 123 | } 124 | 125 | var url; 126 | if (ix != -1 && (url = inviteText.substring(ix)) && url.length > 0) { 127 | let resMeetingId = url.substring(BASE_URL.length); 128 | 129 | // there can be ',' after the meeting, normally added when adding 130 | // physical rooms to the meeting 131 | var regexp = /([a-zA-Z]+).*/g; 132 | var match = regexp.exec(resMeetingId); 133 | if (match && match.length > 1) 134 | resMeetingId = match[1]; 135 | 136 | this.meetingId = resMeetingId; 137 | } 138 | else { 139 | 140 | if (generateRoomNameAsDigits) { 141 | this.meetingId = randomDigitString(10); 142 | } 143 | else 144 | this.meetingId = generateRoomWithoutSeparator(); 145 | 146 | if(NUMBER_RETRIEVE_SCRIPT) { 147 | // queries a predefined location for settings 148 | $.getJSON(NUMBER_RETRIEVE_SCRIPT, 149 | jsonobj => { 150 | this.inviteTextTemplate = jsonobj.inviteTextTemplate; 151 | 152 | // if there is a room name dictionary lets use it and 153 | // generate new room name 154 | if (jsonobj.roomNameDictionary) { 155 | this.meetingId = generateRoomWithoutSeparator( 156 | jsonobj.roomNameDictionary); 157 | } 158 | 159 | if(!jsonobj.numbersEnabled) 160 | return; 161 | 162 | this.numbers = jsonobj.numbers; 163 | this.inviteNumbersTextTemplate 164 | = jsonobj.inviteNumbersTextTemplate; 165 | 166 | if (this.scheduleAutoCreateMeeting) { 167 | this.description.clickAddMeeting( 168 | false, this.location); 169 | this.scheduleAutoCreateMeeting = false; 170 | } 171 | }); 172 | } else { 173 | if (this.scheduleAutoCreateMeeting) { 174 | this.description.clickAddMeeting( 175 | false, this.location); 176 | this.scheduleAutoCreateMeeting = false; 177 | } 178 | } 179 | } 180 | } 181 | 182 | /** 183 | * Adds the jitsi button in buttonContainer. 184 | */ 185 | addJitsiButton() { 186 | var container = this.buttonContainer; 187 | if (!container) 188 | return; 189 | 190 | var description = this.description; 191 | 192 | container.addClass('button_container'); 193 | container.append( 194 | '
'); 199 | description.update(this.location); 200 | } 201 | } 202 | 203 | /** 204 | * Represents the location field. 205 | */ 206 | class Location { 207 | /** 208 | * The text in the location field. 209 | * @abstract 210 | */ 211 | get text() {} 212 | 213 | /** 214 | * Adds location info. 215 | * @abstract 216 | * @param text 217 | */ 218 | addLocationText(text){} 219 | } 220 | 221 | /** 222 | * Represents the description of the event. 223 | */ 224 | class Description { 225 | constructor(event) { 226 | this.event = event; 227 | } 228 | /** 229 | * Updates the description and location field is not already updated. 230 | * @param location 231 | */ 232 | update(location) { 233 | var isDescriptionUpdated = false; 234 | 235 | // checks whether description was updated. 236 | if (this.element != undefined) { 237 | var descriptionContainsURL = 238 | (this.value 239 | && this.value.length >= 1 240 | && this.value.indexOf(BASE_URL) !== -1); 241 | isDescriptionUpdated = 242 | descriptionContainsURL 243 | // checks whether there is the generated name in the location 244 | // input if there is a location 245 | || (location != null 246 | && location.text 247 | && location.text.indexOf(LOCATION_TEXT) != -1); 248 | } 249 | 250 | if(isDescriptionUpdated) { 251 | // update button url of event has all the data 252 | this.updateButtonURL(); 253 | } else { 254 | // update button as event description has no meeting set 255 | this.updateInitialButtonURL(location); 256 | } 257 | } 258 | 259 | /** 260 | * Creates meeting, filling all needed fields. 261 | * @param isDescriptionUpdated - whether description was already updated, 262 | * true when we are editing event. 263 | * @param the location to use to fill the meeting URL 264 | */ 265 | clickAddMeeting(isDescriptionUpdated, location) { 266 | if (!isDescriptionUpdated) { 267 | // Build the invitation content 268 | if (CONFERENCE_MAPPER_SCRIPT) { 269 | // queries a predefined location for settings 270 | $.getJSON(CONFERENCE_MAPPER_SCRIPT 271 | + "?conference=" + this.event.meetingId + "@conference." + BASE_DOMAIN, 272 | jsonobj => { 273 | if (jsonobj.conference && jsonobj.id) { 274 | this.addDescriptionText( 275 | this.getInviteText(jsonobj.id)); 276 | } 277 | else { 278 | this.addDescriptionText( 279 | this.getInviteText()); 280 | } 281 | }); 282 | } 283 | else { 284 | this.addDescriptionText(this.getInviteText()); 285 | } 286 | this.updateButtonURL(); 287 | 288 | if (location) 289 | location.addLocationText( 290 | LOCATION_TEXT + ' - ' + BASE_URL + this.event.meetingId); 291 | } else { 292 | this.updateButtonURL(); 293 | } 294 | } 295 | 296 | /** 297 | * The description html element. 298 | * @abstract 299 | */ 300 | get element() {} 301 | 302 | /** 303 | * The text value of the description of the event. 304 | * @abstract 305 | */ 306 | get value() {} 307 | 308 | /** 309 | * Adds description text to the existing text. 310 | * @abstract 311 | * @param text 312 | */ 313 | addDescriptionText(text){} 314 | 315 | /** 316 | * Generates description text used for the invite. 317 | * @param dialInID optional dial in id 318 | * @returns {String} 319 | */ 320 | getInviteText(dialInID) { 321 | let inviteText; 322 | let hasTemplate = false; 323 | 324 | if (this.event.inviteTextTemplate) { 325 | inviteText = this.event.inviteTextTemplate; 326 | hasTemplate = true; 327 | } else { 328 | inviteText = 329 | "Click the following link to join the meeting " + 330 | "from your computer: " + BASE_URL + this.event.meetingId; 331 | } 332 | 333 | if (this.event.numbers && Object.keys(this.event.numbers).length > 0) { 334 | if (this.event.inviteNumbersTextTemplate) { 335 | inviteText += this.event.inviteNumbersTextTemplate; 336 | hasTemplate = true; 337 | Object.keys(this.event.numbers).forEach(key => { 338 | let value = this.event.numbers[key]; 339 | inviteText = inviteText.replace( 340 | '{' + key + '}', 341 | key + ": " + value); 342 | }); 343 | } else { 344 | inviteText += "\n\n====="; 345 | inviteText +="\n\nJust want to dial in on your phone? "; 346 | inviteText += " \n\nCall one of the following numbers: "; 347 | Object.keys(this.event.numbers).forEach(key => { 348 | let value = this.event.numbers[key]; 349 | inviteText += "\n" + key + ": " + value; 350 | }); 351 | inviteText += "\n\nSay your conference name: '" 352 | + this.event.meetingId 353 | + "' and you will be connected!"; 354 | } 355 | } 356 | 357 | if (hasTemplate) { 358 | inviteText = inviteText.replace(/\{BASE_URL\}/g, BASE_URL); 359 | inviteText 360 | = inviteText.replace(/\{MEETING_ID\}/g, this.event.meetingId); 361 | if (dialInID) { 362 | inviteText 363 | = inviteText.replace(/\{DIALIN_ID\}/g, dialInID); 364 | } 365 | } 366 | 367 | return inviteText; 368 | } 369 | 370 | /** 371 | * Updates the initial button text and click handler when there is 372 | * no meeting scheduled. 373 | */ 374 | updateInitialButtonURL(location) { 375 | var button = $('#jitsi_button a'); 376 | button.html('Add a ' + LOCATION_TEXT); 377 | button.attr('href', '#'); 378 | button.on('click', e => { 379 | e.preventDefault(); 380 | this.clickAddMeeting(false, location); 381 | }); 382 | } 383 | 384 | /** 385 | * Updates the url for the button. 386 | */ 387 | updateButtonURL() { 388 | try { 389 | var button = $('#jitsi_button a'); 390 | button.html("Join your " + LOCATION_TEXT + " now"); 391 | button.off('click'); 392 | button.attr('href', BASE_URL + this.event.meetingId); 393 | button.attr('target', '_new'); 394 | } catch (e) { 395 | console.log(e); 396 | } 397 | } 398 | } 399 | 400 | /** 401 | * The google calendar specific implementation of the event page. 402 | */ 403 | class GEvent extends EventContainer { 404 | constructor(eventEditPage) { 405 | super(); 406 | 407 | this.container = eventEditPage; 408 | } 409 | 410 | /** 411 | * Updates content (adds the button if is not there). 412 | * This is the entry point for all page modifications. 413 | */ 414 | update() { 415 | if ($('table.ep-dp-dt').is(":visible")) { 416 | this.updateMeetingId(); 417 | 418 | if(!this.isButtonPresent()) 419 | this.addJitsiButton(); 420 | } 421 | } 422 | 423 | /** 424 | * The event location. 425 | * @returns {GLocation} 426 | */ 427 | get location() { 428 | if (!this.locationInstance) 429 | this.locationInstance = new GLocation(); 430 | return this.locationInstance; 431 | } 432 | 433 | /** 434 | * The button container holding jitsi button. 435 | * @returns {*} 436 | */ 437 | get buttonContainer() { 438 | // we will create a new raw to place the button 439 | // this row will be after the Video Call row 440 | let neighbor = $(getNodeID('rtc-row')); 441 | if(neighbor.length == 0) 442 | return null; 443 | 444 | let newRowID = getNodePrefix() + '.' + 'jitsi-rtc-row'; 445 | let newRow = $('