Initial commit
commit
5f6d475083
|
@ -0,0 +1 @@
|
|||
go.sum
|
|
@ -0,0 +1,15 @@
|
|||
module github.com/different55/deckspin
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/saracen/fastzip v0.1.11
|
||||
github.com/webview/webview_go v0.0.0-20240220051247-56f456ca3a43
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/klauspost/compress v1.16.5 // indirect
|
||||
github.com/saracen/zipextra v0.0.0-20220303013732-0187cb0159ea // indirect
|
||||
golang.org/x/sync v0.2.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
)
|
|
@ -0,0 +1,95 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"github.com/saracen/fastzip"
|
||||
webview "github.com/webview/webview_go"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed main.js
|
||||
var initScript string
|
||||
|
||||
func main() {
|
||||
w := webview.New(true)
|
||||
defer w.Destroy()
|
||||
w.SetTitle("DeckSpin")
|
||||
w.SetSize(1280, 800, webview.HintNone)
|
||||
w.Navigate("https://spinsha.re")
|
||||
w.Bind("log", fmt.Println)
|
||||
w.Bind("installChart", InstallChart)
|
||||
w.Init(initScript)
|
||||
w.Run()
|
||||
}
|
||||
|
||||
func InstallChart(url string) {
|
||||
println("Installing chart")
|
||||
|
||||
// Make sure the URL is a SpinShare URL
|
||||
if !strings.HasPrefix(url, "https://spinsha.re/") {
|
||||
println("Not a SpinShare URL")
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure it's a chart download URL
|
||||
if !strings.HasSuffix(url, "/download") {
|
||||
println("Not a download URL")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a temporary file.
|
||||
tmp, err := os.CreateTemp("/tmp/", "deckspin")
|
||||
if err != nil {
|
||||
println("Failed to create temporary file")
|
||||
return
|
||||
}
|
||||
defer tmp.Close()
|
||||
|
||||
// Download the ZIP file
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
println("Failed to download ZIP file")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Write the ZIP file to the temporary file
|
||||
_, err = io.Copy(tmp, resp.Body)
|
||||
if err != nil {
|
||||
println("Failed to write ZIP file")
|
||||
return
|
||||
}
|
||||
|
||||
tmpInfo, err := tmp.Stat()
|
||||
if err != nil {
|
||||
print("Failed to get temporary file info")
|
||||
return
|
||||
}
|
||||
|
||||
// Get user home directory.
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
println("Failed to get user home directory")
|
||||
return
|
||||
}
|
||||
|
||||
// Unzip the ZIP file
|
||||
e, err := fastzip.NewExtractorFromReader(tmp, tmpInfo.Size(), filepath.Join(home, ".steam/steam/steamapps/compatdata/1058830/pfx/drive_c/users/steamuser/AppData/LocalLow/Super Spin Digital/Spin Rhythm XD/Custom/"))
|
||||
if err != nil {
|
||||
print("Failed to create extractor")
|
||||
return
|
||||
}
|
||||
|
||||
if err = e.Extract(context.Background()); err != nil {
|
||||
print("Failed to extract ZIP file")
|
||||
return
|
||||
}
|
||||
|
||||
println("Chart installed")
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
window.log("loaded " + window.location.href );
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
// Add an onclick listener to all anchors, buttons, and inputs to check whether
|
||||
// this is a download link and if so, install the chart.
|
||||
let controls = document.querySelectorAll('a:not(.button-disabled):not([disabled]), button:not([disabled]), input:not([disabled])');
|
||||
controls.forEach(function(control) {
|
||||
control.addEventListener("click", function() {
|
||||
let href = control.href;
|
||||
if (href && href.endsWith("/download")) {
|
||||
window.log("Installing chart: " + href);
|
||||
document.body.classList.add("ds-download");
|
||||
window.setTimeout(function() {
|
||||
window.installChart(href);
|
||||
document.body.classList.remove("ds-download");
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add a new stylesheet to the document to style the active control.
|
||||
let style = document.createElement("style");
|
||||
style.innerHTML = `
|
||||
.ds-active {
|
||||
position: relative;
|
||||
}
|
||||
.ds-active::after {
|
||||
content: "";
|
||||
border: 2px solid #e22c78;
|
||||
outline: none;
|
||||
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.ds-download {
|
||||
position: relative;
|
||||
}
|
||||
.ds-download::after {
|
||||
content: "Downloading...";
|
||||
font-size: 2em;
|
||||
color: #e22c78;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
font-weight: 200;
|
||||
}
|
||||
.ds-download main {
|
||||
opacity: 0.25;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
|
||||
// Enable arrow keys for navigation.
|
||||
document.onkeydown = function(e) {
|
||||
switch (e.code) {
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
getControlsInDirection("up");
|
||||
break;
|
||||
case "ArrowDown":
|
||||
e.preventDefault();
|
||||
getControlsInDirection("down");
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
e.preventDefault();
|
||||
getControlsInDirection("left");
|
||||
break;
|
||||
case "ArrowRight":
|
||||
e.preventDefault();
|
||||
getControlsInDirection("right");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function getControlsInDirection(direction) {
|
||||
let controls = document.querySelectorAll('a:not(.button-disabled):not([disabled]), button:not([disabled]), input:not([disabled])');
|
||||
if (!controls.length)
|
||||
return;
|
||||
|
||||
let activeControl = document.querySelector(".ds-active");
|
||||
if (!activeControl) {
|
||||
controls[0].classList.add("ds-active");
|
||||
controls[0].focus();
|
||||
return;
|
||||
}
|
||||
|
||||
let activeRect = activeControl.getBoundingClientRect();
|
||||
let activeCenter = 0;
|
||||
|
||||
switch (direction) {
|
||||
case "up":
|
||||
case "down":
|
||||
activeCenter = activeRect.top+activeRect.height/2;
|
||||
break;
|
||||
case "left":
|
||||
case "right":
|
||||
activeCenter = activeRect.left+activeRect.width/2;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
let closestDistance = Number.MAX_VALUE;
|
||||
let closestControl = null;
|
||||
|
||||
let skipOverlapCheck = false;
|
||||
|
||||
function getClosest(control) {
|
||||
if (control === activeControl)
|
||||
return;
|
||||
|
||||
let rect = control.getBoundingClientRect();
|
||||
let center = 0;
|
||||
let distance;
|
||||
|
||||
switch (direction) {
|
||||
case "up":
|
||||
case "down":
|
||||
center = rect.top + rect.height / 2;
|
||||
break;
|
||||
case "left":
|
||||
case "right":
|
||||
center = rect.left + rect.width / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (direction === "up" && (skipOverlapCheck || (activeRect.left < rect.right && activeRect.right > rect.left)))
|
||||
distance = activeCenter - center;
|
||||
else if (direction === "down" && (skipOverlapCheck || (activeRect.left < rect.right && activeRect.right > rect.left)))
|
||||
distance = center - activeCenter;
|
||||
else if (direction === "left" && (skipOverlapCheck || (activeRect.top < rect.bottom && activeRect.bottom > rect.top)))
|
||||
distance = activeCenter - center;
|
||||
else if (direction === "right" && (skipOverlapCheck || (activeRect.top < rect.bottom && activeRect.bottom > rect.top)))
|
||||
distance = center - activeCenter;
|
||||
else
|
||||
return;
|
||||
|
||||
if (distance <= 0)
|
||||
return;
|
||||
|
||||
if (distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
closestControl = control;
|
||||
}
|
||||
}
|
||||
|
||||
controls.forEach(getClosest);
|
||||
|
||||
if (closestControl) {
|
||||
activeControl.classList.remove("ds-active");
|
||||
closestControl.classList.add("ds-active");
|
||||
closestControl.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// If no control was found, try again without checking for overlap.
|
||||
skipOverlapCheck = true;
|
||||
controls.forEach(getClosest);
|
||||
|
||||
if (closestControl) {
|
||||
activeControl.classList.remove("ds-active");
|
||||
closestControl.classList.add("ds-active");
|
||||
closestControl.focus();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue