Compare commits

...

77 Commits
v0.1 ... v1.1

Author SHA1 Message Date
Timeraa
4f06d804e9 Updated Readme.md 2018-10-16 15:19:47 +02:00
Timeraa
fb87b201da Version bump! 2018-10-16 15:10:49 +02:00
Timeraa
a58090b334 Bug fix 2018-10-16 15:08:35 +02:00
Timeraa
0050bfe36f SoundCloud support 2018-10-16 14:58:40 +02:00
Timeraa
d40233cb1e Typo 2018-10-14 19:56:19 +02:00
Timeraa
3125f8e7d1 Updated Readme file 2018-10-14 19:53:18 +02:00
Timeraa
0646fb77f2 Twitch Support and small improvements in Netflix.js 2018-10-14 16:50:46 +02:00
Timeraa
c5a3a17a94 Netflix Support 2018-10-14 14:48:49 +02:00
Timeraa
e4b1880025 forgot the logo... 2018-10-14 12:51:24 +02:00
Timeraa
c0b74c1605 Small changes to Readme 2018-10-14 12:51:09 +02:00
Timeraa
96aed66af2 Presence fix when changing from YouTube to other service 2018-10-13 14:56:04 +02:00
Timeraa
65e23a90a1 Code organisation 2018-10-13 14:49:59 +02:00
Timeraa
f3405b5724 YT Music improvements, Port change + Netflix "semi-support" 2018-10-12 22:16:34 +02:00
Timeraa
5bef1e5663 code cleanup 2018-10-09 19:44:46 +02:00
Timeraa
5120cee05a Extension organisation + Connection info 2018-10-09 19:04:59 +02:00
Timeraa
895e593d02 syntax fix 2018-10-09 19:04:43 +02:00
Timeraa
32d8070119 Faster song/video recognition 2018-10-09 17:32:49 +02:00
Timeraa
46273ecbe5 Fixed decode error (Mac menubar) 2018-10-09 17:27:20 +02:00
Timeraa
4268099a06 Window organisation 2018-10-09 17:26:54 +02:00
Timeraa
921db2f6cb Updated Mac installer 2018-10-09 15:57:22 +02:00
Florian Metz
f640c45464 Shortened description, too long. 2018-10-07 20:14:39 +02:00
Florian Metz
d4dc7a32a4 Updated readme.md 2018-10-07 20:14:07 +02:00
Florian Metz
33e5171742 Final preparation for V1.0 2018-10-07 17:46:25 +02:00
Timeraa
ebb4857782 Fix for RPC not working with autostart 2018-10-07 15:08:03 +02:00
Timeraa
c47d253967 Final? preparations for V1.0 (Mac OS side) 2018-10-07 03:58:39 +02:00
Timeraa
c58e7d2ccf fixes 2018-10-05 18:35:19 +02:00
Timeraa
f3cad86f6f Bug fixes/squirrel stuffs 2018-10-05 18:31:06 +02:00
Timeraa
a23655de42 small changes. 2018-09-23 18:48:31 +02:00
Timeraa
b7b899ea40 Troubleshooting + organisation stuffs 2018-09-22 22:43:03 +02:00
Timeraa
89029b1bc3 Some improvements 2018-09-22 21:30:50 +02:00
Timeraa
9b4212e00c Added new Discord link to readme 2018-09-22 21:30:38 +02:00
Timeraa
6b158e1e23 Oops, uploaded extension 2018-09-22 21:30:27 +02:00
Timeraa
6cae350ef2 Version 1.0 (Dev-build) 2018-09-22 20:33:32 +02:00
Timeraa
3b83738208 Fixed Markdown/HTML errors 2018-09-18 16:15:04 +02:00
Florian Metz
6b894eadf7 Opera Installation
Added instructions for Opera.
2018-09-18 16:13:28 +02:00
MrAlex
13128b6a0f Opera tutorial 2018-09-17 15:38:03 +02:00
Florian Metz
4b84238fb4 Typo 🧐 2018-09-10 17:28:04 +02:00
Timeraa
7345ecde7b Wrong donation link language 2018-08-06 23:26:43 +02:00
Timeraa
8272cf0ade Donation link.... finally :P 2018-08-06 21:34:02 +02:00
Timeraa
cae3d41af0 Added Line break for Note: 2018-08-02 21:34:49 +02:00
Timeraa
54061018ea Removed useless line 2018-08-02 21:33:19 +02:00
Timeraa
0281b3c9f9 Readme should now be more useful. 2018-08-02 21:32:18 +02:00
Timeraa
70c8d3f837 Organisation, YTM rewrite semi-done 2018-07-31 14:53:17 +02:00
Timeraa
19dcd072c7 V1.0 handleYTM recode 2018-07-30 16:58:32 +02:00
Timeraa
4c5474cb0f V1.0 preparation 2018-07-30 02:14:33 +02:00
Timeraa
2c5baf6b87 Removed .vscode, oops :P 2018-07-24 21:56:45 +02:00
Timeraa
508c2fe5b6 Fix version 2018-07-24 19:49:03 +02:00
Timeraa
200bd1be74 fixed typo 2018-07-24 19:22:34 +02:00
Timeraa
29617de259 updated package.json 2018-07-24 19:21:08 +02:00
Timeraa
96f1a1153b Merge branch 'master' of https://github.com/Timeraa/YT-Presence 2018-07-24 19:20:23 +02:00
Timeraa
c931fd1737 More useful installation instructions 2018-07-24 19:20:01 +02:00
Florian Metz
8f8e9a538d Merge pull request #2 from Timeraa/beta
Release V0.3
2018-07-24 18:57:57 +02:00
Timeraa
8dbec207cb Version 0.3 2018-07-24 18:51:54 +02:00
Timeraa
4ca0fff9d9 Organisation 2018-07-24 17:07:14 +02:00
Timeraa
97378addf5 Organised Syntax 2018-07-24 17:02:08 +02:00
Timeraa
1888243853 Removed redundant code. 2018-07-24 17:01:50 +02:00
Timeraa
e0e61f7e7d Testing for new GUI 2018-07-21 14:12:59 +02:00
Florian Metz
d2b7054d80 Merge pull request #1 from Timeraa/add-license-1
Create LICENSE
2018-07-21 14:05:19 +02:00
Florian Metz
afbd0a3301 Create LICENSE 2018-07-21 14:05:05 +02:00
Timeraa
5a8c10ba82 - Update notice
* Descriptions
2018-07-21 13:51:16 +02:00
Florian Metz
c1b0aaec2d Added Update Note 2018-07-15 21:28:46 +02:00
Timeraa
3ac0b252ff Installation testing 2018-07-11 19:28:37 +02:00
Timeraa
f199c3051b Installer testing 2018-07-11 19:23:00 +02:00
Timeraa
aa575ee267 Installation tests 2018-07-11 19:10:00 +02:00
Timeraa
20c9120b3f removed reimport 2018-07-11 19:03:47 +02:00
Timeraa
8e17cff31c Change Installer 2018-07-11 18:36:35 +02:00
Timeraa
69641c0b93 Testing Installer 2018-07-11 16:36:40 +02:00
Timeraa
0a39d8622c Installer Testing 2018-07-11 16:10:02 +02:00
Timeraa
2a8f5c519b V0.2 2018-07-10 21:50:55 +02:00
Timeraa
b53a898f82 Organisation V0.2 2018-07-10 21:45:52 +02:00
Timeraa
45582751be YouTube Support 2018-07-10 21:45:23 +02:00
Florian Metz
819d238294 Added OS check to prevent Crash on non darwin 2018-07-08 21:32:08 +02:00
Timeraa
76dd0516cd Merge branch 'master' of https://github.com/Timeraa/YT-Presence 2018-07-08 05:40:35 +02:00
Timeraa
1535cbb20d Updated Readme.md 2018-07-08 05:39:58 +02:00
Florian Metz
69c6ac9fdd Update issue templates 2018-07-08 05:32:31 +02:00
Timeraa
2f9bc9a2bc Forgot to chance Version 2018-07-08 05:14:27 +02:00
Timeraa
6125523988 Update V0.1 2018-07-08 05:09:33 +02:00
68 changed files with 10296 additions and 2805 deletions

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,28 @@
---
name: Bug report
about: Create a report to help us improve
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1.
2.
3.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for YT Presence
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
node_modules
out
.vscode
dist

54
Extension/connect.css Normal file
View File

@@ -0,0 +1,54 @@
#ytp-connectinfo {
position: fixed;
top: -50px;
right: 0;
z-index: 10000;
width: 175px;
border-bottom-left-radius: 5px;
height: 50px;
background-color: rgba(255, 255, 255, 0.5);
user-select: none;
animation-name: slideIn;
animation-duration: 5s;
animation-timing-function: cubic-bezier(0.19, 1, 0.22, 1);
color: black;
}
#ytp-connectinfo * {
margin: 0;
padding: 0;
}
#ytp-connectinfo img {
float: left;
margin: 5px;
width: 40px;
height: 40px;
}
#ytp-connectinfo h1 {
font-size: 20px;
margin-top: 4px;
}
#ytp-connectinfo h2 {
font-size: 17px;
}
@keyframes slideIn {
0% {
top: -50px;
}
10% {
top: 0;
}
90% {
top: 0;
}
100% {
top: -50px;
}
}

BIN
Extension/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

49
Extension/manifest.json Normal file
View File

@@ -0,0 +1,49 @@
{
"name": "YT Presence",
"author": "Timeraa",
"version": "1.1",
"description": "YT Presence adds Discord Rich Presence integration, Media controls and much more to YouTube/YouTube Music, Twitch etc...",
"manifest_version": 2,
"icons": {
"2024": "icon.png"
},
"browser_action": {
"default_icon": {
"2024": "icon.png"
}
},
"content_scripts": [
{
"js": ["util/jquery-3.3.1.min.js", "util/socket.io-2.1.1.min.js"],
"css": ["connect.css"],
"matches": [
"https://www.youtube.com/*",
"https://music.youtube.com/*",
"https://www.netflix.com/*",
"https://soundcloud.com/*",
"https://www.twitch.tv/*"
]
},
{
"js": ["presences/YouTubeMusic.js"],
"matches": ["https://music.youtube.com/*"]
},
{
"js": ["presences/YouTube.js"],
"matches": ["https://www.youtube.com/*"]
},
{
"js": ["presences/Netflix.js"],
"matches": ["https://*.netflix.com/*"]
},
{
"js": ["presences/SoundCloud.js"],
"matches": ["https://*.soundcloud.com/*"]
},
{
"js": ["presences/Twitch.js"],
"matches": ["https://*.twitch.tv/*"]
}
],
"permissions": ["http://localhost:3000/", "tabs", "storage"]
}

View File

@@ -0,0 +1,137 @@
let playback = true,
eventType,
playbackNew
var lastPlaybackState = true
setInterval(() => {
if($('.VideoContainer div video')[0] != null && $('.VideoContainer div video')[0].paused && lastPlaybackState == true) {
handlePlayPause()
lastPlaybackState = false;
}
if($('.VideoContainer div video')[0] != null && !$('.VideoContainer div video')[0].paused && lastPlaybackState == false) {
handlePlayPause()
lastPlaybackState = true;
}
}, 500)
//* Create socket connection to application
var socket = io.connect('http://localhost:3020/');
//* Log when connected
socket.on('connect', function () {
console.log('YT Presence: %cConnected to Application', "color: green; font-weight: 700")
if(sessionStorage['ytpconnected'] == null || sessionStorage['ytpconnected'] == 'false') {
$('<div id="ytp-connectinfo"><img draggable="false" src="//github.com/Timeraa/YT-Presence/blob/master/icon.png?raw=true"><h1>YT Presence</h1><h2>Connected</h2></div>').appendTo('body')
setTimeout(() => {
$('#ytp-connectinfo').remove()
}, 5*1000)
sessionStorage['ytpconnected'] = 'true'
}
})
socket.on('disconnect', function() {
sessionStorage['ytpconnected'] = 'false'
$('<div id="ytp-connectinfo"><img draggable="false" src="//github.com/Timeraa/YT-Presence/blob/master/icon.png?raw=true"><h1>YT Presence</h1><h2>Disconnected</h2></div>').appendTo('body')
setTimeout(() => {
$('#ytp-connectinfo').remove()
}, 5*1000)
})
//* When we receive messages from the application
socket.on('mediaKeyHandler', function (data) {
//* Check if the data is for YTM & if music is running
//* Media control buttons
if (musicRunning) {
//* Switch cases for playback / Clicks corresponding buttons
switch (data.playback) {
case "pause":
$('.VideoContainer div video')[0].paused ? $('.VideoContainer div video')[0].play() : $('.VideoContainer div video')[0].pause()
updateData("playPauseVideo")
break
case "nextTrack":
$('.button-nfplayerNextEpisode').click()
//* Prevent playback from being paused again
playback = true
//* Send response back to application
updateData("nextVideo")
break
}
}
})
function handlePlayPause() {
//* Toggle playback variable
if (playback == true) playback = false; else playback = true;
//* Send status to application
updateData(playback ? "playing" : "paused")
}
function checkPlayChange() {
//* Correct playback if out of sync
if (playback == false) {
//* Check if playbutton on page matches variable
if ($('.ytp-play-button svg').prop("outerHTML") == playButton) {
//* Update playback variable
playback = true
//* Pause song
$('.ytp-play-button').click()
}
}
//* Set musicRunning variable to true if url has /watch or music title not empty
if (document.location.pathname.includes("/watch")) musicRunning = true; else musicRunning = false;
}
//* Start interval
window.onload = function () {
setInterval(updateData, 1000)
}
//* Create needed variables
let urlForVideo,
songTime,
calcDifference = []
function updateData(playbackChange = false) {
if (document.location.pathname.includes("/watch")) musicRunning = true; else musicRunning = false;
urlForVideo = document.location.href
if ($(".time-remaining__time").html() != "") {
let data
if (playbackChange != false) {
var eventType = 'playBackChange'
} else {
var eventType = 'updateData'
}
var endTime
if(musicRunning && $('.VideoContainer div video')[0] != undefined) {
var startTime = Date.now();
endTime = Math.floor(startTime / 1000) -
Math.floor($('.VideoContainer div video')[0].currentTime) +
Math.floor($('.VideoContainer div video')[0].duration);
data = {
nflix: {
url: urlForVideo,
seriesTitle: $('.ellipsize-text').children().html(),
season: $($('.ellipsize-text').children().get(1)).html(),
episodeTitle: $($('.ellipsize-text').children().get(2)).html(),
episodeCurrentTimeSeconds: Math.floor($('.VideoContainer div video')[0].currentTime),
episodeEndTimeSeconds: Math.floor($('.VideoContainer div video')[0].duration),
episodeCurrentTime: startTime,
episodeEndTime: endTime,
playback: $('.VideoContainer div video')[0].paused ? "paused" : "playing"
}
}
} else {
data = {
nflix: {
playback: false
}
}
}
if (socket.connected) socket.emit(eventType, data)
}
}

View File

