tangled
alpha
login
or
join now
kris.darkworld.download
/
tuxstrap
0
fork
atom
[Linux-only] basically bloxstap for sober
0
fork
atom
overview
issues
pulls
pipelines
notification and dbus stuff
kris.darkworld.download
7 months ago
a1017c00
40f32301
+114
-24
7 changed files
expand all
collapse all
unified
split
src
api
constants.ts
roblox
GameInfo.ts
index.ts
plugins
debugPlugin.ts
index.ts
systemIO.ts
tsconfig.json
+1
src/api/constants.ts
···
10
10
11
11
export const LOCAL_CONFIG_ROOT = `${homedir()}/.config/tuxstrap`;
12
12
export const SOBER_CONFIG_PATH = `${SOBER_PATH}/config/sober/config.json`;
13
13
+
export const ROBLOX_COOKIES_FILE = `${SOBER_PATH}/data/sober/cookies`;
13
14
14
15
export const DISCORD_APPID = "1005469189907173486";
15
16
export const SMALL_IMAGE_KEY = "roblox";
+23
-4
src/api/roblox/GameInfo.ts
···
1
1
import { TimedDataCache } from "@ocbwoy3/libocbwoy3";
2
2
+
import { ROBLOX_COOKIES_FILE } from "../constants";
3
3
+
import { readFileSync } from "fs";
2
4
3
5
const GameNameCache = new TimedDataCache<string, string>(900); // 30 minutes
4
6
7
7
+
let cookies = "";
8
8
+
9
9
+
try {
10
10
+
cookies = readFileSync(ROBLOX_COOKIES_FILE,"utf-8")?.toString() || "";
11
11
+
} catch {}
12
12
+
5
13
export async function getGameDetails(placeId: string): Promise<string | null> {
6
14
// Check cache first
7
7
-
const cached = GameNameCache.get(placeId);
8
8
-
if (cached) return cached;
15
15
+
if (GameNameCache.has(placeId)) {
16
16
+
return GameNameCache.get(placeId) || "???";
17
17
+
}
9
18
10
19
try {
20
20
+
// roblox is stupid for making this endpoint locked behind an account
11
21
const res = await fetch(
12
22
`https://games.roblox.com/v1/games/multiget-place-details?placeIds=${placeId}`,
13
13
-
{ headers: { accept: "application/json" } }
23
23
+
{
24
24
+
headers: {
25
25
+
accept: "application/json",
26
26
+
cookie: cookies
27
27
+
}
28
28
+
}
14
29
);
15
30
31
31
+
// console.log(res,await res.body?.text());
32
32
+
16
33
if (!res.ok) return null;
17
34
18
35
const data: {
···
22
39
23
40
const gameName = data[0]?.name ?? "Unknown Game";
24
41
25
25
-
if (gameName) GameNameCache.set(placeId, gameName, 900_000);
42
42
+
if (gameName) {
43
43
+
GameNameCache.set(placeId, gameName, 900_000);
44
44
+
}
26
45
27
46
return gameName;
28
47
} catch (e) {
+1
-1
src/index.ts
···
76
76
const child = exec(`flatpak run ${SOBER_APPID} "${robloxLaunchURL}"`);
77
77
78
78
const watcher = new ActivityWatcher(child, {
79
79
-
verbose: false,
79
79
+
verbose: true,
80
80
tuxstrapLaunchTime: Date.now()
81
81
});
82
82
+17
-17
src/plugins/debugPlugin.ts
···
1
1
-
import { getGameDetails } from "api/roblox/GameInfo";
1
1
+
import { getGameDetails } from "../api/roblox/GameInfo";
2
2
import { registerPlugin } from "../api/Plugin";
3
3
-
import { SendNotification } from "api/linux";
4
4
-
import { ServerType } from "api/types";
3
3
+
import { SendNotification } from "../api/linux";
5
4
6
5
registerPlugin(
7
6
{
8
8
-
name: "TuxStrap Debug",
9
9
-
id: "tuxstrap-debug",
7
7
+
name: "TuxStrap Notifs",
8
8
+
id: "tuxstrap-notif",
10
9
forceEnable: true,
11
10
configPrio: -9e9
12
11
},
13
12
(plugin) => {
14
14
-
plugin.on("BLOXSTRAP_RPC", (a) => console.log("BLOXSTRAP_RPC", a));
15
13
plugin.on("GAME_JOIN", async (a) => {
16
16
-
console.log("GAME_JOIN", a);
14
14
+
// console.log("GAME_JOIN", a);
17
15
const gameName = await getGameDetails(a.placeId);
18
16
if (!gameName) return;
19
17
await SendNotification(
20
18
"Roblox",
21
21
-
`${gameName}${a.ipAddrUdmux ? "\n(UDMUX Protected)" : ""}`
19
19
+
`${gameName}${a.ipAddrUdmux ? "\n(UDMUX Protected)" : ""}`,
20
20
+
3000
22
21
);
23
22
});
24
23
plugin.on("TELEPORT", async (a) => {
25
25
-
console.log("TELEPORT", a);
24
24
+
// console.log("TELEPORT", a);
26
25
const pi = plugin.currentState.getPlaceId();
27
26
if (!pi) return;
28
27
const gameName = await getGameDetails(pi);
29
28
if (!gameName) return;
30
29
await SendNotification(
31
30
"Roblox",
32
32
-
`${gameName} is teleporting you to another place (${a.serverType})`
31
31
+
`${gameName} is teleporting you to another place (${a.serverType})`,
32
32
+
3000
33
33
);
34
34
});
35
35
-
plugin.on("GAME_LEAVE", (a) => console.log("GAME_LEAVE", a));
36
36
-
plugin.on("PLAYER_JOIN", (a) => console.log("PLAYER_JOIN", a));
37
37
-
plugin.on("PLAYER_LEAVE", (a) => console.log("PLAYER_LEAVE", a));
38
38
-
plugin.on("STATE_CHANGE", (a) => console.log("STATE_CHANGE", a));
39
39
-
plugin.currentState.onStateChange((a) => {
40
40
-
console.log("onStateChange", a);
41
41
-
});
35
35
+
// plugin.on("GAME_LEAVE", (a) => console.log("GAME_LEAVE", a));
36
36
+
// plugin.on("PLAYER_JOIN", (a) => console.log("PLAYER_JOIN", a));
37
37
+
// plugin.on("PLAYER_LEAVE", (a) => console.log("PLAYER_LEAVE", a));
38
38
+
// plugin.on("STATE_CHANGE", (a) => console.log("STATE_CHANGE", a));
39
39
+
// plugin.currentState.onStateChange((a) => {
40
40
+
// console.log("onStateChange", a);
41
41
+
// });
42
42
}
43
43
);
+1
src/plugins/index.ts
···
1
1
import "./default";
2
2
import "./debugPlugin";
3
3
+
import "./systemIO"
+71
src/plugins/systemIO.ts
···
1
1
+
import { $ } from "bun";
2
2
+
import { GetProxyInterface, SendNotification } from "../api/linux";
3
3
+
import { registerPlugin } from "../api/Plugin";
4
4
+
import { getGameDetails } from "../api/roblox/GameInfo";
5
5
+
6
6
+
registerPlugin(
7
7
+
{
8
8
+
name: "System Clipboard & D-Bus",
9
9
+
id: "tuxstrap-io",
10
10
+
forceEnable: true,
11
11
+
configPrio: -9e9
12
12
+
},
13
13
+
async (plugin) => {
14
14
+
15
15
+
plugin.setFFlag("FFlagClientAllowClipboardControl", true);
16
16
+
plugin.setFFlag("FFlagClientAllowMPRISControl", true);
17
17
+
plugin.setFFlag("FFlagIsLinux", true);
18
18
+
19
19
+
plugin.on("BLOXSTRAP_RPC",async(a)=>{
20
20
+
switch (a.type) {
21
21
+
case "WaylandCopy": {
22
22
+
if (typeof a.data === "string" && a.data.length <= 512) {} else {return};
23
23
+
const gameName = await getGameDetails(plugin.currentState.getPlaceId()!);
24
24
+
if (!gameName) return;
25
25
+
await SendNotification(
26
26
+
"Roblox",
27
27
+
`${gameName} wrote to the Wayland clipboard!`,
28
28
+
3000
29
29
+
);
30
30
+
$`echo ${a.data} | wl-copy -n`.quiet().nothrow();
31
31
+
break;
32
32
+
}
33
33
+
case "CallDBus": {
34
34
+
if (typeof a.data !== "object") return;
35
35
+
if (typeof a.data.busName !== "string") return;
36
36
+
if (typeof a.data.action !== "string") return;
37
37
+
38
38
+
const { busName, action }: {busName: string, action: string} = a.data;
39
39
+
40
40
+
if (busName.length >= 64) return;
41
41
+
if (!busName.startsWith("org.mpris.MediaPlayer2.")) return;
42
42
+
43
43
+
if (!["Play", "Pause", "PlayPause"].includes(action)) return;
44
44
+
45
45
+
try {
46
46
+
const i = await GetProxyInterface(busName, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player");
47
47
+
switch (action) {
48
48
+
case "Play": {
49
49
+
await (i as any).Play();
50
50
+
break;
51
51
+
}
52
52
+
case "Pause": {
53
53
+
await (i as any).Pause();
54
54
+
break;
55
55
+
}
56
56
+
case "PlayPause": {
57
57
+
await (i as any).PlayPause();
58
58
+
break;
59
59
+
}
60
60
+
61
61
+
}
62
62
+
} catch {}
63
63
+
64
64
+
break;
65
65
+
}
66
66
+
67
67
+
}
68
68
+
})
69
69
+
70
70
+
}
71
71
+
);
-2
tsconfig.json
···
25
25
"noUnusedLocals": false,
26
26
"noUnusedParameters": false,
27
27
"noPropertyAccessFromIndexSignature": false,
28
28
-
29
29
-
"baseUrl": "./src/",
30
28
},
31
29
}