Initial commit

main
Different55 2024-06-22 19:59:57 -04:00
commit 5f6d475083
4 changed files with 283 additions and 0 deletions

1
.gitignore vendored 100644
View File

@ -0,0 +1 @@
go.sum

15
go.mod 100644
View File

@ -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
)

95
main.go 100644
View File

@ -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")
}

172
main.js 100644
View File

@ -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();
}
}