@@ -0,0 +1,144 @@
let playback = true,
eventType,
playbackNew
var lastPlaybackState = true
setInterval(() => {
if($('.playControl').hasClass('playing') && lastPlaybackState == true) {
handlePlayPause()
lastPlaybackState = false;
}
if(!$('.playControl').hasClass('playing') && lastPlaybackState == false) {
handlePlayPause()
lastPlaybackState = true;
}
}, 500)
//* Create socket connection to application
var socket = io.connect('http://localhost:3020/');
//* Log when connected
socket.on('connect', function () {
console.log('YT Presence: %cConnected to Application', "color: green; font-weight: 700")
if(sessionStorage['ytpconnected'] == null || sessionStorage['ytpconnected'] == 'false') {
$('<div id="ytp-connectinfo"><img draggable="false" src="//github.com/Timeraa/YT-Presence/blob/master/icon.png?raw=true"><h1>YT Presence</h1><h2>Connected</h2></div>').appendTo('body')
setTimeout(() => {
$('#ytp-connectinfo').remove()
}, 5*1000)
sessionStorage['ytpconnected'] = 'true'
}
})
socket.on('disconnect', function() {
sessionStorage['ytpconnected'] = 'false'
$('<div id="ytp-connectinfo"><img draggable="false" src="//github.com/Timeraa/YT-Presence/blob/master/icon.png?raw=true"><h1>YT Presence</h1><h2>Disconnected</h2></div>').appendTo('body')
setTimeout(() => {
$('#ytp-connectinfo').remove()
}, 5*1000)
})
//* When we receive messages from the application
socket.on('mediaKeyHandler', function (data) {
//* Check if the data is for YTM & if music is running
//* Media control buttons
if ($('.playControl') != undefined) {
//* Switch cases for playback / Clicks corresponding buttons
switch (data.playback) {
case "pause":
$('.playControl').click()
updateData("playPauseVideo")
break
case "nextTrack":
$('.skipControl__next').click()
//* Prevent playback from being paused again
playback = true
//* Send response back to application
updateData("nextVideo")
break
case "previousTrack":
$('.skipControl__previous').click()
//* Prevent playback from being paused again
playback = true
//* Send response back to application
updateData("nextVideo")
break
}
}
})
function handlePlayPause() {
//* Toggle playback variable
if (playback == true) playback = false; else playback = true;
//* Send status to application
updateData(playback ? "playing" : "paused")
}
function checkPlayChange() {
//* Correct playback if out of sync
if (playback == false) {
//* Check if playbutton on page matches variable
if ($('.ytp-play-button svg').prop("outerHTML") == playButton) {
//* Update playback variable
playback = true
//* Pause song
$('.ytp-play-button').click()
}
}
}
//* Start interval
window.onload = function () {
setInterval(updateData, 1000)
}
//* Create needed variables
let urlForVideo,
songTime,
calcDifference = []
function updateData(playbackChange = false) {
urlForVideo = document.location.href
if ($(".time-remaining__time").html() != "") {
let data
if (playbackChange != false) {
var eventType = 'playBackChange'
} else {
var eventType = 'updateData'
}
var endTime
if($('.playbackSoundBadge__titleContextContainer') != undefined) {
var startTime = Date.now();
endTime = Math.floor(startTime/1000) -
getSeconds($('.playbackTimeline__timePassed').children().get(1).innerHTML) + getSeconds($('.playbackTimeline__duration').children().get(1).innerHTML);
data = {
scloud: {
url: urlForVideo,
songTitle: $('.playbackSoundBadge__titleLink').children().get(1).innerHTML,
songAuthor: $('.playbackSoundBadge__titleContextContainer').children().get(0).innerHTML,
songCurrentTimeSeconds: getSeconds($('.playbackTimeline__timePassed').children().get(1).innerHTML),
songEndTimeSeconds: getSeconds($('.playbackTimeline__duration').children().get(1).innerHTML),
songCurrentTime: startTime,
songEndTime: endTime,
playback: $('.playControl').hasClass('playing') ? "playing" : "paused"
}
}
} else {
data = {
scloud: {
playback: false
}
}
}
if (socket.connected) socket.emit(eventType, data)
}
}
function getSeconds(string) {
const a = string.split(":")
const seconds = +a[0] * 60 + +a[1]
return seconds
}

View File

@@ -0,0 +1,121 @@
let playback = true,
eventType,
playbackNew,
lastURL = null,
startTimeStamp
var lastPlaybackState = true
setInterval(() => {
if($('.player-video video')[0] != null && $('.player-video video')[0].paused && lastPlaybackState == true) {
handlePlayPause()
lastPlaybackState = false;
}
if($('.player-video video')[0] != null && !$('.player-video video')[0].paused && lastPlaybackState == false) {
handlePlayPause()
lastPlaybackState = true;
}
}, 500)
//* Create socket connection to application
var socket = io.connect('http://localhost:3020/');
//* Log when connected
socket.on('connect', function () {
console.log('YT Presence: %cConnected to Application', "color: green; font-weight: 700")
if(sessionStorage['ytpconnected'] == null || sessionStorage['ytpconnected'] == 'false') {
$('<div id="ytp-connectinfo"><img draggable="false" src="//github.com/Timeraa/YT-Presence/blob/master/icon.png?raw=true"><h1>YT Presence</h1><h2>Connected</h2></div>').appendTo('body')
setTimeout(() => {
$('#ytp-connectinfo').remove()
}, 5*1000)
sessionStorage['ytpconnected'] = 'true'
}
})
socket.on('disconnect', function() {
sessionStorage['ytpconnected'] = 'false'
$('<div id="ytp-connectinfo"><img draggable="false" src="//github.com/Timeraa/YT-Presence/blob/master/icon.png?raw=true"><h1>YT Presence</h1><h2>Disconnected</h2></div>').appendTo('body')
setTimeout(() => {
$('#ytp-connectinfo').remove()
}, 5*1000)
})
//* When we receive messages from the application
socket.on('mediaKeyHandler', function (data) {
//* Check if the data is for YTM & if music is running
//* Media control buttons
if ($('.player-video video')[0] != null) {
//* Switch cases for playback / Clicks corresponding buttons
switch (data.playback) {
case "pause":
$('.player-video video')[0].paused ? $('.qa-pause-play-button').click() : $('.qa-pause-play-button').click()
updateData("playPauseVideo")
break
}
}
})
function handlePlayPause() {
//* Toggle playback variable
if (playback == true) playback = false; else playback = true;
//* Send status to application
updateData(playback ? "playing" : "paused")
}
function checkPlayChange() {
//* Correct playback if out of sync
if (playback == false) {
//* Check if playbutton on page matches variable
if ($('.ytp-play-button svg').prop("outerHTML") == playButton) {
//* Update playback variable
playback = true
//* Pause song
$('.ytp-play-button').click()
}
}
}
//* Start interval
window.onload = function () {
setInterval(updateData, 1000)
}
//* Create needed variables
let urlForVideo,
songTime,
calcDifference = []
function updateData(playbackChange = false) {
urlForVideo = document.location.href
let data
if (playbackChange != false) {
var eventType = 'playBackChange'
} else {
var eventType = 'updateData'
}
if($('.player-video video')[0] != undefined && $('.tw-ellipsis.tw-mg-b-05').children().length > 0) {
if(urlForVideo != lastURL) {
lastURL = urlForVideo
startTimeStamp = Date.now()
}
data = {
twitch: {
url: urlForVideo,
streamTitle: $('.tw-ellipsis.tw-mg-b-05').children().get(0).innerHTML,
streamHost: $('.channel-header__user h5').html(),
watchingSince: startTimeStamp,
playback: $('.player-video video')[0].paused ? "paused" : "playing"
}
}
} else {
data = {
twitch: {
playback: false
}
}
}
if (socket.connected) socket.emit(eventType, data)
}

View File

@@ -0,0 +1,150 @@
var pauseButton = '<svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%"><use class="ytp-svg-shadow" xlink:href="#ytp-id-91"></use><path class="ytp-svg-fill" d="M 12,26 18.5,22 18.5,14 12,10 z M 18.5,22 25,18 25,18 18.5,14 z" id="ytp-id-91"></path></svg>'
var pauseButton = '<svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%"><use class="ytp-svg-shadow" xlink:href="#ytp-id-93"></use><path class="ytp-svg-fill" d="M 12,26 16,26 16,10 12,10 z M 21,26 25,26 25,10 21,10 z" id="ytp-id-93"></path></svg>'
let playback = true,
eventType,
playbackNew
//* Register listeners
$('.video-stream').click(handlePlayPause)
$('.ytp-play-button').click(handlePlayPause)
//* Create socket connection to application
var socket = io.connect('http://localhost:3020/');
//* Log when connected
socket.on('connect', function () {
console.log('YT Presence: %cConnected to Application', "color: green; font-weight: 700")
if(sessionStorage['ytpconnected'] == null || sessionStorage['ytpconnected'] == 'false') {
$('<div id="ytp-connectinfo"><img draggable="false" src="//github.com/Timeraa/YT-Presence/blob/master/icon.png?raw=true"><h1>YT Presence</h1><h2>Connected</h2></div>').appendTo('body')
setTimeout(() => {
//$('#ytp-connectinfo').remove()
}, 5*1000)
sessionStorage['ytpconnected'] = 'true'
}
})
socket.on('disconnect', function() {
sessionStorage['ytpconnected'] = 'false'
$('<div id="ytp-connectinfo"><img draggable="false" src="//github.com/Timeraa/YT-Presence/blob/master/icon.png?raw=true"><h1>YT Presence</h1><h2>Disconnected</h2></div>').appendTo('body')
setTimeout(() => {
$('#ytp-connectinfo').remove()
}, 5*1000)
})
//* When we receive messages from the application
socket.on('mediaKeyHandler', function (data) {
//* Check if the data is for YTM & if music is running
//* Media control buttons
if (musicRunning) {
//* Switch cases for playback / Clicks corresponding buttons
switch (data.playback) {
case "pause":
$('.video-stream').click()
updateData("playPauseVideo")
break
case "nextTrack":
$('.ytp-next-button')[0].click()
//* Prevent playback from being paused again
playback = true
//* Send response back to application
updateData("nextVideo")
break
case "previousTrack":
$('.ytp-prev-button')[0].click()
//* Send response back to application
updateData("previousVideo")
break
}
}
})
function handlePlayPause() {
//* Toggle playback variable
if (playback == true) playback = false; else playback = true;
//* Send status to application
updateData(playback ? "playing" : "paused")
}
function checkPlayChange() {
//* Correct playback if out of sync
if (playback == false) {
//* Check if playbutton on page matches variable
if ($('.ytp-play-button svg').prop("outerHTML") == playButton) {
//* Update playback variable
playback = true
//* Pause song
$('.ytp-play-button').click()
}
}
//* Set musicRunning variable to true if url has /watch or music title not empty
if (document.location.pathname.includes("/watch") || $(".title.style-scope.ytmusic-player-bar").html() != "") musicRunning = true; else musicRunning = false;
}
//* Start interval
window.onload = function () {
setInterval(updateData, 1000)
}
//* Create needed variables
let urlForVideo,
songTime,
calcDifference = []
function updateData(playbackChange = false) {
if (document.location.pathname.includes("/watch")) musicRunning = true; else musicRunning = false;
urlForVideo = document.location.href
if ($(".ytp-time-current").html() != " " && $('.video-stream')[0] != null) {
let data
if (playbackChange != false) {
var playbackNew = playbackChange
var eventType = 'playBackChange'
} else {
var playbackNew = playback ? "playing" : "paused"
var eventType = 'updateData'
}
var endTime
if(musicRunning) {
var startTime = Date.now();
endTime = Math.floor(startTime / 1000) -
Math.floor($('.video-stream')[0].currentTime) +
Math.floor($('.video-stream')[0].duration);
data = {
yt: {
url: urlForVideo,
videoTitle: $(".title.style-scope.ytd-video-primary-info-renderer")
.children().html(),
videoAuthor: $("#upload-info .style-scope .ytd-video-owner-renderer")
.contents()
.first()
.html(),
videoCurrentTimeSeconds: Math.floor($('.video-stream')[0].currentTime),
videoEndTimeSeconds: Math.floor($('.video-stream')[0].duration),
videoCurrentTime: startTime,
videoEndTime: endTime,
playback: playbackNew
}
}
} else {
if(document.location.pathname.startsWith('www.youtube.com')) console.log("NICE")
data = {
yt: {
playback: false
}
}
}
if (socket.connected && !isNaN($('.video-stream')[0].duration)) socket.emit(eventType, data)
}
}
function getSeconds(string) {
const a = string.split(":")
const seconds = +a[0] * 60 + +a[1]
return seconds
}

View File

@@ -0,0 +1,170 @@
//* Play Pause buttons
var playButton = '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope iron-icon" style="pointer-events: none; display: block; width: 100%; height: 100%;"><g class="style-scope iron-icon"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" class="style-scope iron-icon"></path></g></svg>'
var pauseButton = '<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false" class="style-scope iron-icon" style="pointer-events: none; display: block; width: 100%; height: 100%;"><g class="style-scope iron-icon"><path d="M8 5v14l11-7z" class="style-scope iron-icon"></path></g></svg>'
//* Create needed variables
let splitTime,
songCurrentTime,
songEndTime,
songAuthors = [],
playback = true,
eventType
//* Register listeners
$('.play-pause-button').click(handlePlayPause)
$('.ytmusic-player').click(handlePlayPause)
//* Start interval
$(document).ready(() => {
//* Check Play change
setInterval(checkPlayChange, 250)
//* Update data and send to application
setInterval(updateData, 1000)
})
//* Create socket connection to application
var socket = io.connect('http://localhost:3020/');
//* Log when connected
socket.on('connect', function () {
console.log('YT Presence: %cConnected to Application', "color: green; font-weight: 700")
if(sessionStorage['ytpconnected'] == null || sessionStorage['ytpconnected'] == 'false') {
$('<div id="ytp-connectinfo"><img draggable="false" src="//github.com/Timeraa/YT-Presence/blob/master/icon.png?raw=true"><h1>YT Presence</h1><h2>Connected</h2></div>').appendTo('body')
setTimeout(() => {
$('#ytp-connectinfo').remove()
}, 5*1000)
sessionStorage['ytpconnected'] = 'true'
}
})
socket.on('disconnect', function() {
sessionStorage['ytpconnected'] = 'false'
$('<div id="ytp-connectinfo"><img draggable="false" src="//github.com/Timeraa/YT-Presence/blob/master/icon.png?raw=true"><h1>YT Presence</h1><h2>Disconnected</h2></div>').appendTo('body')
setTimeout(() => {
$('#ytp-connectinfo').remove()
}, 5*1000)
})
socket.on('error', (err) => console.log(`Error while connecting... ${err}`))
//* When we receive messages from the application
socket.on('mediaKeyHandler', function (data) {
//* Check if the data is for YTM & if music is running
//* Media control buttons
if (musicRunning) {
//* Switch cases for playback / Clicks corresponding buttons
switch (data.playback) {
case "pause":
$('.play-pause-button').click()
updateData("playPauseTrack")
break
case "nextTrack":
$('.next-button').click()
//* Prevent playback from being paused again
playback = true
//* Send response back to application
updateData("nextTrack")
break
case "previousTrack":
$('.previous-button').click()
//* Send response back to application
updateData("previousTrack")
break
}
}
})
function handlePlayPause() {
//* Toggle playback variable
if (playback == true) playback = false; else playback = true;
//* Send status to application
updateData(playback ? "playing" : "paused")
}
function checkPlayChange() {
//* Correct playback if out of sync
if (playback == false) {
//* Check if playbutton on page matches variable
if ($(".play-pause-button svg").prop("outerHTML") == playButton) {
//* Update playback variable
playback = true
//* Pause song
$('.play-pause-button').click()
}
}
//* Set musicRunning variable to true if url has /watch or music title not empty
if (document.location.pathname.includes("/watch") || $(".title.style-scope.ytmusic-player-bar").html() != "") musicRunning = true; else musicRunning = false;
}
function updateData(playbackChange = false) {
//* Clear author array
songAuthors = []
if ($(".title.style-scope.ytmusic-player-bar").html() != "") {
//* Get song Time String (2:10 / 3:21)
//* Split to array ["2:10", "3:21"]
splitTime = $(".time-info.style-scope.ytmusic-player-bar").html().split(" / ", 2)
//* Convert to seconds
songCurrentTime = getSeconds(splitTime[0])
songEndTime = getSeconds(splitTime[1])
//* Get all authors
$(".byline.ytmusic-player-bar").contents().each(function (index, item) {
if (item.classList != undefined) {
if (item.classList.contains("yt-simple-endpoint") == true) {
songAuthors.push(item.innerHTML)
}
}
})
//* If no authors found in previous method use this
if (songAuthors.length == 0 || songAuthors.length == 1) {
//* Clear old list
songAuthors = []
songAuthors.push($(".byline.ytmusic-player-bar").contents().first().text())
}
var startTime = Date.now();
var endTime =
Math.floor(startTime / 1000) -
songCurrentTime +
songEndTime;
if (playbackChange != false) {
playbackNew = playbackChange
eventType = 'playBackChange'
} else {
playbackNew = playback ? "playing" : "paused"
eventType = 'updateData'
}
var data = {
ytm: {
songTitle: $(".title.style-scope.ytmusic-player-bar").html(),
songAuthors: songAuthors,
songCurrentTimeSeconds: songCurrentTime,
songCurrentTime: startTime,
songEndTimeSeconds: songEndTime,
songEndTime: endTime,
songCover: $(".image.style-scope.ytmusic-player-bar").attr("src"),
playback: playbackNew
}
}
} else {
var data = {
status: "keepAlive"
}
eventType = 'updateData'
}
if(socket.connected) socket.emit(eventType, data)
}
//* Used to extract seconds from Syntax
//* 1:39 => 99
function getSeconds(string) {
const s = string.split(":")
const seconds = +s[0] * 60 + +s[1]
return seconds
}

