Skip to content

Instantly share code, notes, and snippets.

@RundesBalli
Last active December 30, 2023 17:27
Show Gist options
  • Save RundesBalli/d5e2815b49c95282bb68c37c72a3169c to your computer and use it in GitHub Desktop.
Save RundesBalli/d5e2815b49c95282bb68c37c72a3169c to your computer and use it in GitHub Desktop.
pr0gramm-fliesentischPixel

Preparation

Create template.png. The rest is documented in the code.

Dependencies:

php-cli php-mysql php-gd php-mbstring php-curl
SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
SET NAMES utf8mb4;
DROP DATABASE IF EXISTS `pr0Pixel`;
CREATE DATABASE `pr0Pixel` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;
USE `pr0Pixel`;
DROP TABLE IF EXISTS `canvas`;
CREATE TABLE `canvas` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
`x` int(10) unsigned NOT NULL COMMENT 'Pixel coordinate x',
`y` int(10) unsigned NOT NULL COMMENT 'Pixel coordinate y',
`hex` varchar(6) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'Current hex code',
`targetHex` varchar(6) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Target hex code or NULL',
PRIMARY KEY (`id`),
UNIQUE KEY `x_y` (`x`,`y`),
KEY `x` (`x`),
KEY `y` (`y`),
KEY `hex` (`hex`),
KEY `targetHex` (`targetHex`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Canvas with all pixels';
<?php
/**
* pr0gramm-fliesentischPixel
*
* eventHandler.php CLI Script
*
* @author RundesBalli <GitHub@RundesBalli.com>
* @copyright 2023 RundesBalli
* @version 1.0
* @see https://RundesBalli.com
*/
/**
* =============================================
*
* SETTINGS
*
* =============================================
*/
/**
*
* Template settings
*
*/
/**
* First pixel top left of the template
*
* @var array
*/
$templatePixel = [
'x' => 0,
'y' => 0,
];
/**
* Replace alpha
*
* @var string|null hex code (without #) if replacement should occur, otherwise NULL.
*/
$replaceAlpha = 'ffffff';
/**
*
* Account settings
*
*/
/**
* Token
*
* Use `id` from `me`-cookie
*
* @var string
*/
$token = '';
/**
*
* Database settings
*
*/
/**
* MySQL-Credentials
*
* @var array
* string host MySQL connection host
* string user Username for the MySQL connection
* string pass Password for the MySQL connection
* string db Database on the SQL server in which to work.
* string charset Charset of the connection. Default: utf8
*/
$mysqlCredentials = [
'host' => 'localhost',
'user' => '',
'pass' => '',
'db' => '',
'charset' => 'utf8'
];
/**
* =============================================
*
* Functions
*
* =============================================
*/
/**
* Database connection
*/
$dbl = mysqli_connect($mysqlCredentials['host'], $mysqlCredentials['user'], $mysqlCredentials['pass'], $mysqlCredentials['db']) OR DIE(MYSQLI_ERROR($dbl));
mysqli_set_charset($dbl, $mysqlCredentials['charset']) OR DIE(MYSQLI_ERROR($dbl));
/**
* Defuse
*
* Defuse function to prepare user inputs for the database.
*
* @param string $defuseString String that is to be "defused" in order to pass it into a database query.
* @param bool $trim Specifies whether to remove spaces/lines at the beginning and end.
*
* @return string The prepared "defused" string.
*/
function defuse($defuseString, bool $trim = TRUE) {
if($trim === TRUE) {
$defuseString = trim($defuseString);
}
global $dbl;
return mysqli_real_escape_string($dbl, strip_tags($defuseString));
}
/**
* =============================================
*
* EventHandler
*
* =============================================
*/
/**
* EventHandler
*
* - loading and reading the snapshot
* - read the template
* - read continuous eventstream
*/
while(TRUE) {
/**
*
* SNAPSHOT
*
*/
/**
* Initialize cURL and set options
*/
echo 'Load snapshot.'."\n";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_HEADER => TRUE,
CURLOPT_URL => 'https://sse.pr0gramm.com/api/snapshot',
CURLOPT_USERAGENT => 'RundesBalli :-)',
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Schmuser: true',
'X-pr0gramm-token: '.$token,
],
]);
/**
* Execute cURL
*/
$response = curl_exec($ch);
/**
* Error handling
*/
$errNo = curl_errno($ch);
$errStr = curl_error($ch);
if($errNo != 0) {
die("cURL snapshot - errno: ".$errNo." - errstr: ".$errStr."\n");
}
$httpCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if($httpCode != 200) {
die("cURL snapshot - httpcode: ".$httpCode."\n");
}
/**
* Extract and parse headers
*/
echo 'Parse headers.'."\n";
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = [];
foreach(explode("\n", trim(mb_substr($response, 0, $headerSize))) as $header) {
if(stripos($header, ': ') === FALSE) {
continue;
}
list($headerName, $value) = explode(': ', $header, 2);
$headers[$headerName] = trim($value);
}
$eventIdSnapshot = $headers['X-pr0gramm-Event-Id'];
echo 'eventIdSnapshot: '.$eventIdSnapshot."\n";
/**
* Check if the response is image/png
*/
if($headers['Content-Type'] != 'image/png') {
die('Response is no image/png'."\n");
}
/**
* Close the cURL handle.
*/
curl_close($ch);
/**
* Extract and parse snapshot
*/
echo 'Parse snapshot.'."\n";
$image = imagecreatefromstring(trim(mb_substr($response, $headerSize)));
mysqli_query($dbl, 'TRUNCATE TABLE `canvas`') OR DIE(MYSQLI_ERROR($dbl));
$values = [];
for($x = 0; $x < imagesx($image); $x++) {
for($y = 0; $y < imagesy($image); $y++) {
$rgb = imagecolorsforindex($image, imagecolorat($image, $x, $y));
if($rgb['alpha'] != 0) {
$values[] = '('.$x.', '.$y.', "ffffff")';
continue;
}
$values[] = '('.$x.', '.$y.', "'.defuse(strtolower(sprintf("%02x%02x%02x", $rgb['red'], $rgb['green'], $rgb['blue']))).'")';
}
}
mysqli_query($dbl, 'INSERT INTO `canvas` (`x`, `y`, `hex`) VALUES '.implode(',', $values)) OR DIE(MYSQLI_ERROR($dbl));
imagedestroy($image);
/**
*
* availableColors
*
*/
/**
* Initialize cURL and set options
*/
echo 'Load availableColors.'."\n";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_URL => 'https://sse.pr0gramm.com/api/status',
CURLOPT_USERAGENT => 'eventHandler',
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Schmuser: true',
'X-pr0gramm-token: '.$token,
],
]);
/**
* Execute cURL
*/
$response = curl_exec($ch);
/**
* Error handling
*/
$errNo = curl_errno($ch);
$errStr = curl_error($ch);
if($errNo != 0) {
die("cURL availableColors - errno: ".$errNo." - errstr: ".$errStr."\n");
}
$httpCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if($httpCode != 200) {
die("cURL availableColors - httpcode: ".$httpCode."\n");
}
/**
* Read colors.
*/
$availableColors = json_decode($response, TRUE)['availableColors'];
echo 'Available colors: '.implode(', ', $availableColors)."\n";
/**
* Close the cURL handle.
*/
curl_close($ch);
/**
*
* TEMPLATE
*
* Templatefile: template.png
* Colors will not be checked and have to be correct in the template.
*
*/
/**
* Read and parse template
*/
echo 'Parse template.'."\n";
$image = imagecreatefrompng('./template.png');
$values = [];
for($x = 0; $x < imagesx($image); $x++) {
for($y = 0; $y < imagesy($image); $y++) {
$rgb = imagecolorsforindex($image, imagecolorat($image, $x, $y));
if($rgb['alpha'] != 0) {
$values[] = '('.($x+$templatePixel['x']).', '.($y+$templatePixel['y']).', '.($replaceAlpha !== NULL ? '"'.strtolower($replaceAlpha).'"' : 'NULL').')';
continue;
}
if(!in_array(strtolower(sprintf("%02x%02x%02x", $rgb['red'], $rgb['green'], $rgb['blue'])), $availableColors)) {
echo 'Skipping '.($x+$templatePixel['x']).', '.($y+$templatePixel['y']).', "'.defuse(strtolower(sprintf("%02x%02x%02x", $rgb['red'], $rgb['green'], $rgb['blue']))).'" - not in availableColors'."\n";
continue;
}
$values[] = '('.($x+$templatePixel['x']).', '.($y+$templatePixel['y']).', "'.defuse(strtolower(sprintf("%02x%02x%02x", $rgb['red'], $rgb['green'], $rgb['blue']))).'")';
}
}
mysqli_query($dbl, 'INSERT INTO `canvas` (`x`, `y`, `targetHex`) VALUES '.implode(',', $values).' ON DUPLICATE KEY UPDATE `targetHex`=VALUES(`targetHex`)') OR DIE(MYSQLI_ERROR($dbl));
imagedestroy($image);
/**
*
* EVENTS
*
*/
/**
* Writefunction
*/
if(!function_exists('eventParser')) {
function eventParser($ch, $data) {
foreach(explode("\n", $data) as $stream) {
if(stripos($stream, 'data: ') === FALSE) {
continue;
}
$json = json_decode(str_replace('data: ', '', $stream), TRUE);
if(json_last_error() !== JSON_ERROR_NONE) {
die('eventParser - JSON error: '."\n".var_export($json, TRUE)."\n");
}
global $dbl;
mysqli_query($dbl, 'UPDATE `canvas` SET `hex`="'.defuse(strtolower($json['color'])).'" WHERE `x`='.defuse($json['x']).' AND `y`='.defuse($json['y']).' LIMIT 1') OR DIE(MYSQLI_ERROR($dbl));
echo 'Updating '.$json['x'].','.$json['y'].': '.$json['color']."\n";
}
return strlen($data);
}
}
/**
* Initialize cURL and set options
*/
echo 'Listening to events...'."\n";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://sse.pr0gramm.com/api/events',
CURLOPT_USERAGENT => 'eventHandler',
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_HTTPHEADER => [
'Schmuser: true',
'X-pr0gramm-token: '.$token,
'last-event-id: '.$eventIdSnapshot,
],
CURLOPT_WRITEFUNCTION => "eventParser",
]);
/**
* Execute cURL
*/
curl_exec($ch);
$cURLError = curl_errno($ch);
if($cURLError != 0) {
if($cURLError == 18 OR $cURLError == 7) {
echo 'Restarting...'."\n";
sleep(10);
continue;
}
die("cURL events - errno: ".$cURLError." - errstr: ".curl_error($ch)."\n");
}
curl_close($ch);
}
?>
<?php
/**
* pr0gramm-fliesentischPixel api
*
* eventHandler.php CLI Script
*
* @author RundesBalli <GitHub@RundesBalli.com>
* @copyright 2023 RundesBalli
* @version 1.0
* @see https://RundesBalli.com
*/
/**
* =============================================
*
* SETTINGS
*
* =============================================
*/
/**
*
* Database settings
*
*/
/**
* MySQL-Credentials
*
* @var array
* string host MySQL connection host
* string user Username for the MySQL connection
* string pass Password for the MySQL connection
* string db Database on the SQL server in which to work.
* string charset Charset of the connection. Default: utf8
*/
$mysqlCredentials = [
'host' => 'localhost',
'user' => '',
'pass' => '',
'db' => '',
'charset' => 'utf8'
];
/**
* =============================================
*
* Functions
*
* =============================================
*/
/**
* Database connection
*/
$dbl = mysqli_connect($mysqlCredentials['host'], $mysqlCredentials['user'], $mysqlCredentials['pass'], $mysqlCredentials['db']) OR DIE(MYSQLI_ERROR($dbl));
mysqli_set_charset($dbl, $mysqlCredentials['charset']) OR DIE(MYSQLI_ERROR($dbl));
/**
* =============================================
*
* API
*
* =============================================
*/
header('Content-type: application/json');
/**
* Output one coordinate with color.
*/
$result = mysqli_query($dbl, 'SELECT `id` FROM `canvas` WHERE `targetHex` IS NOT NULL AND `targetHex` != `hex` ORDER BY `y` DESC LIMIT 30') OR DIE(MYSQLI_ERROR($dbl));
#$result = mysqli_query($dbl, 'SELECT `id` FROM `canvas` WHERE `targetHex` IS NOT NULL AND `targetHex` != `hex` ORDER BY `y` ASC LIMIT 30') OR DIE(MYSQLI_ERROR($dbl));
#$result = mysqli_query($dbl, 'SELECT `id` FROM `canvas` WHERE `targetHex` IS NOT NULL AND `targetHex` != `hex` ORDER BY `x` ASC LIMIT 30') OR DIE(MYSQLI_ERROR($dbl));
#$result = mysqli_query($dbl, 'SELECT `id` FROM `canvas` WHERE `targetHex` IS NOT NULL AND `targetHex` != `hex` ORDER BY `x` DESC LIMIT 30') OR DIE(MYSQLI_ERROR($dbl));
/**
* Check if there is any pixel to be colored.
*/
if(!mysqli_num_rows($result)) {
$output = [
'error' => 'nothingToDo',
'err' => 'nothingToDo',
'remainingPixels' => 0,
];
die(json_encode($output));
}
$ids = [];
while($row = mysqli_fetch_assoc($result)) {
$ids[] = $row['id'];
}
$result = mysqli_query($dbl, 'SELECT * FROM `canvas` WHERE `id` IN ('.implode(', ', $ids).') ORDER BY RAND() LIMIT 1') OR DIE(MYSQLI_ERROR($dbl));
if(!mysqli_num_rows($result)) {
$output = [
'error' => 'nothingToDo',
'err' => 'nothingToDo',
'remainingPixels' => 0,
];
die(json_encode($output));
}
$row = mysqli_fetch_assoc($result);
$result = mysqli_query($dbl, 'SELECT COUNT(*) as `c` FROM `canvas` WHERE `targetHex` IS NOT NULL AND `targetHex` != `hex`') OR DIE(MYSQLI_ERROR($dbl));
$row1 = mysqli_fetch_assoc($result);
$output = [
'error' => NULL,
'err' => NULL,
'x' => intval($row['x']),
'y' => intval($row['y']),
'color' => $row['targetHex'],
'remainingPixels' => intval($row1['c']),
];
die(json_encode($output));
?>
<?php
/**
* pixelPlacerClient
*
* https://RundesBalli.com
*/
/**
* Token
*
* Use `id` from `me`-cookie
* (type p.user.id in console to get it)
*
* @var string
*/
$token = '';
/**
* API URL
*
* @var string
*/
$apiURL = '';
/**
* Loop
*/
$try = 0;
while(true) {
/**
* Initialize cURL to get color and coordinates from api
*/
echo 'Getting new pixel...'."\n";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_URL => $apiURL,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
]);
/**
* Execute cURL
*/
$response = json_decode(curl_exec($ch), TRUE);
if(json_last_error() != JSON_ERROR_NONE) {
echo 'Response error. Wait for 10 seconds and retry.'."\n";
$try++;
if($try == 3) {
die('Can not get response from server.'."\n");
}
sleep(10);
continue;
}
$try = 0;
/**
* Error handling
*/
$errNo = curl_errno($ch);
$errStr = curl_error($ch);
if($errNo != 0) {
echo "cURL getPixel - errno: ".$errNo." - errstr: ".$errStr."\n";
sleep(10);
continue;
}
$httpCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if($httpCode != 200) {
echo "cURL getPixel - httpcode: ".$httpCode."\n";
sleep(10);
continue;
}
curl_close($ch);
if($response['error'] == 'nothingToDo') {
echo 'Nothing to do. Sleeping for 3 seconds...'."\n";
sleep(3);
continue;
} elseif($response['error'] !== NULL) {
die("Response error: ".$response['error']."\n");
}
/**
* Initialize cURL and set options
*/
echo 'Placing '.$response['x'].','.$response['y'].': '.$response['color'].'... ';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_URL => 'https://sse.pr0gramm.com/api/place',
CURLOPT_USERAGENT => 'pixelPlacerClient',
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Schmuser: true',
'X-pr0gramm-token: '.$token,
'Content-Type: application/json',
],
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => json_encode([
'x' => intval($response['x']),
'y' => intval($response['y']),
'color' => strtolower($response['color']),
]),
]);
/**
* Execute cURL
*/
$response = curl_exec($ch);
/**
* Error handling
*/
$errNo = curl_errno($ch);
$errStr = curl_error($ch);
if($errNo != 0) {
echo "cURL placing - errno: ".$errNo." - errstr: ".$errStr."\n";
sleep(3);
continue;
}
$httpCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if($httpCode != 200) {
echo "cURL placing - httpcode: ".$httpCode."\n";
sleep(3);
continue;
}
curl_close($ch);
echo 'Placed.'."\n";
/**
* Waiting
*/
$wait = json_decode($response, TRUE)['totalCooldownSeconds']+1;
echo 'Waiting for '.$wait.' seconds until next placement...'."\n";
sleep($wait);
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment