♻️ Le conflict
0
gitassets/PreMiD_Icon.png → .github/Logo.png
vendored
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
110
gitassets/PayPal.svg → .github/PayPal.svg
vendored
@@ -1,55 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="124px" height="33px" viewBox="0 0 124 33" enable-background="new 0 0 124 33" xml:space="preserve">
|
||||
<path fill="#253B80" d="M46.211,6.749h-6.839c-0.468,0-0.866,0.34-0.939,0.802l-2.766,17.537c-0.055,0.346,0.213,0.658,0.564,0.658
|
||||
h3.265c0.468,0,0.866-0.34,0.939-0.803l0.746-4.73c0.072-0.463,0.471-0.803,0.938-0.803h2.165c4.505,0,7.105-2.18,7.784-6.5
|
||||
c0.306-1.89,0.013-3.375-0.872-4.415C50.224,7.353,48.5,6.749,46.211,6.749z M47,13.154c-0.374,2.454-2.249,2.454-4.062,2.454
|
||||
h-1.032l0.724-4.583c0.043-0.277,0.283-0.481,0.563-0.481h0.473c1.235,0,2.4,0,3.002,0.704C47.027,11.668,47.137,12.292,47,13.154z"
|
||||
/>
|
||||
<path fill="#253B80" d="M66.654,13.075h-3.275c-0.279,0-0.52,0.204-0.563,0.481l-0.145,0.916l-0.229-0.332
|
||||
c-0.709-1.029-2.29-1.373-3.868-1.373c-3.619,0-6.71,2.741-7.312,6.586c-0.313,1.918,0.132,3.752,1.22,5.031
|
||||
c0.998,1.176,2.426,1.666,4.125,1.666c2.916,0,4.533-1.875,4.533-1.875l-0.146,0.91c-0.055,0.348,0.213,0.66,0.562,0.66h2.95
|
||||
c0.469,0,0.865-0.34,0.939-0.803l1.77-11.209C67.271,13.388,67.004,13.075,66.654,13.075z M62.089,19.449
|
||||
c-0.316,1.871-1.801,3.127-3.695,3.127c-0.951,0-1.711-0.305-2.199-0.883c-0.484-0.574-0.668-1.391-0.514-2.301
|
||||
c0.295-1.855,1.805-3.152,3.67-3.152c0.93,0,1.686,0.309,2.184,0.892C62.034,17.721,62.232,18.543,62.089,19.449z"/>
|
||||
<path fill="#253B80" d="M84.096,13.075h-3.291c-0.314,0-0.609,0.156-0.787,0.417l-4.539,6.686l-1.924-6.425
|
||||
c-0.121-0.402-0.492-0.678-0.912-0.678h-3.234c-0.393,0-0.666,0.384-0.541,0.754l3.625,10.638l-3.408,4.811
|
||||
c-0.268,0.379,0.002,0.9,0.465,0.9h3.287c0.312,0,0.604-0.152,0.781-0.408L84.564,13.97C84.826,13.592,84.557,13.075,84.096,13.075z
|
||||
"/>
|
||||
<path fill="#179BD7" d="M94.992,6.749h-6.84c-0.467,0-0.865,0.34-0.938,0.802l-2.766,17.537c-0.055,0.346,0.213,0.658,0.562,0.658
|
||||
h3.51c0.326,0,0.605-0.238,0.656-0.562l0.785-4.971c0.072-0.463,0.471-0.803,0.938-0.803h2.164c4.506,0,7.105-2.18,7.785-6.5
|
||||
c0.307-1.89,0.012-3.375-0.873-4.415C99.004,7.353,97.281,6.749,94.992,6.749z M95.781,13.154c-0.373,2.454-2.248,2.454-4.062,2.454
|
||||
h-1.031l0.725-4.583c0.043-0.277,0.281-0.481,0.562-0.481h0.473c1.234,0,2.4,0,3.002,0.704
|
||||
C95.809,11.668,95.918,12.292,95.781,13.154z"/>
|
||||
<path fill="#179BD7" d="M115.434,13.075h-3.273c-0.281,0-0.52,0.204-0.562,0.481l-0.145,0.916l-0.23-0.332
|
||||
c-0.709-1.029-2.289-1.373-3.867-1.373c-3.619,0-6.709,2.741-7.311,6.586c-0.312,1.918,0.131,3.752,1.219,5.031
|
||||
c1,1.176,2.426,1.666,4.125,1.666c2.916,0,4.533-1.875,4.533-1.875l-0.146,0.91c-0.055,0.348,0.213,0.66,0.564,0.66h2.949
|
||||
c0.467,0,0.865-0.34,0.938-0.803l1.771-11.209C116.053,13.388,115.785,13.075,115.434,13.075z M110.869,19.449
|
||||
c-0.314,1.871-1.801,3.127-3.695,3.127c-0.949,0-1.711-0.305-2.199-0.883c-0.484-0.574-0.666-1.391-0.514-2.301
|
||||
c0.297-1.855,1.805-3.152,3.67-3.152c0.93,0,1.686,0.309,2.184,0.892C110.816,17.721,111.014,18.543,110.869,19.449z"/>
|
||||
<path fill="#179BD7" d="M119.295,7.23l-2.807,17.858c-0.055,0.346,0.213,0.658,0.562,0.658h2.822c0.469,0,0.867-0.34,0.939-0.803
|
||||
l2.768-17.536c0.055-0.346-0.213-0.659-0.562-0.659h-3.16C119.578,6.749,119.338,6.953,119.295,7.23z"/>
|
||||
<path fill="#253B80" d="M7.266,29.154l0.523-3.322l-1.165-0.027H1.061L4.927,1.292C4.939,1.218,4.978,1.149,5.035,1.1
|
||||
c0.057-0.049,0.13-0.076,0.206-0.076h9.38c3.114,0,5.263,0.648,6.385,1.927c0.526,0.6,0.861,1.227,1.023,1.917
|
||||
c0.17,0.724,0.173,1.589,0.007,2.644l-0.012,0.077v0.676l0.526,0.298c0.443,0.235,0.795,0.504,1.065,0.812
|
||||
c0.45,0.513,0.741,1.165,0.864,1.938c0.127,0.795,0.085,1.741-0.123,2.812c-0.24,1.232-0.628,2.305-1.152,3.183
|
||||
c-0.482,0.809-1.096,1.48-1.825,2c-0.696,0.494-1.523,0.869-2.458,1.109c-0.906,0.236-1.939,0.355-3.072,0.355h-0.73
|
||||
c-0.522,0-1.029,0.188-1.427,0.525c-0.399,0.344-0.663,0.814-0.744,1.328l-0.055,0.299l-0.924,5.855l-0.042,0.215
|
||||
c-0.011,0.068-0.03,0.102-0.058,0.125c-0.025,0.021-0.061,0.035-0.096,0.035H7.266z"/>
|
||||
<path fill="#179BD7" d="M23.048,7.667L23.048,7.667L23.048,7.667c-0.028,0.179-0.06,0.362-0.096,0.55
|
||||
c-1.237,6.351-5.469,8.545-10.874,8.545H9.326c-0.661,0-1.218,0.48-1.321,1.132l0,0l0,0L6.596,26.83l-0.399,2.533
|
||||
c-0.067,0.428,0.263,0.814,0.695,0.814h4.881c0.578,0,1.069-0.42,1.16-0.99l0.048-0.248l0.919-5.832l0.059-0.32
|
||||
c0.09-0.572,0.582-0.992,1.16-0.992h0.73c4.729,0,8.431-1.92,9.513-7.476c0.452-2.321,0.218-4.259-0.978-5.622
|
||||
C24.022,8.286,23.573,7.945,23.048,7.667z"/>
|
||||
<path fill="#222D65" d="M21.754,7.151c-0.189-0.055-0.384-0.105-0.584-0.15c-0.201-0.044-0.407-0.083-0.619-0.117
|
||||
c-0.742-0.12-1.555-0.177-2.426-0.177h-7.352c-0.181,0-0.353,0.041-0.507,0.115C9.927,6.985,9.675,7.306,9.614,7.699L8.05,17.605
|
||||
l-0.045,0.289c0.103-0.652,0.66-1.132,1.321-1.132h2.752c5.405,0,9.637-2.195,10.874-8.545c0.037-0.188,0.068-0.371,0.096-0.55
|
||||
c-0.313-0.166-0.652-0.308-1.017-0.429C21.941,7.208,21.848,7.179,21.754,7.151z"/>
|
||||
<path fill="#253B80" d="M9.614,7.699c0.061-0.393,0.313-0.714,0.652-0.876c0.155-0.074,0.326-0.115,0.507-0.115h7.352
|
||||
c0.871,0,1.684,0.057,2.426,0.177c0.212,0.034,0.418,0.073,0.619,0.117c0.2,0.045,0.395,0.095,0.584,0.15
|
||||
c0.094,0.028,0.187,0.057,0.278,0.086c0.365,0.121,0.704,0.264,1.017,0.429c0.368-2.347-0.003-3.945-1.272-5.392
|
||||
C20.378,0.682,17.853,0,14.622,0h-9.38c-0.66,0-1.223,0.48-1.325,1.133L0.01,25.898c-0.077,0.49,0.301,0.932,0.795,0.932h5.791
|
||||
l1.454-9.225L9.614,7.699z"/>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="124px" height="33px" viewBox="0 0 124 33" enable-background="new 0 0 124 33" xml:space="preserve">
|
||||
<path fill="#253B80" d="M46.211,6.749h-6.839c-0.468,0-0.866,0.34-0.939,0.802l-2.766,17.537c-0.055,0.346,0.213,0.658,0.564,0.658
|
||||
h3.265c0.468,0,0.866-0.34,0.939-0.803l0.746-4.73c0.072-0.463,0.471-0.803,0.938-0.803h2.165c4.505,0,7.105-2.18,7.784-6.5
|
||||
c0.306-1.89,0.013-3.375-0.872-4.415C50.224,7.353,48.5,6.749,46.211,6.749z M47,13.154c-0.374,2.454-2.249,2.454-4.062,2.454
|
||||
h-1.032l0.724-4.583c0.043-0.277,0.283-0.481,0.563-0.481h0.473c1.235,0,2.4,0,3.002,0.704C47.027,11.668,47.137,12.292,47,13.154z"
|
||||
/>
|
||||
<path fill="#253B80" d="M66.654,13.075h-3.275c-0.279,0-0.52,0.204-0.563,0.481l-0.145,0.916l-0.229-0.332
|
||||
c-0.709-1.029-2.29-1.373-3.868-1.373c-3.619,0-6.71,2.741-7.312,6.586c-0.313,1.918,0.132,3.752,1.22,5.031
|
||||
c0.998,1.176,2.426,1.666,4.125,1.666c2.916,0,4.533-1.875,4.533-1.875l-0.146,0.91c-0.055,0.348,0.213,0.66,0.562,0.66h2.95
|
||||
c0.469,0,0.865-0.34,0.939-0.803l1.77-11.209C67.271,13.388,67.004,13.075,66.654,13.075z M62.089,19.449
|
||||
c-0.316,1.871-1.801,3.127-3.695,3.127c-0.951,0-1.711-0.305-2.199-0.883c-0.484-0.574-0.668-1.391-0.514-2.301
|
||||
c0.295-1.855,1.805-3.152,3.67-3.152c0.93,0,1.686,0.309,2.184,0.892C62.034,17.721,62.232,18.543,62.089,19.449z"/>
|
||||
<path fill="#253B80" d="M84.096,13.075h-3.291c-0.314,0-0.609,0.156-0.787,0.417l-4.539,6.686l-1.924-6.425
|
||||
c-0.121-0.402-0.492-0.678-0.912-0.678h-3.234c-0.393,0-0.666,0.384-0.541,0.754l3.625,10.638l-3.408,4.811
|
||||
c-0.268,0.379,0.002,0.9,0.465,0.9h3.287c0.312,0,0.604-0.152,0.781-0.408L84.564,13.97C84.826,13.592,84.557,13.075,84.096,13.075z
|
||||
"/>
|
||||
<path fill="#179BD7" d="M94.992,6.749h-6.84c-0.467,0-0.865,0.34-0.938,0.802l-2.766,17.537c-0.055,0.346,0.213,0.658,0.562,0.658
|
||||
h3.51c0.326,0,0.605-0.238,0.656-0.562l0.785-4.971c0.072-0.463,0.471-0.803,0.938-0.803h2.164c4.506,0,7.105-2.18,7.785-6.5
|
||||
c0.307-1.89,0.012-3.375-0.873-4.415C99.004,7.353,97.281,6.749,94.992,6.749z M95.781,13.154c-0.373,2.454-2.248,2.454-4.062,2.454
|
||||
h-1.031l0.725-4.583c0.043-0.277,0.281-0.481,0.562-0.481h0.473c1.234,0,2.4,0,3.002,0.704
|
||||
C95.809,11.668,95.918,12.292,95.781,13.154z"/>
|
||||
<path fill="#179BD7" d="M115.434,13.075h-3.273c-0.281,0-0.52,0.204-0.562,0.481l-0.145,0.916l-0.23-0.332
|
||||
c-0.709-1.029-2.289-1.373-3.867-1.373c-3.619,0-6.709,2.741-7.311,6.586c-0.312,1.918,0.131,3.752,1.219,5.031
|
||||
c1,1.176,2.426,1.666,4.125,1.666c2.916,0,4.533-1.875,4.533-1.875l-0.146,0.91c-0.055,0.348,0.213,0.66,0.564,0.66h2.949
|
||||
c0.467,0,0.865-0.34,0.938-0.803l1.771-11.209C116.053,13.388,115.785,13.075,115.434,13.075z M110.869,19.449
|
||||
c-0.314,1.871-1.801,3.127-3.695,3.127c-0.949,0-1.711-0.305-2.199-0.883c-0.484-0.574-0.666-1.391-0.514-2.301
|
||||
c0.297-1.855,1.805-3.152,3.67-3.152c0.93,0,1.686,0.309,2.184,0.892C110.816,17.721,111.014,18.543,110.869,19.449z"/>
|
||||
<path fill="#179BD7" d="M119.295,7.23l-2.807,17.858c-0.055,0.346,0.213,0.658,0.562,0.658h2.822c0.469,0,0.867-0.34,0.939-0.803
|
||||
l2.768-17.536c0.055-0.346-0.213-0.659-0.562-0.659h-3.16C119.578,6.749,119.338,6.953,119.295,7.23z"/>
|
||||
<path fill="#253B80" d="M7.266,29.154l0.523-3.322l-1.165-0.027H1.061L4.927,1.292C4.939,1.218,4.978,1.149,5.035,1.1
|
||||
c0.057-0.049,0.13-0.076,0.206-0.076h9.38c3.114,0,5.263,0.648,6.385,1.927c0.526,0.6,0.861,1.227,1.023,1.917
|
||||
c0.17,0.724,0.173,1.589,0.007,2.644l-0.012,0.077v0.676l0.526,0.298c0.443,0.235,0.795,0.504,1.065,0.812
|
||||
c0.45,0.513,0.741,1.165,0.864,1.938c0.127,0.795,0.085,1.741-0.123,2.812c-0.24,1.232-0.628,2.305-1.152,3.183
|
||||
c-0.482,0.809-1.096,1.48-1.825,2c-0.696,0.494-1.523,0.869-2.458,1.109c-0.906,0.236-1.939,0.355-3.072,0.355h-0.73
|
||||
c-0.522,0-1.029,0.188-1.427,0.525c-0.399,0.344-0.663,0.814-0.744,1.328l-0.055,0.299l-0.924,5.855l-0.042,0.215
|
||||
c-0.011,0.068-0.03,0.102-0.058,0.125c-0.025,0.021-0.061,0.035-0.096,0.035H7.266z"/>
|
||||
<path fill="#179BD7" d="M23.048,7.667L23.048,7.667L23.048,7.667c-0.028,0.179-0.06,0.362-0.096,0.55
|
||||
c-1.237,6.351-5.469,8.545-10.874,8.545H9.326c-0.661,0-1.218,0.48-1.321,1.132l0,0l0,0L6.596,26.83l-0.399,2.533
|
||||
c-0.067,0.428,0.263,0.814,0.695,0.814h4.881c0.578,0,1.069-0.42,1.16-0.99l0.048-0.248l0.919-5.832l0.059-0.32
|
||||
c0.09-0.572,0.582-0.992,1.16-0.992h0.73c4.729,0,8.431-1.92,9.513-7.476c0.452-2.321,0.218-4.259-0.978-5.622
|
||||
C24.022,8.286,23.573,7.945,23.048,7.667z"/>
|
||||
<path fill="#222D65" d="M21.754,7.151c-0.189-0.055-0.384-0.105-0.584-0.15c-0.201-0.044-0.407-0.083-0.619-0.117
|
||||
c-0.742-0.12-1.555-0.177-2.426-0.177h-7.352c-0.181,0-0.353,0.041-0.507,0.115C9.927,6.985,9.675,7.306,9.614,7.699L8.05,17.605
|
||||
l-0.045,0.289c0.103-0.652,0.66-1.132,1.321-1.132h2.752c5.405,0,9.637-2.195,10.874-8.545c0.037-0.188,0.068-0.371,0.096-0.55
|
||||
c-0.313-0.166-0.652-0.308-1.017-0.429C21.941,7.208,21.848,7.179,21.754,7.151z"/>
|
||||
<path fill="#253B80" d="M9.614,7.699c0.061-0.393,0.313-0.714,0.652-0.876c0.155-0.074,0.326-0.115,0.507-0.115h7.352
|
||||
c0.871,0,1.684,0.057,2.426,0.177c0.212,0.034,0.418,0.073,0.619,0.117c0.2,0.045,0.395,0.095,0.584,0.15
|
||||
c0.094,0.028,0.187,0.057,0.278,0.086c0.365,0.121,0.704,0.264,1.017,0.429c0.368-2.347-0.003-3.945-1.272-5.392
|
||||
C20.378,0.682,17.853,0,14.622,0h-9.38c-0.66,0-1.223,0.48-1.325,1.133L0.01,25.898c-0.077,0.49,0.301,0.932,0.795,0.932h5.791
|
||||
l1.454-9.225L9.614,7.699z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.4 KiB |
11
.gitignore
vendored
@@ -1,9 +1,16 @@
|
||||
node_modules
|
||||
out
|
||||
dist
|
||||
server
|
||||
tmp
|
||||
|
||||
.vscode
|
||||
.env
|
||||
|
||||
src/package-lock.json
|
||||
src/package.json
|
||||
src/update.ini
|
||||
|
||||
*.exe
|
||||
*.app
|
||||
*.xml.backup
|
||||
*.xml.backup
|
||||
*.js
|
||||
386
LICENSE
@@ -1,21 +1,373 @@
|
||||
MIT License
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
Copyright (c) 2018 Florian Metz
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
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:
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
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.
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
|
||||
50
README.md
@@ -1,8 +1,12 @@
|
||||
<div align="center">
|
||||
|
||||
# <img src="gitassets/icon.png" width="24px" draggable="false"><b> </b>PreMiD · Discord Rich Presence for your Media!
|
||||
<img src=".github/Logo.png" width="150px" draggable="false"><br>
|
||||
|
||||