2
Extension/util/jquery-3.3.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

7
Extension/util/socket.io-2.1.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Florian Metz
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.

115
README.md Normal file
View File

@@ -0,0 +1,115 @@
## ![version](https://img.shields.io/badge/Version-1.1-brightgreen.svg?style=flat-square) [![Discord](https://img.shields.io/discord/493130730549805057.svg?style=flat-square)](https://discord.gg/Kw7WaYn)
# <img src="icon.png" width="20" draggable="false"><b> </b>YouTube Presence &middot; Watcha watching there? 👀
_YT Presence Features like **Discord Rich Presence** integration, **Media controls** to **YouTube**/**YouTube Music**, **Twitch**, **SoundCloud**, **Netflix**._
<a href="https://discord.gg/Kw7WaYn" title="Join our Discord!">
<img src="discord-logo.svg" width="200px" draggable="false">
</a>
# Table of contents
- [Features](#features)
- [Support](#support)
- [Installation](#installation)
- [Installing the Extension](#installing-the-extension)
- Chrome
- Opera
- [Installing the Application](#installing-the-application)
- Mac OS
- Windows
- [FAQ](#faq-·-frequently-asked-questions)
- Is this project up to date?
- Will this project stay free?
# Features
- Discord Rich Presence Integration
- Media control keys
- Automatically clears Presence after 1 minute of inactivity
- _More soon_
# Support
### **Operating Systems**
- Windows
- Mac OS
### **Browsers**
- Chrome
- Opera
# Installation
## Extension
<details>
<summary><b><u>Installing the Chrome extension</u></b> (Click to expand)</summary>
<h1>Chrome extension installation</h1>
<ol>
<li>Click <a href="https://chrome.google.com/webstore/detail/yt-presence/agjnjboanicjcpenljmaaigopkgdnihi">this</a> link</li>
</li>
<li>Click "add to Chrome"</li>
<li>Install the <a href="#installing-the-application">application</a></li>
</ol>
</details>
<details>
<summary><b><u>Installing the Opera extension</u></b> (Click to expand)</summary>
<h1>Opera extension installation</h1>
<ol>
<li>Download the latest version of the <a href="https://github.com/Timeraa/YT-Presence/releases/latest">extension</a>
</li>
<li>Extract the downloaded <b>.zip</b> file</li>
<li>Open Opera</li>
<li>Go to <a href="chrome://extensions/">chrome://extensions/</a></li>
<li>Drag and drop the extension's folder on the page<br>
<li>Load the extracted <b>Extension</b> folder</li>
<li>Install the <a href="#installing-the-application">application</a></li>
</ol>
</details>
## Application
<details>
<summary><b><u>Mac OS</u></b> (Click to expand)</summary>
<h1>Installation on Mac OS</h1>
<ol>
<li>Download the latest version of the <a href="https://github.com/Timeraa/YT-Presence/releases/latest">application</a>
</li>
<li>Open the downloaded <b>.dmg</b> file</li>
<li>Drag <b>YT Presence</b> Into your <b>Applications</b> Folder</li>
<li>Open your Launchpad</li>
<li>Open <b>YT Presence</b></li>
<li>Install Extension if not already</li>
</ol>
</details>
<details>
<summary><b><u>Windows</u></b> (Click to expand)</summary>
<h1>Installation on Windows</h1>
<ol>
<li>Download the latest installer from <a href="https://github.com/Timeraa/YT-Presence/releases/">here</a></li>
<li>Open the downloaded <b>.exe</b> installer</li>
<li>If SmartScreen comes up press more informations then press run anyways. (Its not a virus, i promise)</li>
<li>YouTube Presence should install itself and start automatically (You can tell by looking at the taskbar)</li>
<li>Install Extension if not already</li>
</ol>
</details>
<br>
# FAQ &middot; Frequently Asked Questions
> ## Is this project still up to date?<br>
- Yes! I use and work on this program and extension almost everyday!<br>There are currently no plans on discontinuing this Project.
> ## Will this project stay free?<br>
- Of course! Its open source! But i would highly appreciate a little bit of Money it helps me for my Future, like a **Driving license**. <br><br><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZU8Q766ACS2WS&lc=US">Donate here</a>
---
### YT Presence V1.1 &middot; By Timeraa

BIN
appIcon.icns Normal file

Binary file not shown.

BIN
appIcon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

11
createInstaller.js Normal file
View File

@@ -0,0 +1,11 @@
var electronInstaller = require('electron-winstaller');
resultPromise = electronInstaller.createWindowsInstaller({
appDirectory: './out/YT Presence-win32-x64',
outputDirectory: './dist/windows/',
exe: './YT Presence.exe',
iconUrl: 'https://raw.githubusercontent.com/Timeraa/YT-Presence/master/appIcon.ico',
noMsi: true
});
resultPromise.then(() => console.log("It worked!"), (e) => console.log(`No dice: ${e.message}`));

BIN
developerMode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

1
discord-logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 272.1"><style>.st0{fill:#7289DA;}</style><path class="st0" d="M142.8 120.1c-5.7 0-10.2 4.9-10.2 11s4.6 11 10.2 11c5.7 0 10.2-4.9 10.2-11s-4.6-11-10.2-11zM106.3 120.1c-5.7 0-10.2 4.9-10.2 11s4.6 11 10.2 11c5.7 0 10.2-4.9 10.2-11 .1-6.1-4.5-11-10.2-11z"/><path class="st0" d="M191.4 36.9h-134c-11.3 0-20.5 9.2-20.5 20.5v134c0 11.3 9.2 20.5 20.5 20.5h113.4l-5.3-18.3 12.8 11.8 12.1 11.1 21.6 18.7V57.4c-.1-11.3-9.3-20.5-20.6-20.5zm-38.6 129.5s-3.6-4.3-6.6-8c13.1-3.7 18.1-11.8 18.1-11.8-4.1 2.7-8 4.6-11.5 5.9-5 2.1-9.8 3.4-14.5 4.3-9.6 1.8-18.4 1.3-25.9-.1-5.7-1.1-10.6-2.6-14.7-4.3-2.3-.9-4.8-2-7.3-3.4-.3-.2-.6-.3-.9-.5-.2-.1-.3-.2-.4-.2-1.8-1-2.8-1.7-2.8-1.7s4.8 7.9 17.5 11.7c-3 3.8-6.7 8.2-6.7 8.2-22.1-.7-30.5-15.1-30.5-15.1 0-31.9 14.4-57.8 14.4-57.8 14.4-10.7 28-10.4 28-10.4l1 1.2c-18 5.1-26.2 13-26.2 13s2.2-1.2 5.9-2.8c10.7-4.7 19.2-5.9 22.7-6.3.6-.1 1.1-.2 1.7-.2 6.1-.8 13-1 20.2-.2 9.5 1.1 19.7 3.9 30.1 9.5 0 0-7.9-7.5-24.9-12.6l1.4-1.6s13.7-.3 28 10.4c0 0 14.4 25.9 14.4 57.8 0-.1-8.4 14.3-30.5 15zM303.8 79.7h-33.2V117l22.1 19.9v-36.2h11.8c7.5 0 11.2 3.6 11.2 9.4v27.7c0 5.8-3.5 9.7-11.2 9.7h-34v21.1h33.2c17.8.1 34.5-8.8 34.5-29.2v-29.8c.1-20.8-16.6-29.9-34.4-29.9zm174 59.7v-30.6c0-11 19.8-13.5 25.8-2.5l18.3-7.4c-7.2-15.8-20.3-20.4-31.2-20.4-17.8 0-35.4 10.3-35.4 30.3v30.6c0 20.2 17.6 30.3 35 30.3 11.2 0 24.6-5.5 32-19.9l-19.6-9c-4.8 12.3-24.9 9.3-24.9-1.4zM417.3 113c-6.9-1.5-11.5-4-11.8-8.3.4-10.3 16.3-10.7 25.6-.8l14.7-11.3c-9.2-11.2-19.6-14.2-30.3-14.2-16.3 0-32.1 9.2-32.1 26.6 0 16.9 13 26 27.3 28.2 7.3 1 15.4 3.9 15.2 8.9-.6 9.5-20.2 9-29.1-1.8l-14.2 13.3c8.3 10.7 19.6 16.1 30.2 16.1 16.3 0 34.4-9.4 35.1-26.6 1-21.7-14.8-27.2-30.6-30.1zm-67 55.5h22.4V79.7h-22.4v88.8zM728 79.7h-33.2V117l22.1 19.9v-36.2h11.8c7.5 0 11.2 3.6 11.2 9.4v27.7c0 5.8-3.5 9.7-11.2 9.7h-34v21.1H728c17.8.1 34.5-8.8 34.5-29.2v-29.8c0-20.8-16.7-29.9-34.5-29.9zm-162.9-1.2c-18.4 0-36.7 10-36.7 30.5v30.3c0 20.3 18.4 30.5 36.9 30.5 18.4 0 36.7-10.2 36.7-30.5V109c0-20.4-18.5-30.5-36.9-30.5zm14.4 60.8c0 6.4-7.2 9.7-14.3 9.7-7.2 0-14.4-3.1-14.4-9.7V109c0-6.5 7-10 14-10 7.3 0 14.7 3.1 14.7 10v30.3zM682.4 109c-.5-20.8-14.7-29.2-33-29.2h-35.5v88.8h22.7v-28.2h4l20.6 28.2h28L665 138.1c10.7-3.4 17.4-12.7 17.4-29.1zm-32.6 12h-13.2v-20.3h13.2c14.1 0 14.1 20.3 0 20.3z"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
loadUnpackedExtension.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

3832
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +1,24 @@
{
"name": "yt-presence",
"name": "yt_presence",
"productName": "YT Presence",
"version": "0.0.1",
"description": "Show your current YouTube Video / Song in your Discord profile.",
"main": "src/index.js",
"version": "1.0.0",
"description": "YT Presence adds cool Features to YouTube/YouTube Music. Like for example Discord Rich Presence integration, Media controls and much more.",
"main": "index.js",
"repository": "Timeraa/YT-Presence",
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"lint": "echo \"No linting configured\""
"start": "electron src/.",
"pkgmac": "electron-packager ./src/ --out=./out/ --asar --overwrite --icon=./appIcon.ico --osx-sign.identity='Florian Metz'",
"pkgwin": "electron-packager ./src/ --out=./out/ --overwrite --icon=./appIcon.ico",
"installer-mac": "npm run pkgmac && electron-installer-dmg ./out/YT\\ Presence-darwin-x64/YT\\ Presence.app YT\\ Presence\\ Installer --overwrite --icon=./appIcon.icns --out=./dist",
"installer-win": "npm run pkgwin && node createInstaller"
},
"keywords": [],
"author": "Timeraa",
"license": "MIT",
"config": {
"forge": {
"packagerConfig": {},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"name": "YT Presence"
}
},
{
"name": "@electron-forge/maker-zip",
"platforms": [
"darwin"
]
},
{
"name": "@electron-forge/maker-deb",
"config": {}
},
{
"name": "@electron-forge/maker-rpm",
"config": {}
}
]
}
},
"dependencies": {
"chalk": "^2.4.1",
"discord-rpc": "^3.0.0-beta.12",
"electron-squirrel-startup": "^1.0.0",
"express": "^4.16.3",
"html-entities": "^1.2.1",
"menubar": "^5.2.3",
"update-electron-app": "^1.3.0"
},
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.22",
"@electron-forge/maker-deb": "^6.0.0-beta.22",
"@electron-forge/maker-rpm": "^6.0.0-beta.22",
"@electron-forge/maker-squirrel": "^6.0.0-beta.22",
"@electron-forge/maker-zip": "^6.0.0-beta.22",
"electron": "2.0.4"
"electron-installer-dmg": "^1.0.0",
"electron-installer-windows": "^1.1.0",
"electron-packager": "^12.2.0",
"electron-winstaller": "^2.7.0"
}
}

BIN
src/assets/images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

BIN
src/assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View File

@@ -1,4 +1,9 @@
{
"yt_client_id": "463097721130188830",
"ytm_client_id": "463151177836658699"
"ytm_client_id": "463151177836658699",
"nflix_client_id": "499981204045430784",
"twitch_client_id": "501021996336021504",
"scloud_client_id": "501021185887436810",
"version": "1.1",
"devBuild": false
}

View File

@@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
Well hey there!!!
</body>
</html>

View File

@@ -1,61 +1,138 @@
const { app, BrowserWindow } = require('electron');
//* Handle Winstall
require('./util/handleWinstall')
//#region Define constants
//* Declare needed constants
const {app} = require('electron')
const AutoLaunch = require('auto-launch')
//* Require config
const config = require('./config.json');
const constants = require('./util/constants')
//* Require electron-config
const os = require('os')
//* Update constant in file
constants.platform = os.platform()
//* Require Update checker
const updater = require('./util/updateChecker')
//* Require Needed packages
const chalk = require("chalk")
//* Setup electron-config
const Config = require('electron-config');
//#endregion
let constants = require('./util/constants')
//* Required for Windows Push notifications
app.setAppUserModelId("eu.Timeraa.yt_presence")
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) { // eslint-disable-line global-require
app.quit();
global.UPDATEAVAIABLE = ""
global.VERSION = config.version
if(config.devBuild) {
global.VERSIONSTRING = VERSION + "-DEV"
} else {
global.VERSIONSTRING = VERSION
}
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
global.BROWSERCONNECTIONSTATE = "NOT_CONNECTED"
global.EXTENSIONSOCKET = null
const setup = () => {
console.log(constants.consolePrefix + chalk.green("Loading..."))
//* YTM global vars
global.CURRENTSONGTITLE = ""
global.CURRENTSONGAUTHORS = []
global.CURRENTSONGAUTHORSSTRING = ""
global.CURRENTSONGSTARTTIME = ""
global.CURRENTSONGSTARTTIMESECONDS = ""
global.CURRENTSONGENDTIME = ""
global.CONSOLEPREFIX = chalk.bold(chalk.bgHex('#db0918')(chalk.hex('#000000')(" Y") + chalk.hex('#ffffff')("T "))) + chalk.cyan(" Presence") + chalk.hex('#ffffff')(": ")
global.YTRPCREADY = false
global.YTMRPCREADY = false
global.NFLIXRPCREADY = false
global.TWITCHRPCREADY = false
global.SCLOUDRPCREADY = false
// Create the browser window.
constants.win = new BrowserWindow({
width: 800,
height: 600,
show: false
});
global.YTRPC = null
global.YTMRPC = null
global.NFLIXRPC = null
global.TWITCHRPC = null
global.SCLOUDRPC = null
global.TRAY = null
// and load the index.html of the app.
constants.win.loadURL(`file://${__dirname}/index.html`);
// Open the DevTools.
constants.win.webContents.openDevTools();
//* Clear console
process.stdout.write("\u001b[2J\u001b[0;0H");
// Emitted when the window is closed.
constants.win.on('closed', () => {
//constants.win = null;
});
//* Single Instance Check
var iShouldQuit = app.makeSingleInstance(() => {return true});
if(iShouldQuit){
console.log(CONSOLEPREFIX + chalk.red("App already running, closing current instance..."))
app.quit();
return;
}
const userSettings = new Config({
name: "userSettings"
});
//* Set default values for electon-config userSettings
if(userSettings.get('enabled') == undefined) userSettings.set('enabled', true)
if(userSettings.get('youTube') == undefined) userSettings.set('youTube', true)
if(userSettings.get('youTubeMusic') == undefined) userSettings.set('youTubeMusic', true)
if(userSettings.get('netflix') == undefined) userSettings.set('netflix', true)
if(userSettings.get('twitch') == undefined) userSettings.set('twitch', true)
if(userSettings.get('soundcloud') == undefined) userSettings.set('soundcloud', true)
if(userSettings.get('titleMenubar') == undefined) userSettings.set('titleMenubar', true)
if(userSettings.get('autoUpdateCheck') == undefined) userSettings.set('autoUpdateCheck', true)
//* Set dock Badge to version
if(constants.platform == "darwin") {
app.dock.setBadge("V" + VERSION)
}
console.log(CONSOLEPREFIX + chalk.yellow("Loading..."))
//* App ready
const appReady = () => {
//* Setup MenuBar
require('./tray/createTray').run()
//* Require shortcuts
require('./util/shortcutHandler')
//* Include PresenceHandler
require('./presenceHandler.js')
};
//* Automatically check for update
if(userSettings.get('autoUpdateCheck') == true)
updater.checkForUpdate(true)
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', setup);
// Quit when all windows are closed.
app.on('window-all-closed', () => {
//app.quit();
});
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
setup();
//* hide Dock icon when everything running
if(constants.platform == "darwin") {
app.dock.hide()
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.
if(userSettings.get('autoLaunch') == undefined || userSettings.get('autoLaunch') == true) {
userSettings.set('autoLaunch', true)
//* Add App to AutoLaunch
console.log(CONSOLEPREFIX + chalk.yellow("Adding App to autostart..."))
let autoLaunch = new AutoLaunch({
name: 'YT Presence',
path: app.getPath('exe'),
isHidden: true
});
require('./menubar/setup')
//* Enable AutoLaunch if disabled
autoLaunch.isEnabled().then(async (isEnabled) => {
if (!isEnabled) autoLaunch.enable();
console.log(CONSOLEPREFIX + chalk.green("Added App to autostart."))
})
//* Catch error
.catch(function(err) {
console.log(CONSOLEPREFIX + chalk.red("Error while adding App to autostart."))
})
}
}
//* Register Handler
app.on('ready', appReady);
//* Prevent default behaviour
app.on('window-all-closed', () => {});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,39 +0,0 @@
var MenuBar = require('menubar')
const {app, Menu, MenuItem} = require('electron')
var main = require("../index")
var constants = require("../util/constants")
console.log(process.cwd())
constants.menuBar = MenuBar({
tooltip: "YouTube Presence",
icon: "./src/menubar/icon.png"
})
constants.menuBar.on('ready', function() {
constants.menuBar.tray.setTitle(" Connecting...")
var menu = new Menu();
menu.append(new MenuItem({
label: "Show Panel",
click: function (menuItem, browserWindow, event) {
constants.win.show()
}
}))
menu.append(new MenuItem({
type: "separator"
}))
menu.append(new MenuItem({
label: "Check for Updates"
}))
menu.append(new MenuItem({
label: "About"
}))
menu.append(new MenuItem({
type: "separator"
}))
menu.append(new MenuItem({
label: "Quit",
role: "quit"
}))
constants.menuBar.tray.setContextMenu(menu)
})

5961
src/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
src/package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "yt_presence",
"productName": "YT Presence",
"version": "1.0.0",
"description": "YT Presence adds cool Features to YouTube/YouTube Music. Like for example Discord Rich Presence integration, Media controls and much more.",
"main": "index.js",
"repository": "Timeraa/YT-Presence",
"keywords": [],
"author": "Timeraa",
"license": "MIT",
"dependencies": {
"auto-launch": "^5.0.5",
"chalk": "^2.4.1",
"discord-rpc": "^3.0.1",
"electron-config": "^1.0.0",
"electron-squirrel-startup": "^1.0.0",
"express": "^4.16.3",
"html-entities": "^1.2.1",
"request": "^2.88.0",
"socket.io": "^2.1.1"
},
"devDependencies": {
"electron-prebuilt-compile": "3.0.2"
}
}

View File

@@ -1,49 +1,120 @@
var {webContents} = require('electron')
//* Require needed packages
const Entities = require('html-entities').AllHtmlEntities
const entities = new Entities()
const chalk = require("chalk")
const { yt_client_id, ytm_client_id } = require("./config")
const express = require("express")
var constants = require('./util/constants.js')
let YTM = require('./presences/handleYTM.js');
var extension = express();
var http = require('http')
var socketServer = http.createServer(extension);
var io = require('socket.io')(socketServer);
//* Create APP
const app = express()
//* Load Config
const Config = require('electron-config');
const userSettings = new Config({
name: "userSettings"
});
app.use(express.json());
//* Define needed variables
var keepAliveSwitch = 0,
lastKeepAliveSwitch = 0,
ytrpcused = false,
ytmrpcused = false,
nflixrpcused = false,
twitchrpcused = false,
scloudrpcused = false
let data
//* Keep alive check to automatically remove presence if browser not running/not using YT
setInterval(keepAliveCheck, 1000)
app.post("/", async (request, response) => {
data = request.body
if(data.connected === true) {
//* Check if presence is ready
if(constants.chromeConnected == false) {
constants.chromeConnected = true;
constants.menuBar.tray.setTitle(" Connected!")
console.log(constants.consolePrefix + chalk.green("Chrome client connected."))
setTimeout(function() {
constants.menuBar.tray.setTitle("")
}, 5*1000)
}
if(constants.chromeConnected == true && constants.presenceReady == true) {
if(serviceType(data.service) == "ytm") YTM.updatePresence(data)
}
}
return response.sendStatus(200);
})
function serviceType(service) {
switch(service) {
case "ytm":
return "ytm"
default:
return false
function keepAliveCheck() {
if (lastKeepAliveSwitch > keepAliveSwitch + 10) {
TRAY.setTitle("")
if (YTMRPCREADY) YTMRPC.clearActivity()
if (YTRPCREADY) YTRPC.clearActivity()
if (NFLIXRPCREADY) NFLIXRPC.clearActivity()
if (TWITCHRPCREADY) TWITCHRPC.clearActivity()
if (SCLOUDRPCREADY) SCLOUDRPC.clearActivity()
}
lastKeepAliveSwitch += 1
}
//* Listen on port 3000
app.listen(3000, () => console.log(constants.consolePrefix + chalk.green("is ready to use!")));
//* Listen on port 3020
socketServer.listen(3020, () => {
console.log(CONSOLEPREFIX + chalk.green("Listening on Port 3020"))
});
//* Socket connection event
io.on('connection', function (socket) {
global.EXTENSIONSOCKET = socket
BROWSERCONNECTIONSTATE = "CONNECTED"
socket.on('playBackChange', (data) => {
updatePresence(data, true)
})
socket.on('updateData', (data) => {
updatePresence(data)
})
})
//* Updates the presence with the incomming data
function updatePresence(data, force = false) {
lastKeepAliveSwitch = 0
if (!userSettings.get('titleMenubar')) constants.tray.setTitle("")
if (data.yt != undefined) {
ytrpcused = true
if (userSettings.get('youTube')) require('./presences/YouTube.js')(data, force); else if (YTRPCREADY) YTRPC.clearActivity()
} else if (data.ytm != undefined) {
ytmrpcused = true
if (userSettings.get('youTubeMusic')) require('./presences/YouTube_Music.js')(data, force); else if (YTMRPCREADY) YTMRPC.clearActivity()
} else if(data.nflix != undefined) {
nflixrpcused = true
if (userSettings.get('netflix')) require('./presences/Netflix.js')(data, force); else if (NFLIXRPCREADY) NFLIXRPC.clearActivity()
} else if(data.twitch != undefined) {
twitchrpcused = true
if (userSettings.get('twitch')) require('./presences/Twitch.js')(data, force); else if (TWITCHRPCREADY) TWITCHRPC.clearActivity()
} else if(data.scloud != undefined) {
scloudrpcused = true
if (userSettings.get('soundcloud')) require('./presences/SoundCloud.js')(data, force); else if(SCLOUDRPCREADY) SCLOUDRPC.clearActivity()
}
if (data.yt == undefined && YTRPCREADY) {
if (ytrpcused == true) {
ytrpcused = false
YTRPC.clearActivity()
TRAY.setTitle("")
}
}
if (data.ytm == undefined && YTMRPCREADY) {
if (ytmrpcused == true) {
ytmrpcused = false
YTMRPC.clearActivity()
TRAY.setTitle("")
}
}
if (data.nflix == undefined && NFLIXRPCREADY) {
if (nflixrpcused == true) {
nflixrpcused = false
NFLIXRPC.clearActivity()
TRAY.setTitle("")
}
}
if (data.twitch == undefined && TWITCHRPCREADY) {
if (twitchrpcused == true) {
twitchrpcused = false
TWITCHRPC.clearActivity()
TRAY.setTitle("")
}
}
if (data.scloud == undefined && SCLOUDRPCREADY) {
if (scloudrpcused == true) {
scloudrpcused = false
SCLOUDRPC.clearActivity()
TRAY.setTitle("")
}
}
}

93
src/presences/Netflix.js Normal file
View File

@@ -0,0 +1,93 @@
const DiscordRPC = require('discord-rpc')
const Entities = require("html-entities").AllHtmlEntities;
const entities = new Entities();
const { nflix_client_id } = require("../config.json");
const Config = require('electron-config');
const userSettings = new Config({
name: 'userSettings'
})
let lastEndTime = 0,
pauseRPCChange = 0,
lastVideoTitle = "",
videoTitle,
videoAuthor,
videoStartTime,
videoEndTime,
seriesTitle
tryLogin()
var retryRPCLogin = setInterval(tryLogin, 10 * 1000)
module.exports = (data, force) => {
if (force) {
if (data.nflix.playback == "paused")
updatePresence(seriesTitle, videoAuthor, "pause", "Paused")
else if (data.nflix.playback == "playing")
updatePresence(seriesTitle, videoAuthor, "play", "Watching", videoStartTime, videoEndTime)
} else {
updateData(data)
if ((data.nflix.playback == "playing" && NFLIXRPCREADY) && (videoEndTime != lastEndTime || videoTitle != lastVideoTitle)) {
lastVideoTitle = videoTitle
lastEndTime = videoEndTime
updatePresence(seriesTitle, videoAuthor, "play", "Watching", videoStartTime, videoEndTime)
}
//* Clear Presence after 1 minute if playback == pause
if(pauseRPCChange >= 60 && pauseRPCChange <= 60) NFLIXRPC.clearActivity()
if(data.nflix.playback == "paused") pauseRPCChange++; else pauseRPCChange = 0;
}
}
/**
* Try to login to RPC until connected
*/
async function tryLogin() {
NFLIXRPC = new DiscordRPC.Client({ transport: "ipc" });
NFLIXRPC.login({ clientId: nflix_client_id })
.catch(err => console.log(`${CONSOLEPREFIX}NFLIXRPC: ${err.message}`))
NFLIXRPC.on("ready", () => {
clearInterval(retryRPCLogin)
NFLIXRPCREADY = true
})
}
/**
* Update the Presence on Discord
* @param {String} details Details to show
* @param {String} state State to show
* @param {String} smallImageKey Small image name
* @param {String} smallImageText Small image text
* @param {Number} startTimestamp Start timestamp
* @param {Number} endTimestamp End timerstamp
*/
function updatePresence(details, state, smallImageKey, smallImageText, startTimestamp = null, endTimestamp = null) {
if(startTimestamp != null && userSettings.get('titleMenubar')) TRAY.setTitle(entities.decode(details)); else TRAY.setTitle("");
NFLIXRPC.setActivity({
details: entities.decode(details),
state: entities.decode(state),
largeImageKey: "nflix_lg",
largeImageText: "YT Presence " + VERSIONSTRING,
smallImageKey: smallImageKey,
smallImageText: smallImageText,
startTimestamp: startTimestamp,
endTimestamp: endTimestamp,
instance: true
})
}
/**
* Update the data used for the Presence
* @param {Object} data Data received from Extension
*/
function updateData(data) {
videoTitle = data.nflix.episodeTitle
videoAuthor = data.nflix.season + " - " + data.nflix.episodeTitle
videoStartTime = Math.floor(data.nflix.episodeCurrentTime / 1000);
videoEndTime = data.nflix.episodeEndTime;
seriesTitle = data.nflix.seriesTitle
}

