chore: 🚀 init
This commit is contained in:
parent
09f2e6b055
commit
a4dceb0da5
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
telegram.config.php
|
||||||
|
uppies/*
|
||||||
|
!uppies/.gitkeep
|
||||||
|
storage.db
|
||||||
38
css/style.css
Normal file
38
css/style.css
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
background-color: #222;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
body, h1, h2, h3 {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
header, main, footer {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bookmark {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
.zfont {
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#img {
|
||||||
|
z-index: -1;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: .5;
|
||||||
|
background-image: url(../img/lorem.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: top right;
|
||||||
|
}
|
||||||
1
css/uikit.min.css
vendored
Normal file
1
css/uikit.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
favicon.png
Normal file
BIN
favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
BIN
img/lorem.png
Normal file
BIN
img/lorem.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
BIN
img/ogp.jpg
Normal file
BIN
img/ogp.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
263
index.php
Normal file
263
index.php
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
include_once('src/storage.php');
|
||||||
|
include_once('src/telegram.php');
|
||||||
|
|
||||||
|
if (empty($_GET['id'])) {
|
||||||
|
$newId = bin2hex(random_bytes(16));
|
||||||
|
header("Location: ?id=$newId");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = htmlspecialchars($_GET['id']);
|
||||||
|
|
||||||
|
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||||
|
$bookmark = $protocol . '://' . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
|
||||||
|
|
||||||
|
$alerts = [
|
||||||
|
'primary' => [],
|
||||||
|
'success' => [],
|
||||||
|
'warning' => [],
|
||||||
|
'danger' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
$data = ['img' => '', 'name' => '', 'url' => '', 'desc' => ''];
|
||||||
|
|
||||||
|
try {
|
||||||
|
$data = Storage::get($id);
|
||||||
|
}
|
||||||
|
catch(Exception $ex) {
|
||||||
|
$alerts['danger'][] = '❌ Error reading database';
|
||||||
|
}
|
||||||
|
|
||||||
|
$img = $data['img'];
|
||||||
|
// echo '<pre>'; print_r($img); echo '</pre>';
|
||||||
|
$name = $data['name'];
|
||||||
|
$url = $data['url'];
|
||||||
|
$desc = $data['desc'];
|
||||||
|
|
||||||
|
if ($_SERVER["REQUEST_METHOD"] === 'POST' && isset($_GET['upload'])) {
|
||||||
|
http_response_code(202);
|
||||||
|
|
||||||
|
$targetDir = "uppies/";
|
||||||
|
$file = $_FILES["files"];
|
||||||
|
$targetFile = $targetDir . basename($file["name"][0]);
|
||||||
|
$imageFileType = strtolower(pathinfo($targetFile, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
// Create the uploads directory if it doesn't exist
|
||||||
|
if (!is_dir($targetDir)) {
|
||||||
|
mkdir($targetDir, 0775, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the file is actually an image
|
||||||
|
$check = getimagesize($file["tmp_name"][0]);
|
||||||
|
if ($check === false) {
|
||||||
|
exit("❌ Error: File is not a valid image.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow only specific image file formats
|
||||||
|
$allowedTypes = ["jpg", "jpeg", "png", "gif"];
|
||||||
|
if (!in_array($imageFileType, $allowedTypes)) {
|
||||||
|
exit("❌ Error: Only JPG, JPEG, PNG and GIF files allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit file size (e.g., 5MB)
|
||||||
|
if ($file["size"][0] > 100 * 1024 * 1024) {
|
||||||
|
exit("❌ Error: File too large (max. 100 MB)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate destination file name
|
||||||
|
$finalPath = $targetDir . $id . '.' . $imageFileType;
|
||||||
|
|
||||||
|
// Move uploaded file to the target directory
|
||||||
|
if (move_uploaded_file($file["tmp_name"][0], $finalPath)) {
|
||||||
|
try {
|
||||||
|
Storage::set_img($id, $id . '.' . $imageFileType);
|
||||||
|
}
|
||||||
|
catch(Exception $ex) {
|
||||||
|
exit("❌ Database error.");
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code(200);
|
||||||
|
exit($finalPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit("❌ General error saving the file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['desc']) && isset($_POST['name']) && isset($_POST['url'])) {
|
||||||
|
$name = htmlspecialchars(trim($_POST['name']));
|
||||||
|
$url = htmlspecialchars(trim($_POST['url']));
|
||||||
|
$desc = htmlspecialchars(trim($_POST['desc']));
|
||||||
|
|
||||||
|
if (!empty($name) && !empty($desc) && !empty($url)) {
|
||||||
|
try {
|
||||||
|
Storage::set_data($id, $name, $url, $desc);
|
||||||
|
Telegram::report("EF Conbook Artist Credits Submission\nname: $name\nurl: $url\ntext:\n$desc");
|
||||||
|
$alerts['success'][] = '✅ Entry saved';
|
||||||
|
}
|
||||||
|
catch(Exception $ex) {
|
||||||
|
$alerts['danger'][] = $ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html prefix="og: http://ogp.me/ns#" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Artist Credits</title>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content="Submit Artist Credits to the Eurofurence Conbook Team" />
|
||||||
|
<meta name="keywords" content="eurofurence, Conbook, artist credits" />
|
||||||
|
<meta name="robots" content="index, follow, noodp" />
|
||||||
|
<meta name="author" content="The Eurofurence Conbook Team" />
|
||||||
|
<meta name="rating" content="general" />
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="favicon.png">
|
||||||
|
|
||||||
|
<meta property="og:image" content="img/ogp.jpg" />
|
||||||
|
<meta property="og:image:secure_url" content="img/ogp.jpg" />
|
||||||
|
<meta property="og:image:type" content="image/jpeg" />
|
||||||
|
<meta property="og:image:width" content="344" />
|
||||||
|
<meta property="og:image:height" content="247" />
|
||||||
|
<meta property="og:image:alt" content="A dog on a cloud." />
|
||||||
|
<meta property="og:title" content="Conbook Art Credits" />
|
||||||
|
<meta property="og:description" content="Submit Artist Credits to the Eurofurence Conbook Team" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content="https://dogpixels.net/ef/conbook-artist-credits" />
|
||||||
|
<meta property="og:site_name" content="Conbook Art Credits" />
|
||||||
|
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content="Conbook Art Credits" />
|
||||||
|
<meta name="twitter:description" content="Submit Artist Credits to the Eurofurence Conbook Team" />
|
||||||
|
<meta name="twitter:image" content="img/ogp.jpg" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/uikit.min.css" type="text/css" />
|
||||||
|
<link rel="stylesheet" href="css/style.css" type="text/css" />
|
||||||
|
|
||||||
|
<script src="js/uikit.min.js"></script>
|
||||||
|
<script src="js/uikit-icons.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Conbook Artist Credit Form</h1>
|
||||||
|
<p>
|
||||||
|
Thank you for submitting art to the Eurofurence Conbook. In the event of your art making an appearance in the book, your are eligible to an entry in the artist credits.<br />
|
||||||
|
Edit your entry below before <strong>the end of July</strong> to make sure you are credited appropriately.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Bookmark this page or save the following url to be able to edit your entry later:</p>
|
||||||
|
<div class="uk-width-1-1 uk-grid-collapse" uk-grid>
|
||||||
|
<div class="uk-width-3-4@m">
|
||||||
|
<input type="text" id="bookmark" disabled value="<?= $bookmark ?>" class="uk-input" />
|
||||||
|
</div>
|
||||||
|
<div class="uk-width-1-4@m">
|
||||||
|
<button type="button" id="copyUrl" class="uk-button uk-button-primary uk-width-1-1" title="✅ Copied">Copy to Clipboard</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="uk-margin">
|
||||||
|
<hr />
|
||||||
|
<p>Please make sure your profile image is large enough for print. Don't worry about cropping or aspect ratio, it will be adjusted into the layout by us.</p>
|
||||||
|
<section class="uk-child-width-1-2@m" uk-grid>
|
||||||
|
<div>
|
||||||
|
<div class="js-upload uk-placeholder uk-margin-top uk-position-relative">
|
||||||
|
<div id="img"></div>
|
||||||
|
<!-- <img id="img" src="" alt="No image uploaded yet" /> -->
|
||||||
|
<span uk-icon="icon: cloud-upload"></span>
|
||||||
|
<span class="uk-text-middle">Upload Your Profile Image<br />PNG, JPG or GIF<br />min. 500 x 500 @ 300 dpi<br /></span>
|
||||||
|
<div uk-form-custom>
|
||||||
|
<input type="file">
|
||||||
|
<span class="uk-link">SELECT FILE</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<progress id="js-progressbar" class="uk-progress" value="0" max="100" hidden></progress>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<form action="" method="POST">
|
||||||
|
<input type="text" name="name" id="name" maxlength="80" value="<?= $name ?>" placeholder="Your nickname (max. 80 characters)" class="uk-input uk-margin-top" />
|
||||||
|
<input type="text" name="url" id="url" maxlength="256" value="<?= $url ?>" placeholder="Your gallery link / homepage (max. 256 characters)" class="uk-input uk-margin-top" />
|
||||||
|
<input type="text" name="desc" id="desc" maxlength="400" value="<?= $desc ?>" placeholder="Your description (max. 400 characters)" class="uk-input uk-margin-top" />
|
||||||
|
<input type="submit" value="Save" class="uk-button uk-button-primary uk-margin-top" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<hr />
|
||||||
|
<p>If you need assistance, please contact <a href="https://help.eurofurence.org/contact/conbook" target="_blank">The Conbook Department</a>.</p>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('copyUrl').addEventListener('click', (e) => {
|
||||||
|
var furl = document.getElementById('bookmark');
|
||||||
|
furl.setSelectionRange(0, furl.value.length);
|
||||||
|
navigator.clipboard.writeText(furl.value);
|
||||||
|
furl.setSelectionRange(0, 0);
|
||||||
|
e.target.innerText = "✅ copied";
|
||||||
|
// setTimeout(() => {e.target.innerText = "Copy to Clipboard"}, 3000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
<?php
|
||||||
|
foreach ($alerts as $status => $msgs) {
|
||||||
|
foreach ($msgs as $msg) {
|
||||||
|
echo 'UIkit.notification("' . $msg . '", "' . $status . '");';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php if ($img !== '') { ?>
|
||||||
|
<script>
|
||||||
|
document.getElementById('img').style.backgroundImage = 'url(uppies/<?= $img ?>?' + new Date().getTime() + ')';
|
||||||
|
</script>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var bar = document.getElementById('js-progressbar');
|
||||||
|
|
||||||
|
UIkit.upload('.js-upload', {
|
||||||
|
url: '<?= $bookmark ?>&upload',
|
||||||
|
|
||||||
|
loadStart: function (e) {
|
||||||
|
bar.removeAttribute('hidden');
|
||||||
|
bar.max = e.total;
|
||||||
|
bar.value = e.loaded;
|
||||||
|
},
|
||||||
|
|
||||||
|
progress: function (e) {
|
||||||
|
bar.max = e.total;
|
||||||
|
bar.value = e.loaded;
|
||||||
|
},
|
||||||
|
|
||||||
|
loadEnd: function (e) {
|
||||||
|
bar.max = e.total;
|
||||||
|
bar.value = e.loaded;
|
||||||
|
},
|
||||||
|
|
||||||
|
completeAll: function (r) {
|
||||||
|
if (r.status == 200) {
|
||||||
|
console.log(r);
|
||||||
|
document.getElementById('img').style.backgroundImage = 'url(' + r.responseText + '?' + new Date().getTime() + ')';
|
||||||
|
UIkit.notification('✅ Image saved', 'success');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
UIkit.notification(r.responseText, 'danger');
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
bar.setAttribute('hidden', 'hidden');
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1
js/uikit-icons.min.js
vendored
Normal file
1
js/uikit-icons.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
js/uikit.min.js
vendored
Normal file
1
js/uikit.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
144
src/storage.php
Normal file
144
src/storage.php
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Persistent Key-Value Storage 1.0
|
||||||
|
* (c) 2025 draconigen@dogpixels.net
|
||||||
|
* AGPL 3.0, see https://www.gnu.org/licenses/agpl-3.0.de.html
|
||||||
|
* Provided "as is", without warranty of any kind.
|
||||||
|
*/
|
||||||
|
|
||||||
|
define('STORAGE_FILE', 'storage.db');
|
||||||
|
define('STORAGE_ENCRYPTION_KEY', ''); // empty disables encryption
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static Class for persistent Key-Value storage.
|
||||||
|
* Creates a database file (default: storage.sqlite) next to the script.
|
||||||
|
*/
|
||||||
|
class Storage {
|
||||||
|
static private function init(): Sqlite3 {
|
||||||
|
$flagInitDatabase = !file_exists(STORAGE_FILE);
|
||||||
|
$db = new SQLite3(STORAGE_FILE, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, STORAGE_ENCRYPTION_KEY);
|
||||||
|
if ($flagInitDatabase) {
|
||||||
|
$db->exec("CREATE TABLE cache (
|
||||||
|
id TEXT NOT NULL UNIQUE,
|
||||||
|
mod DATATIME DEFAULT (DATETIME('now', 'localtime')),
|
||||||
|
img TEXT,
|
||||||
|
name TEXT,
|
||||||
|
url TEXT,
|
||||||
|
desc TEXT,
|
||||||
|
PRIMARY KEY(id)
|
||||||
|
);");
|
||||||
|
}
|
||||||
|
return $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a single entry from database.
|
||||||
|
* @param string $id Key of the entry to retrieve.
|
||||||
|
* @return array|bool JSON-serializable array on success, otherwise false
|
||||||
|
*/
|
||||||
|
static public function get(string $id): array | bool {
|
||||||
|
$db = Storage::init();
|
||||||
|
$stmt = $db->prepare("SELECT name, url, desc, img FROM cache WHERE id=?;");
|
||||||
|
if (!$stmt || !$stmt->bindValue(1, $id, SQLITE3_TEXT)) {
|
||||||
|
throw new Exception($db->lastErrorMsg());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$cur = $stmt->execute();
|
||||||
|
if (!$cur) {
|
||||||
|
throw new Exception($db->lastErrorMsg());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while ($row = $cur->fetchArray()) {
|
||||||
|
return ['img' => $row['img'], 'name' => $row['name'], 'url' => $row['url'], 'desc' => $row['desc']];
|
||||||
|
}
|
||||||
|
return ['img' => '', 'name' => '', 'url' => '', 'desc' => ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all entries from database.
|
||||||
|
* @return array Assoc array of id => [data]
|
||||||
|
*/
|
||||||
|
static public function getAll(): array {
|
||||||
|
$db = Storage::init();
|
||||||
|
$ret = [];
|
||||||
|
$stmt = $db->prepare("SELECT id, img, name, url, desc FROM cache;");
|
||||||
|
if (!$stmt) {
|
||||||
|
throw new Exception($db->lastErrorMsg());
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
$cur = $stmt->execute();
|
||||||
|
if (!$cur) {
|
||||||
|
throw new Exception($db->lastErrorMsg());
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
while ($row = $cur->fetchArray()) {
|
||||||
|
$ret[$row['id']] = [$row['img'], $row['name'], $row['url'], $row['desc']];
|
||||||
|
}
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts or updates data
|
||||||
|
* @param string $id Key of the entry to insert/update.
|
||||||
|
* @param array $data JSON-serializable data array to write to database.
|
||||||
|
* @return bool Success indicator.
|
||||||
|
*/
|
||||||
|
static public function set_data(string $id, string $name, string $url, string $desc): bool {
|
||||||
|
$db = Storage::init();
|
||||||
|
$stmt = $db->prepare("INSERT INTO cache (id, name, url, desc) VALUES(?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name=excluded.name, url=excluded.url, desc=excluded.desc, mod=excluded.mod;");
|
||||||
|
if (!$stmt || !$stmt->bindValue(1, $id, SQLITE3_TEXT) || !$stmt->bindValue(2, $name, SQLITE3_TEXT) || !$stmt->bindValue(3, $url, SQLITE3_TEXT)|| !$stmt->bindValue(4, $desc, SQLITE3_TEXT) || !$stmt->execute()) {
|
||||||
|
throw new Exception($db->lastErrorMsg());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function set_img(string $id, string $img): bool {
|
||||||
|
$db = Storage::init();
|
||||||
|
$stmt = $db->prepare("INSERT INTO cache (id, img) VALUES(?, ?) ON CONFLICT(id) DO UPDATE SET img=excluded.img, mod=excluded.mod;");
|
||||||
|
if (!$stmt || !$stmt->bindValue(1, $id, SQLITE3_TEXT) || !$stmt->bindValue(2, $img, SQLITE3_TEXT) || !$stmt->execute()) {
|
||||||
|
throw new Exception($db->lastErrorMsg());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a single entry.
|
||||||
|
* @param string $id Key of entry to delete.
|
||||||
|
* @return bool Success indicator.
|
||||||
|
*/
|
||||||
|
static public function delete(string $id): bool {
|
||||||
|
$db = Storage::init();
|
||||||
|
$stmt = $db->prepare("DELETE FROM cache WHERE id=?;");
|
||||||
|
if (!$stmt || !$stmt->bindValue(1, $id, SQLITE3_TEXT) || !$stmt->execute()) {
|
||||||
|
throw new Exception($db->lastErrorMsg());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the total count of rows in the database.
|
||||||
|
* @return int Total number of rows.
|
||||||
|
*/
|
||||||
|
static public function count(): int {
|
||||||
|
$db = Storage::init();
|
||||||
|
return $db->querySingle("SELECT COUNT(*) FROM cache;");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all entries older than the given timespan.
|
||||||
|
* @param string $age See https://www.sqlite.org/lang_datefunc.html for valid values.
|
||||||
|
* @return bool Success indicator.
|
||||||
|
*/
|
||||||
|
static public function prune(string $age): bool {
|
||||||
|
$db = Storage::init();
|
||||||
|
$stmt = $db->prepare("DELETE FROM cache WHERE mod <= DATETIME('now', 'localtime', ?);");
|
||||||
|
if (!$stmt || !$stmt->bindValue(1, $age, SQLITE3_TEXT) || !$stmt->execute()) {
|
||||||
|
throw new Exception($db->lastErrorMsg());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/telegram.php
Normal file
50
src/telegram.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
/**
|
||||||
|
* Telegram Reporting Library 1.0
|
||||||
|
* (c) 2025 draconigen@dogpixels.net
|
||||||
|
* AGPL 3.0, see https://www.gnu.org/licenses/agpl-3.0.de.html
|
||||||
|
* Provided "as is", without warranty of any kind.
|
||||||
|
*/
|
||||||
|
|
||||||
|
# configurable telegram config file path
|
||||||
|
define('TELEGRAM_CONFIG_PATH', 'telegram.config.php');
|
||||||
|
|
||||||
|
# create file if it doesn't exist
|
||||||
|
if (!file_exists(TELEGRAM_CONFIG_PATH)) {
|
||||||
|
file_put_contents(TELEGRAM_CONFIG_PATH, "<?php\ndefine('TELEGRAM_BOT_API_TOKEN', '');\ndefine('TELEGRAM_TARGET_USERID', '9724740'); # @draconigen\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
# load config file
|
||||||
|
include_once(TELEGRAM_CONFIG_PATH);
|
||||||
|
|
||||||
|
# check config file contents and raise exception if empty
|
||||||
|
if (empty(TELEGRAM_BOT_API_TOKEN) || empty(TELEGRAM_TARGET_USERID)) {
|
||||||
|
throw new Exception("Telegram config missing in " . TELEGRAM_CONFIG_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Telegram {
|
||||||
|
public static function report(string $message) {
|
||||||
|
# prepare telegram api payload
|
||||||
|
$payload = [
|
||||||
|
'chat_id' => TELEGRAM_TARGET_USERID,
|
||||||
|
'text' => sprintf("```\n%s\n```", htmlspecialchars_decode($message)),
|
||||||
|
'parse_mode' => 'MarkdownV2'
|
||||||
|
];
|
||||||
|
|
||||||
|
# init & configure curl request
|
||||||
|
$curl = curl_init("https://api.telegram.org/bot" . TELEGRAM_BOT_API_TOKEN . "/sendMessage");
|
||||||
|
|
||||||
|
curl_setopt_array($curl, [
|
||||||
|
CURLOPT_POST => true, # set POST request method
|
||||||
|
CURLOPT_POSTFIELDS => http_build_query($payload), # attach url-encoded post data
|
||||||
|
CURLOPT_RETURNTRANSFER => true # return response, rather than printing it to stdout
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
# exec api call and check response
|
||||||
|
$response = curl_exec($curl);
|
||||||
|
if (!json_decode($response)->ok) {
|
||||||
|
throw new Exception("Telegram API Error:\n{$response}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
0
uppies/.gitkeep
Normal file
0
uppies/.gitkeep
Normal file
Loading…
x
Reference in New Issue
Block a user