|
||||
# PreMiD
|
||||
|
||||
## Discord Rich Presence for websites!
|
||||
|
||||

|
||||
[](https://www.codacy.com/project/Timeraa/PreMiD/dashboard?utm_source=github.com&utm_medium=referral&utm_content=PreMiD/PreMiD&utm_campaign=Badge_Grade_Dashboard&branchId=10584370)
|
||||
[](https://discord.gg/WvfVZ8T)
|
||||
|
||||
@@ -10,37 +14,35 @@
|
||||
|
||||
# About
|
||||
|
||||
**PreMiD** is a simple, configurable utility that allows you to show what you're watching/listening in your Discord **now playing status**. It supports many different platforms, and will support multiple users watching the same content simultaneously in an upcoming update.
|
||||
**PreMiD** is a simple, configurable utility that allows you to show what you're doing on the web in your Discord **now playing status**. It supports many different websites, and will support multiple users watching the same content simultaneously in an upcoming update.
|
||||
|
||||
# Features
|
||||
|
||||
· Integrates with Discord's official Rich Presence API library.<br>
|
||||
· Integrates using Discord's official Rich Presence API.<br>
|
||||
· Supports your keyboard's **Media Control** function keys.<br>
|
||||
· Automatically clears your current presence after 1 minute of inactivity.<br>
|
||||
· Supports **YouTube, YouTube Music, Netflix, SoundCloud** and many more to come.<br>
|
||||
· Supports **YouTube**, **YouTube Music**, **Netflix**, **SoundCloud** and many more!<br>
|
||||
· _Watch parties and more are coming soon!_
|
||||
|
||||
# Installation/Troubleshooting
|
||||
|
||||
We moved everything to our (even better) [Wiki](https://wiki.premid.app).
|
||||
### Installation instructions, Troubleshooting guides etc. can be found in our [**Wiki**](https://wiki.premid.app).
|
||||
|
||||
[](https://sourcerer.io/fame/Timeraa/Timeraa/PreMiD/links/0)[](https://sourcerer.io/fame/Timeraa/Timeraa/PreMiD/links/1)[](https://sourcerer.io/fame/Timeraa/Timeraa/PreMiD/links/2)[](https://sourcerer.io/fame/Timeraa/Timeraa/PreMiD/links/3)[](https://sourcerer.io/fame/Timeraa/Timeraa/PreMiD/links/4)[](https://sourcerer.io/fame/Timeraa/Timeraa/PreMiD/links/5)[](https://sourcerer.io/fame/Timeraa/Timeraa/PreMiD/links/6)[](https://sourcerer.io/fame/Timeraa/Timeraa/PreMiD/links/7)
|
||||
# Support us
|
||||
|
||||
<a target="_blank" href="https://www.patreon.com/bePatron?u=4610890" data-patreon-widget-type="become-patron-button"><img src="gitassets/patreonBTN.png" draggable="false" height="76px" alt="Support me on Patreon!"></a>
|
||||
<a target="_blank" href="https://discord.gg/WvfVZ8T" title="Join our Discord!">
|
||||
<img draggable="false" src="https://discordapp.com/api/guilds/493130730549805057/widget.png?style=banner2" height="76px" draggable="false" alt="Join my Discord!">
|
||||
</a>
|
||||
|
||||
<details>
|
||||
<summary><b><u>Other options</u></b> (Click to expand)</summary>
|
||||
|
||||
<ol>
|
||||
|
||||
<a target="_blank" href="https://ko-fi.com/E1E1HLLE"><img draggable="false" height="56px" src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||
<a target="_blank" href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZU8Q766ACS2WS&lc=US"><img src="gitassets/PayPal.svg" height="56px" draggable="false" alt="PayPal"></a>
|
||||
|
||||
#### BitCoin: `18WHUcQbThwExKhUyLGPRFUGSwFqftpsEn`
|
||||
|
||||
</ol>
|
||||
</details>
|
||||
<div style="height:75px">
|
||||
<a target="_blank" href="https://www.patreon.com/bePatron?u=4610890" data-patreon-widget-type="become-patron-button">
|
||||
<img src=".github/Patreon.png" draggable="false" alt="Support me on Patreon!">
|
||||
</a>
|
||||
<a target="_blank" href="https://discord.gg/WvfVZ8T" title="Join our Discord!">
|
||||
<img draggable="false" src="https://discordapp.com/api/guilds/493130730549805057/widget.png?style=banner2" draggable="false" alt="Join my Discord!">
|
||||
</a>
|
||||
</div>
|
||||
<div style="margin-top:10px; height: 50px">
|
||||
<a target="_blank" href="https://ko-fi.com/E1E1HLLE">
|
||||
<img draggable="false" src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||
<a target="_blank" href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ZU8Q766ACS2WS&lc=US">
|
||||
<img height="100%" src=".github/PayPal.svg" draggable="false" alt="PayPal">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
53
devMode.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
console.clear();
|
||||
|
||||
import { copySync, removeSync, writeFileSync } from "fs-extra";
|
||||
import { extname, basename } from "path";
|
||||
import * as glob from "fast-glob";
|
||||
|
||||
(async () => {
|
||||
await removeSync("dist/app/node_modules");
|
||||
|
||||
var dist = await glob("dist/app/**/*", { onlyFiles: true }),
|
||||
src = await glob("src/**/*", { onlyFiles: true });
|
||||
|
||||
//* Delete js files in dist that are no longer existant in src folder
|
||||
await Promise.all(
|
||||
dist
|
||||
.map(f => [
|
||||
f.replace("dist/app/", "").replace(basename(f), basename(f, ".js")),
|
||||
f
|
||||
])
|
||||
.filter(
|
||||
d =>
|
||||
!src
|
||||
.map(f =>
|
||||
f.replace("src/", "").replace(basename(f), basename(f, ".ts"))
|
||||
)
|
||||
.includes(d[0])
|
||||
)
|
||||
.filter(d => extname(d[1]) !== ".map")
|
||||
.map(f => removeSync(f[1]))
|
||||
);
|
||||
|
||||
var packageJSON = require("./package.json"),
|
||||
srcPackageJSON;
|
||||
|
||||
srcPackageJSON = packageJSON;
|
||||
|
||||
delete srcPackageJSON.scripts;
|
||||
srcPackageJSON.devDependencies = {
|
||||
electron: packageJSON.devDependencies.electron
|
||||
};
|
||||
|
||||
await writeFileSync(
|
||||
"./src/package.json",
|
||||
JSON.stringify(srcPackageJSON, null, 2)
|
||||
);
|
||||
|
||||
//* Copy files from src to dist
|
||||
copySync("src", "dist/app", {
|
||||
filter: function(path) {
|
||||
return extname(path) !== ".ts";
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
Before Width: | Height: | Size: 462 KiB |
@@ -1 +0,0 @@
|
||||
<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>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 32 KiB |
@@ -20,7 +20,7 @@
|
||||
<platforms>all</platforms>
|
||||
<distributionFileList>
|
||||
<distributionFile>
|
||||
<origin>update.ini</origin>
|
||||
<origin>../tmp/update.ini</origin>
|
||||
</distributionFile>
|
||||
</distributionFileList>
|
||||
</folder>
|
||||
@@ -106,17 +106,6 @@
|
||||
</runProgram>
|
||||
</preInstallationActionList>
|
||||
<postInstallationActionList>
|
||||
<setInstallerVariable name="update_xml_url" value="https://api.premid.app/app/update/32bit/"/>
|
||||
<setInstallerVariable name="application_version_id" value="0200"/>
|
||||
<writeFile>
|
||||
<path>${installdir}/update.ini</path>
|
||||
<text>[Update]
|
||||
url = ${update_xml_url}
|
||||
version_id = ${application_version_id}
|
||||
update_download_location = ${system_temp_directory}
|
||||
check_for_updates = 1
|
||||
</text>
|
||||
</writeFile>
|
||||
<deleteFile path="${project.rollbackBackupDirectory}"/>
|
||||
<runProgram>
|
||||
<abortOnError>0</abortOnError>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<platforms>all</platforms>
|
||||
<distributionFileList>
|
||||
<distributionFile>
|
||||
<origin>update.ini</origin>
|
||||
<origin>../tmp/update.ini</origin>
|
||||
</distributionFile>
|
||||
</distributionFileList>
|
||||
</folder>
|
||||
@@ -106,17 +106,6 @@
|
||||
</runProgram>
|
||||
</preInstallationActionList>
|
||||
<postInstallationActionList>
|
||||
<setInstallerVariable name="update_xml_url" value="https://api.premid.app/app/update/64bit/"/>
|
||||
<setInstallerVariable name="application_version_id" value="0200"/>
|
||||
<writeFile>
|
||||
<path>${installdir}/update.ini</path>
|
||||
<text>[Update]
|
||||
url = ${update_xml_url}
|
||||
version_id = ${application_version_id}
|
||||
update_download_location = ${system_temp_directory}
|
||||
check_for_updates = 1
|
||||
</text>
|
||||
</writeFile>
|
||||
<deleteFile path="${project.rollbackBackupDirectory}"/>
|
||||
<runProgram>
|
||||
<abortOnError>0</abortOnError>
|
||||
@@ -181,10 +170,10 @@ check_for_updates = 1
|
||||
</ruleList>
|
||||
</runProgram>
|
||||
</preUninstallationActionList>
|
||||
<compressionAlgorithm>lzham-ultra</compressionAlgorithm>
|
||||
<defaultInstallationMode>unattended</defaultInstallationMode>
|
||||
<enableRollback>0</enableRollback>
|
||||
<enableTimestamp>1</enableTimestamp>
|
||||
<osxPlatforms>osx-intel osx-x86_64</osxPlatforms>
|
||||
<outputDirectory>../dist/installer</outputDirectory>
|
||||
<productDisplayIcon>${installdir}/appIcon.ico</productDisplayIcon>
|
||||
<saveRelativePaths>1</saveRelativePaths>
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
var { execFile } = require("child_process"),
|
||||
path = require("path"),
|
||||
bitRockBuilder = "",
|
||||
bitRockUpdater = "";
|
||||
|
||||
if (process.argv.length < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.argv[2] == "windows") {
|
||||
bitRockBuilder = path.resolve(
|
||||
"C:/Program Files (x86)/BitRock InstallBuilder Enterprise 19.4.1/bin/builder-cli.exe"
|
||||
);
|
||||
bitRockUpdater = path.resolve(
|
||||
"C:/Program Files (x86)/BitRock InstallBuilder Enterprise 19.4.1/autoupdate/bin/customize.exe"
|
||||
);
|
||||
}
|
||||
|
||||
if (process.argv[2] == "osx") {
|
||||
bitRockBuilder = path.resolve(
|
||||
"/Applications/BitRock InstallBuilder Enterprise 19.4.1/bin/Builder.app/Contents/MacOS/installbuilder.sh"
|
||||
);
|
||||
bitRockUpdater = path.resolve(
|
||||
"/Applications/BitRock InstallBuilder Enterprise 19.4.1/autoupdate/bin/customize.sh"
|
||||
);
|
||||
}
|
||||
|
||||
execFile(bitRockUpdater, [
|
||||
"build",
|
||||
path.resolve("installer_assets/updater.xml"),
|
||||
process.argv[2]
|
||||
]);
|
||||
|
||||
execFile(bitRockBuilder, [
|
||||
"build",
|
||||
path.resolve("installer_assets/PreMiD.xml"),
|
||||
process.argv[2]
|
||||
]);
|
||||
62
installer_assets/builder.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { execFile } from "child_process";
|
||||
import { platform } from "os";
|
||||
import { resolve } from "path";
|
||||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||
import { parse, stringify } from "ini";
|
||||
import { ensureDirSync, removeSync } from "fs-extra";
|
||||
|
||||
var versionId = "0" + require("../package.json").version.replace(/[.]/g, ""),
|
||||
updateIni = parse(readFileSync("./installer_assets/update.ini", "utf-8"));
|
||||
|
||||
updateIni.Update.version_id = versionId;
|
||||
|
||||
ensureDirSync("./tmp");
|
||||
writeFileSync("./tmp/update.ini", stringify(updateIni));
|
||||
|
||||
var bitRockBuilder = "",
|
||||
bitRockUpdater = "";
|
||||
|
||||
if (platform() === "win32") {
|
||||
bitRockBuilder = resolve(
|
||||
"C:/Program Files (x86)/BitRock Installbuilder/bin/builder-cli.exe"
|
||||
);
|
||||
bitRockUpdater = resolve(
|
||||
"C:/Program Files (x86)/BitRock Installbuilder/autoupdate/bin/customize.exe"
|
||||
);
|
||||
}
|
||||
|
||||
if (platform() === "darwin") {
|
||||
bitRockBuilder = resolve(
|
||||
"/Applications/BitRock Installbuilder/bin/Builder.app/Contents/MacOS/installbuilder.sh"
|
||||
);
|
||||
bitRockUpdater = resolve(
|
||||
"/Applications/BitRock Installbuilder/autoupdate/bin/customize.sh"
|
||||
);
|
||||
}
|
||||
|
||||
if (!existsSync(bitRockUpdater)) {
|
||||
console.log("BitRock installation not found");
|
||||
process.exit(404);
|
||||
}
|
||||
|
||||
var updater: any = execFile(bitRockUpdater, [
|
||||
"build",
|
||||
resolve("./installer_assets/updater.xml"),
|
||||
platform() == "darwin" ? "osx" : "windows"
|
||||
]);
|
||||
|
||||
var builder: any = execFile(bitRockBuilder, [
|
||||
"build",
|
||||
resolve("./installer_assets/PreMiD_x64.xml"),
|
||||
platform() == "darwin" ? "osx" : "windows"
|
||||
]);
|
||||
|
||||
updater.once("close", () => (updater = true));
|
||||
builder.once("close", () => (builder = true));
|
||||
|
||||
var cleanUp = setInterval(() => {
|
||||
if (updater === true && builder === true) {
|
||||
removeSync("./tmp");
|
||||
clearInterval(cleanUp);
|
||||
}
|
||||
}, 100);
|
||||
@@ -1,6 +1,6 @@
|
||||
[Update]
|
||||
|
||||
url = ?
|
||||
version_id = ?
|
||||
check_for_updates = 1
|
||||
update_download_location = ${installer_directory}/updating
|
||||
url=https://api.premid.app/app/update/64bit/
|
||||
version_id= ?
|
||||
check_for_updates=1
|
||||
update_download_location=${installer_directory}/update
|
||||
|
||||
@@ -8,4 +8,5 @@
|
||||
<enableSslSupport>1</enableSslSupport>
|
||||
<outputDirectory>./</outputDirectory>
|
||||
<installerFilename>updater.${platform_exec_suffix}</installerFilename>
|
||||
<osxPlatforms>osx-intel osx-x86_64</osxPlatforms>
|
||||
</autoUpdateProject>
|
||||
4194
package-lock.json
generated
65
package.json
@@ -1,27 +1,42 @@
|
||||
{
|
||||
"name": "premid",
|
||||
"productName": "PreMiD",
|
||||
"version": "2.0.0",
|
||||
"repository": "https://github.com/Timeraa/PreMiD",
|
||||
"scripts": {
|
||||
"start": "npm start --prefix src/.",
|
||||
"dev": "npm run dev --prefix src/.",
|
||||
"pkgmac": "electron-packager ./src/ --out=./dist/ --asar --overwrite --icon=./installer_assets/appIcon.ico --osx-sign.identity='Florian Metz'",
|
||||
"pkgwin": "electron-packager ./src/ --out=./dist/ --overwrite --icon=./installer_assets/appIcon.ico",
|
||||
"pkgwin32": "electron-packager ./src/ --out=./dist/ --overwrite --icon=./installer_assets/appIcon.ico --platform=win32 --arch=ia32",
|
||||
"pkglinux": "electron-packager ./src/ --out=./dist/ --overwrite --icon=./appIcon.ico --asar --platform=linux --arch=x64",
|
||||
"installer_win": "node installer_assets/builder windows"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"chalk": "^2.4.2",
|
||||
"electron-packager": "^14.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"datauri": "^1.1.0",
|
||||
"dotenv": "^6.2.0",
|
||||
"node-fetch": "^2.3.0",
|
||||
"request": "^2.88.0",
|
||||
"request-promise-native": "^1.0.7"
|
||||
}
|
||||
"name": "premid",
|
||||
"productName": "PreMiD",
|
||||
"version": "2.0.1",
|
||||
"repository": "https://github.com/PreMiD/PreMiD",
|
||||
"scripts": {
|
||||
"init": "npm i && tsc pkg devMode installer_assets/builder",
|
||||
"start": "electron dist/app/.",
|
||||
"dev": "nodemon -L --quiet --exec \"node devMode.js & cd dist/app && electron index.js\" -w dist -e js",
|
||||
"pkg": "node devMode && node pkg",
|
||||
"pkgdist": "npm run pkg && node installer_assets/builder compile",
|
||||
"pkgmac": "electron-packager ./src/ --out=./dist/ --asar --overwrite --icon=./installer_assets/appIcon.ico --osx-sign.identity='Florian Metz'",
|
||||
"pkgwin": "electron-packager ./src/ --out=./dist/ --overwrite --icon=./installer_assets/appIcon.ico",
|
||||
"pkgwin32": "electron-packager ./src/ --out=./dist/ --overwrite --icon=./installer_assets/appIcon.ico --platform=win32 --arch=ia32",
|
||||
"pkglinux": "electron-packager ./src/ --out=./dist/ --overwrite --icon=./appIcon.ico --asar --platform=linux --arch=x64"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/auto-launch": "^5.0.1",
|
||||
"@types/discord-rpc": "^3.0.1",
|
||||
"@types/electron-packager": "^13.0.1",
|
||||
"@types/fs-extra": "^7.0.0",
|
||||
"@types/ini": "^1.3.30",
|
||||
"@types/request-promise-native": "^1.0.16",
|
||||
"@types/socket.io": "^2.1.2",
|
||||
"chalk": "^2.4.2",
|
||||
"electron": "5.0.8",
|
||||
"electron-packager": "^12.2.0",
|
||||
"fast-glob": "^3.0.4",
|
||||
"fs-extra": "^8.1.0",
|
||||
"ini": "^1.3.5",
|
||||
"nodemon": "^1.19.1",
|
||||
"source-map-support": "^0.5.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"auto-launch": "5.0.5",
|
||||
"discord-rpc": "3.0.2",
|
||||
"electron-store": "3.3.0",
|
||||
"socket.io": "2.2.0",
|
||||
"sudo-prompt": "9.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
40
pkg.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import electronPackager = require("electron-packager");
|
||||
import { platform } from "os";
|
||||
import { existsSync } from "fs";
|
||||
import { removeSync } from "fs-extra";
|
||||
import { spawn } from "child_process";
|
||||
|
||||
var icon: string;
|
||||
|
||||
var npm = spawn("npm", ["i", "--silent"], {
|
||||
stdio: "inherit",
|
||||
cwd: "./dist/app",
|
||||
detached: true
|
||||
});
|
||||
|
||||
npm.once("close", code => {
|
||||
if (code !== 0) {
|
||||
console.error(`FAIL: ${code}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (platform() == "darwin") icon = "./installer_assets/appIcon.icns";
|
||||
if (platform() == "win32") icon = "./installer_assets/appIcon.ico";
|
||||
|
||||
if (existsSync("./dist/app/update.ini")) removeSync("./dist/app/update.ini");
|
||||
if (existsSync("./dist/app/updater.app"))
|
||||
removeSync("./dist/app/updater.app");
|
||||
if (existsSync("./dist/app/updater.exe"))
|
||||
removeSync("./dist/app/updater.exe");
|
||||
|
||||
electronPackager({
|
||||
dir: "./dist/app",
|
||||
out: "./dist",
|
||||
asar: true,
|
||||
darwinDarkModeSupport: true,
|
||||
icon: icon,
|
||||
overwrite: true
|
||||
}).then(() => {
|
||||
console.log("Successfully packaged app.");
|
||||
});
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
var playback = false,
|
||||
videoTitle,
|
||||
videoAuthor,
|
||||
videoTimestamps,
|
||||
playbackBoolean,
|
||||
smallImageKey,
|
||||
smallImageText
|
||||
|
||||
/**
|
||||
* Updates the Presence data and sends it back
|
||||
* to the background.js for further interaction
|
||||
*/
|
||||
async function updateData() {
|
||||
|
||||
playback =
|
||||
document.location.pathname.includes("/stream/")
|
||||
&& iframe_video != null
|
||||
&& iframe_video.dur != null
|
||||
? true : false
|
||||
|
||||
|
||||
//* If page has all required propertys
|
||||
if(playback) {
|
||||
if($('.entry-title').html().match(new RegExp(" Der Film "), "") != null) {
|
||||
videoTitle = $('.entry-title').html().replace(new RegExp(" Der Film .*"), "")
|
||||
videoAuthor = $('.entry-title').html().match(new RegExp("Der Film .*"), "")
|
||||
} else if($('.entry-title').html().match(new RegExp(" OVA "), "") != null) {
|
||||
videoTitle = $('.entry-title').html().replace(new RegExp(" OVA .*"), "")
|
||||
videoAuthor = $('.entry-title').html().match(new RegExp("OVA .*"), "");
|
||||
} else if($('.entry-title').html().match(new RegExp(" Folge [0-9]*"), "") != " Folge ") {
|
||||
videoTitle = $('.entry-title').html().replace(new RegExp(" Folge [0-9]* .*"), "")
|
||||
videoAuthor = $('.entry-title').html().match(new RegExp(" Folge [0-9]* .*"), "")
|
||||
} else if($('.entry-title').html().match(new RegExp(" Spezial Folge"), "") != null) {
|
||||
videoTitle = $('.entry-title').html().replace(new RegExp(" Spezial Folge .*"), "")
|
||||
videoAuthor = $('.entry-title').html().match(new RegExp(" Spezial Folge .*"), "")
|
||||
} else if($('.entry-title').html().match(new RegExp(" Folge "), "") != null) {
|
||||
videoTitle = $('.entry-title').html().replace(new RegExp(" Folge .*"), "")
|
||||
videoAuthor = $('.entry-title').html().match(new RegExp(" Folge .*"), "")
|
||||
}
|
||||
|
||||
videoTimestamps = getTimestamps(Math.floor(iframe_video.curr), Math.floor(iframe_video.dur))
|
||||
playbackBoolean = !iframe_video.paused
|
||||
smallImageKey = playbackBoolean ? 'play' : 'pause'
|
||||
smallImageText = playbackBoolean ? await getString("presence.playback.playing") : await getString("presence.playback.paused")
|
||||
|
||||
var data = {
|
||||
clientID: '521104874738417664',
|
||||
presenceData: {
|
||||
details: $('<div/>').html(videoTitle).text(),
|
||||
state: $('<div/>').html(videoAuthor).text(),
|
||||
largeImageKey: 'aniflix_lg',
|
||||
largeImageText: chrome.runtime.getManifest().name + ' V' + chrome.runtime.getManifest().version,
|
||||
smallImageKey: smallImageKey,
|
||||
smallImageText: smallImageText,
|
||||
},
|
||||
trayTitle: $('<div/>').html(videoTitle).text(),
|
||||
playback: playbackBoolean,
|
||||
service: 'Aniflix'
|
||||
}
|
||||
|
||||
if(playbackBoolean) {
|
||||
data.presenceData.startTimestamp = videoTimestamps[0]
|
||||
data.presenceData.endTimestamp = videoTimestamps[1]
|
||||
} else {
|
||||
delete data.presenceData.startTimestamp
|
||||
delete data.presenceData.endTimestamp
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage({presence: data})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Aniflix",
|
||||
"description": "Aniflix offers a variety of anime series. In addition, every day many new anime episodes appear on Ger Sub right after Japan release.",
|
||||
"version": 1.0,
|
||||
"color": "#c00",
|
||||
"url": "www.aniflix.tv",
|
||||
"creator": "223238938716798978",
|
||||
"category": "anime"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 26 KiB |
@@ -1,54 +0,0 @@
|
||||
var playback = false,
|
||||
videoTitle,
|
||||
videoAuthor,
|
||||
videoTimestamps,
|
||||
playbackBoolean,
|
||||
smallImageKey,
|
||||
smallImageText
|
||||
|
||||
/**
|
||||
* Updates the Presence data and sends it back
|
||||
* to the background.js for further interaction
|
||||
*/
|
||||
async function updateData() {
|
||||
//console.log($('#showmedia_about_media h4').get(1).innerHTML.trim())
|
||||
playback =
|
||||
$('#showmedia_about_media a:first') != null
|
||||
&& iframe_video.dur != null ? true : false
|
||||
|
||||
//* If page has all required propertys
|
||||
if(playback) {
|
||||
videoTitle = $('#showmedia_about_media a:first').text()
|
||||
videoAuthor = (await getString("presence.episode")).replace("{{episode}}", $('#showmedia_about_media h4').get(1).innerHTML.trim().match(new RegExp("[0-9].*"))[0]) + " - " + $('#showmedia_about_name').text(),
|
||||
|
||||
videoTimestamps = getTimestamps(Math.floor(iframe_video.curr), Math.floor(iframe_video.dur))
|
||||
playbackBoolean = !iframe_video.paused
|
||||
smallImageKey = playbackBoolean ? 'play' : 'pause'
|
||||
smallImageText = playbackBoolean ? await getString("presence.playback.playing") : await getString("presence.playback.paused")
|
||||
|
||||
var data = {
|
||||
clientID: '518544926158487557',
|
||||
presenceData: {
|
||||
details: $('<div/>').html(videoTitle).text(),
|
||||
state: $('<div/>').html(videoAuthor).text(),
|
||||
largeImageKey: 'cr_lg',
|
||||
largeImageText: chrome.runtime.getManifest().name + ' V' + chrome.runtime.getManifest().version,
|
||||
smallImageKey: smallImageKey,
|
||||
smallImageText: smallImageText,
|
||||
},
|
||||
trayTitle: $('<div/>').html(videoTitle).text(),
|
||||
playback: playbackBoolean,
|
||||
service: 'Crunchyroll'
|
||||
}
|
||||
|
||||
if(playbackBoolean) {
|
||||
data.presenceData.startTimestamp = videoTimestamps[0]
|
||||
data.presenceData.endTimestamp = videoTimestamps[1]
|
||||
} else {
|
||||
delete data.presenceData.startTimestamp
|
||||
delete data.presenceData.endTimestamp
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage({presence: data})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Crunchyroll",
|
||||
"description": "Watch the best anime from Crunchyroll online and stream episodes of Bleach, Naruto, Dragon Ball Super, Attack on Titan, Hunter x Hunter, Fairy Tail, and more.",
|
||||
"version": 1.0,
|
||||
"color": "#df6300",
|
||||
"url": "www.crunchyroll.com",
|
||||
"creator": "223238938716798978",
|
||||
"category": "anime"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,95 +0,0 @@
|
||||
var playback = false,
|
||||
videoTitle,
|
||||
videoAuthor,
|
||||
videoTimestamps,
|
||||
playbackBoolean,
|
||||
smallImageKey,
|
||||
smallImageText,
|
||||
|
||||
lastPlaybackState = null,
|
||||
browsingStamp = Math.floor(Date.now()/1000)
|
||||
|
||||
/**
|
||||
* Handles Media Key controls
|
||||
* @param {data} data Data passed by socketConnector.js
|
||||
*/
|
||||
async function handleMediaKeys(data) {
|
||||
if(playback) {
|
||||
switch (data.mediaKeys) {
|
||||
case "pause":
|
||||
playbackBoolean ? $('.VideoContainer video')[0].pause() : $('.VideoContainer video')[0].play()
|
||||
break
|
||||
case "nextTrack":
|
||||
if($('.button-nfplayerNextEpisode') != null) $('.button-nfplayerNextEpisode').click()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Presence data and sends it back
|
||||
* to the background.js for further interaction
|
||||
*/
|
||||
async function updateData() {
|
||||
|
||||
playback =
|
||||
document.location.pathname.includes("/watch")
|
||||
&& $('.video-title h4').text() != ""
|
||||
&& $('.VideoContainer video')[0] != undefined
|
||||
&& !isNaN($('.VideoContainer video')[0].duration)
|
||||
? true : false
|
||||
|
||||
//* Browsing timestamp
|
||||
if(lastPlaybackState != playback) {
|
||||
lastPlaybackState = playback
|
||||
browsingStamp = Math.floor(Date.now()/1000)
|
||||
}
|
||||
|
||||
var data = {
|
||||
clientID: '499981204045430784',
|
||||
presenceData: {
|
||||
largeImageKey: 'nflix_lg',
|
||||
largeImageText: chrome.runtime.getManifest().name + ' V' + chrome.runtime.getManifest().version,
|
||||
smallImageKey: smallImageKey,
|
||||
smallImageText: smallImageText,
|
||||
},
|
||||
trayTitle: $('<div/>').html(videoTitle).text(),
|
||||
playback: playbackBoolean,
|
||||
service: 'Netflix'
|
||||
}
|
||||
|
||||
//* If page has all required propertys
|
||||
if(playback) {
|
||||
videoTitle = $('.video-title h4').text()
|
||||
videoTimestamps = getTimestamps(Math.floor($('.VideoContainer video')[0].currentTime), Math.floor($('.VideoContainer video')[0].duration))
|
||||
playbackBoolean = !$('.VideoContainer video')[0].paused
|
||||
smallImageKey = playbackBoolean ? 'play' : 'pause'
|
||||
smallImageText = playbackBoolean ? await getString("presence.playback.playing") : await getString("presence.playback.paused")
|
||||
|
||||
|
||||
if($('.video-title span').length > 0) {
|
||||
data.presenceData.details = $('<div/>').html(videoTitle).text()
|
||||
data.presenceData.state = $('.video-title span:first').text() + " " + $('.video-title span:last').text()
|
||||
} else {
|
||||
data.presenceData.details = await getString("presence.watching")
|
||||
data.presenceData.state = $('<div/>').html(videoTitle).text()
|
||||
}
|
||||
|
||||
if(playbackBoolean) {
|
||||
data.presenceData.startTimestamp = videoTimestamps[0]
|
||||
data.presenceData.endTimestamp = videoTimestamps[1]
|
||||
} else {
|
||||
delete data.presenceData.startTimestamp
|
||||
delete data.presenceData.endTimestamp
|
||||
}
|
||||
|
||||
} else {
|
||||
data.presenceData.details = await getString("presence.browsing")
|
||||
delete data.presenceData.state
|
||||
delete data.presenceData.smallImageKey
|
||||
data.presenceData.startTimestamp = browsingStamp
|
||||
//* Prevent presence from being cleared after 1 minute
|
||||
data.playback = true
|
||||
}
|
||||
chrome.runtime.sendMessage({presence: data})
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Netflix",
|
||||
"description": "Watch Netflix movies & TV shows online or stream right to your smart TV, game console, PC, Mac, mobile, tablet.",
|
||||
"version": 2.0,
|
||||
"color": "#e50914",
|
||||
"url": "www.netflix.com",
|
||||
"creator": "223238938716798978",
|
||||
"category": "video"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 17 KiB |
@@ -1,59 +0,0 @@
|
||||
var playback = false,
|
||||
videoTitle,
|
||||
videoAuthor,
|
||||
videoTimestamps,
|
||||
playbackBoolean,
|
||||
smallImageKey,
|
||||
smallImageText,
|
||||
urlForVideo,
|
||||
lastURL = null
|
||||
|
||||
/**
|
||||
* Updates the Presence data and sends it back
|
||||
* to the background.js for further interaction
|
||||
*/
|
||||
async function updateData() {
|
||||
urlForVideo = document.location.href;
|
||||
|
||||
if (urlForVideo != lastURL) {
|
||||
lastURL = urlForVideo;
|
||||
startTimeStamp = Math.floor(Date.now() / 1000);
|
||||
}
|
||||
|
||||
//* If page has all required propertys
|
||||
if($('.roomName.on').get(0) != undefined) {
|
||||
|
||||
videoTitle = $('.roomName.on')[0].innerHTML
|
||||
videoAuthor = $('.sessionsCount')[0].innerHTML.match("[0-9]*")[0] + " " + await getString("presence.watching")
|
||||
playbackBoolean = true
|
||||
|
||||
var data = {
|
||||
clientID: '516742299355578380',
|
||||
presenceData: {
|
||||
details: $('<div/>').html(videoTitle).text(),
|
||||
state: $('<div/>').html(videoAuthor).text(),
|
||||
largeImageKey: 'rt_lg',
|
||||
largeImageText: chrome.runtime.getManifest().name + ' V' + chrome.runtime.getManifest().version,
|
||||
startTimestamp: startTimeStamp
|
||||
},
|
||||
trayTitle: $('<div/>').html(videoTitle).text(),
|
||||
playback: playbackBoolean,
|
||||
service: 'Rabb.it'
|
||||
}
|
||||
|
||||
} else if(document.location.pathname == "/") {
|
||||
data = {
|
||||
clientID: '516742299355578380',
|
||||
presenceData: {
|
||||
details: await getString("presence.browsing"),
|
||||
largeImageKey: 'rt_lg',
|
||||
largeImageText: chrome.runtime.getManifest().name + ' V' + chrome.runtime.getManifest().version,
|
||||
startTimestamp: startTimeStamp
|
||||
},
|
||||
trayTitle: "",
|
||||
service: 'Rabbit',
|
||||
playback: true
|
||||
}
|
||||
}
|
||||
chrome.runtime.sendMessage({presence: data})
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Rabb.it",
|
||||
"description": "Discover, share and watch video content anywhere on any device with your friends. React and respond while watching your favorite shows together.",
|
||||
"version": 2.0,
|
||||
"color": "#f60",
|
||||
"url": "www.rabb.it",
|
||||
"creator": "223238938716798978",
|
||||
"category": "video"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 29 KiB |
@@ -1,81 +0,0 @@
|
||||
var playback = false,
|
||||
songTitle,
|
||||
songAuthors,
|
||||
playbackBoolean,
|
||||
smallImageKey,
|
||||
smallImageText
|
||||
|
||||
/**
|
||||
* Handles Media Key controls
|
||||
* @param {data} data Data passed by socketConnector.js
|
||||
*/
|
||||
async function handleMediaKeys(data) {
|
||||
if($('.playbackSoundBadge__titleContextContainer') != undefined) {
|
||||
switch (data.mediaKeys) {
|
||||
case "pause":
|
||||
$('.playControl').click()
|
||||
break
|
||||
case "nextTrack":
|
||||
$('.skipControl__next').click()
|
||||
break
|
||||
case "previousTrack":
|
||||
$('.skipControl__previous').click()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Presence data and sends it back
|
||||
* to the background.js for further interaction
|
||||
*/
|
||||
async function updateData() {
|
||||
playback =
|
||||
$('.playbackSoundBadge') != undefined
|
||||
|
||||
|
||||
//* If page has all required propertys
|
||||
if(playback) {
|
||||
var startTime = Math.floor(Date.now()/1000),
|
||||
endTime = startTime -
|
||||
getSeconds($('.playbackTimeline__timePassed').children().get(1).innerHTML) + getSeconds($('.playbackTimeline__duration').children().get(1).innerHTML);
|
||||
|
||||
songTitle = $('.playbackSoundBadge__titleLink').children().get(1).innerHTML
|
||||
songAuthors = $('.playbackSoundBadge__titleContextContainer').children().get(0).innerHTML
|
||||
playbackBoolean = $('.playControl').hasClass('playing')
|
||||
smallImageKey = playbackBoolean ? 'play' : 'pause'
|
||||
smallImageText = playbackBoolean ? await getString("presence.playback.playing") : await getString("presence.playback.paused")
|
||||
|
||||
var data = {
|
||||
clientID: '501021185887436810',
|
||||
presenceData: {
|
||||
details: $('<div/>').html(songTitle).text(),
|
||||
state: $('<div/>').html(songAuthors).text(),
|
||||
largeImageKey: 'scloud_lg',
|
||||
largeImageText: chrome.runtime.getManifest().name + ' V' + chrome.runtime.getManifest().version,
|
||||
smallImageKey: smallImageKey,
|
||||
smallImageText: smallImageText,
|
||||
},
|
||||
trayTitle: $('<div/>').html(songTitle).text(),
|
||||
playback: playbackBoolean,
|
||||
service: 'SoundCloud'
|
||||
}
|
||||
|
||||
if(playbackBoolean) {
|
||||
data.presenceData.startTimestamp = startTime
|
||||
data.presenceData.endTimestamp = endTime
|
||||
} else {
|
||||
delete data.presenceData.startTimestamp
|
||||
delete data.presenceData.endTimestamp
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage({presence: data})
|
||||
}
|
||||
}
|
||||
|
||||
function getSeconds(string) {
|
||||
const a = string.split(":")
|
||||
|
||||
const seconds = +a[0] * 60 + +a[1]
|
||||
return seconds
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "SoundCloud",
|
||||
"description": "SoundCloud is a music and podcast streaming platform that lets you listen to millions of songs from around the world, or upload your own. Start listening now!",
|
||||
"version": 2.0,
|
||||
"color": "#f50",
|
||||
"url": "soundcloud.com",
|
||||
"creator": "223238938716798978",
|
||||
"category": "music"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 31 KiB |
@@ -1,70 +0,0 @@
|
||||
var playback = false,
|
||||
videoTitle,
|
||||
videoAuthor,
|
||||
videoTimestamps,
|
||||
playbackBoolean,
|
||||
smallImageKey,
|
||||
smallImageText,
|
||||
urlForVideo,
|
||||
lastURL = null
|
||||
|
||||
/**
|
||||
* Handles Media Key controls
|
||||
* @param {data} data Data passed by socketConnector.js
|
||||
*/
|
||||
async function handleMediaKeys(data) {
|
||||
if(playback) {
|
||||
switch (data.mediaKeys) {
|
||||
case "pause":
|
||||
playbackBoolean ? $(".player-video video")[0].pause() : $(".player-video video")[0].play()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Presence data and sends it back
|
||||
* to the background.js for further interaction
|
||||
*/
|
||||
async function updateData() {
|
||||
urlForVideo = document.location.href;
|
||||
|
||||
playback = $(".player-video video")[0] != undefined && $(".video-info__container .tw-mg-b-05 p").innerHTML.split(" · ")[0].length > 0
|
||||
|
||||
//* If page has all required propertys
|
||||
if(playback) {
|
||||
if (urlForVideo != lastURL) {
|
||||
lastURL = urlForVideo;
|
||||
startTimeStamp = Math.floor(Date.now() / 1000);
|
||||
}
|
||||
|
||||
videoTitle = $(".video-info__container .tw-mg-b-05 p").innerHTML.split(" · ")[0]
|
||||
videoAuthor = $(".channel-header__user-avatar h5").innerHTML;
|
||||
playbackBoolean = !$(".player-video video").paused;
|
||||
smallImageKey = playbackBoolean ? 'play' : 'pause'
|
||||
smallImageText = playbackBoolean ? await getString("presence.playback.playing") : await getString("presence.playback.paused")
|
||||
|
||||
var data = {
|
||||
clientID: '501021996336021504',
|
||||
presenceData: {
|
||||
details: $('<div/>').html(videoTitle).text(),
|
||||
state: $('<div/>').html(videoAuthor).text(),
|
||||
largeImageKey: 'twitch_lg',
|
||||
largeImageText: chrome.runtime.getManifest().name + ' V' + chrome.runtime.getManifest().version,
|
||||
smallImageKey: smallImageKey,
|
||||
smallImageText: smallImageText,
|
||||
},
|
||||
trayTitle: $('<div/>').html(videoTitle).text(),
|
||||
playback: playbackBoolean,
|
||||
service: 'Twitch'
|
||||
}
|
||||
|
||||
if(playbackBoolean) {
|
||||
data.presenceData.startTimestamp = startTimeStamp
|
||||
} else {
|
||||
delete data.presenceData.startTimestamp
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage({presence: data})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "Twitch",
|
||||
"description": "We are a global community of millions who come together each day to create their own entertainment.",
|
||||
"version": 2.0,
|
||||
"color": "#6441A4",
|
||||
"url": "www.twitch.tv",
|
||||
"creator": "223238938716798978",
|
||||
"category": "video"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 20 KiB |
@@ -1,111 +0,0 @@
|
||||
var playback = false,
|
||||
songTitle,
|
||||
songAuthors,
|
||||
videoTimestamps,
|
||||
playbackBoolean,
|
||||
smallImageKey,
|
||||
smallImageText
|
||||
|
||||
/**
|
||||
* Handles Media Key controls
|
||||
* @param {data} data Data passed by socketConnector.js
|
||||
*/
|
||||
async function handleMediaKeys(data) {
|
||||
if(playback) {
|
||||
switch (data.mediaKeys) {
|
||||
case "pause":
|
||||
playbackBoolean ? $('.video-stream')[0].pause() : $('.video-stream')[0].play()
|
||||
break
|
||||
case "nextTrack":
|
||||
$('.next-button').click()
|
||||
break
|
||||
case "previousTrack":
|
||||
$('.previous-button').click()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Presence data and sends it back
|
||||
* to the background.js for further interaction
|
||||
*/
|
||||
async function updateData() {
|
||||
playback =
|
||||
$('.ytmusic-player-bar.title').text() != ""
|
||||
&& $('.video-stream')[0] != undefined
|
||||
&& !isNaN($('.video-stream')[0].duration)
|
||||
? true : false
|
||||
|
||||
//* If page has all required propertys
|
||||
if(playback) {
|
||||
songTitle = $('.ytmusic-player-bar.title').text()
|
||||
songAuthors = getAuthors()
|
||||
videoTimestamps = getTimestamps(Math.floor($('.video-stream')[0].currentTime), Math.floor($('.video-stream')[0].duration))
|
||||
playbackBoolean = !$('.video-stream')[0].paused
|
||||
smallImageKey = playbackBoolean ? 'play' : 'pause'
|
||||
smallImageText = playbackBoolean ? await getString("presence.playback.playing") : await getString("presence.playback.paused")
|
||||
|
||||
var data = {
|
||||
clientID: '463151177836658699',
|
||||
presenceData: {
|
||||
details: $('<div/>').html(songTitle).text(),
|
||||
state: $('<div/>').html(songAuthors).text(),
|
||||
largeImageKey: 'ytm_lg',
|
||||
largeImageText: chrome.runtime.getManifest().name + ' V' + chrome.runtime.getManifest().version,
|
||||
smallImageKey: smallImageKey,
|
||||
smallImageText: smallImageText,
|
||||
},
|
||||
trayTitle: $('<div/>').html(songTitle).text(),
|
||||
playback: playbackBoolean,
|
||||
service: 'YouTube Music'
|
||||
}
|
||||
|
||||
if(playbackBoolean) {
|
||||
data.presenceData.startTimestamp = videoTimestamps[0]
|
||||
data.presenceData.endTimestamp = videoTimestamps[1]
|
||||
} else {
|
||||
delete data.presenceData.startTimestamp
|
||||
delete data.presenceData.endTimestamp
|
||||
}
|
||||
|
||||
chrome.runtime.sendMessage({presence: data})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get authors of Song
|
||||
*/
|
||||
function getAuthors() {
|
||||
var songAuthors = [],
|
||||
songAuthorsString = ""
|
||||
|
||||
//* Extract authors as array
|
||||
$(".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())
|
||||
}
|
||||
|
||||
//* Build Song autor string
|
||||
songAuthors.forEach((author, index, authors) => {
|
||||
if (index == 0)
|
||||
songAuthorsString = author;
|
||||
else if (index < authors.length - 2)
|
||||
songAuthorsString = songAuthorsString + ", " + author;
|
||||
else if (index < authors.length - 1) songAuthorsString = songAuthorsString + " and " + author;
|
||||
else songAuthorsString = songAuthorsString + " • " + author;
|
||||
});
|
||||
|
||||
return songAuthorsString
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "YouTube Music",
|
||||
"description": "A new music service with official albums, singles, videos, remixes, live performances and more for Android, iOS and desktop. It's all here.",
|
||||
"version": 2.0,
|
||||
"color": "#da2727",
|
||||
"url": "music.youtube.com",
|
||||
"creator": "223238938716798978",
|
||||
"category": "music"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 8.7 KiB |
@@ -1,107 +0,0 @@
|
||||
var playback = false,
|
||||
videoTitle,
|
||||
videoAuthor,
|
||||
videoTimestamps,
|
||||
playbackBoolean,
|
||||
smallImageKey,
|
||||
smallImageText,
|
||||
extensionData = null
|
||||
|
||||
window.addEventListener("PreMiD_UpdateData", updateData);
|
||||
window.addEventListener("PreMiD_MediaKeys", handleMediaKeys);
|
||||
|
||||
//* Request needed data
|
||||
setTimeout(function() {
|
||||
var event = new CustomEvent('PreMiD_RequestExtensionData', {detail: {strings: {playing: 'presence.playback.playing', paused: 'presence.playback.paused'}, version: "chrome.runtime.getManifest().name + ' V' + chrome.runtime.getManifest().version"}})
|
||||
window.dispatchEvent(event);
|
||||
}, 0)
|
||||
|
||||
//* Bind event to receive Data
|
||||
window.addEventListener("PreMiD_ReceiveExtensionData", function(data) {
|
||||
extensionData = data.detail
|
||||
})
|
||||
|
||||
/**
|
||||
* Handles Media Key controls
|
||||
* @param {data} data Data passed by socketConnector.js
|
||||
*/
|
||||
async function handleMediaKeys(data) {
|
||||
data = data.detail
|
||||
if(playback) {
|
||||
switch (data) {
|
||||
case "pause":
|
||||
playbackBoolean ? $('.video-stream')[0].pause() : $('.video-stream')[0].play()
|
||||
break
|
||||
case "nextTrack":
|
||||
$('.ytp-next-button')[0].click()
|
||||
break
|
||||
case "previousTrack":
|
||||
$('.ytp-prev-button')[0].click()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Presence data and sends it back
|
||||
* to the background.js for further interaction
|
||||
*/
|
||||
async function updateData() {
|
||||
playback =
|
||||
document.location.pathname.includes("/watch")
|
||||
&& $('.ytd-video-primary-info-renderer .title').text() != ""
|
||||
&& $('.video-stream')[0] != undefined
|
||||
&& !isNaN($('.video-stream')[0].duration)
|
||||
? true : false
|
||||
|
||||
//* If page has all required propertys
|
||||
if(playback) {
|
||||
videoTitle = $('.ytd-video-primary-info-renderer .title').text()
|
||||
videoAuthor = $("#upload-info .style-scope .ytd-video-owner-renderer").contents().first().html()
|
||||
videoTimestamps = getTimestamps(Math.floor($('.video-stream')[0].currentTime), Math.floor($('.video-stream')[0].duration))
|
||||
playbackBoolean = !$('.video-stream')[0].paused
|
||||
smallImageKey = playbackBoolean ? 'play' : 'pause'
|
||||
smallImageText = playbackBoolean ? extensionData.strings.playing : extensionData.strings.paused
|
||||
|
||||
var data = {
|
||||
clientID: '463097721130188830',
|
||||
presenceData: {
|
||||
details: $('<div/>').html(videoTitle).text(),
|
||||
state: $('<div/>').html(videoAuthor).text(),
|
||||
largeImageKey: 'yt_lg',
|
||||
largeImageText: extensionData.version,
|
||||
smallImageKey: smallImageKey,
|
||||
smallImageText: smallImageText,
|
||||
},
|
||||
trayTitle: $('<div/>').html(videoTitle).text(),
|
||||
playback: playbackBoolean,
|
||||
service: 'YouTube'
|
||||
}
|
||||
|
||||
if(playbackBoolean) {
|
||||
data.presenceData.startTimestamp = videoTimestamps[0]
|
||||
data.presenceData.endTimestamp = videoTimestamps[1]
|
||||
} else {
|
||||
delete data.presenceData.startTimestamp
|
||||
delete data.presenceData.endTimestamp
|
||||
}
|
||||
|
||||
var event = new CustomEvent('PreMiD_UpdatePresence', {detail: data})
|
||||
window.dispatchEvent(event);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Timestamps
|
||||
* @param {Number} videoTime Song Time seconds
|
||||
* @param {Number} videoDuration Song Duration seconds
|
||||
*/
|
||||
function getTimestamps(videoTime, videoDuration) {
|
||||
var startTime = Date.now();
|
||||
var endTime =
|
||||
Math.floor(startTime / 1000) -
|
||||
videoTime +
|
||||
videoDuration;
|
||||
return [Math.floor(startTime/1000), endTime]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "YouTube",
|
||||
"description": "Enjoy the videos and music you love, upload original content, and share it all with friends, family, and the world on YouTube.",
|
||||
"version": 2.0,
|
||||
"color": "#da2727",
|
||||
"url": "www.youtube.com",
|
||||
"creator": "223238938716798978",
|
||||
"category": "video"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 346 B |
|
Before Width: | Height: | Size: 536 B |
BIN
src/assets/tray/Icon@2x.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src/assets/tray/icon.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
73
src/index.js
@@ -1,73 +0,0 @@
|
||||
//* Import needed variables
|
||||
var { app, BrowserWindow } = require("electron"),
|
||||
pjson = require("./package.json"),
|
||||
os = require("os"),
|
||||
updater = require("./util/updateChecker"),
|
||||
Config = require("electron-store"),
|
||||
options = new Config({ name: "options" });
|
||||
|
||||
//* Clear Console if not packaged
|
||||
if (!app.isPackaged) process.stdout.write("\u001b[2J\u001b[0;0H");
|
||||
|
||||
//* Set app user model id for Notifications on windows
|
||||
app.setAppUserModelId("eu.Timeraa.PreMiD");
|
||||
|
||||
//* Define Global Variables
|
||||
global.PLATFORM = os.platform();
|
||||
global.UPDATEAVAIABLE = "";
|
||||
global.VERSION = pjson.productVersion;
|
||||
|
||||
if (!app.isPackaged) global.VERSIONSTRING = VERSION + "-DEV";
|
||||
else global.VERSIONSTRING = VERSION;
|
||||
|
||||
global.BROWSERCONNECTIONSTATE = "NOT_CONNECTED";
|
||||
global.EXTENSIONSOCKET = null;
|
||||
global.TRAY = null;
|
||||
|
||||
//* Single instance lock
|
||||
app.requestSingleInstanceLock();
|
||||
|
||||
//* Electron initialized
|
||||
app.on("ready", appReady);
|
||||
|
||||
//* Prevent app.quit() when all windows closed
|
||||
app.on("window-all-closed", () => {});
|
||||
|
||||
//* App ready
|
||||
async function appReady() {
|
||||
//* Create BrowserWindow for .setProgressBar
|
||||
var win = new BrowserWindow({ show: false });
|
||||
win.setProgressBar(0);
|
||||
|
||||
//* New Options
|
||||
initOption("autoLaunch");
|
||||
initOption("autoUpdate");
|
||||
initOption("mediaKeys", false);
|
||||
initOption("titleMenubar");
|
||||
|
||||
win.setProgressBar(0.2);
|
||||
|
||||
//* Setup MenuBar
|
||||
require("./tray/createTray")();
|
||||
win.setProgressBar(0.35);
|
||||
|
||||
//* Auto launch
|
||||
require("./util/autoLaunch").init();
|
||||
win.setProgressBar(0.75);
|
||||
|
||||
//* Include PresenceHandler
|
||||
require("./presenceHandler.js");
|
||||
win.setProgressBar(1);
|
||||
win.close();
|
||||
win = null;
|
||||
|
||||
//TODO ONLY HIDE IF NO ERROR
|
||||
if (PLATFORM == "darwin") app.dock.hide();
|
||||
|
||||
//* Automatically check for update
|
||||
if (options.get("autoUpdate") == true) updater.checkForUpdate(true);
|
||||
}
|
||||
|
||||
async function initOption(option, defaultValue = true) {
|
||||
if (options.get(option) == undefined) options.set(option, defaultValue);
|
||||
}
|
||||
59
src/index.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { app, systemPreferences, dialog } from "electron";
|
||||
import { init as initSocket } from "./managers/socketManager";
|
||||
import { init as initTray } from "./managers/trayManager";
|
||||
import { update as initAutoLaunch } from "./managers/launchManager";
|
||||
import { platform } from "os";
|
||||
import inAppFolder from "./util/inAppFolder";
|
||||
import { checkForUpdate } from "./util/updateChecker";
|
||||
|
||||
//* Source mac support
|
||||
if (!app.isPackaged) require("source-map-support").install();
|
||||
|
||||
export var updateCheckerInterval = null;
|
||||
|
||||
//* Set AppUserModelId for task manager etc
|
||||
app.setAppUserModelId("Timeraa.PreMiD");
|
||||
|
||||
//* Disable Hardware Acceleration as we don't render stuff
|
||||
app.disableHardwareAcceleration();
|
||||
|
||||
//* App ready
|
||||
app.once("ready", async () => {
|
||||
//* Handle in App Folder (Mac OS) rejects if user clicks "Quit app"
|
||||
await inAppFolder().catch(() => {
|
||||
app.quit();
|
||||
return;
|
||||
});
|
||||
|
||||
//* Mac OS truted accessability client
|
||||
if (
|
||||
platform() === "darwin" &&
|
||||
!systemPreferences.isTrustedAccessibilityClient(false)
|
||||
)
|
||||
systemPreferences.isTrustedAccessibilityClient(true);
|
||||
|
||||
//* Configure auto launch
|
||||
initAutoLaunch();
|
||||
|
||||
//* Init socket
|
||||
await initSocket();
|
||||
|
||||
//* init application tray icon
|
||||
await initTray();
|
||||
|
||||
//* Check for update
|
||||
checkForUpdate(true);
|
||||
updateCheckerInterval = setInterval(checkForUpdate, 15 * 1000 * 60);
|
||||
|
||||
//* Hide app icon if Mac OS
|
||||
if (platform() === "darwin") app.dock.hide();
|
||||
});
|
||||
|
||||
//* If second instance started, close old one
|
||||
app.on("second-instance", app.quit);
|
||||
|
||||
process.on("uncaughtException", err => {
|
||||
if (platform() === "darwin") app.dock.show();
|
||||
app.focus();
|
||||
dialog.showErrorBox(err.name, err.stack);
|
||||
});
|
||||
224
src/managers/discordManager.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import * as Discord from "discord-rpc";
|
||||
import { app } from "electron";
|
||||
import { platform } from "os";
|
||||
import { tray } from "./trayManager";
|
||||
import { error, info } from "../util/debug";
|
||||
import { init as initInputs, deinit as deinitInputs } from "./inputManager";
|
||||
import { settings } from "./settingsManager";
|
||||
|
||||
interface Presence {
|
||||
/**
|
||||
* Client ID of presence
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Rich Procedual call connection
|
||||
*/
|
||||
rpc: Discord.Client;
|
||||
/**
|
||||
* Connection ready?
|
||||
*/
|
||||
ready: Boolean;
|
||||
}
|
||||
|
||||
interface PresenceData {
|
||||
/**
|
||||
* Client ID of presence
|
||||
*/
|
||||
clientID: string;
|
||||
/**
|
||||
* Tray title to be shown in Mac OS tray
|
||||
*/
|
||||
trayTitle: string;
|
||||
/**
|
||||
* service name of presence
|
||||
* @deprecated
|
||||
*/
|
||||
service: string;
|
||||
/**
|
||||
* Determines if the service is currently playing something back or not, if false it will automatically hide after 1 minute
|
||||
*/
|
||||
playback: boolean;
|
||||
/**
|
||||
* Discord Presence which gets sent directly to Discord app
|
||||
*/
|
||||
presenceData: Discord.Presence;
|
||||
/**
|
||||
* Determines if the service should be hidden (clearActivity)
|
||||
*/
|
||||
hidden: boolean;
|
||||
/**
|
||||
* Determines if the service is mediaKey able / uses them
|
||||
*/
|
||||
mediaKeys: boolean;
|
||||
}
|
||||
|
||||
//* Define Presence array
|
||||
var loggedInPresences: Array<Presence> = [],
|
||||
errorCount = 0,
|
||||
//* Limit in seconds before clearing presence
|
||||
presenceTimeoutLimit = 60,
|
||||
presencePlaybackSwitch = presenceTimeoutLimit,
|
||||
presenceTimeoutInterval = null;
|
||||
|
||||
//* Close all presence connections on app exit to prevent issues
|
||||
app.once("will-quit", () => {
|
||||
info("Closing all rpc connections...");
|
||||
loggedInPresences.map((presence: Presence) => presence.rpc.destroy());
|
||||
});
|
||||
|
||||
/**
|
||||
* Clear PresenceTimout interval
|
||||
*/
|
||||
export function clearPresenceTimeout() {
|
||||
if (!presenceTimeoutInterval) return;
|
||||
|
||||
clearInterval(presenceTimeoutInterval);
|
||||
presenceTimeoutInterval = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys all rpc connections if no playback change since set timeout limit
|
||||
*/
|
||||
function presenceTimeout() {
|
||||
//* Increace if limit not reached
|
||||
if (presencePlaybackSwitch < presenceTimeoutLimit) presencePlaybackSwitch++;
|
||||
|
||||
if (presencePlaybackSwitch == presenceTimeoutLimit) {
|
||||
//* clear input bindings
|
||||
deinitInputs();
|
||||
|
||||
//* Close active rpc connections
|
||||
destroy();
|
||||
|
||||
//* Clear Presence Timeout interval
|
||||
clearPresenceTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Presence function handles data from extension
|
||||
* Opens rpc connection if none exists
|
||||
* Sets activity on discord
|
||||
*/
|
||||
export async function updatePresence(presenceData: PresenceData) {
|
||||
//* If playback == true set to 0, continue
|
||||
//* Else if playBackSwitch is above limit return
|
||||
if (presenceData.playback) presencePlaybackSwitch = 0;
|
||||
else if (presencePlaybackSwitch == presenceTimeoutLimit) return;
|
||||
|
||||
//* Hide presence if specified
|
||||
if (typeof presenceData.hidden === "boolean" && presenceData.hidden) {
|
||||
//* Clear TrayTitle
|
||||
tray.setTitle("");
|
||||
|
||||
//* Unregister keybinds
|
||||
deinitInputs();
|
||||
|
||||
//* Clear activity for all presences
|
||||
loggedInPresences.map(presence =>
|
||||
presence.rpc
|
||||
.clearActivity()
|
||||
.catch(() => errorCount++)
|
||||
.then(() => (errorCount = 0))
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
//* Check if presence found
|
||||
var presence = loggedInPresences.find(
|
||||
lIP => lIP.id === presenceData.clientID
|
||||
);
|
||||
|
||||
//* If not log it in
|
||||
if (!presence) {
|
||||
presence = await loginPresence(presenceData.clientID);
|
||||
if (presence == null) return;
|
||||
} else if (!presence.ready) return;
|
||||
|
||||
//* Set timeout interval if not already
|
||||
if (!presenceTimeoutInterval)
|
||||
presenceTimeoutInterval = setInterval(presenceTimeout, 1000);
|
||||
|
||||
//* set input bindings
|
||||
if (presenceData.mediaKeys) initInputs();
|
||||
else deinitInputs();
|
||||
|
||||
//* Set Activity
|
||||
presence.rpc
|
||||
.setActivity(presenceData.presenceData)
|
||||
.catch(() => errorCount++)
|
||||
.then(() => (errorCount = 0));
|
||||
|
||||
//* Set tray title if os == darwin (Mac OS)
|
||||
if (platform() == "darwin" && settings.get("trayTitle", true)) {
|
||||
presenceData.playback
|
||||
? tray.setTitle(presenceData.trayTitle)
|
||||
: tray.setTitle("");
|
||||
}
|
||||
|
||||
//* If setActivity failed 10 times (10 seconds) close all rpc connections
|
||||
if (errorCount >= 10) {
|
||||
error("Connection to Discord lost, reconnecting...");
|
||||
destroy().then(() => {
|
||||
errorCount = 0;
|
||||
});
|
||||
|
||||
//* Stop timeout interval if set
|
||||
clearPresenceTimeout();
|
||||
}
|
||||
|
||||
//* Increase counter if loggedInPresences > 0
|
||||
if (loggedInPresences.length > 0) errorCount++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create RPC connection to discord
|
||||
* @param clientId client ID of presence
|
||||
*/
|
||||
function loginPresence(clientId: string) {
|
||||
return new Promise<Presence | null>((resolve, reject) => {
|
||||
var presence: Presence = {
|
||||
id: clientId,
|
||||
rpc: new Discord.Client({ transport: "ipc" }),
|
||||
ready: false
|
||||
};
|
||||
|
||||
//* Add presence to object
|
||||
loggedInPresences.push(presence);
|
||||
|
||||
//* Try login with client id
|
||||
presence.rpc.login({ clientId: clientId }).catch(() => {
|
||||
destroy();
|
||||
resolve();
|
||||
});
|
||||
|
||||
//* Once ready change ready to true
|
||||
presence.rpc.once("ready", () => {
|
||||
presence.ready = true;
|
||||
resolve(presence);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy open rpc connections
|
||||
*/
|
||||
export function destroy() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (loggedInPresences.length == 0) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
if (platform() == "darwin") tray.setTitle("");
|
||||
|
||||
Promise.all(
|
||||
loggedInPresences.map((presence: Presence) => {
|
||||
presence.rpc.destroy().catch(() => {});
|
||||
})
|
||||
).then(resolve);
|
||||
|
||||
loggedInPresences = [];
|
||||
});
|
||||
}
|
||||
82
src/managers/inputManager.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { app, globalShortcut } from "electron";
|
||||
import { socket } from "./socketManager";
|
||||
import { info, error, success } from "./../util/debug";
|
||||
import { settings } from "./settingsManager";
|
||||
|
||||
var playPauseSwitch = null,
|
||||
playPauseTimeout = null;
|
||||
|
||||
//* If app will quit deinit shortcuts
|
||||
app.once("will-quit", deinit);
|
||||
|
||||
/**
|
||||
* Register media keyboard shortcuts
|
||||
*/
|
||||
export function init() {
|
||||
//* Check if registered
|
||||
if (
|
||||
globalShortcut.isRegistered("mediaplaypause") ||
|
||||
!settings.get("mediaKeys", true)
|
||||
)
|
||||
return;
|
||||
|
||||
//* Register shortcuts
|
||||
var mpp = globalShortcut.register("mediaplaypause", () => {
|
||||
//* No need to test if not connected
|
||||
if (!socket.connected) return;
|
||||
|
||||
//* Only set this once
|
||||
//* 500ms timeout > switch play/pause, nextrack/previoustrack
|
||||
if (!playPauseTimeout) playPauseTimeout = setTimeout(handlePlayPause, 500);
|
||||
|
||||
//* Increase switch each press
|
||||
playPauseSwitch++;
|
||||
});
|
||||
|
||||
var mnt = globalShortcut.register("medianexttrack", () => {
|
||||
if (socket.connected)
|
||||
socket.emit("mediaKeyHandler", { playback: "nextTrack" });
|
||||
});
|
||||
|
||||
var mpt = globalShortcut.register("mediaprevioustrack", () => {
|
||||
if (socket.connected)
|
||||
socket.emit("mediaKeyHandler", { playback: "previousTrack" });
|
||||
});
|
||||
|
||||
//* Debug either success/error
|
||||
if (mpp && mnt && mpt) success("Registered keyboard shortcuts.");
|
||||
else error("Registering keyboard shortcuts failed.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Play/Pause media key after 500ms timeout
|
||||
*/
|
||||
function handlePlayPause() {
|
||||
switch (playPauseSwitch) {
|
||||
case 1:
|
||||
socket.emit("mediaKeyHandler", { playback: "pause" });
|
||||
break;
|
||||
case 2:
|
||||
socket.emit("mediaKeyHandler", { playback: "nextTrack" });
|
||||
break;
|
||||
case 3:
|
||||
socket.emit("mediaKeyHandler", { playback: "previousTrack" });
|
||||
break;
|
||||
}
|
||||
|
||||
//* Reset switch & timeout vars
|
||||
playPauseSwitch = null;
|
||||
playPauseTimeout = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister all globalShortcuts set by app
|
||||
*/
|
||||
export function deinit() {
|
||||
//* Check if bound
|
||||
if (!globalShortcut.isRegistered("mediaplaypause")) return;
|
||||
|
||||
//* Clear shortcuts
|
||||
info("Clearing keyboard shortcuts...");
|
||||
globalShortcut.unregisterAll();
|
||||
}
|
||||
20
src/managers/launchManager.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import AutoLaunch from "auto-launch";
|
||||
import { app } from "electron";
|
||||
import { settings } from "./settingsManager";
|
||||
import { info } from "../util/debug";
|
||||
|
||||
var autoLaunch = new AutoLaunch({
|
||||
name: app.getName(),
|
||||
isHidden: true
|
||||
});
|
||||
|
||||
export async function update() {
|
||||
if (!app.isPackaged) {
|
||||
info("Skipping autoLaunch.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.get("autoLaunch", true) && !(await autoLaunch.isEnabled()))
|
||||
autoLaunch.enable();
|
||||
else autoLaunch.disable();
|
||||
}
|
||||
64
src/managers/presenceDevManager.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { readdirSync, readFileSync, unwatchFile, watchFile } from "fs";
|
||||
import { dialog, app } from "electron";
|
||||
import { socket } from "./socketManager";
|
||||
import { extname } from "path";
|
||||
import { platform } from "os";
|
||||
|
||||
var presenceDevWatchedFiles = [],
|
||||
currWatchPath = "";
|
||||
|
||||
export async function watchDir(path: string) {
|
||||
var files = await readdirSync(path);
|
||||
currWatchPath = path;
|
||||
presenceDevWatchedFiles = files;
|
||||
|
||||
presenceDevWatchedFiles.map(f => {
|
||||
watchFile(path + "/" + f, { interval: 100 }, async (curr, prev) => {
|
||||
files = await readdirSync(path);
|
||||
readFiles(files, path);
|
||||
});
|
||||
});
|
||||
readFiles(files, path);
|
||||
}
|
||||
|
||||
async function readFiles(files, path) {
|
||||
socket.emit("presenceDevFolder", {
|
||||
files: await Promise.all(
|
||||
files.map(f => {
|
||||
if (extname(f) === ".json")
|
||||
return {
|
||||
file: f,
|
||||
contents: JSON.parse(readFileSync(`${path}/${f}`).toString())
|
||||
};
|
||||
else if (extname(f) === ".js")
|
||||
return { file: f, contents: readFileSync(`${path}/${f}`).toString() };
|
||||
else return;
|
||||
})
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
export async function openFileDialog() {
|
||||
if (presenceDevWatchedFiles.length > 0) {
|
||||
await Promise.all(
|
||||
presenceDevWatchedFiles.map(f => unwatchFile(currWatchPath + "/" + f))
|
||||
);
|
||||
}
|
||||
|
||||
if (platform() === "darwin") app.dock.show();
|
||||
app.focus();
|
||||
dialog.showOpenDialog(
|
||||
null,
|
||||
{
|
||||
properties: ["openDirectory"]
|
||||
},
|
||||
function(path) {
|
||||
//* User clicked cancel
|
||||
if (!path) return;
|
||||
|
||||
//* Start watching folder and send data to extension
|
||||
watchDir(path[0]);
|
||||
if (platform() === "darwin") app.dock.hide();
|
||||
}
|
||||
);
|
||||
}
|
||||
61
src/managers/settingsManager.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import ElectronStore from "electron-store";
|
||||
import { destroy } from "./discordManager";
|
||||
import { tray } from "./trayManager";
|
||||
import { update as updateAutoLaunch } from "./launchManager";
|
||||
import { platform } from "os";
|
||||
import { deinit as deinitInputs, init as initInputs } from "./inputManager";
|
||||
import { info } from "../util/debug";
|
||||
|
||||
interface extensionSettings {
|
||||
/**
|
||||
* If extension is enabled
|
||||
*/
|
||||
enabled: boolean;
|
||||
/**
|
||||
* Autolaunch enabled
|
||||
*/
|
||||
autoLaunch: boolean;
|
||||
/**
|
||||
* Media keys enabled
|
||||
*/
|
||||
mediaKeys: boolean;
|
||||
/**
|
||||
* title menubar (TrayTitle)
|
||||
* @deprecated
|
||||
*/
|
||||
titleMenubar: boolean;
|
||||
/**
|
||||
* language of extension
|
||||
*/
|
||||
language: string;
|
||||
}
|
||||
|
||||
//* Export settings
|
||||
export var settings = new ElectronStore({
|
||||
defaults: {
|
||||
autoLaunch: true,
|
||||
mediaKeys: true,
|
||||
trayTitle: true
|
||||
}
|
||||
});
|
||||
|
||||
export function update(extensionSettings: extensionSettings) {
|
||||
info("Updated settings from extension.");
|
||||
|
||||
//* Destroy rpc connections if disabled
|
||||
if (!extensionSettings.enabled) destroy();
|
||||
//* remove title if disabled
|
||||
if (!extensionSettings.titleMenubar && platform() === "darwin")
|
||||
tray.setTitle("");
|
||||
//* bind/unbind mediakeys
|
||||
if (!extensionSettings.mediaKeys) deinitInputs();
|
||||
else initInputs();
|
||||
//* Update autolaunch if updated
|
||||
if (settings.get("autoLaunch") != extensionSettings.autoLaunch)
|
||||
updateAutoLaunch();
|
||||
|
||||
//* Update Settings
|
||||
settings.set("autoLaunch", extensionSettings.autoLaunch);
|
||||
settings.set("mediaKeys", extensionSettings.mediaKeys);
|
||||
settings.set("trayTitle", extensionSettings.titleMenubar);
|
||||
}
|
||||
92
src/managers/socketManager.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import socketIo from "socket.io";
|
||||
import { createServer, Server } from "http";
|
||||
import { app, dialog } from "electron";
|
||||
import { success, error } from "../util/debug";
|
||||
import {
|
||||
updatePresence,
|
||||
destroy as destroyRPCConnections,
|
||||
clearPresenceTimeout
|
||||
} from "./discordManager";
|
||||
import { deinit as deinitInputs } from "./inputManager";
|
||||
import { update as updateSettings } from "./settingsManager";
|
||||
import { openFileDialog } from "./presenceDevManager";
|
||||
|
||||
export var io: socketIo.Server;
|
||||
export var socket: socketIo.Socket;
|
||||
export var server: Server;
|
||||
|
||||
export function init() {
|
||||
return new Promise(resolve => {
|
||||
server = createServer();
|
||||
io = socketIo(server, { serveClient: false });
|
||||
|
||||
server.listen(3020, () => {
|
||||
resolve();
|
||||
success("Successfully bound port.");
|
||||
});
|
||||
|
||||
//* On Socket errors
|
||||
server.on("error", socketError);
|
||||
|
||||
//* On socket connections
|
||||
io.on("connection", socketConnection);
|
||||
});
|
||||
}
|
||||
|
||||
var retryDiscordClient = null;
|
||||
function socketConnection(cSocket: socketIo.Socket) {
|
||||
//* Show debug
|
||||
success("Connected to extension.");
|
||||
|
||||
//* Set exported socket variable to current socket
|
||||
socket = cSocket;
|
||||
|
||||
//* Handle updateData event
|
||||
socket.on("updateData", updatePresence);
|
||||
|
||||
//* Handle settingsUpdate
|
||||
socket.on("optionUpdate", updateSettings);
|
||||
|
||||
//* Handle presenceDev
|
||||
socket.on("watchPresenceFolder", openFileDialog);
|
||||
|
||||
socket.once("disconnect", () => {
|
||||
//* Clear retryDiscordClient interval
|
||||
if (retryDiscordClient) {
|
||||
clearInterval(retryDiscordClient);
|
||||
retryDiscordClient = null;
|
||||
}
|
||||
|
||||
//* Clear timout interval if set
|
||||
clearPresenceTimeout();
|
||||
|
||||
//* Close open rpc connections
|
||||
destroyRPCConnections().catch(() => {});
|
||||
|
||||
//* Show debug
|
||||
error("Disconnected from extension.");
|
||||
|
||||
//* deinit input bindings
|
||||
deinitInputs();
|
||||
});
|
||||
}
|
||||
|
||||
function socketError(e: any) {
|
||||
error(e.message);
|
||||
|
||||
//* Focus app so user notices
|
||||
app.focus();
|
||||
|
||||
//* If port in use
|
||||
if (e.code === "EADDRINUSE") {
|
||||
//* Show error dialog
|
||||
dialog.showErrorBox(
|
||||
"Error while binding port",
|
||||
`${app.getName()} could not bind to port ${
|
||||
e.port
|
||||
}. Is ${app.getName()} running already?`
|
||||
);
|
||||
}
|
||||
|
||||
app.exit(0);
|
||||
}
|
||||
24
src/managers/trayManager.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Menu, Tray } from "electron";
|
||||
import { join } from "path";
|
||||
|
||||
export var tray: Tray;
|
||||
export var trayContextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
role: "quit"
|
||||
}
|
||||
]);
|
||||
|
||||
/**
|
||||
* Create tray
|
||||
*/
|
||||
export function init() {
|
||||
return new Promise<Tray>(function(resolve) {
|
||||
//* Create tray
|
||||
tray = new Tray(join(__dirname, "../assets/tray/Icon@2x.png"));
|
||||
|
||||
//* Set its context menu
|
||||
tray.setContextMenu(trayContextMenu);
|
||||
//* Resolve promise
|
||||
resolve(tray);
|
||||
});
|
||||
}
|
||||
2027
src/package-lock.json
generated
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"productVersion": "2.0",
|
||||
"productName": "PreMiD",
|
||||
"name": "premid",
|
||||
"description": "Discord Rich Presence for websites.",
|
||||
"version": "2.0.0",
|
||||
"main": "index.js",
|
||||
"author": "Timeraa",
|
||||
"license": "MIT",
|
||||
"homepage": "https://premid.app/",
|
||||
"bugs": {
|
||||
"url": "https://github.com/PreMiD/PreMiD/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PreMiD/PreMiD.git"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "electron ."
|
||||
},
|
||||
"dependencies": {
|
||||
"auto-launch": "5.0.5",
|
||||
"discord-rpc": "3.0.1",
|
||||
"electron-store": "3.2.0",
|
||||
"express": "4.16.4",
|
||||
"socket.io": "2.2.0",
|
||||
"sudo-prompt": "^8.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chalk": "2.4.2",
|
||||
"dotenv": "8.0.0",
|
||||
"electron": "5.0.1"
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
var DiscordRPC = require("discord-rpc"),
|
||||
{ app, dialog } = require("electron"),
|
||||
express = require("express");
|
||||
|
||||
//* Create server to listen for extension
|
||||
var extension = express(),
|
||||
http = require("http"),
|
||||
socketServer = http.createServer(extension),
|
||||
io = require("socket.io")(socketServer);
|
||||
|
||||
//* Load Config
|
||||
const Config = require("electron-store");
|
||||
|
||||
var options = new Config({
|
||||
name: "options"
|
||||
});
|
||||
|
||||
var debug = require("./util/debug");
|
||||
|
||||
//* Define needed variables
|
||||
var lastKeepAliveSwitch = 0;
|
||||
|
||||
//* Keep alive check to automatically remove presence if browser not running/not using YT
|
||||
setInterval(keepAliveCheck, 1000);
|
||||
|
||||
async function keepAliveCheck() {
|
||||
//* Allow up to 5 seconds of potential browser lag
|
||||
if (lastKeepAliveSwitch > 10) {
|
||||
setupServices.forEach(service => {
|
||||
service.rpc.destroy().catch(() => {});
|
||||
});
|
||||
setupServices = [];
|
||||
serviceLogins = [];
|
||||
if (PLATFORM == "darwin") TRAY.setTitle("");
|
||||
}
|
||||
lastKeepAliveSwitch += 1;
|
||||
}
|
||||
|
||||
//* Listen on port 3020
|
||||
socketServer.listen(3020, () => {
|
||||
debug.success(`Listening on Port 3020`);
|
||||
});
|
||||
|
||||
socketServer.on("error", e => {
|
||||
if (e.code == "EADDRINUSE")
|
||||
dialog.showMessageBox({
|
||||
type: "error",
|
||||
title: "Whoopsie! Port already in use...",
|
||||
message: `Whoopsie! Port 3020 is already in use... Is PreMiD running already?`
|
||||
});
|
||||
});
|
||||
|
||||
//* Socket connection event
|
||||
io.on("connection", function(socket) {
|
||||
global.EXTENSIONSOCKET = socket;
|
||||
BROWSERCONNECTIONSTATE = "CONNECTED";
|
||||
|
||||
socket.on("playBackChange", updatePresence);
|
||||
socket.on("updateData", updatePresence);
|
||||
socket.on("optionUpdate", require("./util/settingsHandler"));
|
||||
|
||||
var userClient = new DiscordRPC.Client({ transport: "ipc" });
|
||||
userClient.login({ clientId: "503557087041683458" });
|
||||
userClient.once("ready", () => {
|
||||
discordUser = userClient.user;
|
||||
socket.emit("discordUser", userClient.user);
|
||||
userClient.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
var setupServices = [],
|
||||
serviceLogins = [],
|
||||
presencePauseSwitch = 0,
|
||||
errorCount = 0;
|
||||
|
||||
//* Updates the presence with the incomming data
|
||||
async function updatePresence(data) {
|
||||
lastKeepAliveSwitch = 0;
|
||||
|
||||
if (
|
||||
setupServices.length > 0 &&
|
||||
data.hidden != undefined &&
|
||||
data.hidden == true
|
||||
) {
|
||||
setupServices.map(svice => svice.rpc.clearActivity());
|
||||
return;
|
||||
}
|
||||
|
||||
var setupService = setupServices.find(
|
||||
svice => svice.serviceName == data.service
|
||||
);
|
||||
|
||||
if (!data.playback) presencePauseSwitch++;
|
||||
else presencePauseSwitch = 0;
|
||||
if (presencePauseSwitch >= 60) {
|
||||
if (setupService != null) {
|
||||
require("./util/shortcutHandler").unregister();
|
||||
setupService.rpc.clearActivity();
|
||||
if (PLATFORM == "darwin") TRAY.setTitle("");
|
||||
}
|
||||
} else {
|
||||
if (setupService) {
|
||||
require("./util/shortcutHandler").register();
|
||||
if (options.get("titleMenubar"))
|
||||
if (PLATFORM == "darwin" && data.playback)
|
||||
TRAY.setTitle(data.trayTitle);
|
||||
else TRAY.setTitle("");
|
||||
setupService.rpc
|
||||
.setActivity(data.presenceData)
|
||||
.catch(() => errorCount++)
|
||||
.then(() => (errorCount = 0));
|
||||
|
||||
if (errorCount == 5) {
|
||||
setupServices.map(service => service.rpc.destroy().catch(() => {}));
|
||||
setupServices = [];
|
||||
errorCount = 0;
|
||||
debug.error(
|
||||
"Lost connection to Discord app... Destroying RPC instances."
|
||||
);
|
||||
}
|
||||
|
||||
if (setupServices.length > 0) {
|
||||
errorCount++;
|
||||
}
|
||||
} else {
|
||||
tryLogin(data.service, data.clientID);
|
||||
serviceLogins.push({
|
||||
serviceName: data.service,
|
||||
intervalID: setInterval(
|
||||
() => tryLogin(data.service, data.clientID),
|
||||
10 * 1000
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to login to RPC until connected
|
||||
*/
|
||||
async function tryLogin(service, clientID) {
|
||||
setupServices.push({
|
||||
rpc: new DiscordRPC.Client({ transport: "ipc" }),
|
||||
serviceName: service,
|
||||
ready: false
|
||||
});
|
||||
var serviceRPC = setupServices.find(svice => svice.serviceName == service);
|
||||
serviceRPC.rpc
|
||||
.login({ clientId: clientID })
|
||||
.catch(err => debug.error(`RPC: ${err.message}`));
|
||||
serviceRPC.rpc.once("ready", () => {
|
||||
clearInterval(
|
||||
serviceLogins.find(svice => svice.serviceName == service).intervalID
|
||||
);
|
||||
serviceRPC.ready = true;
|
||||
});
|
||||
}
|
||||
|
||||
app.on("will-quit", () => {
|
||||
debug.info("Closing all active RPC connections...");
|
||||
Promise.all(
|
||||
setupServices.map(service => {
|
||||
service.rpc.clearActivity();
|
||||
service.rpc.destroy();
|
||||
return service;
|
||||
})
|
||||
).then(collection => {
|
||||
debug.success(
|
||||
"Closing all active RPC connections... - Done | " +
|
||||
collection.map(service => service.serviceName).join(", ")
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
var debug = require('../util/debug'),
|
||||
path = require('path'),
|
||||
{ Tray, Menu, app } = require('electron'),
|
||||
cfu = () => require('../util/updateChecker').checkForUpdate(true, true),
|
||||
optionsWindow = () => require('../tray/options').show();
|
||||
|
||||
module.exports = () => {
|
||||
debug.info('Creating Tray...');
|
||||
|
||||
//* Create new Tray
|
||||
TRAY = new Tray(path.join(__dirname, '../assets/images/icon.png'));
|
||||
|
||||
//* Set Tray ToolTip
|
||||
TRAY.setToolTip(`${app.getName()} V${VERSIONSTRING}`);
|
||||
|
||||
//* Create Tray Menu
|
||||
var menuBarMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: `${app.getName()} | V${VERSIONSTRING}`,
|
||||
enabled: false,
|
||||
icon: path.join(__dirname, '../assets/images/icon.png')
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
click: cfu,
|
||||
label: 'Check for Updates'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{ role: 'quit' }
|
||||
]);
|
||||
|
||||
//* Set Tray Menu
|
||||
TRAY.setContextMenu(menuBarMenu);
|
||||
debug.success('Creating Tray - Done');
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
//* Declare needed constants
|
||||
var { app } = require('electron'),
|
||||
AutoLaunch = require('auto-launch'),
|
||||
Config = require('electron-store'),
|
||||
options = new Config({
|
||||
name: 'options'
|
||||
}),
|
||||
debug = require('../util/debug'),
|
||||
autoLaunch = new AutoLaunch({
|
||||
name: 'PreMiD',
|
||||
path: app.getPath('exe'),
|
||||
isHidden: true
|
||||
});
|
||||
|
||||
module.exports.init = async () => {
|
||||
if (!app.isPackaged) {
|
||||
debug.info('Skipping autoLaunch due to development instance.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.get('autoLaunch')) {
|
||||
if (!await autoLaunch.isEnabled()) {
|
||||
debug.info('Adding App to startup items...');
|
||||
autoLaunch
|
||||
.enable()
|
||||
.then(() => debug.success('Adding App to startup items... - Done'))
|
||||
.catch((err) => debug.error('Adding App to startup items... - Error\n' + err.message));
|
||||
} else debug.error('App already added to startup items, skipping...');
|
||||
} else {
|
||||
if (await autoLaunch.isEnabled()) {
|
||||
debug.info('Removing App from startup items...');
|
||||
autoLaunch
|
||||
.disable()
|
||||
.then(() => debug.success('Removing App from startup items... - Done'))
|
||||
.catch((err) => debug.error('Removing App from startup items... - Error\n' + err.message));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
const { app } = require('electron');
|
||||
|
||||
module.exports.info = (message) => {
|
||||
if (!app.isPackaged) {
|
||||
var chalk = require('chalk');
|
||||
console.log(`[${chalk.hex('#596cae')('PreMiD')}] ${chalk.hex('#5050ff')(message)}`);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.success = (message) => {
|
||||
if (!app.isPackaged) {
|
||||
var chalk = require('chalk');
|
||||
console.log(`[${chalk.hex('#596cae')('PreMiD')}] ${chalk.hex('#50ff50')(message)}`);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.error = (message) => {
|
||||
if (!app.isPackaged) {
|
||||
var chalk = require('chalk');
|
||||
console.log(`[${chalk.hex('#596cae')('PreMiD')}] ${chalk.hex('#ff5050')(message)}`);
|
||||
}
|
||||
};
|
||||
26
src/util/debug.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { app } from "electron";
|
||||
if (!app.isPackaged) var chalk = require("chalk");
|
||||
|
||||
/**
|
||||
* Show info message in console
|
||||
* */
|
||||
export function info(message: string) {
|
||||
if (app.isPackaged) return;
|
||||
console.log(`${chalk.bgBlue(chalk.white(" INFO "))} ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show success message in console
|
||||
* */
|
||||
export function success(message: string) {
|
||||
if (app.isPackaged) return;
|
||||
console.log(`${chalk.bgGreen(" SUCCESS ")} ${message}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show error message in console
|
||||
* */
|
||||
export function error(message: string) {
|
||||
if (app.isPackaged) return;
|
||||
console.log(`${chalk.bgRed(" ERROR ")} ${message}`);
|
||||
}
|
||||
43
src/util/inAppFolder.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { platform } from "os";
|
||||
import { app, dialog } from "electron";
|
||||
|
||||
/**
|
||||
* Check if application is in applications folder (Mac OS)
|
||||
* if not tell user via prompt to auto move/quit app
|
||||
*/
|
||||
export default function inAppFolder() {
|
||||
return new Promise((resolve, reject) => {
|
||||
//* Check if platform === darwin, app in applications folder & packaged else resolve promise
|
||||
if (
|
||||
platform() === "darwin" &&
|
||||
!app.isInApplicationsFolder() &&
|
||||
app.isPackaged
|
||||
) {
|
||||
//* Focus app
|
||||
app.focus();
|
||||
//* Show dialog
|
||||
dialog.showMessageBox(
|
||||
null,
|
||||
{
|
||||
message: "Move to Applications folder?",
|
||||
detail: `${app.getName()} must live in the Applications folder to work properly.`,
|
||||
buttons: ["Move to Applications folder", `Quit ${app.getName()}`],
|
||||
defaultId: 0,
|
||||
cancelId: 1
|
||||
},
|
||||
index => {
|
||||
//* If option === "Quit app"
|
||||
if (index === 1) {
|
||||
//* Reject promise & return
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
//* Move to applications folder & resolve promise
|
||||
app.moveToApplicationsFolder();
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
} else resolve();
|
||||
});
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
const Config = require("electron-store");
|
||||
const options = new Config({
|
||||
name: "options"
|
||||
});
|
||||
|
||||
var mediaControls = require("./shortcutHandler"),
|
||||
{ success } = require("../util/debug");
|
||||
|
||||
module.exports = async data => {
|
||||
success("Saved settings from extension");
|
||||
if (options.get("titleMenubar") != data.titleMenubar) {
|
||||
options.set("titleMenubar", data.titleMenubar);
|
||||
if (PLATFORM == "darwin" && !data.titleMenubar) TRAY.setTitle("");
|
||||
}
|
||||
|
||||
if (options.get("autoLaunch") != data.autoLaunch) {
|
||||
options.set("autoLaunch", data.autoLaunch);
|
||||
require("./autoLaunch").init();
|
||||
}
|
||||
|
||||
if (options.get("mediaKeys") != data.mediaKeys) {
|
||||
options.set("mediaKeys", data.mediaKeys);
|
||||
if (data.mediaKeys) mediaControls.register();
|
||||
else mediaControls.unregister();
|
||||
}
|
||||
|
||||
if (options.get("titleMenubar") != data.titleMenubar) {
|
||||
options.set("titleMenubar", data.titleMenubar);
|
||||
if (!data.titleMenubar) TRAY.setTitle("");
|
||||
}
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
var Config = require('electron-store'),
|
||||
options = new Config({
|
||||
name: 'options'
|
||||
}),
|
||||
debug = require('./debug'),
|
||||
{ globalShortcut, app } = require('electron'),
|
||||
pauseSkipToggle = 0,
|
||||
ppTimeout = null;
|
||||
|
||||
module.exports.register = async () => {
|
||||
if (!options.get('mediaKeys') || globalShortcut.isRegistered('mediaplaypause')) return;
|
||||
debug.info('Registering keyboard shortcuts...');
|
||||
|
||||
var nxtTrack = globalShortcut.register('medianexttrack', () => {
|
||||
if (EXTENSIONSOCKET != null) EXTENSIONSOCKET.emit('mediaKeyHandler', { playback: 'nextTrack' });
|
||||
});
|
||||
|
||||
var ppTrack = globalShortcut.register('mediaplaypause', () => {
|
||||
if (pauseSkipToggle < 3) pauseSkipToggle++;
|
||||
else {
|
||||
clearTimeout(ppTimeout);
|
||||
handlePP();
|
||||
}
|
||||
if (!ppTimeout) ppTimeout = setTimeout(handlePP, 500);
|
||||
});
|
||||
|
||||
var prvTrack = globalShortcut.register('mediaprevioustrack', () => {
|
||||
if (EXTENSIONSOCKET != null) EXTENSIONSOCKET.emit('mediaKeyHandler', { playback: 'previousTrack' });
|
||||
});
|
||||
|
||||
if ((ppTrack, nxtTrack, prvTrack)) debug.success('Registering keyboard shortcuts... - Done');
|
||||
else debug.error('Registering keyboard shortcuts... - Error');
|
||||
};
|
||||
|
||||
function handlePP() {
|
||||
ppTimeout = null;
|
||||
if (EXTENSIONSOCKET != null && pauseSkipToggle == 1) EXTENSIONSOCKET.emit('mediaKeyHandler', { playback: 'pause' });
|
||||
if (EXTENSIONSOCKET != null && pauseSkipToggle == 2)
|
||||
EXTENSIONSOCKET.emit('mediaKeyHandler', { playback: 'nextTrack' });
|
||||
if (EXTENSIONSOCKET != null && pauseSkipToggle == 3)
|
||||
EXTENSIONSOCKET.emit('mediaKeyHandler', { playback: 'previousTrack' });
|
||||
pauseSkipToggle = 0;
|
||||
}
|
||||
|
||||
module.exports.unregister = async () => {
|
||||
if (!globalShortcut.isRegistered('mediaplaypause')) return;
|
||||
debug.info('Unregistering keyboard shortcuts...');
|
||||
globalShortcut.unregisterAll();
|
||||
debug.success('Unregistering keyboard shortcuts... - Done');
|
||||
};
|
||||
|
||||
app.on('will-quit', () => {
|
||||
debug.info('Unregistering keyboard shortcuts...');
|
||||
globalShortcut.unregisterAll();
|
||||
debug.success('Unregistering keyboard shortcuts... - Done');
|
||||
});
|
||||
@@ -1,55 +0,0 @@
|
||||
var { app, dialog } = require('electron'),
|
||||
os = require('os'),
|
||||
debug = require('./debug'),
|
||||
{ execFile } = require('child_process'),
|
||||
path = require('path');
|
||||
|
||||
async function checkForUpdate(sendNotification = false, sendNoUpdateInfo = false) {
|
||||
debug.info('Checking for update...');
|
||||
|
||||
var filePath = os.platform();
|
||||
if (filePath == 'win32') filePath = './updater.exe';
|
||||
if (filePath == 'darwin' && !app.isPackaged) filePath = './updater.app/Contents/MacOS/osx-intel';
|
||||
if (filePath == 'darwin' && app.isPackaged) filePath = '/Applications/PreMiD/updater.app/Contents/MacOS/osx-intel';
|
||||
const ls = execFile(filePath, [ '--mode', 'unattended' ]);
|
||||
ls.on('close', (code) => {
|
||||
if (code == 0) {
|
||||
debug.info('Update available!');
|
||||
if (sendNotification)
|
||||
dialog.showMessageBox(
|
||||
{
|
||||
type: 'question',
|
||||
buttons: [ 'Nah, not now', 'Yes!' ],
|
||||
title: 'Update available!',
|
||||
message: `A new version of PreMiD is available for download!\nDo you want to update now?`
|
||||
},
|
||||
function(response) {
|
||||
if (response == 0) return;
|
||||
var sudo = require('sudo-prompt');
|
||||
sudo.exec(
|
||||
`"${path.resolve(
|
||||
'./updater' + osSuffix
|
||||
)}" --mode unattended --unattendedmodebehavior download`,
|
||||
{
|
||||
name: 'PreMiD Updater'
|
||||
},
|
||||
function(error, stdout, stderr) {
|
||||
process.exit(0);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
if (sendNoUpdateInfo)
|
||||
dialog.showMessageBox({
|
||||
type: 'info',
|
||||
title: 'Up to date!',
|
||||
message: `PreMiD is up to date!`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return UPDATEAVAIABLE;
|
||||
}
|
||||
|
||||
module.exports.checkForUpdate = checkForUpdate;
|
||||
91
src/util/updateChecker.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { spawn } from "child_process";
|
||||
import { resolve as pResolve } from "path";
|
||||
import { error, info } from "./debug";
|
||||
import { trayContextMenu } from "../managers/trayManager";
|
||||
import { MenuItem, app } from "electron";
|
||||
import { tray } from "../managers/trayManager";
|
||||
import { platform } from "os";
|
||||
import { updateCheckerInterval } from "../index";
|
||||
var sudoPrompt = require("sudo-prompt");
|
||||
|
||||
var updaterPath: string;
|
||||
|
||||
export async function checkForUpdate(autoUpdate = false) {
|
||||
if (!app.isPackaged) {
|
||||
info("Skipping UpdateChecker");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (platform()) {
|
||||
case "darwin":
|
||||
updaterPath = app.isPackaged
|
||||
? pResolve(
|
||||
"/Applications/PreMiD/updater.app/Contents/MacOS/installbuilder.sh"
|
||||
)
|
||||
: pResolve("./updater.app/Contents/MacOS/installbuilder.sh");
|
||||
break;
|
||||
case "win32":
|
||||
updaterPath = pResolve("./updater.exe");
|
||||
break;
|
||||
}
|
||||
|
||||
var child = spawn(updaterPath, ["--mode", "unattended"]);
|
||||
|
||||
child.on("error", err => {
|
||||
// @ts-ignore
|
||||
if (err.code === "ENOENT") {
|
||||
error("Updater file not found. Skipping updater functions.");
|
||||
clearInterval(updateCheckerInterval);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
child.on("exit", code => {
|
||||
//* If no update return
|
||||
if (code === 1) {
|
||||
info("Up to date!");
|
||||
return;
|
||||
}
|
||||
|
||||
//* If autoUpdate == true
|
||||
if (autoUpdate) {
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
if (trayContextMenu.items.length < 3) {
|
||||
trayContextMenu.insert(
|
||||
0,
|
||||
new MenuItem({
|
||||
label: "Update available!",
|
||||
click() {
|
||||
update();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
trayContextMenu.insert(
|
||||
1,
|
||||
new MenuItem({
|
||||
type: "separator"
|
||||
})
|
||||
);
|
||||
tray.setContextMenu(trayContextMenu);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function update() {
|
||||
sudoPrompt.exec(
|
||||
`${updaterPath} --mode unattended --unattendedmodebehavior download`,
|
||||
{
|
||||
name: app.getName()
|
||||
},
|
||||
(error: Error) => {
|
||||
if (error) {
|
||||
checkForUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
16
tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2018",
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist/app",
|
||||
"removeComments": true,
|
||||
"esModuleInterop": true,
|
||||
"paths": {
|
||||
"*": ["node_modules/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["devMode.ts", "pkg.ts", "installer_assets/builder.ts"]
|
||||
}
|
||||