View File

@@ -0,0 +1,91 @@
const DiscordRPC = require('discord-rpc')
const Entities = require("html-entities").AllHtmlEntities;
const entities = new Entities();
const { scloud_client_id } = require("../config.json");
const Config = require('electron-config');
const userSettings = new Config({
name: 'userSettings'
})
let lastEndTime = 0,
pauseRPCChange = 0,
lastSongTitle = "",
songAuthor,
songStartTime,
songEndTime,
songTitle
tryLogin()
var retryRPCLogin = setInterval(tryLogin, 10 * 1000)
module.exports = (data, force) => {
if (force) {
if (data.scloud.playback == "paused")
updatePresence(songTitle, songAuthor, "pause", "Paused")
else if (data.scloud.playback == "playing")
updatePresence(songTitle, songAuthor, "play", "Playing back.", songStartTime, songEndTime)
} else {
updateData(data)
if ((data.scloud.playback == "playing" && SCLOUDRPCREADY) && (songEndTime != lastEndTime || songTitle != lastSongTitle)) {
lastSongTitle = songTitle
lastEndTime = songEndTime
updatePresence(songTitle, songAuthor, "play", "Playing back", songStartTime, songEndTime)
}
//* Clear Presence after 1 minute if playback == pause
if(pauseRPCChange >= 60 && pauseRPCChange <= 60) SCLOUDRPC.clearActivity()
if(data.scloud.playback == "paused") pauseRPCChange++; else pauseRPCChange = 0;
}
}
/**
* Try to login to RPC until connected
*/
async function tryLogin() {
SCLOUDRPC = new DiscordRPC.Client({ transport: "ipc" });
SCLOUDRPC.login({ clientId: scloud_client_id })
.catch(err => console.log(`${CONSOLEPREFIX}SCLOUDRPC: ${err.message}`))
SCLOUDRPC.on("ready", () => {
clearInterval(retryRPCLogin)
SCLOUDRPCREADY = true
})
}
/**
* Update the Presence on Discord
* @param {String} details Details to show
* @param {String} state State to show
* @param {String} smallImageKey Small image name
* @param {String} smallImageText Small image text
* @param {Number} startTimestamp Start timestamp
* @param {Number} endTimestamp End timerstamp
*/
function updatePresence(details, state, smallImageKey, smallImageText, startTimestamp = null, endTimestamp = null) {
if(startTimestamp != null && userSettings.get('titleMenubar')) TRAY.setTitle(entities.decode(details)); else TRAY.setTitle("");
SCLOUDRPC.setActivity({
details: entities.decode(details),
state: entities.decode(state),
largeImageKey: "scloud_lg",
largeImageText: "YT Presence " + VERSIONSTRING,
smallImageKey: smallImageKey,
smallImageText: smallImageText,
startTimestamp: startTimestamp,
endTimestamp: endTimestamp,
instance: true
})
}
/**
* Update the data used for the Presence
* @param {Object} data Data received from Extension
*/
function updateData(data) {
songAuthor = data.scloud.songAuthor
songStartTime = Math.floor(data.scloud.songCurrentTime / 1000);
songEndTime = data.scloud.songEndTime;
songTitle = data.scloud.songTitle
}

