From ef58aa530e68964596d97bbde48800e767ac5b5a Mon Sep 17 00:00:00 2001 From: Victor Westerlund Date: Thu, 4 Feb 2021 17:50:24 +0100 Subject: [PATCH] Added DB interface and get/update endpoints. The bot userscript is now fully operational, the only thing left to do is to make it send the actual payload we need for the endpoint. Added setup for MySQL database. Added DB interface with a concept-stage set/update endpoint. --- classes/Database.php | 77 +++++++++++++++++++++++++++++++++++++++++ classes/Message.php | 8 +++++ endpoint/get.php | 16 +++++++++ endpoint/update.php | 18 ++++++++++ setup-db.sql | 50 +++++++++++++++++++++++++++ userscript-bot/bot.js | 79 +++++++++++++++++++++++++++++++++---------- 6 files changed, 231 insertions(+), 17 deletions(-) create mode 100644 classes/Database.php create mode 100644 classes/Message.php create mode 100644 endpoint/update.php 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