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.
This commit is contained in:
Victor Westerlund 2021-02-04 17:50:24 +01:00
parent 51a70545b0
commit ef58aa530e
6 changed files with 231 additions and 17 deletions

77
classes/Database.php Normal file
View file

@ -0,0 +1,77 @@
<?php
class DBConnector extends mysqli {
public static $config = [
"host" => "",
"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);
}
}

8
classes/Message.php Normal file
View file

@ -0,0 +1,8 @@
<?php
header("Content-Type: application/json");
function error($code,$message) {
http_response_code($code);
die("{\"status\":\"error\",\"code\":\"${code}\",\"message\":\"${message}\"}");
}

View file

@ -0,0 +1,16 @@
<?php
require "../classes/Message.php";
require "../classes/Database.php";
$user_id = $_GET["userID"] ?? error("400","No userID provided");
$db = new DBConnector();
$avatar = $db->get_avatar($user_id);
if($avatar) {
echo "{\"status\":\"OK\",\"avatar\":\"${avatar}\"}";
return;
}
error("404","No avatar was found for the supplied userID");

18
endpoint/update.php Normal file
View file

@ -0,0 +1,18 @@
<?php
require "../classes/Message.php";
require "../classes/Database.php";
$sharedSecret = ""; // Key to update the database
if($sharedSecret !== "") {
error("403","Invalid shared secret.");
}
$db = new DBConnector();
if(!$db->set_avatar("foo","bario")) {
error("500","Something went wrong.");
}
echo "{\"status\":\"OK\"}";

View file

@ -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 */;

View file

@ -1,29 +1,74 @@
(function() { (function() {
'use strict'; 'use strict';
const targetJSName = "FhFdCc"; /* The Stadia Service Worker "mgsw.js" acts like a proxy and rejects requests to the StadiaAvatar endpoint.
let observer = null; Enable the "Bypass for network" toggle in DevTools->Application->Service Workers to circumvent this issue. */
const receivedMessage = (mutations,observer) => { const endpoint = "";
const mutation = mutations[3].addedNodes[0]; const sharedSecret = ""; // Key to update the database
console.log(mutations);
const className = {
newMessage: "daVSod",
messageContent: "dgrMg",
messageWindow: "P9pxvf",
backButton: "rkvT7c"
} }
function attachObserver() { let interval = null;
const target = document.querySelector(`[jsname="${targetJSName}"]`);
observer = new MutationObserver(receivedMessage); async function updateAvatar(userID,payload) {
observer.observe(target,{ const response = await fetch(endpoint,{
subtree: true, method: "POST",
childList: true, mode: "no-cors",
attributes: true, headers: {
characterData: true "Content-Type": "text/plain"
},
body: `{"sharedSecret":"${sharedSecret}","userID":"${userID}","payload":"${payload}"}`
}); });
return response.text();
} }
// Start the StadiaAvatar bot with 'window._StadiaAvatar()' const backToMessageList = (mutations,observer) => {
window._StadiaAvatar = () => { observer.disconnect();
attachObserver(); // Click the back button once the animation has ended
console.log("StadiaAvatar bot started."); 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.");
}
} }
})(); })();