88
src/presences/Twitch.js Normal file
View File

@@ -0,0 +1,88 @@
const DiscordRPC = require('discord-rpc')
const Entities = require("html-entities").AllHtmlEntities;
const entities = new Entities();
const { twitch_client_id } = require("../config.json");
const Config = require('electron-config');
const userSettings = new Config({
name: 'userSettings'
})
let lastEndTime = 0,
pauseRPCChange = 0,
laststreamTitle = "",
streamTitle,
streamHost,
videoStartTime
tryLogin()
var retryRPCLogin = setInterval(tryLogin, 10 * 1000)
module.exports = (data, force) => {
if (force) {
if (data.twitch.playback == "paused")
updatePresence(streamTitle, streamHost, "pause", "Paused")
else if (data.twitch.playback == "playing")
updatePresence(streamTitle, streamHost, "play", "Watching", videoStartTime)
} else {
updateData(data)
if ((data.twitch.playback == "playing" && TWITCHRPCREADY) && streamTitle != laststreamTitle) {
laststreamTitle = streamTitle
updatePresence(streamTitle, streamHost, "play", "Watching", videoStartTime)
}
//* Clear Presence after 1 minute if playback == pause
if(pauseRPCChange >= 60 && pauseRPCChange <= 60) TWITCHRPC.clearActivity()
if(data.twitch.playback == "paused") pauseRPCChange++; else pauseRPCChange = 0;
}
}
/**
* Try to login to RPC until connected
*/
async function tryLogin() {
TWITCHRPC = new DiscordRPC.Client({ transport: "ipc" });
TWITCHRPC.login({ clientId: twitch_client_id })
.catch(err => console.log(`${CONSOLEPREFIX}TWITCHRPC: ${err.message}`))
TWITCHRPC.on("ready", () => {
clearInterval(retryRPCLogin)
TWITCHRPCREADY = true
})
}
/**
* Update the Presence on Discord
* @param {String} details Details to show
* @param {String} state State to show
* @param {String} smallImageKey Small image name
* @param {String} smallImageText Small image text
* @param {Number} startTimestamp Start timestamp
* @param {Number} endTimestamp End timerstamp
*/
function updatePresence(details, state, smallImageKey, smallImageText, startTimestamp = null) {
if(startTimestamp != null && userSettings.get('titleMenubar')) TRAY.setTitle(entities.decode(details)); else TRAY.setTitle("");
TWITCHRPC.setActivity({
details: entities.decode(details),
state: entities.decode(state),
largeImageKey: "twitch_lg",
largeImageText: "YT Presence " + VERSIONSTRING,
smallImageKey: smallImageKey,
smallImageText: smallImageText,
startTimestamp: startTimestamp,
instance: true
})
}
/**
* Update the data used for the Presence
* @param {Object} data Data received from Extension
*/
function updateData(data) {
streamTitle = data.twitch.streamTitle
streamHost = data.twitch.streamHost
videoStartTime = Math.floor(data.twitch.watchingSince / 1000);
}

91
src/presences/YouTube.js Normal file
View File

@@ -0,0 +1,91 @@
const DiscordRPC = require('discord-rpc')
const Entities = require("html-entities").AllHtmlEntities;
const entities = new Entities();
const { yt_client_id } = require("../config.json");
const Config = require('electron-config');
const userSettings = new Config({
name: 'userSettings'
})
let lastEndTime = 0,
pauseRPCChange = 0,
lastVideoTitle = "",
videoTitle,
videoAuthor,
videoStartTime,
videoEndTime
tryLogin()
var retryRPCLogin = setInterval(tryLogin, 10 * 1000)
module.exports = (data, force) => {
if (force) {
if (data.yt.playback == "paused")
updatePresence(videoTitle, videoAuthor, "pause", "Playback paused.")
else if (data.yt.playback == "playing")
updatePresence(videoTitle, videoAuthor, "play", "Playing back.", videoStartTime, videoEndTime)
} else {
updateData(data)
if ((data.yt.playback == "playing" && YTRPCREADY) && (videoEndTime != lastEndTime || videoTitle != lastVideoTitle)) {
lastVideoTitle = videoTitle
lastEndTime = videoEndTime
updatePresence(videoTitle, videoAuthor, "play", "Playing back.", videoStartTime, videoEndTime)
}
//* Clear Presence after 1 minute if playback == pause
if(pauseRPCChange >= 60 && pauseRPCChange <= 60) YTRPC.clearActivity()
if(data.yt.playback == "paused") pauseRPCChange++; else pauseRPCChange = 0;
}
}
/**
* Try to login to RPC until connected
*/
async function tryLogin() {
YTRPC = new DiscordRPC.Client({ transport: "ipc" });
YTRPC.login({ clientId: yt_client_id })
.catch(err => console.log(`${CONSOLEPREFIX}YTRPC: ${err.message}`))
YTRPC.on("ready", () => {
clearInterval(retryRPCLogin)
YTRPCREADY = true
})
}
/**
* Update the Presence on Discord
* @param {String} details Details to show
* @param {String} state State to show
* @param {String} smallImageKey Small image name
* @param {String} smallImageText Small image text
* @param {Number} startTimestamp Start timestamp
* @param {Number} endTimestamp End timerstamp
*/
function updatePresence(details, state, smallImageKey, smallImageText, startTimestamp = null, endTimestamp = null) {
if(startTimestamp != null && userSettings.get('titleMenubar')) TRAY.setTitle(entities.decode(details)); else TRAY.setTitle("");
YTRPC.setActivity({
details: entities.decode(details),
state: entities.decode(state),
largeImageKey: "yt_lg",
largeImageText: "YT Presence " + VERSIONSTRING,
smallImageKey: smallImageKey,
smallImageText: smallImageText,
startTimestamp: startTimestamp,
endTimestamp: endTimestamp,
instance: true
})
}
/**
* Update the data used for the Presence
* @param {Object} data Data received from Extension
*/
function updateData(data) {
videoTitle = data.yt.videoTitle
videoAuthor = data.yt.videoAuthor
videoStartTime = Math.floor(data.yt.videoCurrentTime / 1000);
videoEndTime = data.yt.videoEndTime;
}

View File

@@ -0,0 +1,111 @@
const DiscordRPC = require("discord-rpc")
const Entities = require("html-entities").AllHtmlEntities;
const entities = new Entities();
const { ytm_client_id } = require("../config.json");
const Config = require('electron-config');
const userSettings = new Config({name: 'userSettings'})
//* Define needed variables
let lastEndTime = 0,
pauseRPCChange = 0,
songTitle,
songAuthorString,
songEndTime,
songStartTime
//* Try to connect RPC
tryLogin()
var retryRPCLogin = setInterval(tryLogin, 10 * 1000)
module.exports = (data, force) => {
//* If YTM RPC Ready
if(YTMRPCREADY) {
//* Is update forced?
if (force) {
//* Check what playback state and set its Presence
if (data.ytm.playback == "paused")
updatePresence(songTitle, songAuthorString, "pause", "Playback paused.");
else if (data.ytm.playback == "playing")
updatePresence(songTitle, songAuthorString, "play", "Playing back.", songStartTime, songEndTime);
} else {
//* Update Presence data
updateData(data)
//* Only update Presence if time changed
if (data.ytm.playback == "playing" && songEndTime != lastEndTime) {
lastEndTime = songEndTime
updatePresence(songTitle, songAuthorString, "play", "Playing back.", songStartTime, songEndTime)
}
//* Clear Presence after 1 minute if playback == pause
if(pauseRPCChange >= 60 && pauseRPCChange <= 60) {
YTMRPC.clearActivity()
TRAY.setTitle("")
}
//* Increase pauseChange if paused
if(data.ytm.playback == "paused") pauseRPCChange++; else pauseRPCChange = 0;
}
}
}
/**
* Try to login to RPC until connected
*/
async function tryLogin() {
YTMRPC = new DiscordRPC.Client({ transport: "ipc" });
YTMRPC.login({ clientId: ytm_client_id })
.catch(err => console.log(`${CONSOLEPREFIX}YTMRPC: ${err.message}`))
YTMRPC.on("ready", () => {
clearInterval(retryRPCLogin)
YTMRPCREADY = true
})
}
/**
* Update the Presence on Discord
* @param {String} details Details to show
* @param {String} state State to show
* @param {String} smallImageKey Small image name
* @param {String} smallImageText Small image text
* @param {Number} startTimestamp Start timestamp
* @param {Number} endTimestamp End timerstamp
*/
function updatePresence(details, state, smallImageKey, smallImageText, startTimestamp = null, endTimestamp = null) {
if(startTimestamp != null && userSettings.get('titleMenubar')) TRAY.setTitle(entities.decode(songTitle)); else TRAY.setTitle("");
YTMRPC.setActivity({
details: entities.decode(details),
state: entities.decode(state),
largeImageKey: "ytm_lg",
largeImageText: "YT Presence " + VERSIONSTRING,
smallImageKey: smallImageKey,
smallImageText: smallImageText,
startTimestamp: startTimestamp,
endTimestamp: endTimestamp,
instance: true
})
}
/**
* Update the data used for the Presence
* @param {Object} data Data received from Extension
*/
function updateData(data) {
songTitle = data.ytm.songTitle
var songAuthors = data.ytm.songAuthors
songStartTime = Math.floor(data.ytm.songCurrentTime / 1000);
songEndTime = data.ytm.songEndTime;
//* Create author string from author array
songAuthors.forEach((author, index, authors) => {
if (index == 0)
songAuthorString = author;
else if (index < authors.length - 2)
songAuthorString = songAuthorString + ", " + author;
else if (index < authors.length - 1) songAuthorString = songAuthorString + " and " + author;
else songAuthorString = songAuthorString + " &bull; " + author;
});
}

View File

@@ -1,61 +0,0 @@
const DiscordRPC = require("discord-rpc");
const constants = require("../util/constants.js");
const { ytm_client_id } = require("../config.json");
const Entities = require("html-entities").AllHtmlEntities;
const entities = new Entities();
const rpc = new DiscordRPC.Client({ transport: "ipc" });
let presence,
startTime = new Date(),
endTime = new Date(),
lastTitle,
lastStartingTime
async function updatePresence(data) {
if (data.currentSongStartTime == lastStartingTime) {
if ((data.currentSongAuthor && data.currentSongTitle) != undefined) {
constants.menuBar.tray.setTitle("")
rpc.setActivity({
details: entities.decode(data.currentSongTitle),
state: entities.decode(data.currentSongAuthor),
smallImageKey: "pause",
smallImageText: "Playback paused.",
largeImageKey: "ytm_lg",
instance: true
});
}
} else {
startTime = new Date();
endTime =
Math.floor(startTime / 1000) -
data.currentSongStartTime +
data.currentSongEndTime;
lastTitle = data.currentSongTitle;
lastStartingTime = data.currentSongStartTime;
if (data.url.includes("watch?v=")) {
if ((data.currentSongAuthor && data.currentSongTitle) != undefined) {
constants.menuBar.tray.setTitle(" " + data.currentSongTitle)
rpc.setActivity({
details: entities.decode(data.currentSongTitle),
state: entities.decode(data.currentSongAuthor),
smallImageKey: "play",
smallImageText: "Playing back.",
largeImageKey: "ytm_lg",
startTimestamp: startTime,
endTimestamp: endTime,
instance: true
});
}
}
}
}
rpc.on("ready", () => {
constants.presenceReady = true;
});
exports.updatePresence = updatePresence;
rpc.login(ytm_client_id).catch(console.error);

32
src/tray/createTray.js Normal file
View File

@@ -0,0 +1,32 @@
const path = require('path')
const constants = require("../util/constants")
const {Tray, Menu, MenuItem} = require('electron')
exports.run = () => {
TRAY = new Tray(path.join(__dirname, "../assets/images/icon.png"))
TRAY.setToolTip(`YT Presence V${VERSIONSTRING}`)
constants.menuBarMenu = new Menu()
constants.menuBarMenu.append(new MenuItem({
label: `YT Presence | V${VERSIONSTRING}`,
enabled: false,
icon: path.join(__dirname, "../assets/images/icon.png")
}))
constants.menuBarMenu.append(new MenuItem({type: "separator"}))
constants.menuBarMenu.append(new MenuItem({
click: require('./showAbout').run,
label: "About"
}))
constants.menuBarMenu.append(new MenuItem({
click: cfu,
label: "Check for update"
}))
constants.menuBarMenu.append(new MenuItem({
click: require('./showPreferences').run,
label: "Preferences"
}))
constants.menuBarMenu.append(new MenuItem({type: "separator"}))
constants.menuBarMenu.append(new MenuItem({role: "quit"}))
TRAY.setContextMenu(constants.menuBarMenu)
}
cfu = () => require('../util/updateChecker').checkForUpdate(true, true)

22
src/tray/showAbout.js Normal file
View File

@@ -0,0 +1,22 @@
const path = require('path')
const {BrowserWindow} = require('electron')
exports.run = () => {
var aboutWindow = new BrowserWindow({
center: true,
maximizable: false,
minimizable: false,
resizable: false,
width: 400,
height: 600,
alwaysOnTop: true
})
aboutWindow.setMenu(null)
aboutWindow.loadURL("file://" + path.join(__dirname, "../windows/about.html"))
aboutWindow.on('close', () => {
aboutWindow = null;
})
}

View File

@@ -0,0 +1,35 @@
const path = require('path')
const {BrowserWindow} = require('electron')
const os = require('os');
let menuBarHeight
exports.run = () => {
switch(os.platform()) {
case "darwin":
menuBarHeight = 440;
break;
default:
menuBarHeight = 410;
break;
}
var settingsWindow = new BrowserWindow({
center: true,
maximizable: false,
resizable: false,
height: menuBarHeight,
width: 400
})
settingsWindow.setMenu(null)
settingsWindow.loadURL("file://" + path.join(__dirname, "../windows/preferences.html"))
settingsWindow.on('blur', () => {
settingsWindow.close()
})
settingsWindow.on('close', () => {
settingsWindow = null;
})
}

View File

@@ -1,19 +1,14 @@
const chalk = require("chalk")
let consolePrefix, win, menuBar, menuBarMenu, chromeConnected, presenceReady, presence, setup;
module.exports = {
consolePrefix: chalk.bold(chalk.gray("<[ ") + chalk.bgRed(chalk.black(" Y") + chalk.white("T ")) + chalk.cyan(" Presence") + chalk.gray(" ]> ")),
app: "",
win: "",
menuBar: "",
menuBarMenu: "",
chromeConnected: false,
presenceReady: false,
setup: false,
presence: {
details: "Waiting for music",
state: "to start playing...",
largeImageKey: "ytm_lg",
instance: true
}
newVersion: false,
introRan: false,
lastResponse: false,
platform: "",
data: ""
};

View File

@@ -0,0 +1,60 @@
if (require('electron-squirrel-startup')) return;
const {app} = require('electron');
// this should be placed at top of main.js to handle setup events quickly
if (handleSquirrelEvent()) {
// squirrel event handled and app will exit in 1000ms, so don't do anything else
return;
}
function handleSquirrelEvent() {
if (process.argv.length === 1) {
return false;
}
const ChildProcess = require('child_process');
const path = require('path');
const appFolder = path.resolve(process.execPath, '..');
const rootAtomFolder = path.resolve(appFolder, '..');
const updateDotExe = path.resolve(path.join(rootAtomFolder, 'Update.exe'));
const exeName = path.basename(process.execPath);
const spawn = function(command, args) {
let spawnedProcess, error;
try {
spawnedProcess = ChildProcess.spawn(command, args, {detached: true});
} catch (error) {}
return spawnedProcess;
};
const spawnUpdate = function(args) {
return spawn(updateDotExe, args);
};
const squirrelEvent = process.argv[1];
switch (squirrelEvent) {
case '--squirrel-install':
case '--squirrel-updated':
// Install desktop and start menu shortcuts
spawnUpdate(['--createShortcut', exeName]);
setTimeout(app.quit, 1000);
return true;
case '--squirrel-uninstall':
// Remove desktop and start menu shortcuts
spawnUpdate(['--removeShortcut', exeName]);
setTimeout(app.quit, 1000);
return true;
case '--squirrel-obsolete':
app.quit();
return true;
}
};

