2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
dev-build
|
||||||
|
node_modules
|
||||||
BIN
build/assets/fonts/youtube-sans-bold.ttf
Normal file
BIN
build/assets/gif/clickUserNamesToAtThem.gif
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
build/assets/gif/emotesMenu.gif
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
build/assets/gif/optionsMenu.gif
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
build/assets/gif/pinYourExtension.gif
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
build/assets/gif/theaterMode.gif
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
build/assets/icons/3x.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
build/assets/icons/MonkaSSS.jpg
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
build/assets/icons/blob.gif
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
build/assets/icons/icon128.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
build/assets/icons/icon48.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
build/assets/icons/icon512.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
build/assets/icons/omegalul.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
build/assets/icons/pepepls.gif
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
build/assets/screenShots/arrow.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
build/assets/screenShots/screen1.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
build/assets/screenShots/screen2.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
build/assets/screenShots/screen3.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
build/assets/screenShots/screen4.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
build/assets/screenShots/screenShot1.jpg
Normal file
|
After Width: | Height: | Size: 607 KiB |
BIN
build/assets/screenShots/screenShot2.jpg
Normal file
|
After Width: | Height: | Size: 608 KiB |
BIN
build/assets/screenShots/screenShot3.jpg
Normal file
|
After Width: | Height: | Size: 533 KiB |
BIN
build/assets/screenShots/screenShot4.jpg
Normal file
|
After Width: | Height: | Size: 533 KiB |
BIN
build/assets/screenShots/screenShot5.jpg
Normal file
|
After Width: | Height: | Size: 573 KiB |
1
build/background.js
Normal file
1
build/content.js
Normal file
165
build/html/options.html
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
padding: 10px;
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideDiv {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Options</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<span style="display:inline-block;vertical-align: middle; margin-right: 2%;">
|
||||||
|
<img src="../assets/icons/icon128.png" alt="🔴" style="height: 2.5em;">
|
||||||
|
</span>
|
||||||
|
<span style="display:inline-block;vertical-align: middle;">
|
||||||
|
<h1>Live Chat Options</h1>
|
||||||
|
</span>
|
||||||
|
<span class="">
|
||||||
|
<button class="info_button" id="infoButton" >Info</button>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div id="optionsMenu" class="tabcontent active">
|
||||||
|
<div class="section">
|
||||||
|
<h2 class="options-heading">Emote Options</h2>
|
||||||
|
<div class="options-description"></div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="enableBTTVEmotes">Enable BTTV - Top, Trending and Global Emotes (200+)</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="enableBTTVEmotes" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="enableFrankerEmotes">Enable FrankerFacez - Top 100 emotes</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="enableFrankerEmotes" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="enableTwitchEmotes">Enable Twitch - Global emotes</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="enableTwitchEmotes" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hr"></div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="kappaFix">Kappa Fix</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="kappaFix" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="hr"></div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2 class="options-heading">Chat Options</h2>
|
||||||
|
<div class="options-description"></div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="setLiveChat">Make LIVE CHAT Default</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="setLiveChat" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="theaterModeFix">Improved Theater Mode</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="theaterModeFix" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="setTwitchColors">Twitch Styling</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="setTwitchColors" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="hideAuthorIcons">Hide Author Icons</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="hideAuthorIcons" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="showTimeStamp">Show TimeStamp</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="showTimeStamp" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="hideWelcomBanner">Hide Welcome Banner</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="hideWelcomBanner" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="alternateLineColor">Alternate Line Colors</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="alternateLineColor" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="setAuthorColor">Colorful User Names</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="setAuthorColor" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hr"></div>
|
||||||
|
<h2 class="options-heading">Font Size</h2>
|
||||||
|
<span>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="slidecontainer">
|
||||||
|
<input disabled type="range" min="1" max="50" value="13" class="slider option-input" id="textSizeSlider">
|
||||||
|
</div>
|
||||||
|
<div class="option-cell">
|
||||||
|
<label for="allowTextSlider"></label>
|
||||||
|
</div>
|
||||||
|
<div class="option-cell">
|
||||||
|
<input disabled type="checkbox" id="allowTextSlider" class="option-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<div id="save-status"> </div>
|
||||||
|
|
||||||
|
<div class="omega"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../options.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
96
build/html/welcome.html
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Welcome Page</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="success-overlay">
|
||||||
|
|
||||||
|
<div class="icon-container">
|
||||||
|
<i class="material-icons">done</i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="../assets/icons/icon512.png" alt="Live Chat Logo">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1><span class="fw-light">Welcome to the </span><span class="fw-bold">Chat</span></h1>
|
||||||
|
|
||||||
|
<p class="heading-note">This is still very beta</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="links_div">
|
||||||
|
<a class="links_bottom" href="https://discord.gg/pVNnTDA" title="Join our Discord Community">Join our Discord Community</a>
|
||||||
|
<a class="links_bottom" href="https://streamelements.com/wompmacho-5882/tip" title="Donations">Donations</a>
|
||||||
|
<a class="links_bottom" href="https://www.youtube.com/wompmacho" title="Youtube">Youtube</a>
|
||||||
|
<a class="links_bottom" href="mailto:wompmacho@gmail.com">Contact Me</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
Soon to come:
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Specific channel Emotes</li>
|
||||||
|
<li>Search for Emote Panel</li>
|
||||||
|
<li>Autocomplete for emote Selection</li>
|
||||||
|
<li><s>Theater Mode Fix for that quality Stream and Chat time</s></li>
|
||||||
|
<li>Moderation Options</li>
|
||||||
|
<li>User Profile Info</li>
|
||||||
|
<li>Other Styling Options</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>How To:</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
There is still a lot of stuff I am working on. This is very Beta at the moment so
|
||||||
|
</p>
|
||||||
|
<h2>Use At Your Own Risk</h2>
|
||||||
|
<p>
|
||||||
|
This is a Chrome Extension for Youtube Live Streams, adding some Quality of Life improvements for the Chat.
|
||||||
|
Adds Top, Trending and Global (500ish) Emotes from popular sites. These Update with what is Trending.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Don't forget to pin this extension for easy access.
|
||||||
|
</p>
|
||||||
|
<img src="../assets/gif/pinYourExtension.gif" alt="pinYourExtension.gif">
|
||||||
|
<p>
|
||||||
|
Youtube Live is slow to load its pages currently.
|
||||||
|
Give the extension a moment while the page's iframes are loading.
|
||||||
|
Once you see the Emote Panel Icon you can open the Emote Selection Panel.
|
||||||
|
</p>
|
||||||
|
<img src="../assets/gif/emotesMenu.gif" alt="emotesMenu.gif">
|
||||||
|
<p>
|
||||||
|
Enhanced Theater Mode Is Now Available. Just click the check in the option panel and refresh your page.
|
||||||
|
</p>
|
||||||
|
<img style="height: 540px; width: 960px;" src="../assets/gif/theaterMode.gif" alt="theaterMode.gif">
|
||||||
|
<p>
|
||||||
|
Over around 500 Top and Trending Emotes are loaded from popular sites.
|
||||||
|
You can Enable/Disable them in the Options Menu.
|
||||||
|
</p>
|
||||||
|
<img src="../assets/gif/optionsMenu.gif" alt="optionsMenu.gif">
|
||||||
|
<p>
|
||||||
|
Can Also Click on UserNames To Autofill an @ Notification
|
||||||
|
</p>
|
||||||
|
<img src="../assets/gif/clickUserNamesToAtThem.gif" alt="clickUserNamesToAtThem.gif">
|
||||||
|
|
||||||
|
<h2>Use At Your Own Risk</h2>
|
||||||
|
|
||||||
|
<h1>Enjoy!</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../welcomePage.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
48
build/manifest.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
|
||||||
|
"name": "🔴 LIVE CHAT",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"description": "Enhances the YouTube Live Streaming experience with Emotes, Custom Styling and quality of life improvements.",
|
||||||
|
"icons": {
|
||||||
|
"48": "assets/icons/icon48.png",
|
||||||
|
"128": "assets/icons/icon128.png",
|
||||||
|
"512": "assets/icons/icon512.png"
|
||||||
|
},
|
||||||
|
|
||||||
|
"permissions": [
|
||||||
|
"storage",
|
||||||
|
"background"
|
||||||
|
],
|
||||||
|
|
||||||
|
"options_ui": {
|
||||||
|
"page": "html/options.html",
|
||||||
|
"chrome_style": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"browser_action": {
|
||||||
|
"default_popup": "html/options.html",
|
||||||
|
"chrome_style": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"background.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": [
|
||||||
|
"*://*.youtube.com/*"
|
||||||
|
],
|
||||||
|
"js": [ "content.js" ],
|
||||||
|
"run_at": "document_end",
|
||||||
|
"all_frames": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"web_accessible_resources": [
|
||||||
|
"assets/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
1
build/options.js
Normal file
1
build/welcomePage.js
Normal file
4714
package-lock.json
generated
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "LiveChat",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"description": "Enhances the YouTube Live Streaming experience with Emotes, Custom Styling and quality of life improvements.",
|
||||||
|
"scripts": {
|
||||||
|
"start": "webpack --progress --watch",
|
||||||
|
"watch": "npm start",
|
||||||
|
"build": "webpack --progress",
|
||||||
|
"prod": "webpack --progress --config webpack.prod.js -p"
|
||||||
|
},
|
||||||
|
"author": "wompmacho",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.19.0",
|
||||||
|
"date-fns": "^1.29.0",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"preact": "^8.2.7",
|
||||||
|
"uglifyjs-webpack-plugin": "^1.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"copy-webpack-plugin": "^4.5.2",
|
||||||
|
"css-loader": "^1.0.0",
|
||||||
|
"style-loader": "^0.22.1",
|
||||||
|
"stylus": "^0.54.5",
|
||||||
|
"stylus-loader": "^3.0.2",
|
||||||
|
"webpack": "^4.17.1",
|
||||||
|
"webpack-cli": "^3.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/assets/fonts/youtube-sans-bold.ttf
Normal file
BIN
src/assets/gif/clickUserNamesToAtThem.gif
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
src/assets/gif/emotesMenu.gif
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
src/assets/gif/optionsMenu.gif
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
src/assets/gif/pinYourExtension.gif
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
src/assets/gif/theaterMode.gif
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
src/assets/icons/3x.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/icons/MonkaSSS.jpg
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
src/assets/icons/blob.gif
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
src/assets/icons/icon.psd
Normal file
BIN
src/assets/icons/icon128.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
src/assets/icons/icon48.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/assets/icons/icon512.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
src/assets/icons/omegalul.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src/assets/icons/pepepls.gif
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
src/assets/screenShots/arrow.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/assets/screenShots/extScreenShots.psd
Normal file
BIN
src/assets/screenShots/screen1.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
src/assets/screenShots/screen2.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
src/assets/screenShots/screen3.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/assets/screenShots/screen4.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
src/assets/screenShots/screenShot1.jpg
Normal file
|
After Width: | Height: | Size: 607 KiB |
BIN
src/assets/screenShots/screenShot2.jpg
Normal file
|
After Width: | Height: | Size: 608 KiB |
BIN
src/assets/screenShots/screenShot3.jpg
Normal file
|
After Width: | Height: | Size: 533 KiB |
BIN
src/assets/screenShots/screenShot4.jpg
Normal file
|
After Width: | Height: | Size: 533 KiB |
BIN
src/assets/screenShots/screenShot5.jpg
Normal file
|
After Width: | Height: | Size: 573 KiB |
38
src/background/Setup.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import PersistentSyncStorage from '../helpers/PersistentSyncStorage';
|
||||||
|
import CONFIG from '../config';
|
||||||
|
|
||||||
|
const ensure = () => {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
|
||||||
|
// Resolves if setup is complete
|
||||||
|
if(PersistentSyncStorage.data.setupComplete) {
|
||||||
|
// Ensure new options (on extension update) are added to options object
|
||||||
|
PersistentSyncStorage.set({
|
||||||
|
options: Object.assign({}, CONFIG.defaultOptions, PersistentSyncStorage.data.options)
|
||||||
|
});
|
||||||
|
|
||||||
|
return res();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise inits setup
|
||||||
|
const onSetupComplete = (request, sender, sendResponse) => {
|
||||||
|
if(request.name === 'setupComplete') {
|
||||||
|
chrome.runtime.onMessage.removeListener(onSetupComplete);
|
||||||
|
|
||||||
|
PersistentSyncStorage.set({
|
||||||
|
setupComplete: true
|
||||||
|
});
|
||||||
|
|
||||||
|
res();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
chrome.tabs.create({ url: './html/welcome.html' });
|
||||||
|
chrome.runtime.onMessage.addListener(onSetupComplete);
|
||||||
|
console.log('Setup Complete');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {ensure};
|
||||||
27
src/background/index.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import PersistentSyncStorage from 'src/helpers/PersistentSyncStorage';
|
||||||
|
|
||||||
|
import Setup from './Setup';
|
||||||
|
|
||||||
|
import CONFIG from 'src/config';
|
||||||
|
|
||||||
|
class Main {
|
||||||
|
constructor() {
|
||||||
|
this.init = this.init.bind(this);
|
||||||
|
|
||||||
|
PersistentSyncStorage.on('ready', () => {
|
||||||
|
this.setupOptions();
|
||||||
|
Setup.ensure().then(this.init);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {}
|
||||||
|
|
||||||
|
setupOptions() {
|
||||||
|
// Ensure options store is setup
|
||||||
|
if(!PersistentSyncStorage.has('options')) {
|
||||||
|
PersistentSyncStorage.set({ options: CONFIG.defaultOptions });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const main = new Main();
|
||||||
26
src/config.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
const CONFIG = {
|
||||||
|
defaultOptions: {
|
||||||
|
|
||||||
|
// Emote Options
|
||||||
|
enableBTTVEmotes: true,
|
||||||
|
enableFrankerEmotes: true,
|
||||||
|
enableTwitchEmotes: true,
|
||||||
|
kappaFix: true,
|
||||||
|
|
||||||
|
// Chat Options
|
||||||
|
theaterModeFix: false,
|
||||||
|
setAuthorColor: false,
|
||||||
|
showTimeStamp: false,
|
||||||
|
alternateLineColor: false,
|
||||||
|
hideAuthorIcons: false,
|
||||||
|
hideWelcomBanner: false,
|
||||||
|
setTwitchColors: false,
|
||||||
|
textSizeSlider: 'inherit',
|
||||||
|
setLiveChat: true,
|
||||||
|
allowTextSlider: false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CONFIG;
|
||||||
|
|
||||||
48
src/content/ChatScroller.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
class ChatScroller {
|
||||||
|
constructor() {
|
||||||
|
this.scroll = this.scroll.bind(this);
|
||||||
|
this.start = this.start.bind(this);
|
||||||
|
this.stop = this.stop.bind(this);
|
||||||
|
|
||||||
|
this.scroller = null;
|
||||||
|
this.interval = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.getScroller()
|
||||||
|
.then(() => {
|
||||||
|
//this.scroller.addEventListener('mouseleave', this.start);
|
||||||
|
this.scroller.addEventListener('mouseenter', this.stop);
|
||||||
|
this.start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.interval = setInterval(
|
||||||
|
this.scroll,
|
||||||
|
250
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll() {
|
||||||
|
this.scroller.scrollTop = 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
getScroller() {
|
||||||
|
const checkForScroller = (res, rej) => {
|
||||||
|
this.scroller = document.getElementById('item-scroller');
|
||||||
|
if(this.scroller !== null) {
|
||||||
|
res();
|
||||||
|
} else {
|
||||||
|
setTimeout(checkForScroller.bind(this, res, rej), 250);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return new Promise(checkForScroller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatScroller;
|
||||||
230
src/content/ChatWatcher.js
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
import Emotes from './Emotes';
|
||||||
|
import Message from './Message';
|
||||||
|
import PersistentSyncStorage from 'src/helpers/PersistentSyncStorage';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ChatWatcher {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.watchChat = this.watchChat.bind(this);
|
||||||
|
this._chatContainer = null;
|
||||||
|
this._observer = null;
|
||||||
|
this.messages = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
this.getChatContainer().then(Emotes.init).then(() => {
|
||||||
|
this.addEmotePopup();
|
||||||
|
this.watchChat();
|
||||||
|
this.parsePreloadedMessages();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getChatContainer() {
|
||||||
|
// Parent of actual chat (children are messages)
|
||||||
|
const checkForContainer = (res, rej) => {
|
||||||
|
this._chatContainer = document.querySelector('#items.style-scope.yt-live-chat-item-list-renderer');
|
||||||
|
if(this._chatContainer !== null) {
|
||||||
|
res();
|
||||||
|
} else {
|
||||||
|
setTimeout(checkForContainer.bind(this, res, rej), 250);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return new Promise(checkForContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
parsePreloadedMessages() {
|
||||||
|
const messages = this._chatContainer.children;
|
||||||
|
|
||||||
|
for(let i = messages.length-1; i >= 0; i--) {
|
||||||
|
const node = messages[i];
|
||||||
|
if(this.isMessageNode(node)) {
|
||||||
|
const message = new Message(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchChat() {
|
||||||
|
console.log('Chat observer started');
|
||||||
|
this._observer = new MutationObserver(mutations => {
|
||||||
|
|
||||||
|
mutations.forEach(mutation => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const { addedNodes, removedNodes } = mutation;
|
||||||
|
|
||||||
|
// Added nodes
|
||||||
|
if(typeof addedNodes !== 'undefined' && addedNodes.length > 0) {
|
||||||
|
for(let i = 0, length = addedNodes.length-1; i <= length; i++) {
|
||||||
|
const node = addedNodes[i];
|
||||||
|
if(this.isMessageNode(node)) {
|
||||||
|
this.onNewMessage(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removed nodes
|
||||||
|
if(typeof removedNodes !== 'undefined' && removedNodes.length > 0) {
|
||||||
|
for(let i = 0, length = removedNodes.length-1; i <= length; i++) {
|
||||||
|
const node = removedNodes[i];
|
||||||
|
if(this.isMessageNode(node) && this.isObservedMessage(node)) {
|
||||||
|
this.onObservedMessageRemoved(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this._observer.observe(this._chatContainer, {
|
||||||
|
childList: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false,
|
||||||
|
subtree: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onNewMessage(node) {
|
||||||
|
const message = new Message(node);
|
||||||
|
|
||||||
|
// Don't store message if has 0 emotes
|
||||||
|
if(message.hasEmotes) {
|
||||||
|
this.messages.set(message.id, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onObservedMessageRemoved(node) {
|
||||||
|
const messageId = node.getAttribute('message-id');
|
||||||
|
const message = this.messages.get(messageId);
|
||||||
|
if(message != undefined){
|
||||||
|
message.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messages.delete(messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
isMessageNode(node) {
|
||||||
|
return node.tagName === 'YT-LIVE-CHAT-TEXT-MESSAGE-RENDERER';
|
||||||
|
}
|
||||||
|
|
||||||
|
isObservedMessage(node) {
|
||||||
|
return node.getAttribute('message-id') !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
addEmotePopup(){
|
||||||
|
|
||||||
|
// create emote button
|
||||||
|
const emoteButton = document.createElement('button');
|
||||||
|
emoteButton.classList.add('emoteButton');
|
||||||
|
emoteButton.textContent = '';
|
||||||
|
|
||||||
|
// append button to action-buttons list
|
||||||
|
const chatButtonSelectionList = document.getElementById('action-buttons');
|
||||||
|
chatButtonSelectionList.parentNode.insertBefore(emoteButton, chatButtonSelectionList);
|
||||||
|
|
||||||
|
// create popupDiv
|
||||||
|
const popUpDiv = document.createElement('div');
|
||||||
|
popUpDiv.classList.add('popup');
|
||||||
|
popUpDiv.classList.add('hideElement');
|
||||||
|
|
||||||
|
function emoteAppend(keysITer){
|
||||||
|
|
||||||
|
// create divider
|
||||||
|
var hr = document.createElement('hr');
|
||||||
|
hr.classList.add('emoteDivider');
|
||||||
|
|
||||||
|
for (let index = 0; index < keysITer.length; index++) {
|
||||||
|
const element = keysITer[index];
|
||||||
|
var emote_div = document.createElement('emote_div');
|
||||||
|
emote_div.innerHTML = (Emotes.get(element).html);
|
||||||
|
popUpDiv.appendChild(emote_div);
|
||||||
|
}
|
||||||
|
popUpDiv.appendChild(hr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create text
|
||||||
|
var bttv_text = document.createElement('h2');
|
||||||
|
bttv_text.textContent = "BTTV";
|
||||||
|
bttv_text.classList.add('emotePopUpText');
|
||||||
|
var franker_text = document.createElement('h2');
|
||||||
|
franker_text.textContent = "FrankerFacez";
|
||||||
|
franker_text.classList.add('emotePopUpText');
|
||||||
|
var twitch_text = document.createElement('h2');
|
||||||
|
twitch_text.textContent = "Twitch";
|
||||||
|
twitch_text.classList.add('emotePopUpText');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// need ittr to search each dict and append to dom
|
||||||
|
let keysITer = null;
|
||||||
|
|
||||||
|
keysITer = Array.from(Emotes.specialEmotesDictionary.keys());
|
||||||
|
emoteAppend(keysITer);
|
||||||
|
|
||||||
|
if(PersistentSyncStorage.data.options.enableBTTVEmotes){
|
||||||
|
popUpDiv.appendChild(bttv_text);
|
||||||
|
keysITer = Array.from(Emotes.bttv_Dictionary.keys());
|
||||||
|
emoteAppend(keysITer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(PersistentSyncStorage.data.options.enableFrankerEmotes){
|
||||||
|
popUpDiv.appendChild(franker_text);
|
||||||
|
keysITer = Array.from(Emotes.franker_Dictionary.keys());
|
||||||
|
emoteAppend(keysITer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(PersistentSyncStorage.data.options.enableTwitchEmotes){
|
||||||
|
popUpDiv.appendChild(twitch_text);
|
||||||
|
keysITer = Array.from(Emotes.twitch_Dictionary.keys());
|
||||||
|
emoteAppend(keysITer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// add div to doc
|
||||||
|
chatButtonSelectionList.appendChild(popUpDiv);
|
||||||
|
|
||||||
|
// listen for popup button
|
||||||
|
emoteButton.addEventListener('click', function(){
|
||||||
|
popUpDiv.classList.toggle('hideElement');
|
||||||
|
console.log('emote popup button clicked');
|
||||||
|
});
|
||||||
|
|
||||||
|
// get input area
|
||||||
|
var inputArea = document.querySelector('#input.yt-live-chat-text-input-field-renderer');
|
||||||
|
var inputAreaLabel = document.querySelector('#label.yt-live-chat-text-input-field-renderer');
|
||||||
|
|
||||||
|
// add alt tag to chat
|
||||||
|
function emoteToTextArea(){
|
||||||
|
inputArea.textContent += this.alt + " ";
|
||||||
|
inputArea.focus();
|
||||||
|
inputAreaLabel.textContent = "";
|
||||||
|
popUpDiv.classList.toggle('hideElement');
|
||||||
|
console.log(this.alt + " emote button selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
// listener button for emotes
|
||||||
|
var EMOTICONS = document.getElementsByTagName('img');
|
||||||
|
for (let index = 0; index < EMOTICONS.length; index++) {
|
||||||
|
const element = EMOTICONS[index];
|
||||||
|
element.addEventListener('click', emoteToTextArea, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log((keysITer.length+1) + " Emotes Added");
|
||||||
|
}// end addEmotePopup
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
}// end chat watcher
|
||||||
|
|
||||||
|
export default ChatWatcher;
|
||||||
16
src/content/Emotes/Emote.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
class Emote {
|
||||||
|
constructor({ code, url }) {
|
||||||
|
this.code = code;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
get html() {
|
||||||
|
return (`
|
||||||
|
<span class="Emote">
|
||||||
|
<img title="${this.code}" src="${this.url}" alt="${this.code}">
|
||||||
|
</span>
|
||||||
|
`).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Emote;
|
||||||
194
src/content/Emotes/index.js
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import PersistentSyncStorage from 'src/helpers/PersistentSyncStorage';
|
||||||
|
import Emote from './Emote';
|
||||||
|
|
||||||
|
|
||||||
|
class Emotes {
|
||||||
|
constructor() {
|
||||||
|
this.dictionary = new Map();
|
||||||
|
|
||||||
|
// identification for popup
|
||||||
|
this.twitch_Dictionary = new Map();
|
||||||
|
this.bttv_Dictionary = new Map();
|
||||||
|
this.franker_Dictionary = new Map();
|
||||||
|
this.specialEmotesDictionary = new Map();
|
||||||
|
|
||||||
|
|
||||||
|
this.init = this.init.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
return Promise.all([
|
||||||
|
(PersistentSyncStorage.data.options.enableBTTVEmotes && this.loadBTTVEmote()),
|
||||||
|
(PersistentSyncStorage.data.options.enableFrankerEmotes && this.loadFrankerEmotes()),
|
||||||
|
(PersistentSyncStorage.data.options.enableTwitchEmotes && this.loadTwitchEmotes()),
|
||||||
|
(this.specialEmotes())
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
return this.dictionary.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, value) {
|
||||||
|
return this.dictionary.set(key, new Emote(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
has(key) {
|
||||||
|
return this.dictionary.has(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
bbtv_ToDict(json){
|
||||||
|
for (let index = 0; index < json.length; index++) {
|
||||||
|
|
||||||
|
const { emote, total } = json[index];
|
||||||
|
|
||||||
|
const url = `https://cdn.betterttv.net/emote/${emote.id}/3x`;
|
||||||
|
|
||||||
|
this.dictionary.set(emote.code, new Emote({ code: emote.code, url }));
|
||||||
|
this.bttv_Dictionary.set(emote.code, new Emote({ code: emote.code, url }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bbtv_cached_ToDict(json){
|
||||||
|
for (let index = 0; index < json.length; index++) {
|
||||||
|
|
||||||
|
const { id, code } = json[index];
|
||||||
|
|
||||||
|
const url = `https://cdn.betterttv.net/emote/${id}/3x`;
|
||||||
|
|
||||||
|
this.dictionary.set(code, new Emote({ code: code, url }));
|
||||||
|
this.bttv_Dictionary.set(code, new Emote({ code: code, url }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadEmote is where we collect an object array of emotes from bttv api
|
||||||
|
async loadBTTVEmote(){
|
||||||
|
|
||||||
|
// top 100 emotes query = ?limit=100&offset=100
|
||||||
|
const bttv_top_api_url = "https://api.betterttv.net/3/emotes/shared/top?limit=100";
|
||||||
|
const bttv_top_api_response = await fetch(bttv_top_api_url);
|
||||||
|
var top_Json = await bttv_top_api_response.json();
|
||||||
|
|
||||||
|
// tredning emotes
|
||||||
|
const bttv_trending_api_url = "https://api.betterttv.net/3/emotes/shared/trending?limit=100";
|
||||||
|
const bttv_trending_api_response = await fetch(bttv_trending_api_url);
|
||||||
|
var trending_Json = await bttv_trending_api_response.json();
|
||||||
|
|
||||||
|
// global emotes are weird, stored in seperate cache and do not give all the normal attributes
|
||||||
|
const bttv_global_api_url = "https://api.betterttv.net/3/cached/emotes/global";
|
||||||
|
const bttv_global_api_response = await fetch(bttv_global_api_url);
|
||||||
|
var global_Json = await bttv_global_api_response.json();
|
||||||
|
|
||||||
|
this.bbtv_ToDict(top_Json);
|
||||||
|
this.bbtv_ToDict(trending_Json);
|
||||||
|
this.bbtv_cached_ToDict(global_Json);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
frankerToDict(json){
|
||||||
|
for (let index = 0; index < json.emoticons.length; index++) {
|
||||||
|
|
||||||
|
const { name, urls } = json.emoticons[index];
|
||||||
|
|
||||||
|
var url = "";
|
||||||
|
if(urls[4] != undefined){
|
||||||
|
url = urls[4];
|
||||||
|
}else if(urls[2] != undefined){
|
||||||
|
url = urls[2];
|
||||||
|
}else{
|
||||||
|
url = urls[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dictionary.set(name, new Emote({ code: name, url }));
|
||||||
|
this.franker_Dictionary.set(name, new Emote({ code: name, url }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadFrankerEmotes is where we collect an object array of emotes from franker api
|
||||||
|
async loadFrankerEmotes(){
|
||||||
|
|
||||||
|
const franker_top_api_url = "https://api.frankerfacez.com/v1/emoticons?sort=count-desc";
|
||||||
|
|
||||||
|
const first50Response = await fetch(franker_top_api_url);
|
||||||
|
var first50json = await first50Response.json();
|
||||||
|
var next50Link = first50json._links.next;
|
||||||
|
const second50Response = await fetch(next50Link);
|
||||||
|
var second50json = await second50Response.json();
|
||||||
|
|
||||||
|
// Top 100
|
||||||
|
this.frankerToDict(first50json);
|
||||||
|
this.frankerToDict(second50json);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
twitchToDict(json){
|
||||||
|
for (let index = 0; index < json.emotes.length; index++) {
|
||||||
|
|
||||||
|
const { code, id } = json.emotes[index];
|
||||||
|
|
||||||
|
const url = `https://static-cdn.jtvnw.net/emoticons/v1/${id}/3.0`;
|
||||||
|
|
||||||
|
this.dictionary.set(code, new Emote({ code: code, url }));
|
||||||
|
this.twitch_Dictionary.set(code, new Emote({ code: code, url }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTwitchEmotes is where we collect an object array of emotes from twitch api
|
||||||
|
async loadTwitchEmotes(){
|
||||||
|
|
||||||
|
// https://api.twitchemotes.com/api/v4/channels/0 - twitch globals - 232 items
|
||||||
|
// https://static-cdn.jtvnw.net/emoticons/v1/25/1.0 - cdn
|
||||||
|
|
||||||
|
// Global
|
||||||
|
const twitch_global_api_url = "https://api.twitchemotes.com/api/v4/channels/0";
|
||||||
|
const twitch_global_api_response = await fetch(twitch_global_api_url);
|
||||||
|
var twitch_global_Json = await twitch_global_api_response.json();
|
||||||
|
|
||||||
|
this.twitchToDict(twitch_global_Json);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ♥
|
||||||
|
specialEmotes(){
|
||||||
|
|
||||||
|
var emoteObj = {
|
||||||
|
"emotes": [
|
||||||
|
{
|
||||||
|
"code": "wompWTF",
|
||||||
|
"url": "https://static-cdn.jtvnw.net/emoticons/v1/301653066/3.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "wompISeeYou",
|
||||||
|
"url": "https://static-cdn.jtvnw.net/emoticons/v1/301506153/3.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "wompCry",
|
||||||
|
"url": "https://static-cdn.jtvnw.net/emoticons/v1/301506193/3.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BabyCorona",
|
||||||
|
"url": "https://static-cdn.jtvnw.net/emoticons/v1/301629296/3.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "LEL",
|
||||||
|
"url": "https://static-cdn.jtvnw.net/emoticons/v1/431249/3.0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let index = 0; index < emoteObj.emotes.length; index++) {
|
||||||
|
const element = emoteObj[index];
|
||||||
|
const { code, url } = emoteObj.emotes[index];
|
||||||
|
this.dictionary.set(code, new Emote({ code: code, url}));
|
||||||
|
this.specialEmotesDictionary.set(code, new Emote({ code: code, url}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}// End Emotes
|
||||||
|
|
||||||
|
export default new Emotes;
|
||||||
255
src/content/Message.js
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
import Emotes from './Emotes';
|
||||||
|
import PersistentSyncStorage from 'src/helpers/PersistentSyncStorage';
|
||||||
|
|
||||||
|
var colorNumberIndex = 0;
|
||||||
|
|
||||||
|
class Message {
|
||||||
|
constructor(messageNode) {
|
||||||
|
this.node = messageNode;
|
||||||
|
this.id = this.node.id; // this.id should not be used to reference the node, dom id changes due to optimisitc updates
|
||||||
|
this.hasEmotes = null;
|
||||||
|
this.observer = null;
|
||||||
|
this.parsedText = ''; // This should be fine since you can't edit/change messages
|
||||||
|
|
||||||
|
this.parseText();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Set Defaults
|
||||||
|
this.setDefaultSelections();
|
||||||
|
|
||||||
|
|
||||||
|
if(this.hasEmotes) {
|
||||||
|
this.node.setAttribute('message-id', this.id);
|
||||||
|
this.setHtml();
|
||||||
|
this.watch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get textNode() {
|
||||||
|
const node = this.node.querySelector('#message');
|
||||||
|
return {
|
||||||
|
node,
|
||||||
|
text: node.innerText
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
parseText() {
|
||||||
|
const rawWords = this.textNode.text.split(' ');
|
||||||
|
|
||||||
|
for(let i = 0, length = rawWords.length; i < length; i++) {
|
||||||
|
const word = this.parseIllegalCharcters(rawWords[i]);
|
||||||
|
const emote = Emotes.get(word);
|
||||||
|
|
||||||
|
//console.log(Emotes.get(word));
|
||||||
|
|
||||||
|
|
||||||
|
if(typeof emote === 'undefined') {
|
||||||
|
this.parsedText += word + ' ';
|
||||||
|
} else {
|
||||||
|
this.hasEmotes = true;
|
||||||
|
this.parsedText += emote.html + ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch() {
|
||||||
|
this.observer = new MutationObserver(mutations => {
|
||||||
|
let emoteRemoved = false;
|
||||||
|
|
||||||
|
mutations.forEach(mutation => {
|
||||||
|
if(typeof mutation.removedNodes === 'undefined') return;
|
||||||
|
if(mutation.removedNodes.length <= 0) return; // This must be after undefined check
|
||||||
|
|
||||||
|
for(let i = 0, length = mutation.removedNodes.length; i < length; i++) {
|
||||||
|
const removedNode = mutation.removedNodes[i];
|
||||||
|
if(typeof removedNode.className === 'string' && // check if className exists, is 'SVGAnimatedString' when window resized and removed
|
||||||
|
~removedNode.className.indexOf('Emote') !== 0) {
|
||||||
|
emoteRemoved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
if(emoteRemoved && document.body.contains(this.node)) {
|
||||||
|
this.setHtml();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.observer.observe(this.node, {
|
||||||
|
childList: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: false,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setHtml() {
|
||||||
|
this.textNode.node.innerHTML = this.parsedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseIllegalCharcters(word) {
|
||||||
|
// === 'ZERO WIDTH NO-BREAK SPACE'
|
||||||
|
return word.replace('', '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if(this.observer !== null) {
|
||||||
|
this.observer.disconnect();
|
||||||
|
this.observer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Setting Options for Each Message
|
||||||
|
setDefaultSelections(){
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Welcome Banner
|
||||||
|
var welcomBanner = document.querySelector("yt-live-chat-viewer-engagement-message-renderer");
|
||||||
|
|
||||||
|
// Set Hide Welcome Banner
|
||||||
|
if (PersistentSyncStorage.data.options.hideWelcomBanner) {
|
||||||
|
welcomBanner.classList.add("hideElement");
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Checks for kappa and replaces emoji element with kappa
|
||||||
|
if(PersistentSyncStorage.data.options.kappaFix) {
|
||||||
|
|
||||||
|
var stupidKappa = document.querySelectorAll('#message.yt-live-chat-text-message-renderer .emoji.yt-live-chat-text-message-renderer');
|
||||||
|
|
||||||
|
for (let index = 0; index < stupidKappa.length; index++) {
|
||||||
|
const stupidElement = stupidKappa[index];
|
||||||
|
var stupidToolTip = stupidElement.getAttribute('shared-tooltip-text');
|
||||||
|
|
||||||
|
if(stupidToolTip == ':full_moon_face:'){
|
||||||
|
const newSpan = document.createElement('span');
|
||||||
|
newSpan.classList.add('Emote');
|
||||||
|
newSpan.innerHTML = '<img src="https://static-cdn.jtvnw.net/emoticons/v1/25/3.0" alt="kappa">';
|
||||||
|
|
||||||
|
stupidElement.parentNode.replaceChild(newSpan, stupidElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//Set Author Colors
|
||||||
|
if(PersistentSyncStorage.data.options.setAuthorColor && this.node.getAttribute('author-type') !== 'owner') {
|
||||||
|
this.setAuthorColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Author Icons
|
||||||
|
var author_photo = this.node.querySelector('#author-photo');
|
||||||
|
|
||||||
|
// Set Hide Author Icons
|
||||||
|
if (PersistentSyncStorage.data.options.hideAuthorIcons) {
|
||||||
|
author_photo.classList.add("hideElement");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeStamp
|
||||||
|
var timestamp = this.node.querySelector('#timestamp');
|
||||||
|
|
||||||
|
// Set Show TimeStamp
|
||||||
|
if (PersistentSyncStorage.data.options.showTimeStamp) {
|
||||||
|
this.node.classList.add("showTimeStamp");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Author Name @ auto paste in text area
|
||||||
|
this.node.querySelector('#author-name').addEventListener("click", function(){
|
||||||
|
var inputArea = document.querySelector('#input.yt-live-chat-text-input-field-renderer');
|
||||||
|
var inputAreaLabel = document.querySelector('#label.yt-live-chat-text-input-field-renderer');
|
||||||
|
inputArea.innerText = "@" + this.innerText;
|
||||||
|
const textLength = inputArea.innerText.length;
|
||||||
|
inputArea.focus();
|
||||||
|
inputAreaLabel.innerText = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Set Font Size
|
||||||
|
var textSizeSlider = PersistentSyncStorage.data.options.textSizeSlider;
|
||||||
|
|
||||||
|
if (PersistentSyncStorage.data.options.allowTextSlider) {
|
||||||
|
this.node.setAttribute('style', 'font-size:' + textSizeSlider + 'px' + '!important');
|
||||||
|
this.node.classList.add("AuthorFix");
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Set Twitch Styling
|
||||||
|
if (PersistentSyncStorage.data.options.setTwitchColors) {
|
||||||
|
this.node.classList.add("setTwitchColors");
|
||||||
|
author_photo.classList.add("hideElement");
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Set Alternate message Colors
|
||||||
|
if (PersistentSyncStorage.data.options.alternateLineColor) {
|
||||||
|
this.alternateLineColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
}// end setDefaultSelections
|
||||||
|
|
||||||
|
setAuthorColor() {
|
||||||
|
|
||||||
|
let imageSrc = null;
|
||||||
|
|
||||||
|
if(this.node.hasChildNodes && this.node.contains(this.node.querySelector('#author-photo'))){
|
||||||
|
|
||||||
|
if(this.node.querySelector('#author-photo').querySelector('img').src != null){
|
||||||
|
imageSrc = this.node.querySelector('#author-photo').querySelector('img').src;
|
||||||
|
|
||||||
|
const idRegexp = /\/-([A-Za-z-_\d])/;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(idRegexp.exec(imageSrc) !== null){
|
||||||
|
const parsedSRC = idRegexp.exec(imageSrc)[1];
|
||||||
|
this.node.classList.add(`chat-color-${parsedSRC}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// for some reason nodes from user img.src are getting weird link on occasion
|
||||||
|
console.log(error);
|
||||||
|
console.log(imageSrc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// changes color every line
|
||||||
|
alternateLineColor(){
|
||||||
|
|
||||||
|
if(colorNumberIndex % 2 == 0){
|
||||||
|
this.node.classList.add("set-background-color-one");
|
||||||
|
}
|
||||||
|
if(colorNumberIndex % 2 !== 0){
|
||||||
|
this.node.classList.add("set-background-color-two");
|
||||||
|
}
|
||||||
|
colorNumberIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// removes color attr
|
||||||
|
removelternateLineColor(){
|
||||||
|
this.node.classList.remove("set-background-color-one");
|
||||||
|
this.node.classList.remove("set-background-color-two");
|
||||||
|
}
|
||||||
|
|
||||||
|
}// end Message
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default Message;
|
||||||
39
src/content/RouteWatcher.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
|
class RouteWatcher extends EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.target = document.querySelector('head > title');
|
||||||
|
this.observer = null;
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.observer = new MutationObserver(mutations => {
|
||||||
|
mutations.forEach((m) => {
|
||||||
|
/**
|
||||||
|
* Title is set to 'YouTube Gaming' on main routes
|
||||||
|
* and between routes.
|
||||||
|
*/
|
||||||
|
if(m.target.innerText === 'YouTube Gaming') {
|
||||||
|
this.emit('main');
|
||||||
|
} else {
|
||||||
|
this.emit('change');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if(this.target !== null) { // Popout chat does not have title tag
|
||||||
|
this.observer.observe(this.target, {
|
||||||
|
childList: true,
|
||||||
|
attributes: false,
|
||||||
|
characterData: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RouteWatcher;
|
||||||
188
src/content/index.js
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import "src/stylus/content.styl";
|
||||||
|
import ChatScroller from "./ChatScroller";
|
||||||
|
import ChatWatcher from "./ChatWatcher";
|
||||||
|
import RouteWatcher from "./RouteWatcher";
|
||||||
|
|
||||||
|
|
||||||
|
import {
|
||||||
|
isLivestream, isYoutubeGaming,
|
||||||
|
isYoutubeEmbed, isYoutubeVanilla,
|
||||||
|
isPopOut
|
||||||
|
} from "src/helpers/Identification";
|
||||||
|
|
||||||
|
import PersistentSyncStorage from "src/helpers/PersistentSyncStorage";
|
||||||
|
|
||||||
|
let MAIN = null;
|
||||||
|
const theater_wrapper = document.createElement('theater_wrapper');
|
||||||
|
document.body.appendChild(theater_wrapper);
|
||||||
|
var alreadyTheater = false;
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
class Main {
|
||||||
|
constructor() {
|
||||||
|
this.chatWatcher = null;
|
||||||
|
this.chatScroller = null;
|
||||||
|
this.routeWatcher = null;
|
||||||
|
this.onRouteChange = this.onRouteChange.bind(this);
|
||||||
|
this.load();
|
||||||
|
|
||||||
|
|
||||||
|
// button class - ytp-size-button ytp-button
|
||||||
|
// right player controls - ytp-right-controls
|
||||||
|
// player div id - ytd-player
|
||||||
|
// chatframe id - chatframe
|
||||||
|
// movieframe id - movie_player_fix
|
||||||
|
// dono ticker id - ticker
|
||||||
|
|
||||||
|
// player-theater-container
|
||||||
|
}
|
||||||
|
|
||||||
|
load() {
|
||||||
|
this.routeWatcher = new RouteWatcher();
|
||||||
|
this.routeWatcher.on("change", this.onRouteChange);
|
||||||
|
this.onRouteChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
onRouteChange() {
|
||||||
|
if(isLivestream() && ((isYoutubeGaming()) || (isYoutubeVanilla()) || (isYoutubeEmbed()) || isPopOut())) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isLivestream()) {
|
||||||
|
if (PersistentSyncStorage.data.options.theaterModeFix) {
|
||||||
|
if(document.getElementById('player-container') != null && document.getElementById('player-theater-container') != null){
|
||||||
|
theaterMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}// end onRouteChange
|
||||||
|
|
||||||
|
init() {
|
||||||
|
|
||||||
|
this.chatWatcher = new ChatWatcher();
|
||||||
|
this.chatWatcher.init();
|
||||||
|
this.chatScroller = new ChatScroller();
|
||||||
|
this.chatScroller.init();
|
||||||
|
|
||||||
|
setDefaults();
|
||||||
|
console.log("INIT");
|
||||||
|
|
||||||
|
}// end init
|
||||||
|
|
||||||
|
}// end main
|
||||||
|
|
||||||
|
|
||||||
|
function setDefaults() {
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
//Live Chat Default Option
|
||||||
|
if (PersistentSyncStorage.data.options.setLiveChat) {
|
||||||
|
document.getElementsByClassName("yt-simple-endpoint style-scope yt-dropdown-menu").item(1).click();
|
||||||
|
} else {
|
||||||
|
// do nothing, let user pick option if not set as default in options menu
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Every Frame Loaded
|
||||||
|
PersistentSyncStorage.on("ready", () => {
|
||||||
|
MAIN = new Main();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function checkMode(){
|
||||||
|
|
||||||
|
if(alreadyTheater){
|
||||||
|
console.log('enterTheater');
|
||||||
|
alreadyTheater = false;
|
||||||
|
enterTheaterMode();
|
||||||
|
}else{
|
||||||
|
// is reverse because at the time of check dom elements havent moved yet
|
||||||
|
if(document.getElementById('player-theater-container').contains(document.getElementById('player-container'))){
|
||||||
|
console.log('exitTheater');
|
||||||
|
exitTheaterMode();
|
||||||
|
}else{
|
||||||
|
console.log('enterTheater');
|
||||||
|
enterTheaterMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterTheaterMode() {
|
||||||
|
|
||||||
|
const movie_player = document.getElementById('movie_player');
|
||||||
|
const chat_frame = document.getElementById('chatframe');
|
||||||
|
const info_frame = document.getElementById('info-contents');
|
||||||
|
|
||||||
|
const masthead_container = document.getElementById('masthead-container');
|
||||||
|
|
||||||
|
|
||||||
|
masthead_container.hidden = true;
|
||||||
|
|
||||||
|
theater_wrapper.classList.add('theater_wrapper_fix');
|
||||||
|
movie_player.classList.add('movie_player_fix');
|
||||||
|
chat_frame.classList.add('chat_frame_fix');
|
||||||
|
info_frame.classList.add('info_contents_fix');
|
||||||
|
|
||||||
|
|
||||||
|
theater_wrapper.append(info_frame);
|
||||||
|
theater_wrapper.append(movie_player);
|
||||||
|
theater_wrapper.append(chat_frame);
|
||||||
|
|
||||||
|
document.body.classList.add('body_Fix');
|
||||||
|
|
||||||
|
|
||||||
|
}// end enterTheaterMode
|
||||||
|
|
||||||
|
function exitTheaterMode(){
|
||||||
|
|
||||||
|
const movie_player = document.getElementById('movie_player');
|
||||||
|
const chat_frame = document.getElementById('chatframe');
|
||||||
|
const info_frame = document.getElementById('info-contents');
|
||||||
|
|
||||||
|
const movie_player_container = document.getElementById('player-container');
|
||||||
|
const player_container_parent = document.getElementById('player-container-inner');
|
||||||
|
|
||||||
|
const chat_frame_parent = document.getElementById('chat');
|
||||||
|
const info_frame_before = document.getElementById('meta');
|
||||||
|
|
||||||
|
const masthead_container = document.getElementById('masthead-container');
|
||||||
|
|
||||||
|
|
||||||
|
masthead_container.hidden = false;
|
||||||
|
|
||||||
|
theater_wrapper.classList.remove('theater_wrapper_fix');
|
||||||
|
movie_player.classList.remove('movie_player_fix');
|
||||||
|
chat_frame.classList.remove('chat_frame_fix');
|
||||||
|
info_frame.classList.remove('info_contents_fix');
|
||||||
|
|
||||||
|
movie_player_container.prepend(movie_player);
|
||||||
|
player_container_parent.prepend(movie_player_container);
|
||||||
|
chat_frame_parent.prepend(chat_frame);
|
||||||
|
info_frame_before.before(info_frame);
|
||||||
|
|
||||||
|
document.body.classList.remove('body_Fix');
|
||||||
|
}
|
||||||
|
|
||||||
|
function theaterMode(){
|
||||||
|
|
||||||
|
var theaterButton = document.querySelector('button.ytp-size-button.ytp-button');
|
||||||
|
|
||||||
|
if(theaterButton){
|
||||||
|
|
||||||
|
|
||||||
|
if(document.getElementById('player-theater-container').contains(document.getElementById('player-container'))){
|
||||||
|
|
||||||
|
// for when page loads first time - check is reversed after this
|
||||||
|
alreadyTheater = true;
|
||||||
|
checkMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// add button
|
||||||
|
theaterButton.addEventListener('click', checkMode, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/helpers/Identification.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
export const isLivestream = () => {
|
||||||
|
const timeDisplay = document.querySelector('.ytp-time-display');
|
||||||
|
const chatApp = document.querySelector('yt-live-chat-app');
|
||||||
|
const chatHeader = document.querySelector('.yt-live-chat-renderer-0');
|
||||||
|
const timeDisplayCheck = timeDisplay && timeDisplay.classList.contains('ytp-live');
|
||||||
|
const chatCheck = (document.body.contains(chatApp) || document.body.contains(chatHeader));
|
||||||
|
|
||||||
|
return (timeDisplayCheck || chatCheck);
|
||||||
|
};
|
||||||
|
|
||||||
|
// isYoutubeGaming checks for the presence of ytg-app, the top level element for YT Gaming
|
||||||
|
export const isYoutubeGaming = () => {
|
||||||
|
return !!document.querySelector('ytg-app');
|
||||||
|
};
|
||||||
|
|
||||||
|
// isYoutubeEmbed checks that this is an iframe, and it is being used on youtube.com
|
||||||
|
export const isYoutubeVanilla = () => {
|
||||||
|
// window.frameElement is only available from youtube.com sites from within iframe per CORS
|
||||||
|
return !!window.frameElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
// isYoutubeEmbed checks that this is an iframe, and it is **not** loaded from youtube.com (main site uses embed too)
|
||||||
|
export const isYoutubeEmbed = () => {
|
||||||
|
|
||||||
|
// If the frameElement is available, then CORS means that we must be on youtube.com.
|
||||||
|
if (window.frameElement) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the window location isn't the parent location, then we are in an iframe.
|
||||||
|
return (window.location != window.parent.location);
|
||||||
|
};
|
||||||
|
|
||||||
|
// isPopOut fix for popout page
|
||||||
|
export const isPopOut = () => {
|
||||||
|
|
||||||
|
// If the frameElement is available, then CORS means that we must be on youtube.com.
|
||||||
|
if (window.frameElement) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks href for page
|
||||||
|
if(window.location.href.includes('is_popout=1')){
|
||||||
|
return !!window.location.href.includes('popout=1');
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
20
src/helpers/OpenActiveTab.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
|
||||||
|
/* Open new tab if tab is not already open, otherwise focus that tab */
|
||||||
|
export default url => {
|
||||||
|
const matchUrl = url.replace(/^(https|http)/i, '*');
|
||||||
|
chrome.tabs.query({ url: matchUrl }, tabs => { // url must be valid match pattern - https://developer.chrome.com/extensions/match_patterns
|
||||||
|
if(tabs && tabs.length) {
|
||||||
|
// tab.id is not present in some rare cases, so if error around here, that could be the cause.
|
||||||
|
chrome.tabs.update(tabs[0].id, { active: true });
|
||||||
|
} else {
|
||||||
|
chrome.tabs.create({ url });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// for(let i = 0, tab; tab = tabs[i]; i++) {
|
||||||
|
// if(tab.url && tab.url === url) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
};
|
||||||
51
src/helpers/PersistentSyncStorage.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { SyncStorage } from '../utils/chrome';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
|
class PersistentSyncStorage extends EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._data = null;
|
||||||
|
this.state = 'initiating';
|
||||||
|
|
||||||
|
this._init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _init() {
|
||||||
|
const fetchedData = await SyncStorage.get();
|
||||||
|
this._initListener();
|
||||||
|
this._data = fetchedData;
|
||||||
|
this.state = 'ready';
|
||||||
|
this.emit(this.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
_initListener() {
|
||||||
|
SyncStorage.listen((changes) => {
|
||||||
|
|
||||||
|
Object.keys(changes).forEach((changeKey) => {
|
||||||
|
if(changes[changeKey].hasOwnProperty('newValue')) {
|
||||||
|
this._data[changeKey] = changes[changeKey].newValue;
|
||||||
|
} else {
|
||||||
|
console.error('No newValue in sync storge change');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.emit('change', this.data, changes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
set(items) {
|
||||||
|
return SyncStorage.set(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
get data() {
|
||||||
|
return this._data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
has(item) {
|
||||||
|
return this.data.hasOwnProperty(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new PersistentSyncStorage();
|
||||||
165
src/html/options.html
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
padding: 10px;
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hideDiv {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Options</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<span style="display:inline-block;vertical-align: middle; margin-right: 2%;">
|
||||||
|
<img src="../assets/icons/icon128.png" alt="🔴" style="height: 2.5em;">
|
||||||
|
</span>
|
||||||
|
<span style="display:inline-block;vertical-align: middle;">
|
||||||
|
<h1>Live Chat Options</h1>
|
||||||
|
</span>
|
||||||
|
<span class="">
|
||||||
|
<button class="info_button" id="infoButton" >Info</button>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<div id="optionsMenu" class="tabcontent active">
|
||||||
|
<div class="section">
|
||||||
|
<h2 class="options-heading">Emote Options</h2>
|
||||||
|
<div class="options-description"></div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="enableBTTVEmotes">Enable BTTV - Top, Trending and Global Emotes (200+)</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="enableBTTVEmotes" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="enableFrankerEmotes">Enable FrankerFacez - Top 100 emotes</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="enableFrankerEmotes" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="enableTwitchEmotes">Enable Twitch - Global emotes</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="enableTwitchEmotes" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hr"></div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="kappaFix">Kappa Fix</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="kappaFix" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="hr"></div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2 class="options-heading">Chat Options</h2>
|
||||||
|
<div class="options-description"></div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="setLiveChat">Make LIVE CHAT Default</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="setLiveChat" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="theaterModeFix">Improved Theater Mode</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="theaterModeFix" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="setTwitchColors">Twitch Styling</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="setTwitchColors" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="hideAuthorIcons">Hide Author Icons</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="hideAuthorIcons" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="showTimeStamp">Show TimeStamp</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="showTimeStamp" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="hideWelcomBanner">Hide Welcome Banner</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="hideWelcomBanner" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="alternateLineColor">Alternate Line Colors</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="alternateLineColor" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="option-cell"><label for="setAuthorColor">Colorful User Names</label></div>
|
||||||
|
<div class="option-cell"><input disabled type="checkbox" id="setAuthorColor" class="option-input"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hr"></div>
|
||||||
|
<h2 class="options-heading">Font Size</h2>
|
||||||
|
<span>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<div class="options-table">
|
||||||
|
<div class="option-row">
|
||||||
|
<div class="slidecontainer">
|
||||||
|
<input disabled type="range" min="1" max="50" value="13" class="slider option-input" id="textSizeSlider">
|
||||||
|
</div>
|
||||||
|
<div class="option-cell">
|
||||||
|
<label for="allowTextSlider"></label>
|
||||||
|
</div>
|
||||||
|
<div class="option-cell">
|
||||||
|
<input disabled type="checkbox" id="allowTextSlider" class="option-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<div id="save-status"> </div>
|
||||||
|
|
||||||
|
<div class="omega"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../options.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
96
src/html/welcome.html
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Welcome Page</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="success-overlay">
|
||||||
|
|
||||||
|
<div class="icon-container">
|
||||||
|
<i class="material-icons">done</i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="logo">
|
||||||
|
<img src="../assets/icons/icon512.png" alt="Live Chat Logo">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1><span class="fw-light">Welcome to the </span><span class="fw-bold">Chat</span></h1>
|
||||||
|
|
||||||
|
<p class="heading-note">This is still very beta</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="links_div">
|
||||||
|
<a class="links_bottom" href="https://discord.gg/pVNnTDA" title="Join our Discord Community">Join our Discord Community</a>
|
||||||
|
<a class="links_bottom" href="https://streamelements.com/wompmacho-5882/tip" title="Donations">Donations</a>
|
||||||
|
<a class="links_bottom" href="https://www.youtube.com/wompmacho" title="Youtube">Youtube</a>
|
||||||
|
<a class="links_bottom" href="mailto:wompmacho@gmail.com">Contact Me</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
Soon to come:
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Specific channel Emotes</li>
|
||||||
|
<li>Search for Emote Panel</li>
|
||||||
|
<li>Autocomplete for emote Selection</li>
|
||||||
|
<li><s>Theater Mode Fix for that quality Stream and Chat time</s></li>
|
||||||
|
<li>Moderation Options</li>
|
||||||
|
<li>User Profile Info</li>
|
||||||
|
<li>Other Styling Options</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2>How To:</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
There is still a lot of stuff I am working on. This is very Beta at the moment so
|
||||||
|
</p>
|
||||||
|
<h2>Use At Your Own Risk</h2>
|
||||||
|
<p>
|
||||||
|
This is a Chrome Extension for Youtube Live Streams, adding some Quality of Life improvements for the Chat.
|
||||||
|
Adds Top, Trending and Global (500ish) Emotes from popular sites. These Update with what is Trending.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Don't forget to pin this extension for easy access.
|
||||||
|
</p>
|
||||||
|
<img src="../assets/gif/pinYourExtension.gif" alt="pinYourExtension.gif">
|
||||||
|
<p>
|
||||||
|
Youtube Live is slow to load its pages currently.
|
||||||
|
Give the extension a moment while the page's iframes are loading.
|
||||||
|
Once you see the Emote Panel Icon you can open the Emote Selection Panel.
|
||||||
|
</p>
|
||||||
|
<img src="../assets/gif/emotesMenu.gif" alt="emotesMenu.gif">
|
||||||
|
<p>
|
||||||
|
Enhanced Theater Mode Is Now Available. Just click the check in the option panel and refresh your page.
|
||||||
|
</p>
|
||||||
|
<img style="height: 540px; width: 960px;" src="../assets/gif/theaterMode.gif" alt="theaterMode.gif">
|
||||||
|
<p>
|
||||||
|
Over around 500 Top and Trending Emotes are loaded from popular sites.
|
||||||
|
You can Enable/Disable them in the Options Menu.
|
||||||
|
</p>
|
||||||
|
<img src="../assets/gif/optionsMenu.gif" alt="optionsMenu.gif">
|
||||||
|
<p>
|
||||||
|
Can Also Click on UserNames To Autofill an @ Notification
|
||||||
|
</p>
|
||||||
|
<img src="../assets/gif/clickUserNamesToAtThem.gif" alt="clickUserNamesToAtThem.gif">
|
||||||
|
|
||||||
|
<h2>Use At Your Own Risk</h2>
|
||||||
|
|
||||||
|
<h1>Enjoy!</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../welcomePage.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
48
src/manifest.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
|
||||||
|
"name": "🔴 LIVE CHAT",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"description": "Enhances the YouTube Live Streaming experience with Emotes, Custom Styling and quality of life improvements.",
|
||||||
|
"icons": {
|
||||||
|
"48": "assets/icons/icon48.png",
|
||||||
|
"128": "assets/icons/icon128.png",
|
||||||
|
"512": "assets/icons/icon512.png"
|
||||||
|
},
|
||||||
|
|
||||||
|
"permissions": [
|
||||||
|
"storage",
|
||||||
|
"background"
|
||||||
|
],
|
||||||
|
|
||||||
|
"options_ui": {
|
||||||
|
"page": "html/options.html",
|
||||||
|
"chrome_style": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"browser_action": {
|
||||||
|
"default_popup": "html/options.html",
|
||||||
|
"chrome_style": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"background.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": [
|
||||||
|
"*://*.youtube.com/*"
|
||||||
|
],
|
||||||
|
"js": [ "content.js" ],
|
||||||
|
"run_at": "document_end",
|
||||||
|
"all_frames": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"web_accessible_resources": [
|
||||||
|
"assets/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
155
src/options.js
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
|
||||||
|
import './stylus/options.styl';
|
||||||
|
import dateFormat from 'date-fns/format';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import PersistentSyncStorage from './helpers/PersistentSyncStorage';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// hides element after short timeout
|
||||||
|
const hideDebounce = debounce(ele => {
|
||||||
|
ele.classList.remove('show');
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// little popup/fade save status message
|
||||||
|
const setSavingStatus = (status) => {
|
||||||
|
const SaveStatusEle = document.getElementById('save-status');
|
||||||
|
|
||||||
|
switch(status) {
|
||||||
|
case 'saving':
|
||||||
|
SaveStatusEle.innerHTML = 'Saving ...';
|
||||||
|
break;
|
||||||
|
case 'saved':
|
||||||
|
SaveStatusEle.innerHTML = 'Saved';
|
||||||
|
hideDebounce(SaveStatusEle);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SaveStatusEle.innerHTML = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveStatusEle.classList.add('show');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
var textSizeSlider = document.getElementById("textSizeSlider");
|
||||||
|
var allowTextSlider = document.getElementById("allowTextSlider");
|
||||||
|
|
||||||
|
var sliderValue;
|
||||||
|
textSizeSlider.oninput = function(){
|
||||||
|
sliderValue = textSizeSlider.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
allowTextSlider.oninput = function(){
|
||||||
|
|
||||||
|
if(allowTextSlider.checked == true){
|
||||||
|
textSizeSlider.disabled = false;
|
||||||
|
}else {
|
||||||
|
textSizeSlider.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const optionOnChange = (input) => {
|
||||||
|
|
||||||
|
var inputValueKey;
|
||||||
|
|
||||||
|
if(input.id === 'textSizeSlider'){
|
||||||
|
inputValueKey = input.value;
|
||||||
|
if(PersistentSyncStorage.data.options.hasOwnProperty(input.id)) {
|
||||||
|
inputValueKey = PersistentSyncStorage.data.options[input.id];
|
||||||
|
textSizeSlider.value = inputValueKey;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
inputValueKey = 'checked';
|
||||||
|
if(PersistentSyncStorage.data.options.hasOwnProperty(input.id)) {
|
||||||
|
input[inputValueKey] = PersistentSyncStorage.data.options[input.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventType = 'change';
|
||||||
|
|
||||||
|
const onChange = (() => {
|
||||||
|
const saveOption = () => {
|
||||||
|
|
||||||
|
setSavingStatus('saving');
|
||||||
|
|
||||||
|
// [input.id]: inputValueKey | number vs true or false statement| [input.id]: input[inputValueKey]
|
||||||
|
if(input.id === 'textSizeSlider'){
|
||||||
|
|
||||||
|
inputValueKey = sliderValue;
|
||||||
|
|
||||||
|
const updatedOptions = Object.assign({}, PersistentSyncStorage.data.options, {
|
||||||
|
[input.id]: inputValueKey
|
||||||
|
});
|
||||||
|
|
||||||
|
PersistentSyncStorage.set({ options: updatedOptions })
|
||||||
|
.then(() => {
|
||||||
|
setSavingStatus('saved');
|
||||||
|
});
|
||||||
|
|
||||||
|
}else{
|
||||||
|
const updatedOptions = Object.assign({}, PersistentSyncStorage.data.options, {
|
||||||
|
[input.id]: input[inputValueKey]
|
||||||
|
});
|
||||||
|
|
||||||
|
PersistentSyncStorage.set({ options: updatedOptions })
|
||||||
|
.then(() => {
|
||||||
|
setSavingStatus('saved');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return saveOption;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return onChange;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Executed code
|
||||||
|
const OptionInputs = document.querySelectorAll('.option-input');
|
||||||
|
|
||||||
|
PersistentSyncStorage.on('ready', () => {
|
||||||
|
|
||||||
|
OptionInputs.forEach((input) => {
|
||||||
|
const inputOnChange = optionOnChange(input);
|
||||||
|
input.addEventListener('change', inputOnChange);
|
||||||
|
|
||||||
|
|
||||||
|
switch (input.id) {
|
||||||
|
case 'allowTextSlider':
|
||||||
|
input.removeAttribute('disabled');
|
||||||
|
|
||||||
|
|
||||||
|
if(PersistentSyncStorage.data.options.allowTextSlider == true){
|
||||||
|
textSizeSlider.disabled = false;
|
||||||
|
}else{
|
||||||
|
textSizeSlider.disabled = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'theaterModeFix':
|
||||||
|
// do nothing, stay disabled
|
||||||
|
input.removeAttribute('disabled');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'textSizeSlider' :
|
||||||
|
// do nothing
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
input.removeAttribute('disabled');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var infoButton = document.getElementById('infoButton');
|
||||||
|
infoButton.addEventListener('click', function(){
|
||||||
|
chrome.tabs.create({ url: './html/welcome.html' });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
73
src/stylus/_chatColors.styl
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
colors = {
|
||||||
|
'A': #f44336,
|
||||||
|
'B': #e91e63,
|
||||||
|
'C': #9c27b0,
|
||||||
|
'D': #673ab7,
|
||||||
|
'E': #536dfe,
|
||||||
|
'F': #2196f3,
|
||||||
|
'G': #03a9f4,
|
||||||
|
'H': #00bcd4,
|
||||||
|
'I': #009688,
|
||||||
|
'J': #4caf50,
|
||||||
|
'K': #8bc34a,
|
||||||
|
'L': #cddc39,
|
||||||
|
'M': #ffeb3b,
|
||||||
|
'N': #ffc107,
|
||||||
|
'O': #ff9800,
|
||||||
|
'P': #ff5722,
|
||||||
|
'Q': #f44336,
|
||||||
|
'R': #e91e63,
|
||||||
|
'S': #9c27b0,
|
||||||
|
'T': #673ab7,
|
||||||
|
'U': #536dfe,
|
||||||
|
'V': #2196f3,
|
||||||
|
'W': #03a9f4,
|
||||||
|
'X': #00bcd4,
|
||||||
|
'Y': #009688,
|
||||||
|
'Z': #4caf50,
|
||||||
|
|
||||||
|
'a': #8bc34a,
|
||||||
|
'b': #cddc39,
|
||||||
|
'c': #ffeb3b,
|
||||||
|
'd': #ffc107,
|
||||||
|
'e': #ff9800,
|
||||||
|
'f': #ff5722,
|
||||||
|
'g': #f44336,
|
||||||
|
'h': #e91e63,
|
||||||
|
'i': #9c27b0,
|
||||||
|
'j': #673ab7,
|
||||||
|
'k': #536dfe,
|
||||||
|
'l': #2196f3,
|
||||||
|
'm': #03a9f4,
|
||||||
|
'n': #00bcd4,
|
||||||
|
'o': #009688,
|
||||||
|
'p': #4caf50,
|
||||||
|
'q': #8bc34a,
|
||||||
|
'r': #cddc39,
|
||||||
|
's': #ffeb3b,
|
||||||
|
't': #ffc107,
|
||||||
|
'u': #ff9800,
|
||||||
|
'v': #ff5722,
|
||||||
|
'w': #f44336,
|
||||||
|
'x': #e91e63,
|
||||||
|
'y': #9c27b0,
|
||||||
|
'z': #673ab7,
|
||||||
|
|
||||||
|
'0': #536dfe,
|
||||||
|
'1': #2196f3,
|
||||||
|
'2': #03a9f4,
|
||||||
|
'3': #00bcd4,
|
||||||
|
'4': #009688,
|
||||||
|
'5': #4caf50,
|
||||||
|
'6': #8bc34a,
|
||||||
|
'7': #cddc39,
|
||||||
|
'8': #ffeb3b,
|
||||||
|
'9': #ffc107,
|
||||||
|
'-': #ff9800,
|
||||||
|
'_': #ff5722,
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, col in colors
|
||||||
|
.chat-color-{id} #content #author-name
|
||||||
|
color: col !important
|
||||||
|
|
||||||
223
src/stylus/content.styl
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
@import '_chatColors.styl'
|
||||||
|
|
||||||
|
.item-offset
|
||||||
|
//height: 100%
|
||||||
|
|
||||||
|
#items
|
||||||
|
// Chat user avatar
|
||||||
|
img.yt-img-shadow
|
||||||
|
//height: inherit;
|
||||||
|
//width: inherit;
|
||||||
|
//align-content: center;
|
||||||
|
//display: initial;
|
||||||
|
//border-radius: inherit;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Owner chat messages
|
||||||
|
.yt-live-chat-item-list-renderer[author-type="owner"]
|
||||||
|
//background: rgba(#fff, 0.1);
|
||||||
|
#content #author-name
|
||||||
|
//text-shadow: #ffd600 0px 0px 10px
|
||||||
|
|
||||||
|
// Chat user menu button (3 dot menu)
|
||||||
|
#menu #menu-button.yt-live-chat-text-message-renderer
|
||||||
|
//padding: 5px !important
|
||||||
|
//width: 28px !important
|
||||||
|
//height: 28px !important
|
||||||
|
|
||||||
|
|
||||||
|
// Chat user name
|
||||||
|
#author-name.yt-live-chat-text-message-renderer
|
||||||
|
&:after
|
||||||
|
//content: ' :'
|
||||||
|
//color: #fff
|
||||||
|
|
||||||
|
|
||||||
|
.AuthorFix
|
||||||
|
#author-photo.yt-live-chat-text-message-renderer
|
||||||
|
img.yt-img-shadow
|
||||||
|
display: inline-block
|
||||||
|
vertical-align: middle
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 1.5em;
|
||||||
|
width: 1.5em;
|
||||||
|
vertical-align: sub;
|
||||||
|
#content
|
||||||
|
display: inline-block
|
||||||
|
vertical-align: middle
|
||||||
|
#timestamp.yt-live-chat-text-message-renderer
|
||||||
|
font-size: 1em
|
||||||
|
display: inline-block
|
||||||
|
vertical-align: middle
|
||||||
|
|
||||||
|
.showTimeStamp
|
||||||
|
#timestamp.yt-live-chat-text-message-renderer
|
||||||
|
display: inline-block
|
||||||
|
|
||||||
|
|
||||||
|
.Emote
|
||||||
|
img
|
||||||
|
height: 1.75em
|
||||||
|
align-self: center
|
||||||
|
vertical-align: sub;
|
||||||
|
display:inline-block
|
||||||
|
vertical-align: middle
|
||||||
|
|
||||||
|
emote_div:hover
|
||||||
|
background-size: 100% //100%
|
||||||
|
background-color: rgba(255, 255, 255, .6)
|
||||||
|
|
||||||
|
.hideElement
|
||||||
|
display: none !important
|
||||||
|
|
||||||
|
.set-background-color-one
|
||||||
|
background-color: #303030 !important
|
||||||
|
|
||||||
|
.set-background-color-two
|
||||||
|
background-color: transparent !important
|
||||||
|
|
||||||
|
.setTwitchColors
|
||||||
|
text-shadow: 0 0 1px #000, 0 0 2px #000 !important
|
||||||
|
background: #18181b !important
|
||||||
|
font-family: 'Roboto' !important
|
||||||
|
font-size: 1.3rem !important
|
||||||
|
line-height: 1.5em !important
|
||||||
|
color: #FAFAFA !important
|
||||||
|
#timestamp.yt-live-chat-text-message-renderer
|
||||||
|
display: none
|
||||||
|
yt-live-chat-author-chip[is-highlighted] #author-name.owner.yt-live-chat-author-chip, #author-name.owner.yt-live-chat-author-chip
|
||||||
|
background-color: transparent
|
||||||
|
color: green
|
||||||
|
yt-live-chat-author-chip[is-highlighted] #author-name.yt-live-chat-author-chip
|
||||||
|
background-color: transparent
|
||||||
|
|
||||||
|
.emoteDivider
|
||||||
|
width: 60vw
|
||||||
|
border: 2px solid #d3d3d3;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 2%;
|
||||||
|
margin-bottom: 2%;
|
||||||
|
margin-left: auto
|
||||||
|
margin-right: auto
|
||||||
|
|
||||||
|
.emotePopUpText
|
||||||
|
margin-bottom: 2%;
|
||||||
|
|
||||||
|
|
||||||
|
.popup
|
||||||
|
background-color: #202020
|
||||||
|
position: absolute;
|
||||||
|
top: 15%
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
margin-left: auto
|
||||||
|
margin-right: auto
|
||||||
|
height 70%
|
||||||
|
width: 75%
|
||||||
|
z-index: 999
|
||||||
|
text-align: center
|
||||||
|
border-radius: 5px
|
||||||
|
border: #303030 1px solid
|
||||||
|
font-size: 1em
|
||||||
|
overflow: hidden
|
||||||
|
overflow-y: scroll
|
||||||
|
padding: 1%;
|
||||||
|
padding-top: 2%;
|
||||||
|
|
||||||
|
.emoteButton
|
||||||
|
background-color:rgba(255, 255, 255, .1);
|
||||||
|
background-image: url("https://cdn.frankerfacez.com/emoticon/447885/4")
|
||||||
|
background-repeat: no-repeat
|
||||||
|
background-position: center
|
||||||
|
background-size: 80% 80%;
|
||||||
|
width: var(--yt-live-chat-32px-icon-button_-_width)
|
||||||
|
height: var(--yt-live-chat-32px-icon-button_-_height)
|
||||||
|
padding: var(--yt-live-chat-32px-icon-button_-_padding)
|
||||||
|
border-radius: 10px;
|
||||||
|
border: none
|
||||||
|
cursor: pointer
|
||||||
|
|
||||||
|
.emoteButton:hover
|
||||||
|
background-color:rgba(255, 255, 255, .8)
|
||||||
|
|
||||||
|
.emoteButton:focus
|
||||||
|
outline:0
|
||||||
|
|
||||||
|
.body_Fix
|
||||||
|
height: 100% !important
|
||||||
|
margin: 0 !important
|
||||||
|
overflow: hidden !important
|
||||||
|
|
||||||
|
.theater_wrapper_fix
|
||||||
|
padding: 2px;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
position: fixed
|
||||||
|
z-index: 900 !important
|
||||||
|
height: 100vh !important
|
||||||
|
width: 100vw !important
|
||||||
|
|
||||||
|
.movie_player_fix
|
||||||
|
height: 100vh;
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100vw - 25vw);
|
||||||
|
video
|
||||||
|
left: 0 !important
|
||||||
|
top: 0 !important
|
||||||
|
height: 100vh !important
|
||||||
|
width: calc(100vw - 25vw) !important
|
||||||
|
#html5-video-player
|
||||||
|
top: 0 !important
|
||||||
|
width: calc(100vw - 25vw) !important
|
||||||
|
.ytp-title
|
||||||
|
color: rgba(255, 255, 255, .8) !important
|
||||||
|
.ytp-title-channel
|
||||||
|
all: unset
|
||||||
|
.ytp-gradient-top
|
||||||
|
max-width: calc(100vw - 25vw) !important
|
||||||
|
.ytp-chrome-bottom
|
||||||
|
width: calc(100vw - 25vw) !important
|
||||||
|
left: 0 !important
|
||||||
|
.html5-endscreen
|
||||||
|
width: calc(100vw - 25vw) !important
|
||||||
|
.ytp-chapter-hover-container
|
||||||
|
width: calc(100vw - 25vw) !important
|
||||||
|
.ytp-gradient-bottom
|
||||||
|
width: calc(100vw - 25vw) !important
|
||||||
|
.ytp-iv-video-content
|
||||||
|
width: calc(100vw - 25vw) !important
|
||||||
|
left: 0 !important
|
||||||
|
.ytp-player-content.ytp-iv-player-content
|
||||||
|
width: calc(100vw - 25vw) !important
|
||||||
|
left: -12px !important
|
||||||
|
bottom: 10vh !important;
|
||||||
|
.ytp-upnext.ytp-player-content.ytp-upnext-autoplay-paused.ytp-suggestion-set
|
||||||
|
width: calc(100vw - 25vw) !important
|
||||||
|
left: 0 !important
|
||||||
|
.ytp-bezel-text-hide
|
||||||
|
width: calc(100vw - 25vw) !important
|
||||||
|
left: 0 !important
|
||||||
|
.ytp-spinner
|
||||||
|
left: 40% !important
|
||||||
|
.ytp-cued-thumbnail-overlay
|
||||||
|
width: calc(100vw - 25vw) !important
|
||||||
|
|
||||||
|
.info_contents_fix
|
||||||
|
z-index: 901 !important
|
||||||
|
height: 80px
|
||||||
|
top: 0
|
||||||
|
position: absolute !important
|
||||||
|
width: calc(100vw - 25vw) !important
|
||||||
|
|
||||||
|
ytd-video-primary-info-renderer
|
||||||
|
padding: 1rem
|
||||||
|
border-bottom: none
|
||||||
|
|
||||||
|
.chat_frame_fix
|
||||||
|
height: 100vh !important
|
||||||
|
width: calc(calc(100vw - 75vw) - 1px) !important;
|
||||||
|
position: absolute !important
|
||||||
|
right: 0px !important
|
||||||
|
top: 0px !important
|
||||||
|
border: 1px solid #4e4e4e
|
||||||
|
|
||||||
76
src/stylus/options.styl
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
body
|
||||||
|
background-color: #1e1e1e
|
||||||
|
color: #ffffff
|
||||||
|
|
||||||
|
.section
|
||||||
|
&:nth-child(1)
|
||||||
|
.options-heading
|
||||||
|
margin: 0
|
||||||
|
|
||||||
|
.options-heading
|
||||||
|
margin: 10px 0 0 0
|
||||||
|
|
||||||
|
.options-description
|
||||||
|
margin: 0 0 10px 0
|
||||||
|
opacity: 0.8
|
||||||
|
|
||||||
|
table
|
||||||
|
border: none
|
||||||
|
border-spacing: 0
|
||||||
|
width: 100%
|
||||||
|
tr td
|
||||||
|
padding: 0
|
||||||
|
&:nth-child(1)
|
||||||
|
width: 80%
|
||||||
|
&:nth-child(2)
|
||||||
|
width: 20%
|
||||||
|
text-align: right
|
||||||
|
|
||||||
|
.hr
|
||||||
|
background-color: #d1d1d1
|
||||||
|
height: 1px
|
||||||
|
margin: 10px 0
|
||||||
|
|
||||||
|
#save-status
|
||||||
|
text-align: center
|
||||||
|
opacity: 0
|
||||||
|
transition: all 1s linear
|
||||||
|
&.show
|
||||||
|
opacity: 1
|
||||||
|
transition: none
|
||||||
|
|
||||||
|
.options-table
|
||||||
|
.option-row
|
||||||
|
display:flex
|
||||||
|
flex-direction: row
|
||||||
|
align-items: space-between
|
||||||
|
justify-content: space-between
|
||||||
|
.option-cell
|
||||||
|
padding-right: 5px
|
||||||
|
&:last-child
|
||||||
|
padding-right: 0px
|
||||||
|
|
||||||
|
.light-text
|
||||||
|
color: rgba(#000, 0.6)
|
||||||
|
|
||||||
|
input[type=checkbox][disabled]
|
||||||
|
outline: 1px solid red
|
||||||
|
cursor: not-allowed
|
||||||
|
opacity: .9
|
||||||
|
|
||||||
|
.info_button
|
||||||
|
display:inline-block
|
||||||
|
padding:0.3em 1.2em
|
||||||
|
margin:0 0.3em 0.3em 0
|
||||||
|
border-radius:2em
|
||||||
|
box-sizing: border-box
|
||||||
|
text-decoration:none
|
||||||
|
font-weight:300
|
||||||
|
color:#1e1e1e
|
||||||
|
text-align:center
|
||||||
|
margin-left: 150px
|
||||||
|
transition: all 0.2s
|
||||||
|
|
||||||
|
.info_button:hover
|
||||||
|
color: red
|
||||||
|
|
||||||
146
src/stylus/setupPage.styl
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,700|Material+Icons")
|
||||||
|
|
||||||
|
body
|
||||||
|
font-family: 'Roboto', Arial, sans-serif
|
||||||
|
font-size: 16px
|
||||||
|
background: #222
|
||||||
|
color: #fff
|
||||||
|
margin: 25px
|
||||||
|
|
||||||
|
font-weights = {
|
||||||
|
light: 300,
|
||||||
|
regular: 400,
|
||||||
|
bold: 700
|
||||||
|
}
|
||||||
|
for fw, w in font-weights
|
||||||
|
.fw-{fw}
|
||||||
|
font-weight: w
|
||||||
|
|
||||||
|
.success-overlay
|
||||||
|
visibility: hidden
|
||||||
|
opacity: 0
|
||||||
|
position: fixed
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
z-index: 10
|
||||||
|
background: rgba(#191919, 0.95)
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
height: 100%
|
||||||
|
width: 100%
|
||||||
|
transition: visibility 0s, opacity 200ms ease
|
||||||
|
&.show
|
||||||
|
visibility: visible
|
||||||
|
opacity: 1
|
||||||
|
.material-icons
|
||||||
|
font-size: 20em
|
||||||
|
opacity: 0
|
||||||
|
&.show
|
||||||
|
opacity: 1
|
||||||
|
animation: jackInTheBox 700ms 1
|
||||||
|
.success-message
|
||||||
|
font-size: 3em
|
||||||
|
.close-message
|
||||||
|
font-size: 0.7em
|
||||||
|
opacity: 0.7
|
||||||
|
|
||||||
|
.container
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
align-items: center
|
||||||
|
h1
|
||||||
|
margin: 0
|
||||||
|
font-size: 3em
|
||||||
|
letter-spacing: 3px
|
||||||
|
.heading-note
|
||||||
|
font-size: 18px
|
||||||
|
color: #bbb
|
||||||
|
.option
|
||||||
|
text-align: center
|
||||||
|
.option-note
|
||||||
|
font-size: 12px
|
||||||
|
font-style: italic
|
||||||
|
color: #999
|
||||||
|
.complete-setup-button
|
||||||
|
background: #333
|
||||||
|
display: flex
|
||||||
|
justify-content: center
|
||||||
|
align-items: center
|
||||||
|
padding: 10px
|
||||||
|
cursor: pointer
|
||||||
|
text-transform: uppercase
|
||||||
|
border-radius: 3px
|
||||||
|
box-shadow: 0 2px 5px 0 rgba(#000, 0.7)
|
||||||
|
transition: background 50ms ease
|
||||||
|
&:hover
|
||||||
|
background: #404040
|
||||||
|
.material-icons
|
||||||
|
margin-right: 7px
|
||||||
|
|
||||||
|
|
||||||
|
.links_bottom
|
||||||
|
display: inline-block;
|
||||||
|
margin: 1%
|
||||||
|
|
||||||
|
|
||||||
|
a
|
||||||
|
background-color: #252526;
|
||||||
|
color: white;
|
||||||
|
padding-top: 1em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
border-collapse: collapse;
|
||||||
|
text-align: center
|
||||||
|
border: 1px solid #4e4e4e
|
||||||
|
|
||||||
|
a:hover
|
||||||
|
background-color: #d3d3d3;
|
||||||
|
color: black
|
||||||
|
|
||||||
|
|
||||||
|
a:active
|
||||||
|
box-shadow: none;
|
||||||
|
top: 5px;
|
||||||
|
|
||||||
|
.links_div
|
||||||
|
width: 20vw
|
||||||
|
|
||||||
|
p
|
||||||
|
margin: 2%
|
||||||
|
max-width: 30vw
|
||||||
|
|
||||||
|
h2
|
||||||
|
margin: 2%
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 2%
|
||||||
|
width: 60vw
|
||||||
|
border: 3px solid #d3d3d3;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// jackInTheBox from animate.css https://github.com/daneden/animate.css
|
||||||
|
@keyframes jackInTheBox
|
||||||
|
from
|
||||||
|
opacity: 0
|
||||||
|
transform: scale(0.1) rotate(60deg)
|
||||||
|
transform-origin: center center
|
||||||
|
|
||||||
|
50%
|
||||||
|
transform: rotate(-20deg)
|
||||||
|
|
||||||
|
70%
|
||||||
|
opacity: 1
|
||||||
|
transform: rotate(6deg)
|
||||||
|
|
||||||
|
to
|
||||||
|
transform: scale(1)
|
||||||
|
|
||||||
35
src/utils/chrome/LocalStorage.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 wompmacho
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import Storage from './Storage';
|
||||||
|
|
||||||
|
class LocalStorage extends Storage {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.store = 'local';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LocalStorage;
|
||||||
115
src/utils/chrome/Notifications.js
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 wompmacho
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class Notifications {
|
||||||
|
|
||||||
|
create(notificationId = null, options) {
|
||||||
|
// notificationId is optional
|
||||||
|
if(typeof notificationId === 'object') {
|
||||||
|
options = notificationId;
|
||||||
|
notificationId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
// resolve args = notificationId:string
|
||||||
|
chrome.notifications.create(notificationId, options, res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update(notificationId, options) {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
// resolve args = wasUpdated:boolean
|
||||||
|
chrome.notifications.update(notificationId, options, res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear(notificationId) {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
// resolve args = wasCleared:boolean
|
||||||
|
chrome.notifications.clear(notificationId, res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll() {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
// resolve args = notifications:object
|
||||||
|
chrome.notifications.getAll(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getPermissionLevel() {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
// resolve args = level:PermissionLevel (https://developer.chrome.com/apps/notifications#type-PermissionLevel)
|
||||||
|
chrome.notifications.getPermissionLevel(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(event, notificationId = null, callback) {
|
||||||
|
// event = 'onClosed' | 'onClicked' | 'onButtonClicked' | 'onPermissionLevelChanged' | 'onShowSettings'
|
||||||
|
// notificationId is optional
|
||||||
|
if(typeof notificationId === 'function') {
|
||||||
|
callback = notificationId;
|
||||||
|
notificationId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(event === 'onPermissionLevelChanged' || event === 'onShowSettings') {
|
||||||
|
return this._nonNotificationIdListen(event, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.chrome.com/apps/notifications#events
|
||||||
|
*
|
||||||
|
* Resolve args (by event):
|
||||||
|
* onClosed = notificationId:string, byUser:boolean
|
||||||
|
* onClicked = notificationId:string
|
||||||
|
* onButtonClicked = notificationId:string, buttonIndex:integer
|
||||||
|
*
|
||||||
|
* onPermissionLevelChanged = level:PermissionLevel (https://developer.chrome.com/apps/notifications#type-PermissionLevel)
|
||||||
|
* onShowSettings = (none)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This callback relates only to those events that have notificationId arg
|
||||||
|
const ListenerCallback = (() => {
|
||||||
|
if(notificationId !== null) {
|
||||||
|
return (passedNotificationId, ...args) => {
|
||||||
|
if(notificationId === passedNotificationId) {
|
||||||
|
callback(passedNotificationId, ...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
chrome.notifications[event].addListener(ListenerCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
_nonNotificationIdListen(event, callback) {
|
||||||
|
chrome.notifications[event].addListener(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Notifications;
|
||||||
92
src/utils/chrome/Storage.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 wompmacho
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class Storage {
|
||||||
|
|
||||||
|
get(keys = null) {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
const returnSingle = typeof keys === 'string' || typeof keys === 'number';
|
||||||
|
// resolve args = items:object
|
||||||
|
chrome.storage[this.store].get(keys, (items) => {
|
||||||
|
if(returnSingle) {
|
||||||
|
res(items[keys]);
|
||||||
|
} else {
|
||||||
|
res(items);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getBytesInUse(keys = null) {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
// resolve args = bytesInUse:integer
|
||||||
|
chrome.storage[this.store].getBytesInUse(keys, res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
set(items) {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
// resolve args = (none)
|
||||||
|
chrome.storage[this.store].set(items, res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(keys) {
|
||||||
|
// resolve args = (none)
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
chrome.storage[this.store].remove(keys, res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
// resolve args = (none)
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
chrome.storage[this.store].clear(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
listen(item, onChange) {
|
||||||
|
if(typeof item === 'function') {
|
||||||
|
onChange = item;
|
||||||
|
item = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.storage.onChanged.addListener((changes, areaName) => {
|
||||||
|
if(areaName === this.store) {
|
||||||
|
if(item !== null) {
|
||||||
|
if(changes.hasOwnProperty(item)) {
|
||||||
|
const oldValue = changes[item].oldValue || null;
|
||||||
|
const newValue = changes[item].newValue || null;
|
||||||
|
onChange(oldValue, newValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onChange(changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Storage;
|
||||||
35
src/utils/chrome/SyncStorage.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 wompmacho
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import Storage from './Storage';
|
||||||
|
|
||||||
|
class SyncStorage extends Storage {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.store = 'sync';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SyncStorage;
|
||||||
37
src/utils/chrome/index.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 wompmacho
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import _LocalStorage from './LocalStorage';
|
||||||
|
import _SyncStorage from './SyncStorage';
|
||||||
|
import _Notifications from './Notifications';
|
||||||
|
|
||||||
|
// export default {
|
||||||
|
// LocalStorage: new _LocalStorage,
|
||||||
|
// SyncStorage: new _SyncStorage,
|
||||||
|
// Notifications: new _Notifications
|
||||||
|
// }
|
||||||
|
|
||||||
|
export const LocalStorage = new _LocalStorage;
|
||||||
|
export const SyncStorage = new _SyncStorage;
|
||||||
|
export const Notifications = new _Notifications;
|
||||||
71
src/welcomePage.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
|
||||||
|
import './stylus/setupPage.styl';
|
||||||
|
import PersistentSyncStorage from './helpers/PersistentSyncStorage';
|
||||||
|
|
||||||
|
|
||||||
|
// THIS IS A JS PAGE FOR SETUP.HTML
|
||||||
|
|
||||||
|
|
||||||
|
// --- Definitions ---
|
||||||
|
const completeButton = document.querySelector('.complete-setup-button');
|
||||||
|
const successOverlay = document.querySelector('.success-overlay');
|
||||||
|
const successIcon = successOverlay.querySelector('.material-icons');
|
||||||
|
const successCloseMessageCountdown = successOverlay.querySelector('.countdown');
|
||||||
|
|
||||||
|
const setupComplete = () => {
|
||||||
|
|
||||||
|
// successOverlay.classList.add('show');
|
||||||
|
|
||||||
|
// setTimeout(() => {
|
||||||
|
// successIcon.classList.add('show');
|
||||||
|
// }, 100);
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////////// Neat if I ever want to close a window
|
||||||
|
// let closeCountdown = 5; // seconds
|
||||||
|
// const closeTimeout = () => {
|
||||||
|
// successCloseMessageCountdown.innerHTML = closeCountdown;
|
||||||
|
// setTimeout(() => {
|
||||||
|
// if(closeCountdown > 1) {
|
||||||
|
// closeCountdown -= 1;
|
||||||
|
// closeTimeout();
|
||||||
|
// } else {
|
||||||
|
// chrome.tabs.getCurrent((tab) => {
|
||||||
|
// chrome.tabs.remove(tab.id);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }, 1000);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// successCloseMessageCountdown.innerHTML = closeCountdown;
|
||||||
|
// closeTimeout();
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Main ---
|
||||||
|
|
||||||
|
const main = () => {
|
||||||
|
|
||||||
|
PersistentSyncStorage.on('ready', () => {
|
||||||
|
if(!!PersistentSyncStorage.data.setupComplete === true) {
|
||||||
|
console.log('Setup is already complete, Should not be here');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let setupOutput = {};
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
name: 'setupComplete',
|
||||||
|
data: setupOutput
|
||||||
|
}, setupComplete);
|
||||||
|
|
||||||
|
console.log('Competed setup, sent message');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Executed ---
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
||||||
|
|
||||||
53
webpack.config.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// Packages
|
||||||
|
const webpack = require('webpack'),
|
||||||
|
path = require('path');
|
||||||
|
|
||||||
|
// Webpack Plugins
|
||||||
|
const CopyPlugin = require('copy-webpack-plugin');
|
||||||
|
|
||||||
|
// Paths
|
||||||
|
const srcPath = path.join(__dirname, 'src'),
|
||||||
|
distPath = path.join(__dirname, 'dev-build'),
|
||||||
|
node_modulesPath = path.join(__dirname, 'node_modules');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: 'development',
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
src: srcPath
|
||||||
|
}
|
||||||
|
},
|
||||||
|
context: srcPath,
|
||||||
|
entry: {
|
||||||
|
content: './content/',
|
||||||
|
background: './background/',
|
||||||
|
options: './options.js',
|
||||||
|
welcomePage: './welcomePage.js'
|
||||||
|
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: distPath,
|
||||||
|
filename: './[name].js'
|
||||||
|
},
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{ test: /\.styl$/, use: ['style-loader', 'css-loader', 'stylus-loader'], exclude: /node_modules/ }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
new CopyPlugin([
|
||||||
|
'manifest.json',
|
||||||
|
'html/**/*',
|
||||||
|
'assets/**/*'
|
||||||
|
], {
|
||||||
|
ignore: [
|
||||||
|
'**/*.psd'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
],
|
||||||
|
|
||||||
|
devtool: '#inline-cheap-source-map'
|
||||||
|
};
|
||||||
62
webpack.prod.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// Packages
|
||||||
|
const webpack = require('webpack'),
|
||||||
|
path = require('path');
|
||||||
|
|
||||||
|
// Webpack Plugins
|
||||||
|
const CopyPlugin = require('copy-webpack-plugin'),
|
||||||
|
UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||||
|
|
||||||
|
// Paths
|
||||||
|
const srcPath = path.join(__dirname, 'src'),
|
||||||
|
distPath = path.join(__dirname, 'build'),
|
||||||
|
node_modulesPath = path.join(__dirname, 'node_modules');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: 'production',
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
src: srcPath
|
||||||
|
}
|
||||||
|
},
|
||||||
|
context: srcPath,
|
||||||
|
entry: {
|
||||||
|
content: './content/',
|
||||||
|
background: './background/',
|
||||||
|
options: './options.js',
|
||||||
|
welcomePage: './welcomePage.js'
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: distPath,
|
||||||
|
filename: './[name].js'
|
||||||
|
},
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{ test: /\.styl$/, use: ['style-loader', 'css-loader', 'stylus-loader'], exclude: /node_modules/ }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
new CopyPlugin([
|
||||||
|
'manifest.json',
|
||||||
|
'html/**/*',
|
||||||
|
'assets/**/*',
|
||||||
|
], {
|
||||||
|
ignore: [
|
||||||
|
'**/*.psd'
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
|
||||||
|
new UglifyJsPlugin({
|
||||||
|
uglifyOptions: {
|
||||||
|
output: {
|
||||||
|
beautify: false,
|
||||||
|
comments: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
|
||||||
|
devtool: '#inline-cheap-source-map'
|
||||||
|
};
|
||||||