diff --git a/classes/Database.php b/classes/Database.php new file mode 100644 index 0000000..5902c80 --- /dev/null +++ b/classes/Database.php @@ -0,0 +1,77 @@ + "", + "username" => "", + "password" => "", + "database" => "stadia_avatars" + ]; + + public function __construct() { + parent::init(); + + if(!parent::options(MYSQLI_INIT_COMMAND,"SET AUTOCOMMIT = 0")) { + die("Setting MYSQLI_INIT_COMMAND failed"); + } + + if(!parent::options(MYSQLI_OPT_CONNECT_TIMEOUT,5)) { + die("Setting MYSQLI_OPT_CONNECT_TIMEOUT failed"); + } + + if(!parent::real_connect(DBConnector::$config["host"],DBConnector::$config["username"],DBConnector::$config["password"],DBConnector::$config["database"])) { + die("Connect Error (".mysqli_connect_errno().") ".mysqli_connect_error()); + } + } + + private function check_connection() { + if(parent::connect_errno) { + die("Invalid connection"); + } + } + + private function insert_avatar($user_id,$value) { + $time = time(); + $query = "INSERT INTO avatars (userid, avatar, modified) VALUES ('${user_id}', '${value}', '${time}');"; + + if($result = parent::query($query) === true) { + http_response_code("206"); + return true; + } + + return false; + } + + private function update_avatar($user_id,$value) { + $time = time(); + $query = "UPDATE avatars SET avatar = '${value}', modified = '${time}' WHERE avatars.userid = '${user_id}';"; + + if($result = parent::query($query) === true) { + return true; + } + + return false; + } + + // ---- + + public function get_avatar($user_id) { + $query = "SELECT userid, avatar FROM avatars WHERE userid = '${user_id}';"; + + if($result = parent::query($query)) { + return $result->fetch_array(MYSQLI_NUM)[1]; + } + + return false; + } + + public function set_avatar($user_id,$value) { + if($this->get_avatar($user_id)) { + return $this->update_avatar($user_id,$value); + } + + return $this->insert_avatar($user_id,$value); + } + + } \ No newline at end of file diff --git a/classes/Message.php b/classes/Message.php new file mode 100644 index 0000000..454d718 --- /dev/null +++ b/classes/Message.php @@ -0,0 +1,8 @@ +get_avatar($user_id); + + if($avatar) { + echo "{\"status\":\"OK\",\"avatar\":\"${avatar}\"}"; + return; + } + + error("404","No avatar was found for the supplied userID"); \ No newline at end of file diff --git a/endpoint/update.php b/endpoint/update.php new file mode 100644 index 0000000..36af3ce --- /dev/null +++ b/endpoint/update.php @@ -0,0 +1,18 @@ +set_avatar("foo","bario")) { + error("500","Something went wrong."); + } + + echo "{\"status\":\"OK\"}"; \ No newline at end of file diff --git a/setup-db.sql b/setup-db.sql index e69de29..0b99f79 100644 --- a/setup-db.sql +++ b/setup-db.sql @@ -0,0 +1,50 @@ +-- phpMyAdmin SQL Dump +-- version 4.9.5 +-- https://www.phpmyadmin.net/ +-- +-- Host: victorwesterlund.com +-- Generation Time: Feb 04, 2021 at 11:15 AM +-- Server version: 10.3.27-MariaDB-0+deb10u1 +-- PHP Version: 7.3.19-1~deb10u1 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET AUTOCOMMIT = 0; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `stadia_avatars` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `avatars` +-- + +CREATE TABLE `avatars` ( + `userid` varchar(255) NOT NULL, + `avatar` tinytext NOT NULL, + `modified` int(255) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `avatars` +-- +ALTER TABLE `avatars` + ADD PRIMARY KEY (`userid`); +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/userscript-bot/bot.js b/userscript-bot/bot.js index a30c446..79e26dc 100644 --- a/userscript-bot/bot.js +++ b/userscript-bot/bot.js @@ -1,29 +1,74 @@ (function() { 'use strict'; - const targetJSName = "FhFdCc"; - let observer = null; + /* The Stadia Service Worker "mgsw.js" acts like a proxy and rejects requests to the StadiaAvatar endpoint. + Enable the "Bypass for network" toggle in DevTools->Application->Service Workers to circumvent this issue. */ - const receivedMessage = (mutations,observer) => { - const mutation = mutations[3].addedNodes[0]; - console.log(mutations); + const endpoint = ""; + const sharedSecret = ""; // Key to update the database + + const className = { + newMessage: "daVSod", + messageContent: "dgrMg", + messageWindow: "P9pxvf", + backButton: "rkvT7c" } - function attachObserver() { - const target = document.querySelector(`[jsname="${targetJSName}"]`); + let interval = null; - observer = new MutationObserver(receivedMessage); - observer.observe(target,{ - subtree: true, - childList: true, - attributes: true, - characterData: true + async function updateAvatar(userID,payload) { + const response = await fetch(endpoint,{ + method: "POST", + mode: "no-cors", + headers: { + "Content-Type": "text/plain" + }, + body: `{"sharedSecret":"${sharedSecret}","userID":"${userID}","payload":"${payload}"}` }); + return response.text(); } - // Start the StadiaAvatar bot with 'window._StadiaAvatar()' - window._StadiaAvatar = () => { - attachObserver(); - console.log("StadiaAvatar bot started."); + const backToMessageList = (mutations,observer) => { + observer.disconnect(); + // Click the back button once the animation has ended + setTimeout(() => document.getElementsByClassName(className.backButton)[0].click(),1000); + } + + function pollNewMessages() { + const messages = document.getElementsByClassName(className.newMessage); + if(!messages) { + return false; + } + + for(const message of messages) { + const userID = message.getAttribute("data-playerid"); + const userMessage = message.getElementsByClassName(className.messageContent)[0].innerText; + + message.click(); // Send the AckMessage request natively by simulating a click + + // Wait for the message window to open + const messageWindow = document.getElementsByClassName(className.messageWindow)[0]; + + const observer = new MutationObserver(backToMessageList); + observer.observe(messageWindow,{ + attributes: true, + subtree: true + }); + + updateAvatar(userID,userMessage) + .catch(err => console.error("Failed to update avatar",err)); + } + } + + window._StadiaAvatar = { + // Start the StadiaAvatar bot with 'window._StadiaAvatar.start()' + start: (delay = 500) => { + interval = setInterval(() => pollNewMessages(),delay); + console.log("StadiaAvatar bot started."); + }, + stop: () => { + interval = null + console.log("StadiaAvatar bot stopped."); + } } })(); \ No newline at end of file