View File

@@ -0,0 +1,18 @@
const chalk = require('chalk')
var { globalShortcut } = require('electron')
console.log(CONSOLEPREFIX + chalk.yellow("Registering keyboard shortcuts..."))
globalShortcut.register('medianexttrack', () => {
if (EXTENSIONSOCKET != null) EXTENSIONSOCKET.emit('mediaKeyHandler', { playback: "nextTrack" })
})
globalShortcut.register('mediaplaypause', () => {
if (EXTENSIONSOCKET != null) EXTENSIONSOCKET.emit('mediaKeyHandler', { playback: "pause" })
})
globalShortcut.register('mediaprevioustrack', () => {
if (EXTENSIONSOCKET != null) EXTENSIONSOCKET.emit('mediaKeyHandler', { playback: "previousTrack" })
})
console.log(CONSOLEPREFIX + chalk.green("Successfully registered keyboard shortcuts."))

69
src/util/updateChecker.js Normal file
View File

@@ -0,0 +1,69 @@
const { Notification, BrowserWindow } = require('electron');
const path = require('path')
const request = require("request")
const chalk = require("chalk")
let constants = require('./constants')
function checkForUpdate(sendNotification = false, sendNoUpdateInfo = false) {
console.log(CONSOLEPREFIX + chalk.cyan("Checking for update..."))
request({
url: "https://api.github.com/repos/Timeraa/YT-Presence/releases/latest",
json: true,
headers: {'user-agent': 'node.js'}
}, function (error, response, body) {
if(error) {
console.log(CONSOLEPREFIX + chalk.red("Error while checking for update. " + error))
return
}
//* Remove v from version
var gitVersion = body.tag_name.replace('v', '')
//* Compare version
if(gitVersion > VERSION) {
global.UPDATEAVAIABLE = gitVersion
constants.newVersion = gitVersion
console.log(CONSOLEPREFIX + chalk.cyan("New version avaiable: ") + chalk.red(`V${VERSION}`) + chalk.blue(' > ') + chalk.yellow(`V${gitVersion}`))
var updateWindow = new BrowserWindow({
center: true,
maximizable: false,
minimizable: false,
height: 500,
width: 400,
alwaysOnTop: true
})
updateWindow.setMenu(null)
updateWindow.loadURL("file://" + path.join(__dirname, "../windows/update.html"))
updateWindow.webContents.on('did-finish-load', () => {
updateWindow.webContents.send('updateData', body);
});
updateWindow.on('close', () => {
updateWindow = null;
})
} else {
global.UPDATEAVAIABLE = false
console.log(CONSOLEPREFIX + chalk.cyan("Up to date! ") + chalk.yellow(`V${VERSION}`))
if(sendNoUpdateInfo) {
const noUpdateAvaiableNotification = new Notification({
title: 'YT Presence',
body: `You are up to date! (V${VERSION})`,
silent: true
})
noUpdateAvaiableNotification.show()
}
}
})
return UPDATEAVAIABLE
}
module.exports.checkForUpdate = checkForUpdate

131
src/windows/about.html Normal file
View File

@@ -0,0 +1,131 @@
<html>
<head>
<title>About YouTube Presence</title>
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/about.css">
<link href="css/ripple.css" rel="stylesheet">
</head>
<body>
<h1 id="hello">Hi there!</h1>
<img src="https://avatars2.githubusercontent.com/u/29104008?s=150">
<div id="background"></div>
<h1 id="name">Florian Metz</h1>
<div id="nickname">
<p>T</p>
<p>i</p>
<p>m</p>
<p>e</p>
<p>r</p>
<p>a</p>
<p>a</p>
</div>
<div id="aboutMe">
<p>
My name is <a href="https://github.com/Timeraa">Timeraa (Florian Metz)</a>. I am a Web developer, master a lot of
known languages and i love the TV series
called<br>
<a href="https://en.wikipedia.org/wiki/My_Little_Pony:_Friendship_Is_Magic">My little Pony: Friendship is Magic</a>.
</p>
</div>
<div id="aboutYTP">
<p>
I started this project because i recently switched to <a href="https://music.youtube.com/">YouTube Music</a>, now
i wanted to have <b>Rich Presence
Integration</b> for <a href="https://discordapp.com/">Discord</a>, so i just started to create this. And now
you can use it. :P
</p>
</div>
<button id="supportMe">
Support me!
<div class="rippleJS"></div>
</button>
<script>if (typeof module === 'object') { window.module = module; module = undefined; }</script>
<script src="js/jquery-3.3.1.min.js"></script>
<script src="js/anime.min.js"></script>
<script src="js/preferences.js"></script>
<script src="js/ripple.min.js"></script>
<script>if (window.module) module = window.module;</script>
<script>
var shell = require('electron').shell;
//open links externally by default
$(document).on('click', 'a[href^="http"]', function (event) {
event.preventDefault();
shell.openExternal(this.href);
});
$('#supportMe').click(() => {
shell.openExternal("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZU8Q766ACS2WS&lc=US");
})
$(window).on("load", () => {
$('html').addClass("ready")
anime.timeline({
targets: 'img',
scale: [
{ value: 0, duration: 0 },
{ value: 1.25, duration: 1000 },
{ value: 1, duration: 500 }
],
easing: 'easeInOutExpo',
}).add({
targets: '#background',
scale: [0, 20],
duration: 750,
offset: 900
}).add({
targets: '#hello',
translateY: [75, -25, 0],
opacity: [0, 1],
scale: [0, 1],
duration: 2000,
offset: 750
}).add({
targets: '#name',
translateY: [0, 25],
opacity: [0, 1],
scale: [0, 1],
duration: 2000,
offset: 900
}).add({
targets: '#nickname p',
opacity: [0, 1],
duration: 500,
color: ['rgba(0,0,0,0)', 'rgb(0, 150, 200)', '#fff'],
delay: function (el, i) {
return 2500 + 75 * i
},
offset: 0
}).add({
targets: '#aboutMe',
translateY: [-25, 0],
opacity: [0, 1],
scale: [0.75, 1],
easing: 'easeInOutSine',
duration: 1500,
offset: 3250
}).add({
targets: '#aboutYTP',
translateY: [-25, 0],
opacity: [0, 1],
scale: [0.75, 1],
easing: 'easeInOutSine',
duration: 1500,
offset: 4500
}).add({
targets: '#supportMe',
opacity: [0, 1],
scale: [0, 1],
duration: 1500,
offset: 5000
})
})
</script>
</body>
</html>

99
src/windows/css/about.css Normal file
View File

@@ -0,0 +1,99 @@
* {
color: #fff;
}
html,
body {
text-align: center;
overflow: hidden;
}
html:not(.ready) * {
display: none;
}
#background {
position: absolute;
left: 50%;
margin-left: -75px;
background-color: #1d1d1d;
width: 150px;
height: 150px;
border-radius: 50%;
z-index: -1;
}
h1 {
margin-top: 35px;
}
#name {
margin-top: 150px;
}
#nickname p {
position: relative;
display: inline;
}
img {
position: absolute;
left: 50%;
margin-left: -75px;
width: 150px;
border-radius: 50%;
box-shadow: 0 0 10px 5px rgba(0, 0, 0, 0.5);
}
#aboutMe,
#aboutYTP {
width: 380px;
margin: auto;
}
a {
position: relative;
color: rgb(0, 150, 255);
text-decoration: none;
cursor: pointer;
}
a:after {
content: "";
position: absolute;
left: 0;
bottom: -1px;
height: 2px;
width: 0;
transition: width 0.25s ease-out;
background-color: rgb(0, 125, 255);
}
a:hover:after {
width: 100%;
}
p {
font-weight: 500;
}
button {
transition: 0.25s all ease-out;
cursor: pointer;
appearance: none;
-webkit-appearance: none;
background-color: rgb(0, 160, 255);
color: white;
width: 150px;
height: 35px;
border: none;
border-radius: 15px;
outline: none;
font-size: 17px;
font-weight: 700;
}
button:hover {
background-color: rgb(0, 175, 255);
box-shadow: 0 0 5px 3px rgba(0, 174, 255, 0.5);
}

View File

@@ -0,0 +1,78 @@
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 35px;
height: 15px;
}
/* Hide default HTML checkbox */
.switch input {
display: none;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: 0.25s;
transition: 0.25s;
}
.slider:before {
position: absolute;
content: "";
height: 17px;
width: 17px;
left: -2.5px;
top: -2.5px;
background-color: white;
-webkit-transition: 0.25s;
transition: 0.25s cubic-bezier(0.215, 0.61, 0.355, 1);
border: 1px solid lightgray;
}
input:checked + .slider {
background-color: #2196f3;
}
input:disabled + .slider {
background-color: rgb(160, 160, 160);
cursor: not-allowed;
}
input:disabled + .slider:before {
background-color: lightgray;
border: 1px solid rgb(160, 160, 160);
cursor: not-allowed;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196f3;
}
input:hover + .slider {
transform: scale(1.1);
}
input:checked + .slider:before {
transform: translateX(20px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.slider.round:before {
border-radius: 50%;
}

View File

@@ -0,0 +1,79 @@
html {
transition: 0.5s background-color ease-out;
}
#header {
background-color: #ce1d2a;
color: white;
width: 100%;
height: 60px;
}
#header img {
margin: 5px;
width: 50px;
}
#header h1 {
display: inline-block;
margin: 0;
top: 10px;
position: absolute;
font-weight: 900;
}
#options {
border-collapse: collapse;
width: 90%;
margin-left: 5%;
margin-right: 5%;
font-weight: 500;
}
#options th:nth-child(1) {
width: 90%;
}
#options th:nth-child(2) {
width: 10%;
}
#options tbody tr {
transition: 1s color ease-in-out, 0.75s border ease-out;
height: 35px;
}
#options tbody tr:not(:last-child) {
border-bottom: 1px solid #ccc;
}
#footer {
position: fixed;
bottom: 0;
background-color: #ce1d2a;
color: white;
height: 40px;
width: 100%;
text-align: center;
}
#footer #close-btn {
position: relative;
transition: background-color 0.2s ease-out;
margin: 5px;
appearance: none;
border: none;
outline: none;
cursor: pointer;
background-color: rgba(255, 255, 255, 0.15);
color: white;
width: 100px;
height: 30px;
border-radius: 5px;
font-size: 20px;
font-weight: 600;
}
#footer #close-btn:hover {
background-color: rgba(255, 255, 255, 0.25);
}

47
src/windows/css/reset.css Normal file
View File

@@ -0,0 +1,47 @@
@font-face {
font-family: "Montserrat";
src: url("../fonts/Montserrat/Montserrat-Regular.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "Montserrat";
src: url("../fonts/Montserrat/Montserrat-Medium.ttf") format("truetype");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: "Montserrat";
src: url("../fonts/Montserrat/Montserrat-Bold.ttf") format("truetype");
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: "Montserrat";
src: url("../fonts/Montserrat/Montserrat-Black.ttf") format("truetype");
font-weight: 900;
font-style: normal;
}
@font-face {
font-family: "Montserrat";
src: url("../fonts/Montserrat/Montserrat-BlackItalic.ttf") format("truetype");
font-weight: 900;
font-style: italic;
}
* {
font-family: "Montserrat", sans-serif;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: default;
}
html,
body {
margin: 0;
padding: 0;
}

View File

@@ -0,0 +1,67 @@
/** NOTE: When this is updated, refresh ripple.js' minified version. */
/**
* contains a ripple. Will normally grow to fixed size (200px/200px), not
* related to the holder itself.
*/
.rippleJS {
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
border-radius: inherit;
/** Forces webkit to properly contain content within border-radius. */
-webkit-mask-image: -webkit-radial-gradient(circle, white, black);
}
/** adds default border-radius */
.rippleJS.fill::after { /** allows webkit/blink to tap on corners */
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
content: "";
}
.rippleJS.fill {
border-radius: 1000000px; /** "large" number, but not 100% */
}
.rippleJS .ripple {
position: absolute;
border-radius: 100%;
background: currentColor;
opacity: 0.2;
width: 0;
height: 0;
/** only animate transform and opacity */
-webkit-transition: -webkit-transform 0.4s ease-out, opacity 0.4s ease-out;
transition: transform 0.4s ease-out, opacity 0.4s ease-out;
/** initially hidden */
-webkit-transform: scale(0);
transform: scale(0);
pointer-events: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.rippleJS .ripple.held {
opacity: 0.4;
-webkit-transform: scale(1);
transform: scale(1);
}
.rippleJS .ripple.done {
opacity: 0.0;
}

View File

@@ -0,0 +1,76 @@
#header {
background-color: #ce1d2a;
color: white;
width: 100%;
height: 60px;
}
#header img {
margin: 5px;
width: 50px;
}
#header h1 {
display: inline-block;
margin: 0;
top: 10px;
position: absolute;
font-weight: 900;
}
#content {
margin: 5px;
margin-bottom: 65px;
}
#footer {
position: fixed;
bottom: 0;
background-color: #ce1d2a;
color: white;
height: 60px;
width: 100%;
}
#footer #update {
position: relative;
transition: background-color 0.2s ease-out;
margin: 15px;
appearance: none;
border: none;
outline: none;
cursor: pointer;
background-color: rgb(0, 160, 255);
color: white;
width: 100px;
height: 30px;
border-radius: 5px;
font-size: 20px;
font-weight: 600;
}
#footer #update:hover {
background-color: rgb(0, 175, 255);
}
#footer #later {
position: relative;
transition: background-color 0.2s ease-out;
float: right;
margin: 15px;
appearance: none;
border: none;
outline: none;
cursor: pointer;
background-color: rgba(255, 255, 255, 0.15);
color: white;
width: 100px;
height: 30px;
border-radius: 5px;
font-size: 20px;
font-weight: 600;
}
#footer #later:hover {
background-color: rgba(255, 255, 255, 0.25);
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

33
src/windows/js/anime.min.js vendored Normal file
View File

