├── widgets
├── template
│ ├── renderer.js
│ ├── README.md
│ ├── config.json
│ └── index.html
├── spotify-listener
│ ├── execute.bat
│ ├── media
│ │ ├── preview.png
│ │ ├── skip-back.png
│ │ ├── pause-play.png
│ │ ├── skip-forward.png
│ │ ├── pause.svg
│ │ ├── play.svg
│ │ ├── pause-play.svg
│ │ ├── skip-back.svg
│ │ └── skip-forward.svg
│ ├── execute.vbs
│ ├── package.json
│ ├── exec-python.vbs
│ ├── result.json
│ ├── README.md
│ ├── config.json
│ ├── preload.js
│ ├── install.js
│ ├── main.js
│ ├── index.html
│ ├── retrieve-media-playback-info.py
│ ├── renderer.js
│ └── styles.css
├── media_thumb.jpg
└── README.md
├── src
├── execute.bat
├── widgets.bat
├── widgets.vbs
├── renderer.js
├── execute.vbs
├── package.json
├── index.html
├── config.json
├── preload.js
├── install.js
└── main.js
├── .gitignore
├── install.bat
├── .github
└── workflows
│ ├── test.yml
│ └── publish.yml
├── test.bat
├── package.json
├── LICENSE
├── README.md
├── icon.svg
└── bin
└── cli.js
/widgets/template/renderer.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/execute.bat:
--------------------------------------------------------------------------------
1 | cd %~dp0 & npm start
2 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/execute.bat:
--------------------------------------------------------------------------------
1 | cd %~dp0 & npm start
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 | node_modules
3 | widgets/widgets.svg
4 |
--------------------------------------------------------------------------------
/src/widgets.bat:
--------------------------------------------------------------------------------
1 | for /d %%i in ("widgets\*") do "wscript %%i\execute.vbs"
2 |
--------------------------------------------------------------------------------
/widgets/template/README.md:
--------------------------------------------------------------------------------
1 | # Template Widget
2 |
3 | This is an example of a widget
4 |
--------------------------------------------------------------------------------
/src/widgets.vbs:
--------------------------------------------------------------------------------
1 | CreateObject("Wscript.Shell").Run "cmd /c widgets\widgets.bat", 0, False
2 |
--------------------------------------------------------------------------------
/src/renderer.js:
--------------------------------------------------------------------------------
1 | // Here you can add script for your widgets
2 | // NodeJS APIs are available
3 |
--------------------------------------------------------------------------------
/widgets/media_thumb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underpig1/widget-builder/HEAD/widgets/media_thumb.jpg
--------------------------------------------------------------------------------
/widgets/spotify-listener/media/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underpig1/widget-builder/HEAD/widgets/spotify-listener/media/preview.png
--------------------------------------------------------------------------------
/widgets/spotify-listener/media/skip-back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underpig1/widget-builder/HEAD/widgets/spotify-listener/media/skip-back.png
--------------------------------------------------------------------------------
/widgets/spotify-listener/media/pause-play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underpig1/widget-builder/HEAD/widgets/spotify-listener/media/pause-play.png
--------------------------------------------------------------------------------
/widgets/spotify-listener/media/skip-forward.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/underpig1/widget-builder/HEAD/widgets/spotify-listener/media/skip-forward.png
--------------------------------------------------------------------------------
/src/execute.vbs:
--------------------------------------------------------------------------------
1 | CreateObject("Wscript.Shell").Run "cmd /c """ & CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) & "\execute.bat""", 0, False
2 |
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "./main.js",
3 | "scripts": {
4 | "start": "electron ."
5 | },
6 | "devDependencies": {
7 | "electron": "16.0.6"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/execute.vbs:
--------------------------------------------------------------------------------
1 | CreateObject("Wscript.Shell").Run "cmd /c """ & CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) & "\execute.bat""", 0, False
2 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "./main.js",
3 | "scripts": {
4 | "start": "electron ."
5 | },
6 | "devDependencies": {
7 | "electron": "16.0.6"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/exec-python.vbs:
--------------------------------------------------------------------------------
1 | CreateObject("Wscript.Shell").Run "python """ & CreateObject("Scripting.FileSystemObject").GetParentFolderName(WScript.ScriptFullName) & "\retrieve-media-playback-info.py"" loop", 0, False
2 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/result.json:
--------------------------------------------------------------------------------
1 | {"album_artist": "", "album_title": "", "album_track_count": 0, "artist": "", "genres": [], "playback_type": 1, "subtitle": "", "title": "Advertisement", "track_number": 0, "timeline_position": "21", "timeline_duration": "30", "playing": 1}
--------------------------------------------------------------------------------
/widgets/spotify-listener/README.md:
--------------------------------------------------------------------------------
1 | # Spotify listener
2 |
3 | 
4 |
5 | # Install
6 |
7 | First, make sure you install `widget-builder` with `npm` and download this repository.
8 |
9 | `cd` to the `spotify-listener` folder and run:
10 | ```
11 | widgets install
12 | ```
13 |
14 | On restart, the widget should be up and running.
15 |
--------------------------------------------------------------------------------
/install.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | mkdir "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\widgets"
3 | attrib +h "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\widgets" /s /d
4 | xcopy src\widgets.bat "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\widgets"
5 | xcopy src\widgets.vbs "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\"
6 | npm install -g
7 |
--------------------------------------------------------------------------------
/widgets/template/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "template",
3 | "version": "1.0.0",
4 | "description": "Custom desktop widget",
5 | "index": "./index.html",
6 | "properties": {
7 | "x": 1700,
8 | "y": 100,
9 | "width": 100,
10 | "height": 100,
11 | "transparent": false,
12 | "interact": true,
13 | "draggable": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/widgets/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Clock
6 |
7 |
8 | Hello!
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World!
6 |
7 |
8 | Hello World!
9 | Here is your content
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | test:
11 | runs-on: windows-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: actions/setup-node@v2
15 | with:
16 | node-version: 16
17 | - run: npm install -g
18 | - run: npm start
19 | - run: npm test
20 |
--------------------------------------------------------------------------------
/src/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "widget",
3 | "version": "1.0.0",
4 | "description": "Custom desktop widget",
5 | "index": "./index.html",
6 | "properties": {
7 | "x": 100,
8 | "y": 100,
9 | "width": 100,
10 | "height": 100,
11 | "transparent": false,
12 | "interact": true,
13 | "draggable": true,
14 | "top": false
15 | },
16 | "requirements": [],
17 | "install": ""
18 | }
19 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "spotify-listener",
3 | "version": "1.0.0",
4 | "description": "Spotify listener",
5 | "index": "./index.html",
6 | "properties": {
7 | "x": 1450,
8 | "y": 200,
9 | "width": 355,
10 | "height": 215,
11 | "transparent": true,
12 | "interact": true,
13 | "draggable": true,
14 | "top": true
15 | },
16 | "install": "pip install winrt"
17 | }
--------------------------------------------------------------------------------
/test.bat:
--------------------------------------------------------------------------------
1 | REM initialize tests
2 | npm install -g
3 | npm start
4 |
5 | REM test CLI
6 | widgets init example-widgets\clock
7 | cd example-widgets\clock
8 | widgets init
9 | widgets build
10 | widgets publish
11 | cd ../dist
12 | widgets install
13 | widgets list
14 | widgets uninstall clock
15 |
16 | REM test meta
17 | cmd /c "%appdata%\Microsoft\Windows\Start Menu\Programs\Startup\widgets.vbs"
18 | cmd /c "%appdata%\Microsoft\Windows\Start Menu\Programs\Startup\widgets\widgets.bat"
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "widget-builder",
3 | "version": "1.0.3",
4 | "description": "CLI and program to compile HTML into custom desktop widgets",
5 | "license": "MIT",
6 | "keywords": ["windows", "widgets", "javascript", "html"],
7 | "website": "https://github.com/underpig1/widget-builder",
8 | "main": "bin/cli.js",
9 | "bin": {
10 | "widgets": "./bin/cli.js"
11 | },
12 | "scripts": {
13 | "start": "cmd /c install.bat",
14 | "test": "cmd /c test.bat"
15 | },
16 | "author": "underpig",
17 | "dependencies": {
18 | "electron": "^17.2.0",
19 | "fs-extra": "^10.0.1",
20 | "yargs": "^17.4.0",
21 | "path": "^0.12.7"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: publish
2 |
3 | on:
4 | release:
5 | types: [ published ]
6 |
7 | jobs:
8 | test:
9 | runs-on: windows-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - uses: actions/setup-node@v2
13 | with:
14 | node-version: 16
15 | - run: npm install -g
16 | - run: npm start
17 | - run: npm test
18 | publish:
19 | runs-on: windows-latest
20 | steps:
21 | - uses: actions/checkout@v2
22 | - uses: actions/setup-node@v2
23 | with:
24 | node-version: 16
25 | registry-url: https://registry.npmjs.org/
26 | - run: npm publish
27 | env:
28 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
29 |
--------------------------------------------------------------------------------
/widgets/README.md:
--------------------------------------------------------------------------------
1 | # Contributing Widgets
2 |
3 | Here are the instructions for publishing widgets for others to use:
4 | 1) Fork this repository
5 | 2) Prepare your widget for publishing by using `widgets publish [path/to/your/project]`
6 | 3) Find the `dist` folder that was created in the same directory as your widget project
7 | 4) Move it to this folder in your forked repository and rename it to your widget title
8 | 5) Add a `README.md` containing a description and optional preview of your widget
9 | 6) Submit a pull request
10 |
11 | When you submit a pull request, make sure it includes the following:
12 | - A `README.md` with a description and optional preview
13 | - A properly-formatted `config.json` file
14 | - A master html file
15 |
--------------------------------------------------------------------------------
/src/preload.js:
--------------------------------------------------------------------------------
1 | draggable = require("./config.json").properties.draggable
2 |
3 | window.addEventListener("DOMContentLoaded", () => {
4 | if (draggable) {
5 | var top_bar = document.createElement("div")
6 | top_bar.style.position = "absolute"
7 | top_bar.style.width = "100%"
8 | top_bar.style.height = "32px"
9 | top_bar.style.top = top_bar.style.left = 0
10 | top_bar.style.webkitAppRegion = "drag"
11 | top_bar.style.zIndex = "-1"
12 | document.body.appendChild(top_bar)
13 | }
14 | draggable_elements = document.querySelectorAll(".draggable")
15 | for (var el of draggable_elements) {
16 | el.style.webkitAppRegion = "drag"
17 | }
18 |
19 | interactable_elements = document.querySelectorAll(".interact")
20 | for (var el of interactable_elements) {
21 | el.style.webkitAppRegion = "no-drag"
22 | }
23 | })
24 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/preload.js:
--------------------------------------------------------------------------------
1 | draggable = require("./config.json").properties.draggable
2 |
3 | window.addEventListener("DOMContentLoaded", () => {
4 | if (draggable) {
5 | var top_bar = document.createElement("div")
6 | top_bar.style.position = "absolute"
7 | top_bar.style.width = "100%"
8 | top_bar.style.height = "32px"
9 | top_bar.style.top = top_bar.style.left = 0
10 | top_bar.style.webkitAppRegion = "drag"
11 | top_bar.style.zIndex = "-1"
12 | document.body.appendChild(top_bar)
13 | }
14 | draggable_elements = document.querySelectorAll(".draggable")
15 | for (var el of draggable_elements) {
16 | el.style.webkitAppRegion = "drag"
17 | }
18 |
19 | interactable_elements = document.querySelectorAll(".interact")
20 | for (var el of interactable_elements) {
21 | el.style.webkitAppRegion = "no-drag"
22 | }
23 | })
24 |
--------------------------------------------------------------------------------
/src/install.js:
--------------------------------------------------------------------------------
1 | const path = require("path")
2 | const fs = require("fs-extra")
3 | const pkg = require(path.join(__dirname, "config.json"))
4 | const exec = require("child_process").exec
5 |
6 | var target_dir = process.env.APPDATA + "\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\widgets\\" + pkg.name
7 |
8 | fs.copy(__dirname, target_dir).then(() => { console.log("Successfully installed " + pkg.name + "! Use 'widgets list' to see all enabled widgets") }).catch(err => console.error(err))
9 |
10 | setTimeout(() => {
11 | if (pkg.hasOwnProperty("requirements")) {
12 | for (var req of pkg.requirements) {
13 | run_cmd("cd \"" + target_dir + "\" & npm install ")
14 | }
15 | }
16 | if (pkg.hasOwnProperty("install")) {
17 | if (Array.isArray(pkg.install)) {
18 | for (var cmd of pkg.install) {
19 | run_cmd(cmd)
20 | }
21 | }
22 | else {
23 | run_cmd(pkg.install)
24 | }
25 | }
26 | }, 1000)
27 |
28 | function run_cmd(cmd) {
29 | exec(cmd, (err, stdout, stderr) => {
30 | if (err) {
31 | console.error(err)
32 | return
33 | }
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Underpig
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/install.js:
--------------------------------------------------------------------------------
1 | const path = require("path")
2 | const fs = require("fs-extra")
3 | const pkg = require(path.join(__dirname, "config.json"))
4 | const exec = require("child_process").exec
5 |
6 | var target_dir = process.env.APPDATA + "\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\widgets\\" + pkg.name
7 |
8 | fs.copy(__dirname, target_dir).then(() => { console.log("Successfully installed " + pkg.name + "! Use 'widgets list' to see all enabled widgets") }).catch(err => console.error(err))
9 |
10 | setTimeout(() => {
11 | if (pkg.hasOwnProperty("requirements")) {
12 | for (var req of pkg.requirements) {
13 | run_cmd("cd \"" + target_dir + "\" & npm install ")
14 | }
15 | }
16 | if (pkg.hasOwnProperty("install")) {
17 | if (Array.isArray(pkg.install)) {
18 | for (var cmd of pkg.install) {
19 | run_cmd(cmd)
20 | }
21 | }
22 | else {
23 | run_cmd(pkg.install)
24 | }
25 | }
26 | }, 1000)
27 |
28 | function run_cmd(cmd) {
29 | exec(cmd, (err, stdout, stderr) => {
30 | if (err) {
31 | console.error(err)
32 | return
33 | }
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | const {app, BrowserWindow} = require("electron")
2 | const path = require("path")
3 |
4 | const config = require(path.join(__dirname, "config.json"))
5 |
6 | function init() {
7 | const main = new BrowserWindow({
8 | width: config.properties.width,
9 | height: config.properties.height,
10 | frame: false,
11 | transparent: config.properties.transparent,
12 | show: false,
13 | webPreferences: {
14 | preload: path.join(__dirname, "preload.js"),
15 | nodeIntegration: true,
16 | contextIsolation: false
17 | }
18 | })
19 | main.setPosition(config.properties.x, config.properties.y)
20 | main.setSkipTaskbar(true)
21 | main.setResizable(false)
22 | if (!config.properties.interact) {
23 | const top = new BrowserWindow({parent: main, modal: true, transparent: true, frame: false, show: true, width: 0, height: 0})
24 | top.setSkipTaskbar(true)
25 | }
26 | main.setAlwaysOnTop(config.properties.top)
27 | main.once("ready-to-show", () => main.show())
28 | main.loadFile(config.index)
29 | }
30 |
31 | app.whenReady().then(() => {
32 | init()
33 | app.on("activate", function () {
34 | if (BrowserWindow.getAllWindows().length === 0) init()
35 | })
36 | })
37 |
38 | app.on("window-all-closed", function () {
39 | if (process.platform !== "darwin") app.quit()
40 | })
41 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/main.js:
--------------------------------------------------------------------------------
1 | const {app, BrowserWindow} = require("electron")
2 | const path = require("path")
3 |
4 | const config = require(path.join(__dirname, "config.json"))
5 |
6 | function init() {
7 | const main = new BrowserWindow({
8 | width: config.properties.width,
9 | height: config.properties.height,
10 | frame: false,
11 | transparent: config.properties.transparent,
12 | show: false,
13 | webPreferences: {
14 | preload: path.join(__dirname, "preload.js"),
15 | nodeIntegration: true,
16 | contextIsolation: false
17 | }
18 | })
19 | main.setPosition(config.properties.x, config.properties.y)
20 | main.setSkipTaskbar(true)
21 | main.setResizable(false)
22 | if (!config.properties.interact) {
23 | const top = new BrowserWindow({parent: main, modal: true, transparent: true, frame: false, show: true, width: 0, height: 0})
24 | top.setSkipTaskbar(true)
25 | }
26 | main.setAlwaysOnTop(config.properties.top)
27 | main.once("ready-to-show", () => main.show())
28 | main.loadFile(config.index)
29 | }
30 |
31 | app.whenReady().then(() => {
32 | init()
33 | app.on("activate", function () {
34 | if (BrowserWindow.getAllWindows().length === 0) init()
35 | })
36 | })
37 |
38 | app.on("window-all-closed", function () {
39 | if (process.platform !== "darwin") app.quit()
40 | })
41 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Spotify listener
7 |
8 |
9 |
10 |
11 |
12 |
13 |
22 |
28 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/retrieve-media-playback-info.py:
--------------------------------------------------------------------------------
1 | # https://stackoverflow.com/questions/65011660/how-can-i-get-the-title-of-the-currently-playing-media-in-windows-10-with-python
2 |
3 | import asyncio
4 | import sys
5 | from winrt.windows.media.control import GlobalSystemMediaTransportControlsSessionManager as MediaManager
6 | from winrt.windows.storage.streams import DataReader, Buffer, InputStreamOptions
7 | import os
8 | import json
9 | import time
10 |
11 | parent = os.path.dirname(os.path.abspath(__file__))
12 |
13 | async def get_current_session():
14 | sessions = await MediaManager.request_async()
15 | current_session = sessions.get_current_session()
16 | return current_session
17 |
18 | async def retrieve():
19 | global media
20 |
21 | sessions = await MediaManager.request_async()
22 | current_session = sessions.get_current_session()
23 | if current_session:
24 | info = await current_session.try_get_media_properties_async()
25 | info_dir = {song_attr: info.__getattribute__(song_attr) for song_attr in dir(info) if song_attr[0] != "_"}
26 | info_dir["genres"] = list(info_dir["genres"])
27 |
28 | timeline_properties = current_session.get_timeline_properties()
29 | timeline_position = str(timeline_properties.position.duration)[:-7]
30 | timeline_duration = str(timeline_properties.end_time.duration)[:-7]
31 | info_dir["timeline_position"] = timeline_position
32 | info_dir["timeline_duration"] = timeline_duration
33 |
34 | return info_dir
35 |
36 | def get_thumbnail(thumbnail):
37 | async def read_stream_into_buffer(stream_ref, buffer):
38 | readable_stream = await stream_ref.open_read_async()
39 | readable_stream.read_async(buffer, buffer.capacity, InputStreamOptions.READ_AHEAD)
40 |
41 | thumb_read_buffer = Buffer(5000000)
42 | asyncio.run(read_stream_into_buffer(thumbnail, thumb_read_buffer))
43 |
44 | buffer_reader = DataReader.from_buffer(thumb_read_buffer)
45 | byte_buffer = buffer_reader.read_bytes(thumb_read_buffer.length)
46 |
47 | with open(parent + "\\media\\album-cover.jpg", "wb+") as fobj:
48 | fobj.write(bytearray(byte_buffer))
49 |
50 | # main
51 |
52 | if len(sys.argv) > 1:
53 | if sys.argv[1] == "toggle":
54 | try:
55 | current_session = asyncio.run(get_current_session())
56 | current_session.try_toggle_play_pause_async()
57 | sys.stdout.write("1")
58 | except:
59 | pass
60 | elif sys.argv[1] == "loop":
61 | while True:
62 | media = asyncio.run(retrieve())
63 | if media != None:
64 | try:
65 | get_thumbnail(media["thumbnail"])
66 | media = {song_attr: media[song_attr] for song_attr in media if song_attr != "thumbnail"}
67 | media["playing"] = 1
68 | with open(parent + "\\result.json", "w") as file:
69 | json.dump(media, file)
70 | except:
71 | with open(parent + "\\result.json", "r+") as file:
72 | data = json.load(file)
73 | data["playing"] = 0
74 | file.seek(0)
75 | json.dump(data, file)
76 | file.truncate()
77 | else:
78 | with open(parent + "\\result.json", "r+") as file:
79 | data = json.load(file)
80 | data["playing"] = 0
81 | file.seek(0)
82 | json.dump(data, file)
83 | file.truncate()
84 | time.sleep(0.1)
85 | elif sys.argv[1] == "skip-back":
86 | try:
87 | current_session = asyncio.run(get_current_session())
88 | current_session.try_skip_previous_async()
89 | sys.stdout.write("1")
90 | except:
91 | pass
92 | elif sys.argv[1] == "skip-forward":
93 | try:
94 | current_session = asyncio.run(get_current_session())
95 | current_session.try_skip_next_async()
96 | sys.stdout.write("1")
97 | except:
98 | pass
99 | elif sys.argv[1] == "pause":
100 | try:
101 | current_session = asyncio.run(get_current_session())
102 | current_session.try_pause_async()
103 | sys.stdout.write("1")
104 | except:
105 | pass
106 | elif sys.argv[1] == "play":
107 | try:
108 | current_session = asyncio.run(get_current_session())
109 | current_session.try_play_async()
110 | sys.stdout.write("1")
111 | except:
112 | pass
113 | elif sys.argv[1] == "rewind":
114 | try:
115 | current_session = asyncio.run(get_current_session())
116 | current_session.try_rewind_async()
117 | sys.stdout.write("1")
118 | except:
119 | pass
120 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
Widget Builder
4 |

5 |

6 |

7 |
Develop, install, and distribute HTML widgets for your Windows desktop with a simple CLI
8 |
9 |
10 |
11 | # Table of Contents
12 | 1. [Installation](#installation)
13 | 2. [Quickstart Guide](#quickstart-guide)
14 | 3. [Samples](#samples)
15 | 4. [Publishing and installing](#publishing-and-installing-widgets)
16 | 1. [Publishing](#publishing)
17 | 2. [Installing](#installing)
18 | 5. [The Config File](#the-config-file)
19 | 6. [Using the CLI](#using-the-cli)
20 | 7. [Contributing Widgets](#contributing-widgets)
21 |
22 |
23 |
24 | # Installation
25 | Download, unzip, navigate to the folder, and install with `npm`:
26 | ```
27 | $ npm install -g
28 | $ npm start
29 | ```
30 | or download directly from `npm`:
31 | ```
32 | $ npm install -g widget-builder
33 | ```
34 |
35 | # Quickstart Guide
36 | 1) Create a new folder
37 | 2) `cd` to the folder, enter `widgets init`, and fill in your project's name
38 | 3) Mess around with the generated HTML file to customize your widget's appearance
39 | 4) Add and link JavaScript or CSS files to your master HTML file
40 | 5) Modify the config file
41 | 6) Navigate to the folder and enter `widgets build` to locally build and install your widget
42 |
43 |
44 |
45 | # Samples
46 | Check out [Spotify listener](widgets/spotify-listener/media/preview.png), a widget for listening to your favorite tunes:
47 | [](https://github.com/underpig1/widget-builder/tree/master/widgets/spotify-listener)
48 |
49 | # Publishing and installing widgets
50 | ### Publishing
51 | So you want to share your widget for distribution? Here's what to do:
52 | 1) `cd` to your project folder
53 | 2) Run `widgets publish`
54 | 3) A new `dist` folder will be generated in the same directory as your project folder. You can now distribute this folder, and others can install it with `widgets install`
55 |
56 | ### Installing
57 | Here's how to install a widget that was shared with you:
58 | 1) Navigate to the folder
59 | 2) Run `widgets install`
60 |
61 |
62 |
63 | # The Config File
64 | Every widgets project contains a `config.json` file. This file tells the program what settings you would like to use for your widget.
65 | Here's a standard config file:
66 | ```json
67 | {
68 | "name": "widget",
69 | "version": "1.0.0",
70 | "description": "Custom desktop widget",
71 | "index": "./index.html",
72 | "properties": {
73 | "x": 100,
74 | "y": 100,
75 | "width": 100,
76 | "height": 100,
77 | "transparent": false,
78 | "interact": true,
79 | "draggable": true
80 | }
81 | }
82 | ```
83 | | Property | Definition |
84 | | ---- | ---- |
85 | | `name` (string) | Project name |
86 | | `version` (string) | Project version |
87 | | `index` (string) | The reference to your master HTML file. Other references (like JS or CSS) should be linked in this file. |
88 | | `x`, `y`, `width`, and `height` (integers) | The position and dimensions of your widget when it is first start up |
89 | | `transparent` (boolean) | Make the widget's background transparent |
90 | | `interact` (boolean) | Make the widget interactable |
91 | | `draggable` (boolean) | Make the widget draggable |
92 | | `top` (boolean) | Make the widget stay on top of all windows |
93 | | `requirements` (array) | npm packages required for the widget to function; these packages are locally installed when the widget is installed |
94 | | `install` (string or array) | Script(s) to run during widget installation |
95 |
96 |
97 |
98 | # Using the CLI
99 | Once widget builder is installed, the CLI can be accessed with the keyword `widgets`
100 |
101 | ### Commands
102 | | Command | Definition |
103 | | ---- | ---- |
104 | | `widgets build [folder]` | Builds HTML files to desktop widget and installs |
105 | | `widgets publish [folder]` | Generates a dist file that can be installed by the widgets cli |
106 | | `widgets install [folder]` | Installs widget at folder |
107 | | `widgets init [folder]` | Initializes widgets project |
108 | | `widgets list` | Lists all installed widgets |
109 | | `widgets uninstall ` | Uninstall widget by name |
110 | | `widgets config ` | Configure widget by name |
111 | | `widgets start [folder]` | Starts the widget at folder |
112 |
113 | ### Options
114 | | Command | Definition |
115 | | ---- | ---- |
116 | | `widgets --help` | Show help |
117 | | `widgets --version` | Displays the current version |
118 |
119 | # Contributing Widgets
120 | [Here](widgets/README.md) you can find instructions for sharing widgets you have created
121 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/renderer.js:
--------------------------------------------------------------------------------
1 | var child_process = require("child_process")
2 | var path = require("path")
3 | const fs = require("fs")
4 |
5 | // MediaController("result", (result) => {
6 | // var result = JSON.parse(result.replace(/'(?=(?:(?:[^"]*"){2})*[^"]*$)/g, "\""))
7 | // console.log(result)
8 | // })
9 |
10 | child_process.exec(`python \"${path.join(__dirname, "retrieve-media-playback-info.py")}\" loop`, (err, stdout, stderr) => {
11 | if (err) {
12 | console.log(err)
13 | return
14 | }
15 | })
16 |
17 | var progress_time = document.getElementById("progress-time")
18 | var progress_indicator = document.getElementById("progress-indicator")
19 | var song_title = document.getElementById("song-title")
20 | var song_artist = document.getElementById("song-artist")
21 | var pause_play = document.getElementById("pause-play")
22 | var album_cover = ".\\media\\album-cover.jpg"
23 | var pause = `url("media/pause.svg")`
24 | var play = `url("media/play.svg")`
25 | var main = document.getElementById("main")
26 | var backup = document.getElementById("backup")
27 | var preview = document.getElementById("preview")
28 | var preview_backup = document.getElementById("preview-backup")
29 | var paused = true
30 | var closed = true
31 |
32 | MediaController("pause")
33 |
34 | function MediaController(command, callback = null) {
35 | child_process.exec(`python \"${path.join(__dirname, "retrieve-media-playback-info.py")}\" ${command}`, (err, stdout, stderr) => {
36 | if (err) {
37 | console.log(err)
38 | return
39 | }
40 | if (callback != null) {
41 | callback(stdout)
42 | }
43 | })
44 | }
45 |
46 | function skipBack() {
47 | MediaController("skip-back")
48 | paused = false
49 | }
50 | function pausePlay() {
51 | if (!closed) {
52 | if (paused) {
53 | MediaController("play")
54 | paused = false
55 | }
56 | else {
57 | MediaController("pause")
58 | paused = true
59 | }
60 | }
61 | }
62 | function skipForward() {
63 | MediaController("skip-forward")
64 | paused = false
65 | }
66 |
67 | var previous_timer = [0, 0]
68 | var previous_result = [0, 0]
69 |
70 | setInterval(() => {
71 | try {
72 | var result = JSON.parse(fs.readFileSync(path.join(__dirname, "result.json")))
73 | }
74 | catch {
75 | var result = {"playing": 0}
76 | }
77 | if (result.playing == 1) {
78 | var closed = false
79 | }
80 | else {
81 | var closed = true
82 | }
83 | progress_indicator.style.width = Math.floor(parseInt(result.timeline_position)/parseInt(result.timeline_duration) * 100).toString() + "%"
84 | if (result.timeline_position != "") {
85 | var minutes = Math.floor(parseInt(result.timeline_position) / 60)
86 | var seconds = Math.floor(parseInt(result.timeline_position) - minutes * 60)
87 | if (previous_result.toString() == [seconds, minutes].toString() && !paused && !closed) {
88 | seconds = previous_timer[0]
89 | minutes = previous_timer[1]
90 | seconds == 59 ? (minutes += 1, seconds = 0) : seconds += 1
91 | song_title.innerText = `past: ${previous_timer[0]} now: ${seconds}`
92 | }
93 | else {
94 | previous_result = [seconds, minutes]
95 | }
96 | previous_timer = [seconds, minutes]
97 | seconds = seconds.toString()
98 | parseInt(seconds) < 10 ? seconds = "0" + seconds : null
99 | minutes = minutes.toString()
100 | }
101 | else {
102 | seconds = "00"
103 | minutes = "0"
104 | }
105 | progress_time.innerText = `${minutes}:${seconds}`
106 | song_title.innerText = result.title
107 | song_artist.innerText = result.artist
108 | main.style.background = "url(\"media/album-cover.jpg\")"
109 | if (song_title.innerText.length >= 16) {
110 | song_title.style.animation = `loop-scroll ${song_title.innerText.length/2}s linear infinite`
111 | }
112 | else {
113 | song_title.style.animation = "none"
114 | }
115 | if (song_artist.innerText.length >= 16) {
116 | song_artist.style.animation = `loop-scroll ${song_artist.innerText.length/2}s linear infinite`
117 | song_artist.style.right = "auto"
118 | song_artist.style.left = "0"
119 | }
120 | else {
121 | song_artist.style.animation = "none"
122 | song_artist.style.right = "0"
123 | song_artist.style.left = "auto"
124 | }
125 | if (paused) {
126 | pause_play.style.backgroundImage = play
127 | pause_play.childNodes[0].childNodes[0].style.backgroundImage = play
128 | }
129 | else {
130 | pause_play.style.backgroundImage = pause
131 | pause_play.childNodes[0].childNodes[0].style.backgroundImage = pause
132 | }
133 | var time = new Date().getTime()
134 | main.style.setProperty("background-image", "url('media/album-cover.jpg?v=" + time + "')", "important")
135 | setTimeout(() => {
136 | backup.style.setProperty("background-image", "url('media/album-cover.jpg?v=" + time + "')", "important")
137 | }, 500)
138 | preview.style.setProperty("background-image", "url('media/album-cover.jpg?v=" + time + "')", "important")
139 | setTimeout(() => {
140 | preview_backup.style.setProperty("background-image", "url('media/album-cover.jpg?v=" + time + "')", "important")
141 | }, 500)
142 | }, 1000)
143 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | .main {
7 | display: flex;
8 | width: 345px;
9 | height: 200px;
10 | border-radius: 12px;
11 | background-size: 135% !important;
12 | box-shadow: 0 5px 20px #00000011;
13 | background: #505050;
14 | background-position: center center !important;
15 | background-repeat: no-repeat;
16 | }
17 |
18 | .gradient {
19 | display: block;
20 | position: absolute;
21 | width: inherit;
22 | height: inherit;
23 | top: 0;
24 | left: 0;
25 | border-radius: inherit;
26 | background-size: 100%;
27 | background-image: linear-gradient(to bottom, #00000050, #000000FF);
28 | }
29 |
30 | #backup {
31 | display: block;
32 | position: absolute;
33 | width: inherit;
34 | height: inherit;
35 | top: 0;
36 | left: 0;
37 | border-radius: inherit;
38 | background: #505050;
39 | background-size: inherit !important;
40 | background-position: inherit !important;
41 | background-repeat: inherit;
42 | z-index: -1;
43 | }
44 |
45 | #preview {
46 | position: absolute;
47 | top: 17.5px;
48 | right: 30px;
49 | width: 50px;
50 | height: 50px;
51 | border-radius: 5px;
52 | background-size: 135% !important;
53 | box-shadow: 0 5px 20px #00000011;
54 | background: #505050;
55 | background-position: center top !important;
56 | background-repeat: no-repeat;
57 | }
58 |
59 | #preview-backup {
60 | position: absolute;
61 | top: 0;
62 | right: 0;
63 | width: 100%;
64 | height: 100%;
65 | border-radius: inherit;
66 | background-size: inherit !important;
67 | box-shadow: inherit;
68 | background: #505050;
69 | background-position: inherit !important;
70 | background-repeat: inherit;
71 | }
72 |
73 | .sector {
74 | display: flex;
75 | position: absolute;
76 | min-width: 345px;
77 | left: 0;
78 | align-items: center;
79 | }
80 |
81 | p {
82 | font-size: 12;
83 | font-family: "Segoe UI";
84 | color: #D0D0D0;
85 | }
86 |
87 | .buttons {
88 | display: inline-flex;
89 | margin-top: 15px;
90 | margin-left: auto;
91 | margin-right: auto;
92 | }
93 |
94 | .btn {
95 | display: inline-table;
96 | border: none;
97 | border-radius: 17.5px;
98 | outline: none;
99 | background: #505050;
100 | transition: transform 0.1s;
101 | transform: scale(1);
102 | background-size: cover;
103 | clip-path: inset(0px round 17.5px);
104 | }
105 |
106 | .btn-small {
107 | position: relative;
108 | width: 35px;
109 | height: 35px;
110 | border-radius: 17.5px;
111 | }
112 |
113 | .btn-large {
114 | width: 45px;
115 | height: 45px;
116 | border-radius: 22.5px;
117 | margin-left: 25px;
118 | margin-right: 25px;
119 | clip-path: inset(0px round 22.5px);
120 | }
121 |
122 | .btn:active {
123 | outline: none;
124 | transform: scale(0.9);
125 | }
126 |
127 | .hover-space {
128 | display: inline-block;
129 | position: absolute;
130 | left: 0;
131 | top: 100%;
132 | width: 100%;
133 | height: 100%;
134 | background: #D0D0D0;
135 | transition: top 0.2s;
136 | }
137 |
138 | .hover-space div {
139 | display: inline-block;
140 | position: absolute;
141 | left: 0;
142 | top: -100%;
143 | width: 100%;
144 | height: 100%;
145 | opacity: 1;
146 | pointer-events: none;
147 | filter: invert(29%) sepia(0%) saturate(1%) hue-rotate(357deg) brightness(99%) contrast(89%);
148 | background-size: cover;
149 | transition: top 0.2s, opacity 0.2s;
150 | }
151 |
152 | .btn:hover .hover-space {
153 | top: 0;
154 | }
155 |
156 | .btn:hover .hover-space div {
157 | top: 0;
158 | }
159 |
160 | .btn:active .hover-space div {
161 | opacity: 0 !important;
162 | }
163 |
164 | .info {
165 | text-align: center;
166 | }
167 |
168 | .progress-bar {
169 | display: inline-flex;
170 | position: absolute;
171 | top: 35px;
172 | left: 35px;
173 | right: 35px;
174 | height: 6px;
175 | border-radius: 6px;
176 | background: #505050;
177 | }
178 |
179 | #progress-indicator {
180 | display: inline-flex;
181 | position: absolute;
182 | height: 6px;
183 | width: 98%;
184 | border-radius: 6px;
185 | background: #303030;
186 | transition: width 0.1s;
187 | }
188 |
189 | #skip-back, #skip-back div {
190 | background-image: url("media/skip-back.svg");
191 | }
192 |
193 | #pause-play, #pause-play div {
194 | background-image: url("media/play.svg");
195 | }
196 |
197 | #skip-forward, #skip-forward div {
198 | background-image: url("media/skip-forward.svg");
199 | }
200 |
201 | .scroll-title {
202 | position: absolute;
203 | margin: 0;
204 | padding: 0;
205 | width: 130px;
206 | height: 45px;
207 | overflow-x: hidden;
208 | white-space: nowrap;
209 | }
210 |
211 | .scroll-title.l p {
212 | left: 0;
213 | }
214 |
215 | .scroll-title.r p {
216 | right: 0;
217 | }
218 |
219 | .scroll-title p {
220 | position: absolute;
221 | top: 0;
222 | }
223 |
224 | ::-webkit-scrollbar {
225 | display: none;
226 | }
227 |
228 | @keyframes loop-scroll {
229 | 0% {
230 | transform: translateX(0);
231 | }
232 | 20% {
233 | transform: translateX(0);
234 | }
235 | 50% {
236 | transform: translateX(calc(130px - 100%));
237 | }
238 | 70% {
239 | transform: translateX(calc(130px - 100%));
240 | }
241 | 100% {
242 | transform: translateX(0);
243 | }
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/media/pause.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
160 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/media/play.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
160 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/media/pause-play.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
160 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/media/skip-back.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
159 |
--------------------------------------------------------------------------------
/widgets/spotify-listener/media/skip-forward.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
159 |
--------------------------------------------------------------------------------
/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
113 |
--------------------------------------------------------------------------------
/bin/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const path = require("path")
4 | const build = require(path.join(__dirname, "..\\build\\build.js"))
5 | const fs = require("fs")
6 | const readline = require("readline").createInterface({
7 | input: process.stdin,
8 | output: process.stdout
9 | })
10 |
11 | require("yargs")
12 | .scriptName("widgets")
13 | .usage("$0 [arguments]")
14 | .command("build [folder]", "Builds HTML files to desktop widget and installs", (yargs) => {
15 | yargs.positional("folder", {
16 | type: "string",
17 | describe: "The folder containing the content to build (includes HTML and optional config.json)"
18 | })
19 | }, function (argv) {
20 | argv.folder ? folder = argv.folder : folder = path.resolve(".")
21 | console.log("Building widget at " + folder)
22 | build.build_local(folder)
23 | console.log("widget successfully built and installed! Run 'widgets list' to see all enabled widgets")
24 | })
25 | .command("publish [folder]", "Generates a dist file that can be installed by the widgets cli", (yargs) => {
26 | yargs.positional("folder", {
27 | type: "string",
28 | describe: "The folder containing the content to build (includes HTML and optional config.json)"
29 | })
30 | }, function (argv) {
31 | argv.folder ? folder = argv.folder : folder = path.resolve(".")
32 | console.log("Publishing widget at " + folder)
33 | build.build_dist(folder)
34 | console.log("widget successfully built to " + path.join(path.dirname(folder), "dist"))
35 | })
36 | .command("install [folder]", "Installs widget at folder", (yargs) => {
37 | yargs.positional("folder", {
38 | type: "string",
39 | describe: "The folder containing the widget to install"
40 | })
41 | }, function (argv) {
42 | argv.folder ? folder = argv.folder : folder = path.resolve(".")
43 | const child_process = require("child_process")
44 | child_process.exec("node " + path.join(folder, "install.js"), (err, stdout, stderr) => {
45 | if (err) {
46 | console.log(err)
47 | return
48 | }
49 | })
50 | console.log("widget successfully installed at " + folder + "!")
51 | })
52 | .command("init [folder]", "Initializes widgets project", (yargs) => {
53 | yargs.positional("folder", {
54 | type: "string",
55 | describe: "The folder to initialize"
56 | })
57 | }, function (argv) {
58 | argv.folder ? folder = argv.folder : folder = path.resolve(".")
59 | fs.copyFile(path.join(__dirname, "..\\src\\config.json"), path.join(folder, "config.json"), (err) => { if (err) console.error(err) })
60 | fs.copyFile(path.join(__dirname, "..\\src\\index.html"), path.join(folder, "index.html"), (err) => { if (err) console.error(err) })
61 | fs.copyFile(path.join(__dirname, "..\\src\\renderer.js"), path.join(folder, "renderer.js"), (err) => { if (err) console.error(err) })
62 | setTimeout(() => {
63 | readline.question("Name of your project: ", name => {
64 | config = require(path.join(folder, "config.json"))
65 | config.name = name
66 | fs.writeFile(path.join(folder, "config.json"), JSON.stringify(config, null, 2), (err) => { if (err) console.error(err) })
67 | readline.close()
68 | console.log("widget successfully initialized at " + folder + "! You can now edit your project's properties at config.json")
69 | })
70 | }, 10)
71 | })
72 | .command("list", "Lists all installed widgets", (yargs) => {}, function (argv) {
73 | installed = get_all_installed_widgets()
74 | installed = installed.map((x) => x.name + (x.config.version ? " --- " + x.config.version : ""))
75 | installed = installed.join("\n")
76 | console.log(installed)
77 | })
78 | .command("uninstall ", "Uninstall widget", (yargs) => {
79 | yargs.positional("widget", {
80 | type: "string",
81 | describe: "The widget to uninstall"
82 | })
83 | }, function (argv) {
84 | installed = get_all_installed_widgets()
85 | target = installed.filter((x) => x.name == argv.widget)[0]
86 | fs.rmdirSync(target.path, { recursive: true, force: true })
87 | console.log(target.name + " successfully uninstalled!")
88 | })
89 | .command("config ", "Configure widget by name", (yargs) => {
90 | yargs.positional("widget", {
91 | type: "string",
92 | describe: "The widget to configure"
93 | })
94 | }, function (argv) {
95 | installed = get_all_installed_widgets()
96 | target = installed.filter((x) => x.name == argv.widget)[0]
97 | var exec = require("child_process").exec
98 | try {
99 | exec(`start \"\" \"${path.join(target.path, "config.json")}\"`)
100 | console.log("Now editing config.json of " + target.name)
101 | }
102 | catch (err) {
103 | console.error("Cannot open " + path.join(target.path, "config.json"))
104 | }
105 | })
106 | .command("start [folder]", "Starts the widget at folder", (yargs) => {
107 | yargs.positional("folder", {
108 | type: "string",
109 | describe: "The folder containing the content to start (includes HTML and optional config.json)"
110 | })
111 | }, function (argv) {
112 | argv.folder ? folder = argv.folder : folder = path.resolve(".")
113 | console.log("Starting widget at " + folder)
114 | build.build_start(folder)
115 | setTimeout(() => {
116 | var exec = require("child_process").exec
117 | try {
118 | exec(`npm start \"${folder}\"`)
119 | console.log("widget successfully started at " + folder)
120 | }
121 | catch (err) {
122 | console.error("Cannot start " + folder)
123 | }
124 | }, 20)
125 | })
126 | .help()
127 | .argv
128 |
129 | function get_all_installed_widgets() {
130 | src = process.env.APPDATA + "\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\widgets"
131 | installed = []
132 | get_directories = (src) => {
133 | return fs.readdirSync(src)
134 | .map((f) => f = path.join(src, f))
135 | .filter((f) => { return fs.statSync(f).isDirectory() })
136 | }
137 | directories = get_directories(src)
138 | for (f of directories) {
139 | try {
140 | var config = require(path.join(f, "config.json"))
141 | installed.push({
142 | "name": config.name,
143 | "path": f,
144 | "filename": path.basename(f),
145 | "config": config
146 | })
147 | }
148 | catch (error) {}
149 | }
150 |
151 | return installed
152 | }
153 |
--------------------------------------------------------------------------------