first commit
This commit is contained in:
commit
27c3f1662d
22
README
Normal file
22
README
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
mkach - анонимный имиджборд
|
||||||
|
|
||||||
|
Установка:
|
||||||
|
1. Создайте базу данных MySQL
|
||||||
|
2. Импортируйте sql/create.sql
|
||||||
|
3. Настройте config.php
|
||||||
|
4. Создайте папку uploads/ с правами 755
|
||||||
|
|
||||||
|
Особенности:
|
||||||
|
- Анонимные посты без регистрации
|
||||||
|
- Поддержка изображений (jpg, png, gif, webp)
|
||||||
|
- Rate limiting на основе IP
|
||||||
|
- Стиль 4chan/2ch
|
||||||
|
- Ключ доступа для входа
|
||||||
|
- Автообновление постов
|
||||||
|
|
||||||
|
Безопасность:
|
||||||
|
- Валидация файлов
|
||||||
|
- Ограничение размера
|
||||||
|
- Защита от XSS
|
||||||
|
- Rate limiting
|
||||||
|
- Безопасная загрузка файлов
|
39
RateLimiter.php
Normal file
39
RateLimiter.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
class RateLimiter {
|
||||||
|
private $db;
|
||||||
|
private $maxRequests = 3;
|
||||||
|
private $timeWindow = 30;
|
||||||
|
|
||||||
|
public function __construct($db) {
|
||||||
|
$this->db = $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isAllowed($ip, $action = 'post') {
|
||||||
|
$stmt = $this->db->prepare('
|
||||||
|
SELECT COUNT(*) FROM rate_limits
|
||||||
|
WHERE ip_address = ? AND action_type = ?
|
||||||
|
AND created_at > DATE_SUB(NOW(), INTERVAL ? SECOND)
|
||||||
|
');
|
||||||
|
$stmt->execute([$ip, $action, $this->timeWindow]);
|
||||||
|
$count = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
if ($count >= $this->maxRequests) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare('
|
||||||
|
INSERT INTO rate_limits (ip_address, action_type) VALUES (?, ?)
|
||||||
|
');
|
||||||
|
$stmt->execute([$ip, $action]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cleanup() {
|
||||||
|
$stmt = $this->db->prepare('
|
||||||
|
DELETE FROM rate_limits
|
||||||
|
WHERE created_at < DATE_SUB(NOW(), INTERVAL ? SECOND)
|
||||||
|
');
|
||||||
|
$stmt->execute([$this->timeWindow]);
|
||||||
|
}
|
||||||
|
}
|
187
board.php
Normal file
187
board.php
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
header('X-Frame-Options: DENY');
|
||||||
|
header('X-XSS-Protection: 1; mode=block');
|
||||||
|
|
||||||
|
if (!isset($_SESSION['authenticated'])) {
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = require 'config.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = new PDO(
|
||||||
|
"mysql:host={$config['db']['host']};dbname={$config['db']['name']}",
|
||||||
|
$config['db']['user'],
|
||||||
|
$config['db']['pass']
|
||||||
|
);
|
||||||
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die('Connection failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'RateLimiter.php';
|
||||||
|
$rateLimiter = new RateLimiter($db);
|
||||||
|
$rateLimiter->cleanup();
|
||||||
|
|
||||||
|
$ip = $_SERVER['REMOTE_ADDR'];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (!$rateLimiter->isAllowed($ip)) {
|
||||||
|
$error = 'Слишком много запросов';
|
||||||
|
} else {
|
||||||
|
$message = trim($_POST['message'] ?? '');
|
||||||
|
$file = $_FILES['file'] ?? null;
|
||||||
|
|
||||||
|
if ($message || ($file && $file['error'] === UPLOAD_ERR_OK)) {
|
||||||
|
$postId = sprintf('%06d', mt_rand(1, 999999));
|
||||||
|
|
||||||
|
$fileName = null;
|
||||||
|
$fileSize = null;
|
||||||
|
$fileType = null;
|
||||||
|
|
||||||
|
if ($file && $file['error'] === UPLOAD_ERR_OK) {
|
||||||
|
$fileExt = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||||
|
|
||||||
|
if (!in_array($fileExt, $config['allowed_types'])) {
|
||||||
|
$error = 'Неподдерживаемый тип файла';
|
||||||
|
} elseif ($file['size'] > $config['max_file_size']) {
|
||||||
|
$error = 'Файл слишком большой';
|
||||||
|
} else {
|
||||||
|
$uploadDir = $config['upload_path'];
|
||||||
|
if (!is_dir($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileName = $postId . '.' . $fileExt;
|
||||||
|
$filePath = $uploadDir . $fileName;
|
||||||
|
|
||||||
|
if (move_uploaded_file($file['tmp_name'], $filePath)) {
|
||||||
|
$fileSize = $file['size'];
|
||||||
|
$fileType = $fileExt;
|
||||||
|
} else {
|
||||||
|
$error = 'Ошибка загрузки файла';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($error)) {
|
||||||
|
$stmt = $db->prepare('
|
||||||
|
INSERT INTO posts (post_id, message, file_name, file_size, file_type, ip_address)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
');
|
||||||
|
$stmt->execute([$postId, $message, $fileName, $fileSize, $fileType, $ip]);
|
||||||
|
|
||||||
|
header('Location: board.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = 'Введите сообщение или загрузите файл';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($_GET['logout'])) {
|
||||||
|
session_destroy();
|
||||||
|
header('Location: index.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $db->query('SELECT * FROM posts ORDER BY created_at DESC LIMIT 100');
|
||||||
|
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die('Database error');
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>mkach</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>mkach</h1>
|
||||||
|
<div class="header-buttons">
|
||||||
|
<a href="?logout=1" class="logout-btn">Выход</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="board-container">
|
||||||
|
<?php if (!empty($error)): ?>
|
||||||
|
<div class="error"><?= htmlspecialchars($error) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="posts-container" id="posts">
|
||||||
|
<?php foreach ($posts as $post): ?>
|
||||||
|
<div class="post">
|
||||||
|
<div class="post-header">
|
||||||
|
<span class="post-id">№<?= htmlspecialchars($post['post_id']) ?></span>
|
||||||
|
<span class="post-time"><?= date('d.m.Y H:i:s', strtotime($post['created_at'])) ?></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($post['file_name']): ?>
|
||||||
|
<div class="post-file">
|
||||||
|
<a href="uploads/<?= htmlspecialchars($post['file_name']) ?>" target="_blank">
|
||||||
|
<img src="uploads/<?= htmlspecialchars($post['file_name']) ?>" alt="File" class="post-image">
|
||||||
|
</a>
|
||||||
|
<div class="file-info">
|
||||||
|
<?= htmlspecialchars($post['file_name']) ?>
|
||||||
|
(<?= number_format($post['file_size'] / 1024, 1) ?> KB)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ($post['message']): ?>
|
||||||
|
<div class="post-message"><?= nl2br(htmlspecialchars($post['message'])) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="post-form">
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<div class="form-row">
|
||||||
|
<textarea name="message" placeholder="Сообщение (необязательно)" class="message-input"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<input type="file" name="file" accept=".jpg,.jpeg,.png,.gif,.webp" class="file-input">
|
||||||
|
<button type="submit" class="send-btn">Отправить</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let isScrolledToBottom = true;
|
||||||
|
const postsContainer = document.getElementById('posts');
|
||||||
|
|
||||||
|
postsContainer.addEventListener('scroll', () => {
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = postsContainer;
|
||||||
|
isScrolledToBottom = Math.abs(scrollHeight - clientHeight - scrollTop) < 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
fetch(window.location.href)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(html => {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(html, 'text/html');
|
||||||
|
const oldHeight = postsContainer.scrollHeight;
|
||||||
|
postsContainer.innerHTML = doc.getElementById('posts').innerHTML;
|
||||||
|
|
||||||
|
if (isScrolledToBottom) {
|
||||||
|
postsContainer.scrollTop = postsContainer.scrollHeight;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 10000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
13
config.php
Normal file
13
config.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'db' => [
|
||||||
|
'host' => 'localhost',
|
||||||
|
'name' => 'mkach',
|
||||||
|
'user' => 'mkach',
|
||||||
|
'pass' => 'your_password'
|
||||||
|
],
|
||||||
|
'access_key' => 'mkalwaysthebest1337',
|
||||||
|
'upload_path' => 'uploads/',
|
||||||
|
'max_file_size' => 5242880,
|
||||||
|
'allowed_types' => ['jpg', 'jpeg', 'png', 'gif', 'webp']
|
||||||
|
];
|
103
index.php
Normal file
103
index.php
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
header('X-Frame-Options: DENY');
|
||||||
|
header('X-XSS-Protection: 1; mode=block');
|
||||||
|
|
||||||
|
$config = require 'config.php';
|
||||||
|
|
||||||
|
if (!isset($_SESSION['authenticated'])) {
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$key = $_POST['access_key'] ?? '';
|
||||||
|
if (hash('sha256', $key) === hash('sha256', $config['access_key'])) {
|
||||||
|
$_SESSION['authenticated'] = true;
|
||||||
|
header('Location: board.php');
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = 'Неверный ключ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>mkach</title>
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
background: #000;
|
||||||
|
color: #0f0;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
text-align: center;
|
||||||
|
background: rgba(0,20,0,0.8);
|
||||||
|
padding: 40px;
|
||||||
|
border: 1px solid #0f0;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 20px rgba(0,255,0,0.3);
|
||||||
|
}
|
||||||
|
h1 { margin-bottom: 30px; font-size: 2.5em; }
|
||||||
|
.form-group { margin-bottom: 20px; }
|
||||||
|
input[type="password"] {
|
||||||
|
background: #000;
|
||||||
|
border: 1px solid #0f0;
|
||||||
|
color: #0f0;
|
||||||
|
padding: 15px;
|
||||||
|
font-size: 18px;
|
||||||
|
width: 300px;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
input[type="password"]:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 10px rgba(0,255,0,0.5);
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background: #000;
|
||||||
|
border: 1px solid #0f0;
|
||||||
|
color: #0f0;
|
||||||
|
padding: 15px 30px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #0f0;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: #f00;
|
||||||
|
margin-top: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>mkach</h1>
|
||||||
|
<form method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="password" name="access_key" placeholder="Введите ключ доступа" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit">Войти</button>
|
||||||
|
</form>
|
||||||
|
<?php if (isset($error)): ?>
|
||||||
|
<div class="error"><?= htmlspecialchars($error) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Location: board.php');
|
21
sql/create.sql
Normal file
21
sql/create.sql
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
CREATE TABLE posts (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
post_id VARCHAR(6) NOT NULL UNIQUE,
|
||||||
|
message TEXT,
|
||||||
|
file_name VARCHAR(255),
|
||||||
|
file_size INT,
|
||||||
|
file_type VARCHAR(10),
|
||||||
|
ip_address VARCHAR(45),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_post_id (post_id),
|
||||||
|
INDEX idx_created_at (created_at)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE rate_limits (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
ip_address VARCHAR(45) NOT NULL,
|
||||||
|
action_type VARCHAR(20) NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_ip_action (ip_address, action_type),
|
||||||
|
INDEX idx_created_at (created_at)
|
||||||
|
);
|
169
styles.css
Normal file
169
styles.css
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #f0e0d6;
|
||||||
|
font-family: 'Arial', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #800000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: #efefef;
|
||||||
|
border: 1px solid #b7c5d9;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #800000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
background: #d6daf0;
|
||||||
|
border: 1px solid #b7c5d9;
|
||||||
|
padding: 5px 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #800000;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn:hover {
|
||||||
|
background: #e5e9f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-container {
|
||||||
|
background: #efefef;
|
||||||
|
border: 1px solid #b7c5d9;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.posts-container {
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post {
|
||||||
|
background: #f0e0d6;
|
||||||
|
border: 1px solid #d9bfb7;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-header {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-id {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #800000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-time {
|
||||||
|
color: #707070;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-file {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-image {
|
||||||
|
max-width: 300px;
|
||||||
|
max-height: 300px;
|
||||||
|
border: 1px solid #d9bfb7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #707070;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-message {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-form {
|
||||||
|
background: #efefef;
|
||||||
|
border: 1px solid #b7c5d9;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 80px;
|
||||||
|
border: 1px solid #b7c5d9;
|
||||||
|
padding: 8px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-input {
|
||||||
|
border: 1px solid #b7c5d9;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn {
|
||||||
|
background: #d6daf0;
|
||||||
|
border: 1px solid #b7c5d9;
|
||||||
|
padding: 8px 15px;
|
||||||
|
color: #800000;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-btn:hover {
|
||||||
|
background: #e5e9f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
background: #ffd6d6;
|
||||||
|
border: 1px solid #ff9999;
|
||||||
|
color: #cc0000;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f0e0d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #d9bfb7;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #c4a6a0;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user