Files
AppAndProgramHacking/Fediverse/Akkoma/emoji_uploader_(admin).php.v1.fails.on.mobile
2026-01-03 13:57:56 +01:00

330 lines
14 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
session_start();
class PleromaAdminAuth {
private $baseUrl;
private $userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36';
private $cookieFile;
public function __construct($baseUrl) {
$this->baseUrl = rtrim($baseUrl, '/');
$this->cookieFile = sys_get_temp_dir() . '/pleroma_admin_cookies_' . session_id() . '.txt';
}
public function login($username, $password) {
// Step 1: Initialise session by visiting admin page
$adminPage = $this->baseUrl . '/pleroma/admin/';
$ch = curl_init($adminPage);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_USERAGENT => $this->userAgent,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_COOKIEJAR => $this->cookieFile,
CURLOPT_COOKIEFILE => $this->cookieFile,
CURLOPT_TIMEOUT => 30
]);
curl_exec($ch);
curl_close($ch);
// Step 2: OAuth password grant (exact flow used by modern AdminFE)
$oauthUrl = $this->baseUrl . '/oauth/token';
$postData = [
'grant_type' => 'password',
'username' => $username,
'password' => $password,
// These are the default AdminFE client credentials work on nearly all instances
'client_id' => 'ECfTrzsW11bkG7simInOJf614Ee2sBhOlvE-69KDMzs',
'client_secret' => 'r5EbNudkfePUwvRmc7DbafP0yOXpUC4lvipSrU8QOpo',
];
$ch = curl_init($oauthUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($postData),
CURLOPT_USERAGENT => $this->userAgent,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_COOKIEJAR => $this->cookieFile,
CURLOPT_COOKIEFILE => $this->cookieFile,
CURLOPT_HTTPHEADER => [
'Content-Type: application/x-www-form-urlencoded',
'Accept: application/json'
],
CURLOPT_TIMEOUT => 30
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($httpCode === 200 && isset($data['access_token'])) {
$_SESSION['bearer_token'] = $data['access_token'];
return ['success' => true, 'message' => 'Admin OAuth login successful', 'token' => $data['access_token']];
}
return ['success' => false, 'message' => 'Login failed', 'http_code' => $httpCode, 'response' => $response];
}
public function getCookieFile() {
return $this->cookieFile;
}
public function cleanup() {
if (file_exists($this->cookieFile)) {
unlink($this->cookieFile);
}
}
}
class PleromaEmojiUploader {
private $baseUrl;
private $bearerToken;
private $maxFileSize = 1048576; // 1 MB
private $maxDimensions = 256; // 256×256 px
public function __construct($baseUrl, $bearerToken) {
$this->baseUrl = rtrim($baseUrl, '/');
$this->bearerToken = $bearerToken;
}
private function validateFile($file, &$error) {
$error = '';
if (!is_uploaded_file($file['tmp_name'])) {
$error = 'No file uploaded';
return false;
}
if ($file['size'] > $this->maxFileSize) {
$error = 'File too large (max 1 MB)';
return false;
}
$filename = pathinfo($file['name'], PATHINFO_FILENAME);
if (!preg_match('/^[a-zA-Z0-9_]+$/', $filename)) {
$error = 'Shortcode (filename without extension) may only contain letters, numbers and underscores';
return false;
}
$allowed = ['png', 'gif', 'webp', 'jpg', 'jpeg'];
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed)) {
$error = 'Only PNG, GIF, WebP, JPG/JPEG allowed';
return false;
}
$info = getimagesize($file['tmp_name']);
if (!$info || $info[0] > $this->maxDimensions || $info[1] > $this->maxDimensions) {
$error = 'Image dimensions too large (max 256×256 px)';
return false;
}
return true;
}
public function uploadEmoji($packName, $uploadedFile) {
$error = '';
if (!$this->validateFile($uploadedFile, $error)) {
return ['success' => false, 'error' => $error];
}
// Modern endpoint (20242026 Akkoma/Pleroma)
$url = $this->baseUrl . '/api/v1/pleroma/emoji/packs/files?name=' . urlencode($packName);
$postData = [
'file' => new CURLFile($uploadedFile['tmp_name'], $uploadedFile['type'], $uploadedFile['name'])
];
$ch = curl_init($url);
$headers = [
'Authorization: Bearer ' . $this->bearerToken,
'Accept: application/json'
];
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postData,
CURLOPT_USERAGENT => 'Mozilla/5.0',
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 60
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$shortcode = pathinfo($uploadedFile['name'], PATHINFO_FILENAME);
return [
'success' => ($httpCode >= 200 && $httpCode < 300),
'http_code' => $httpCode,
'shortcode' => $shortcode,
'pack' => $packName,
'response' => $response
];
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pleroma/Akkoma Admin Login + Emoji Upload (2026)</title>
<style>
body { font-family: Arial, sans-serif; background: #f5f5f5; margin: 0; padding: 20px; }
.container { max-width: 900px; margin: 0 auto; background: #fff; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.tabs { display: flex; background: #8e44ad; }
.tab { flex: 1; padding: 16px; text-align: center; color: white; font-weight: bold; cursor: pointer; }
.tab.active { background: #9b59b6; }
.panel { padding: 30px; display: none; }
.panel.active { display: block; }
h1 { color: #333; border-bottom: 2px solid #8e44ad; padding-bottom: 10px; }
.warning { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
input[type="text"], input[type="password"], input[type="url"], input[type="file"] {
width: 100%; padding: 10px; margin: 8px 0; border: 1px solid #ddd; border-radius: 5px; font-size: 16px; box-sizing: border-box;
}
button { background: #8e44ad; color: white; padding: 12px; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; width: 100%; }
button:hover { background: #9b59b6; }
.result { margin-top: 30px; padding: 20px; border-radius: 5px; }
.success { background: #d4edda; border: 1px solid #c3e6cb; color: #155724; }
.error { background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; }
.token-box { background: #f3e8ff; padding: 12px; border-radius: 5px; margin: 10px 0; word-break: break-all; }
</style>
</head>
<body>
<div class="container">
<div class="tabs">
<div class="tab active" data-tab="login">1. Admin Login</div>
<div class="tab" data-tab="upload">2. Emoji Uploader</div>
</div>
<div id="login" class="panel active">
<h1>🔐 Admin Login (OAuth)</h1>
<div class="warning">
<strong>⚠️ Administrators only!</strong><br>
Uses the same OAuth password flow as the official admin panel.
</div>
<form method="POST">
<label>Server URL:</label>
<input type="url" name="server_url" placeholder="https://your-instance.com" required
value="<?php echo htmlspecialchars($_POST['server_url'] ?? ''); ?>">
<label>Admin Username/Email:</label>
<input type="text" name="username" placeholder="admin@domain.com" required
value="<?php echo htmlspecialchars($_POST['username'] ?? ''); ?>">
<label>Admin Password:</label>
<input type="password" name="password" required>
<button type="submit" name="admin_login">Login as Admin</button>
</form>
<?php
if (isset($_POST['admin_login'])) {
$serverUrl = trim($_POST['server_url']);
$username = trim($_POST['username']);
$password = $_POST['password'];
if (empty($serverUrl) || empty($username) || empty($password)) {
echo '<div class="result error">Please fill all fields.</div>';
} elseif (!filter_var($serverUrl, FILTER_VALIDATE_URL)) {
echo '<div class="result error">Invalid URL.</div>';
} else {
$auth = new PleromaAdminAuth($serverUrl);
$result = $auth->login($username, $password);
if ($result['success']) {
$_SESSION['base_url'] = $serverUrl;
$_SESSION['bearer_token'] = $result['token'];
echo '<div class="result success">';
echo '<h3>✅ Login Successful!</h3>';
echo '<p>You are now authenticated.</p>';
if (!empty($result['token'])) {
echo '<div class="token-box"><strong>Bearer Token:</strong><br>' . htmlspecialchars($result['token']) . '</div>';
}
echo '<p>Switch to the Emoji Uploader tab.</p>';
echo '</div>';
} else {
$auth->cleanup();
echo '<div class="result error">';
echo '<h3>❌ Login Failed</h3>';
echo '<p>' . htmlspecialchars($result['message']) . '</p>';
if (isset($result['http_code'])) echo '<p>HTTP Code: ' . $result['http_code'] . '</p>';
echo '</div>';
}
}
}
?>
</div>
<div id="upload" class="panel">
<h1>🎨 Emoji Uploader</h1>
<div class="warning">
Requires successful admin login.<br>
<strong>Pack must already exist</strong> (create it first in the web Admin → Settings → Emoji if needed).
</div>
<?php if (isset($_SESSION['bearer_token']) && isset($_SESSION['base_url'])): ?>
<form method="POST" enctype="multipart/form-data">
<label>Pack Name:</label>
<input type="text" name="packName" placeholder="CeaMoji" value="<?php echo htmlspecialchars($_POST['packName'] ?? 'CeaMoji'); ?>" required>
<label>Emoji Image (max 1 MB, 256×256 px):</label>
<input type="file" name="emojiFile" accept=".png,.gif,.webp,.jpg,.jpeg" required>
<button type="submit" name="do_upload">Upload Emoji</button>
</form>
<?php
if (isset($_POST['do_upload'])) {
$uploader = new PleromaEmojiUploader($_SESSION['base_url'], $_SESSION['bearer_token']);
$uploadResult = $uploader->uploadEmoji($_POST['packName'], $_FILES['emojiFile']);
echo '<div class="result ' . ($uploadResult['success'] ? 'success' : 'error') . '">';
if ($uploadResult['success']) {
echo '<h3>✅ Emoji Uploaded Successfully!</h3>';
echo '<p>Shortcode: <strong>:' . htmlspecialchars($uploadResult['shortcode']) . ':</strong></p>';
echo '<p>Pack: <strong>' . htmlspecialchars($uploadResult['pack']) . '</strong></p>';
echo '<p>You may need to refresh the admin panel or reload emojis for it to appear immediately.</p>';
} else {
echo '<h3>❌ Upload Failed</h3>';
echo '<p>HTTP Code: ' . $uploadResult['http_code'] . '</p>';
if (!empty($uploadResult['response'])) {
echo '<pre>' . htmlspecialchars($uploadResult['response']) . '</pre>';
}
}
echo '</div>';
}
?>
<?php else: ?>
<div class="result error">
<strong>No active admin session.</strong><br>
Please log in first on the Login tab.
</div>
<?php endif; ?>
</div>
</div>
<script>
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
tab.classList.add('active');
document.getElementById(tab.dataset.tab).classList.add('active');
});
});
</script>
</body>
</html>