@@ -0,0 +1,33 @@
/*
2017 Julian Garnier
Released under the MIT license
*/
var $jscomp={scope:{}};$jscomp.defineProperty="function"==typeof Object.defineProperties?Object.defineProperty:function(e,r,p){if(p.get||p.set)throw new TypeError("ES3 does not support getters and setters.");e!=Array.prototype&&e!=Object.prototype&&(e[r]=p.value)};$jscomp.getGlobal=function(e){return"undefined"!=typeof window&&window===e?e:"undefined"!=typeof global&&null!=global?global:e};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_";
$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.symbolCounter_=0;$jscomp.Symbol=function(e){return $jscomp.SYMBOL_PREFIX+(e||"")+$jscomp.symbolCounter_++};
$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var e=$jscomp.global.Symbol.iterator;e||(e=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[e]&&$jscomp.defineProperty(Array.prototype,e,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(e){var r=0;return $jscomp.iteratorPrototype(function(){return r<e.length?{done:!1,value:e[r++]}:{done:!0}})};
$jscomp.iteratorPrototype=function(e){$jscomp.initSymbolIterator();e={next:e};e[$jscomp.global.Symbol.iterator]=function(){return this};return e};$jscomp.array=$jscomp.array||{};$jscomp.iteratorFromArray=function(e,r){$jscomp.initSymbolIterator();e instanceof String&&(e+="");var p=0,m={next:function(){if(p<e.length){var u=p++;return{value:r(u,e[u]),done:!1}}m.next=function(){return{done:!0,value:void 0}};return m.next()}};m[Symbol.iterator]=function(){return m};return m};
$jscomp.polyfill=function(e,r,p,m){if(r){p=$jscomp.global;e=e.split(".");for(m=0;m<e.length-1;m++){var u=e[m];u in p||(p[u]={});p=p[u]}e=e[e.length-1];m=p[e];r=r(m);r!=m&&null!=r&&$jscomp.defineProperty(p,e,{configurable:!0,writable:!0,value:r})}};$jscomp.polyfill("Array.prototype.keys",function(e){return e?e:function(){return $jscomp.iteratorFromArray(this,function(e){return e})}},"es6-impl","es3");var $jscomp$this=this;
(function(e,r){"function"===typeof define&&define.amd?define([],r):"object"===typeof module&&module.exports?module.exports=r():e.anime=r()})(this,function(){function e(a){if(!h.col(a))try{return document.querySelectorAll(a)}catch(c){}}function r(a,c){for(var d=a.length,b=2<=arguments.length?arguments[1]:void 0,f=[],n=0;n<d;n++)if(n in a){var k=a[n];c.call(b,k,n,a)&&f.push(k)}return f}function p(a){return a.reduce(function(a,d){return a.concat(h.arr(d)?p(d):d)},[])}function m(a){if(h.arr(a))return a;
h.str(a)&&(a=e(a)||a);return a instanceof NodeList||a instanceof HTMLCollection?[].slice.call(a):[a]}function u(a,c){return a.some(function(a){return a===c})}function C(a){var c={},d;for(d in a)c[d]=a[d];return c}function D(a,c){var d=C(a),b;for(b in a)d[b]=c.hasOwnProperty(b)?c[b]:a[b];return d}function z(a,c){var d=C(a),b;for(b in c)d[b]=h.und(a[b])?c[b]:a[b];return d}function T(a){a=a.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i,function(a,c,d,k){return c+c+d+d+k+k});var c=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(a);
a=parseInt(c[1],16);var d=parseInt(c[2],16),c=parseInt(c[3],16);return"rgba("+a+","+d+","+c+",1)"}function U(a){function c(a,c,b){0>b&&(b+=1);1<b&&--b;return b<1/6?a+6*(c-a)*b:.5>b?c:b<2/3?a+(c-a)*(2/3-b)*6:a}var d=/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(a)||/hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/g.exec(a);a=parseInt(d[1])/360;var b=parseInt(d[2])/100,f=parseInt(d[3])/100,d=d[4]||1;if(0==b)f=b=a=f;else{var n=.5>f?f*(1+b):f+b-f*b,k=2*f-n,f=c(k,n,a+1/3),b=c(k,n,a);a=c(k,n,a-1/3)}return"rgba("+
255*f+","+255*b+","+255*a+","+d+")"}function y(a){if(a=/([\+\-]?[0-9#\.]+)(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(a))return a[2]}function V(a){if(-1<a.indexOf("translate")||"perspective"===a)return"px";if(-1<a.indexOf("rotate")||-1<a.indexOf("skew"))return"deg"}function I(a,c){return h.fnc(a)?a(c.target,c.id,c.total):a}function E(a,c){if(c in a.style)return getComputedStyle(a).getPropertyValue(c.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase())||"0"}function J(a,c){if(h.dom(a)&&
u(W,c))return"transform";if(h.dom(a)&&(a.getAttribute(c)||h.svg(a)&&a[c]))return"attribute";if(h.dom(a)&&"transform"!==c&&E(a,c))return"css";if(null!=a[c])return"object"}function X(a,c){var d=V(c),d=-1<c.indexOf("scale")?1:0+d;a=a.style.transform;if(!a)return d;for(var b=[],f=[],n=[],k=/(\w+)\((.+?)\)/g;b=k.exec(a);)f.push(b[1]),n.push(b[2]);a=r(n,function(a,b){return f[b]===c});return a.length?a[0]:d}function K(a,c){switch(J(a,c)){case "transform":return X(a,c);case "css":return E(a,c);case "attribute":return a.getAttribute(c)}return a[c]||
0}function L(a,c){var d=/^(\*=|\+=|-=)/.exec(a);if(!d)return a;var b=y(a)||0;c=parseFloat(c);a=parseFloat(a.replace(d[0],""));switch(d[0][0]){case "+":return c+a+b;case "-":return c-a+b;case "*":return c*a+b}}function F(a,c){return Math.sqrt(Math.pow(c.x-a.x,2)+Math.pow(c.y-a.y,2))}function M(a){a=a.points;for(var c=0,d,b=0;b<a.numberOfItems;b++){var f=a.getItem(b);0<b&&(c+=F(d,f));d=f}return c}function N(a){if(a.getTotalLength)return a.getTotalLength();switch(a.tagName.toLowerCase()){case "circle":return 2*
Math.PI*a.getAttribute("r");case "rect":return 2*a.getAttribute("width")+2*a.getAttribute("height");case "line":return F({x:a.getAttribute("x1"),y:a.getAttribute("y1")},{x:a.getAttribute("x2"),y:a.getAttribute("y2")});case "polyline":return M(a);case "polygon":var c=a.points;return M(a)+F(c.getItem(c.numberOfItems-1),c.getItem(0))}}function Y(a,c){function d(b){b=void 0===b?0:b;return a.el.getPointAtLength(1<=c+b?c+b:0)}var b=d(),f=d(-1),n=d(1);switch(a.property){case "x":return b.x;case "y":return b.y;
case "angle":return 180*Math.atan2(n.y-f.y,n.x-f.x)/Math.PI}}function O(a,c){var d=/-?\d*\.?\d+/g,b;b=h.pth(a)?a.totalLength:a;if(h.col(b))if(h.rgb(b)){var f=/rgb\((\d+,\s*[\d]+,\s*[\d]+)\)/g.exec(b);b=f?"rgba("+f[1]+",1)":b}else b=h.hex(b)?T(b):h.hsl(b)?U(b):void 0;else f=(f=y(b))?b.substr(0,b.length-f.length):b,b=c&&!/\s/g.test(b)?f+c:f;b+="";return{original:b,numbers:b.match(d)?b.match(d).map(Number):[0],strings:h.str(a)||c?b.split(d):[]}}function P(a){a=a?p(h.arr(a)?a.map(m):m(a)):[];return r(a,
function(a,d,b){return b.indexOf(a)===d})}function Z(a){var c=P(a);return c.map(function(a,b){return{target:a,id:b,total:c.length}})}function aa(a,c){var d=C(c);if(h.arr(a)){var b=a.length;2!==b||h.obj(a[0])?h.fnc(c.duration)||(d.duration=c.duration/b):a={value:a}}return m(a).map(function(a,b){b=b?0:c.delay;a=h.obj(a)&&!h.pth(a)?a:{value:a};h.und(a.delay)&&(a.delay=b);return a}).map(function(a){return z(a,d)})}function ba(a,c){var d={},b;for(b in a){var f=I(a[b],c);h.arr(f)&&(f=f.map(function(a){return I(a,
c)}),1===f.length&&(f=f[0]));d[b]=f}d.duration=parseFloat(d.duration);d.delay=parseFloat(d.delay);return d}function ca(a){return h.arr(a)?A.apply(this,a):Q[a]}function da(a,c){var d;return a.tweens.map(function(b){b=ba(b,c);var f=b.value,e=K(c.target,a.name),k=d?d.to.original:e,k=h.arr(f)?f[0]:k,w=L(h.arr(f)?f[1]:f,k),e=y(w)||y(k)||y(e);b.from=O(k,e);b.to=O(w,e);b.start=d?d.end:a.offset;b.end=b.start+b.delay+b.duration;b.easing=ca(b.easing);b.elasticity=(1E3-Math.min(Math.max(b.elasticity,1),999))/
1E3;b.isPath=h.pth(f);b.isColor=h.col(b.from.original);b.isColor&&(b.round=1);return d=b})}function ea(a,c){return r(p(a.map(function(a){return c.map(function(b){var c=J(a.target,b.name);if(c){var d=da(b,a);b={type:c,property:b.name,animatable:a,tweens:d,duration:d[d.length-1].end,delay:d[0].delay}}else b=void 0;return b})})),function(a){return!h.und(a)})}function R(a,c,d,b){var f="delay"===a;return c.length?(f?Math.min:Math.max).apply(Math,c.map(function(b){return b[a]})):f?b.delay:d.offset+b.delay+
b.duration}function fa(a){var c=D(ga,a),d=D(S,a),b=Z(a.targets),f=[],e=z(c,d),k;for(k in a)e.hasOwnProperty(k)||"targets"===k||f.push({name:k,offset:e.offset,tweens:aa(a[k],d)});a=ea(b,f);return z(c,{children:[],animatables:b,animations:a,duration:R("duration",a,c,d),delay:R("delay",a,c,d)})}function q(a){function c(){return window.Promise&&new Promise(function(a){return p=a})}function d(a){return g.reversed?g.duration-a:a}function b(a){for(var b=0,c={},d=g.animations,f=d.length;b<f;){var e=d[b],
k=e.animatable,h=e.tweens,n=h.length-1,l=h[n];n&&(l=r(h,function(b){return a<b.end})[0]||l);for(var h=Math.min(Math.max(a-l.start-l.delay,0),l.duration)/l.duration,w=isNaN(h)?1:l.easing(h,l.elasticity),h=l.to.strings,p=l.round,n=[],m=void 0,m=l.to.numbers.length,t=0;t<m;t++){var x=void 0,x=l.to.numbers[t],q=l.from.numbers[t],x=l.isPath?Y(l.value,w*x):q+w*(x-q);p&&(l.isColor&&2<t||(x=Math.round(x*p)/p));n.push(x)}if(l=h.length)for(m=h[0],w=0;w<l;w++)p=h[w+1],t=n[w],isNaN(t)||(m=p?m+(t+p):m+(t+" "));
else m=n[0];ha[e.type](k.target,e.property,m,c,k.id);e.currentValue=m;b++}if(b=Object.keys(c).length)for(d=0;d<b;d++)H||(H=E(document.body,"transform")?"transform":"-webkit-transform"),g.animatables[d].target.style[H]=c[d].join(" ");g.currentTime=a;g.progress=a/g.duration*100}function f(a){if(g[a])g[a](g)}function e(){g.remaining&&!0!==g.remaining&&g.remaining--}function k(a){var k=g.duration,n=g.offset,w=n+g.delay,r=g.currentTime,x=g.reversed,q=d(a);if(g.children.length){var u=g.children,v=u.length;
if(q>=g.currentTime)for(var G=0;G<v;G++)u[G].seek(q);else for(;v--;)u[v].seek(q)}if(q>=w||!k)g.began||(g.began=!0,f("begin")),f("run");if(q>n&&q<k)b(q);else if(q<=n&&0!==r&&(b(0),x&&e()),q>=k&&r!==k||!k)b(k),x||e();f("update");a>=k&&(g.remaining?(t=h,"alternate"===g.direction&&(g.reversed=!g.reversed)):(g.pause(),g.completed||(g.completed=!0,f("complete"),"Promise"in window&&(p(),m=c()))),l=0)}a=void 0===a?{}:a;var h,t,l=0,p=null,m=c(),g=fa(a);g.reset=function(){var a=g.direction,c=g.loop;g.currentTime=
0;g.progress=0;g.paused=!0;g.began=!1;g.completed=!1;g.reversed="reverse"===a;g.remaining="alternate"===a&&1===c?2:c;b(0);for(a=g.children.length;a--;)g.children[a].reset()};g.tick=function(a){h=a;t||(t=h);k((l+h-t)*q.speed)};g.seek=function(a){k(d(a))};g.pause=function(){var a=v.indexOf(g);-1<a&&v.splice(a,1);g.paused=!0};g.play=function(){g.paused&&(g.paused=!1,t=0,l=d(g.currentTime),v.push(g),B||ia())};g.reverse=function(){g.reversed=!g.reversed;t=0;l=d(g.currentTime)};g.restart=function(){g.pause();
g.reset();g.play()};g.finished=m;g.reset();g.autoplay&&g.play();return g}var ga={update:void 0,begin:void 0,run:void 0,complete:void 0,loop:1,direction:"normal",autoplay:!0,offset:0},S={duration:1E3,delay:0,easing:"easeOutElastic",elasticity:500,round:0},W="translateX translateY translateZ rotate rotateX rotateY rotateZ scale scaleX scaleY scaleZ skewX skewY perspective".split(" "),H,h={arr:function(a){return Array.isArray(a)},obj:function(a){return-1<Object.prototype.toString.call(a).indexOf("Object")},
pth:function(a){return h.obj(a)&&a.hasOwnProperty("totalLength")},svg:function(a){return a instanceof SVGElement},dom:function(a){return a.nodeType||h.svg(a)},str:function(a){return"string"===typeof a},fnc:function(a){return"function"===typeof a},und:function(a){return"undefined"===typeof a},hex:function(a){return/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a)},rgb:function(a){return/^rgb/.test(a)},hsl:function(a){return/^hsl/.test(a)},col:function(a){return h.hex(a)||h.rgb(a)||h.hsl(a)}},A=function(){function a(a,
d,b){return(((1-3*b+3*d)*a+(3*b-6*d))*a+3*d)*a}return function(c,d,b,f){if(0<=c&&1>=c&&0<=b&&1>=b){var e=new Float32Array(11);if(c!==d||b!==f)for(var k=0;11>k;++k)e[k]=a(.1*k,c,b);return function(k){if(c===d&&b===f)return k;if(0===k)return 0;if(1===k)return 1;for(var h=0,l=1;10!==l&&e[l]<=k;++l)h+=.1;--l;var l=h+(k-e[l])/(e[l+1]-e[l])*.1,n=3*(1-3*b+3*c)*l*l+2*(3*b-6*c)*l+3*c;if(.001<=n){for(h=0;4>h;++h){n=3*(1-3*b+3*c)*l*l+2*(3*b-6*c)*l+3*c;if(0===n)break;var m=a(l,c,b)-k,l=l-m/n}k=l}else if(0===
n)k=l;else{var l=h,h=h+.1,g=0;do m=l+(h-l)/2,n=a(m,c,b)-k,0<n?h=m:l=m;while(1e-7<Math.abs(n)&&10>++g);k=m}return a(k,d,f)}}}}(),Q=function(){function a(a,b){return 0===a||1===a?a:-Math.pow(2,10*(a-1))*Math.sin(2*(a-1-b/(2*Math.PI)*Math.asin(1))*Math.PI/b)}var c="Quad Cubic Quart Quint Sine Expo Circ Back Elastic".split(" "),d={In:[[.55,.085,.68,.53],[.55,.055,.675,.19],[.895,.03,.685,.22],[.755,.05,.855,.06],[.47,0,.745,.715],[.95,.05,.795,.035],[.6,.04,.98,.335],[.6,-.28,.735,.045],a],Out:[[.25,
.46,.45,.94],[.215,.61,.355,1],[.165,.84,.44,1],[.23,1,.32,1],[.39,.575,.565,1],[.19,1,.22,1],[.075,.82,.165,1],[.175,.885,.32,1.275],function(b,c){return 1-a(1-b,c)}],InOut:[[.455,.03,.515,.955],[.645,.045,.355,1],[.77,0,.175,1],[.86,0,.07,1],[.445,.05,.55,.95],[1,0,0,1],[.785,.135,.15,.86],[.68,-.55,.265,1.55],function(b,c){return.5>b?a(2*b,c)/2:1-a(-2*b+2,c)/2}]},b={linear:A(.25,.25,.75,.75)},f={},e;for(e in d)f.type=e,d[f.type].forEach(function(a){return function(d,f){b["ease"+a.type+c[f]]=h.fnc(d)?
d:A.apply($jscomp$this,d)}}(f)),f={type:f.type};return b}(),ha={css:function(a,c,d){return a.style[c]=d},attribute:function(a,c,d){return a.setAttribute(c,d)},object:function(a,c,d){return a[c]=d},transform:function(a,c,d,b,f){b[f]||(b[f]=[]);b[f].push(c+"("+d+")")}},v=[],B=0,ia=function(){function a(){B=requestAnimationFrame(c)}function c(c){var b=v.length;if(b){for(var d=0;d<b;)v[d]&&v[d].tick(c),d++;a()}else cancelAnimationFrame(B),B=0}return a}();q.version="2.2.0";q.speed=1;q.running=v;q.remove=
function(a){a=P(a);for(var c=v.length;c--;)for(var d=v[c],b=d.animations,f=b.length;f--;)u(a,b[f].animatable.target)&&(b.splice(f,1),b.length||d.pause())};q.getValue=K;q.path=function(a,c){var d=h.str(a)?e(a)[0]:a,b=c||100;return function(a){return{el:d,property:a,totalLength:N(d)*(b/100)}}};q.setDashoffset=function(a){var c=N(a);a.setAttribute("stroke-dasharray",c);return c};q.bezier=A;q.easings=Q;q.timeline=function(a){var c=q(a);c.pause();c.duration=0;c.add=function(d){c.children.forEach(function(a){a.began=
!0;a.completed=!0});m(d).forEach(function(b){var d=z(b,D(S,a||{}));d.targets=d.targets||a.targets;b=c.duration;var e=d.offset;d.autoplay=!1;d.direction=c.direction;d.offset=h.und(e)?b:L(e,b);c.began=!0;c.completed=!0;c.seek(d.offset);d=q(d);d.began=!0;d.completed=!0;d.duration>b&&(c.duration=d.duration);c.children.push(d)});c.seek(0);c.reset();c.autoplay&&c.restart();return c};return c};q.random=function(a,c){return Math.floor(Math.random()*(c-a+1))+a};return q});

2
src/windows/js/jquery-3.3.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,231 @@
//* Declare needed constants
const remote = require('electron').remote
const app = remote.app
const AutoLaunch = require('auto-launch');
const Config = require('electron-config')
const userSettings = new Config({
name: "userSettings"
})
var togglePresence,
toggleYouTube,
toggleYouTubeMusic,
toggleTitleMenubar;
$(() => {
var os = require('os')
if (os.platform() == "darwin") {
$('<tr><td class="noselect">Title menubar</td><td class="right"><label class="switch"><input class="toggleTitleMenubar" type="checkbox"><span class="slider round"></span></label></td></tr>').insertAfter('.soundcloudToggle')
}
togglePresence = $(".togglePresence"),
toggleYouTube = $(".toggleYouTube"),
toggleYouTubeMusic = $(".toggleYouTubeMusic");
toggleNetflix = $(".toggleNetflix");
toggleTwitch = $(".toggleTwitch");
toggleSoundCloud = $(".toggleSoundCloud");
toggleTitleMenubar = $(".toggleTitleMenubar");
toggleAutoLaunch = $(".toggleAutoLaunch");
toggleAutoUpdate = $(".toggleAutoUpdate");
togglePresence.change(switchPresence);
toggleYouTube.change(switchYouTube);
toggleYouTubeMusic.change(switchYouTubeMusic);
toggleTitleMenubar.change(switchTitleMenubar);
toggleAutoLaunch.change(switchAutomaticStartup);
toggleAutoUpdate.change(switchAutoUpdate);
toggleNetflix.change(switchNetflix);
toggleTwitch.change(switchTwitch);
toggleSoundCloud.change(switchSoundCloud);
if (userSettings.get('enabled') == true) togglePresence.prop("checked", true)
else {
togglePresence.prop("checked", false)
toggleYouTube.prop("checked", false)
toggleYouTubeMusic.prop("checked", false)
toggleNetflix.prop("checked", false)
toggleTwitch.prop("checked", false)
toggleSoundCloud.prop("checked", false)
toggleYouTube.attr("disabled", "disabled")
toggleYouTubeMusic.attr("disabled", "disabled")
toggleNetflix.attr("disabled", "disabled")
toggleTwitch.attr("disabled", "disabled")
toggleSoundCloud.attr("disabled", "disabled")
toggleTitleMenubar.attr("disabled", "disabled")
}
if (userSettings.get('youTube') == true) {
toggleYouTube.prop("checked", true)
toggleYouTube.removeAttr("disabled")
} else toggleYouTube.prop("checked", false)
if (userSettings.get('youTubeMusic') == true) {
toggleYouTubeMusic.prop("checked", true)
toggleYouTubeMusic.removeAttr("disabled")
} else toggleYouTubeMusic.prop("checked", false)
if (userSettings.get('netflix') == true) {
toggleNetflix.prop("checked", true)
toggleNetflix.removeAttr("disabled")
} else toggleNetflix.prop("checked", false)
if (userSettings.get('twitch') == true) {
toggleTwitch.prop("checked", true)
toggleTwitch.removeAttr("disabled")
} else toggleTwitch.prop("checked", false)
if (userSettings.get('soundcloud') == true) {
toggleSoundCloud.prop("checked", true)
toggleSoundCloud.removeAttr("disabled")
} else toggleSoundCloud.prop("checked", false)
if (userSettings.get('titleMenubar') == true) {
toggleTitleMenubar.prop("checked", true)
toggleTitleMenubar.removeAttr("disabled")
} else {
toggleTitleMenubar.prop("checked", false)
}
if (userSettings.get('autoLaunch') == true) {
toggleAutoLaunch.prop("checked", true)
toggleAutoLaunch.removeAttr("disabled")
} else toggleAutoLaunch.prop("checked", false)
if (userSettings.get('autoUpdateCheck') == true) {
toggleAutoUpdate.prop("checked", true)
toggleAutoUpdate.removeAttr("disabled")
} else toggleAutoUpdate.prop("checked", false)
})
function switchPresence() {
if (userSettings.get('enabled') == true) {
userSettings.set('enabled', false);
userSettings.set('youTube', false);
userSettings.set('youTubeMusic', false);
userSettings.set('netflix', false);
userSettings.set('soundcloud', false);
toggleYouTube.prop("checked", false)
toggleYouTube.attr("disabled", "disabled")
toggleYouTubeMusic.prop("checked", false)
toggleYouTubeMusic.attr("disabled", "disabled")
toggleNetflix.prop("checked", false)
toggleNetflix.attr("disabled", "disabled")
toggleTwitch.prop("checked", false)
toggleTwitch.attr("disabled", "disabled")
toggleSoundCloud.prop("checked", false)
toggleSoundCloud.attr("disabled", "disabled")
} else {
userSettings.set('enabled', true);
toggleYouTube.removeAttr("disabled")
toggleYouTubeMusic.removeAttr("disabled")
toggleNetflix.removeAttr("disabled")
toggleTwitch.removeAttr("disabled")
toggleSoundCloud.removeAttr("disabled")
}
}
function switchYouTube() {
if (userSettings.get('youTube') == true) {
userSettings.set('youTube', false)
toggleYouTube.prop("checked", false)
} else {
userSettings.set('youTube', true)
toggleYouTube.prop("checked", true)
}
}
function switchYouTubeMusic() {
if (userSettings.get('youTubeMusic') == true) {
userSettings.set('youTubeMusic', false);
toggleYouTubeMusic.prop("checked", false)
} else {
userSettings.set('youTubeMusic', true);
toggleYouTubeMusic.prop("checked", true)
}
}
function switchNetflix() {
if (userSettings.get('netflix') == true) {
userSettings.set('netflix', false);
toggleNetflix.prop("checked", false)
} else {
userSettings.set('netflix', true);
toggleNetflix.prop("checked", true)
}
}
function switchTwitch() {
if (userSettings.get('twitch') == true) {
userSettings.set('twitch', false);
toggleTwitch.prop("checked", false)
} else {
userSettings.set('twitch', true);
toggleTwitch.prop("checked", true)
}
}
function switchSoundCloud() {
if (userSettings.get('soundcloud') == true) {
userSettings.set('soundcloud', false);
toggleSoundCloud.prop("checked", false)
} else {
userSettings.set('soundcloud', true);
toggleSoundCloud.prop("checked", true)
}
}
function switchTitleMenubar() {
if (userSettings.get('titleMenubar') == true) {
userSettings.set('titleMenubar', false);
toggleTitleMenubar.prop("checked", false)
} else {
userSettings.set('titleMenubar', true);
toggleTitleMenubar.prop("checked", true)
}
}
function switchAutomaticStartup() {
//* Add App to AutoLaunch
let autoLaunch = new AutoLaunch({
name: 'YT Presence',
path: app.getPath('exe'),
isHidden: true
});
if (userSettings.get('autoLaunch') == true) {
autoLaunch.isEnabled().then((isEnabled) => {
if(isEnabled) autoLaunch.disable()
}).catch((err) => {
console.log("Error while adding App to autostart.")
})
userSettings.set('autoLaunch', false);
toggleAutoLaunch.prop("checked", false)
} else {
autoLaunch.isEnabled().then((isEnabled) => {
if(!isEnabled) autoLaunch.enable()
}).catch((err) => {
console.log("Error while adding App to autostart.")
})
userSettings.set('autoLaunch', true);
toggleAutoLaunch.prop("checked", true)
}
}
function switchAutoUpdate() {
if (userSettings.get('autoUpdateCheck') == true) {
userSettings.set('autoUpdateCheck', false);
toggleAutoUpdate.prop("checked", false)
} else {
userSettings.set('autoUpdateCheck', true);
toggleAutoUpdate.prop("checked", true)
}
}

5
src/windows/js/ripple.min.js vendored Normal file
View File

@@ -0,0 +1,5 @@
(function(){function h(c){c=c.target;var a=c.childNodes.length;if("button"!==c.localName||!a)return c.classList.contains("rippleJS")?c:null;for(var b=0;b<a;++b){var g=c.childNodes[b],e=g.classList;if(e&&e.contains("rippleJS"))return g}return null}
function n(c,a){var b=h(a);if(b){var g=b.classList,e=b.getAttribute("data-event");if(!e||e===c){b.setAttribute("data-event",c);var d=b.getBoundingClientRect();e=a.offsetX;void 0!==e?a=a.offsetY:(e=a.clientX-d.left,a=a.clientY-d.top);var f=document.createElement("div");d=d.width===d.height?1.412*d.width:Math.sqrt(d.width*d.width+d.height*d.height);var k=2*d+"px";f.style.width=k;f.style.height=k;f.style.marginLeft=-d+e+"px";f.style.marginTop=-d+a+"px";f.className="ripple";b.appendChild(f);window.setTimeout(function(){f.classList.add("held")},
0);var l="mousedown"===c?"mouseup":"touchend",m=function(){document.removeEventListener(l,m);f.classList.add("done");window.setTimeout(function(){b.removeChild(f);b.children.length||(g.remove("active"),b.removeAttribute("data-event"))},650)};document.addEventListener(l,m)}}}
function p(){var c=c||document;c.addEventListener("mousedown",function(a){0===a.button&&n(a.type,a)},{passive:!0});c.addEventListener("touchstart",function(a){for(var b=0;b<a.changedTouches.length;++b)n(a.type,a.changedTouches[b])},{passive:!0})};(function(){function c(){var a=document.createElement("div");a.className="rippleJS";document.body.appendChild(a);var b="absolute"===window.getComputedStyle(a).position;document.body.removeChild(a);b||(a=document.createElement("style"),a.textContent='/*rippleJS*/.rippleJS,.rippleJS.fill::after{position:absolute;top:0;left:0;right:0;bottom:0}.rippleJS{display:block;overflow:hidden;border-radius:inherit;-webkit-mask-image:-webkit-radial-gradient(circle,#fff,#000)}.rippleJS.fill::after{content:""}.rippleJS.fill{border-radius:1000000px}.rippleJS .ripple{position:absolute;border-radius:100%;background:currentColor;opacity:.2;width:0;height:0;-webkit-transition:-webkit-transform .4s ease-out,opacity .4s ease-out;transition:transform .4s ease-out,opacity .4s ease-out;-webkit-transform:scale(0);transform:scale(0);pointer-events:none;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.rippleJS .ripple.held{opacity:.4;-webkit-transform:scale(1);transform:scale(1)}.rippleJS .ripple.done{opacity:0}',
document.head.insertBefore(a,document.head.firstChild));p()}"complete"===document.readyState?c():window.addEventListener("load",c)})();}())

3
src/windows/js/showdown.min.js vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,122 @@
<html>
<head>
<title>Preferences &bull; YT Presence</title>
<link href="css/reset.css" rel="stylesheet">
<link href="css/preferences.css" rel="stylesheet">
<link href="css/inputs.css" rel="stylesheet">
<link href="css/ripple.css" rel="stylesheet">
</head>
<body>
<div id="header">
<img draggable="false" src="../assets/images/logo.png">
<h1>YT Presence</h1>
</div>
<div id="content">
<table id="options">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Enabled</td>
<td>
<label class="switch">
<input class="togglePresence" type="checkbox">
<span class="slider round"></span>
</label>
</td>
</tr>
<tr>
<td>YouTube</td>
<td>
<label class="switch">
<input class="toggleYouTube" type="checkbox">
<span class="slider round"></span>
</label>
</td>
</tr>
<tr class="ytmToggle">
<td>YouTube Music</td>
<td>
<label class="switch">
<input class="toggleYouTubeMusic" type="checkbox">
<span class="slider round"></span>
</label>
</td>
</tr>
<tr class="nflixToggle">
<td>Netflix</td>
<td>
<label class="switch">
<input class="toggleNetflix" type="checkbox">
<span class="slider round"></span>
</label>
</td>
</tr>
<tr class="twitchToggle">
<td>Twitch</td>
<td>
<label class="switch">
<input class="toggleTwitch" type="checkbox">
<span class="slider round"></span>
</label>
</td>
</tr>
<tr class="soundcloudToggle">
<td>SoundCloud</td>
<td>
<label class="switch">
<input class="toggleSoundCloud" type="checkbox">
<span class="slider round"></span>
</label>
</td>
</tr>
<tr>
<td>Launch on system startup</td>
<td>
<label class="switch">
<input class="toggleAutoLaunch" type="checkbox">
<span class="slider round"></span>
</label>
</td>
</tr>
<tr>
<td>Automatically check for updates</td>
<td>
<label class="switch">
<input class="toggleAutoUpdate" type="checkbox">
<span class="slider round"></span>
</label>
</td>
</tr>
</tbody>
</table>
</div>
<div id="footer">
<button id="close-btn">
Close
<div class="rippleJS"></div>
</button>
</div>
<script>if (typeof module === 'object') { window.module = module; module = undefined; }</script>
<script src="js/jquery-3.3.1.min.js"></script>
<script src="js/preferences.js"></script>
<script src="js/ripple.min.js"></script>
<script>if (window.module) module = window.module;</script>
<script>
document.getElementById("close-btn").addEventListener("click", function (e) {
var window = remote.getCurrentWindow();
window.close();
});
</script>
</body>
</html>

60
src/windows/update.html Normal file
View File

@@ -0,0 +1,60 @@
<html>
<head>
<title>YT Presence &bull; Update avaiable!</title>
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/update.css">
<link href="css/ripple.css" rel="stylesheet">
</head>
<body>
<div id="header">
<img draggable="false" src="../assets/images/logo.png">
<h1>YT Presence</h1>
</div>
<div id="content">
<p id="description"></p>
</div>
<div id="footer">
<button id="update">
Update
<div class="rippleJS"></div>
</button>
<button id="later">
Later
<div class="rippleJS"></div>
</button>
</div>
<script>if (typeof module === 'object') { window.module = module; module = undefined; }</script>
<script src="js/jquery-3.3.1.min.js"></script>
<script src="js/showdown.min.js"></script>
<script src="js/ripple.min.js"></script>
<script>if (window.module) module = window.module;</script>
<script>
var converter = new showdown.Converter()
const ipc = require('electron').ipcRenderer;
const remote = require('electron').remote
</script>
<script>
var shell = require('electron').shell;
ipc.on('updateData', (event, message) => {
$('#description').html(converter.makeHtml(message.body))
$('#update').click(() => {
console.log(message)
shell.openExternal(`https://github.com/Timeraa/YT-Presence/releases/tag/${message.tag_name}`);
remote.getCurrentWindow().close()
})
})
$('#later').click(e => {
remote.getCurrentWindow().close()
})
</script>
</body>
</html>