diff --git a/dec.py b/dec.py deleted file mode 100644 index bd95dc9..0000000 --- a/dec.py +++ /dev/null @@ -1,74 +0,0 @@ -import os -import sys -import fileinput - -R = '\033[1;31m' -G = '\033[1;32m' -Y = '\033[1;33m' -C = '\033[1;36m' -W = '\033[1;37m' - -logo = """ - \033[1;33m _____ _ - \033[1;33m| __ \ \033[1;36mBash File\033[1;33m | | - \033[1;33m| | | | ___ ___ _ __ _ _ _ __ | |_ ___ _ __ - \033[1;33m| | | |/ _ \/ __| '__| | | | '_ \| __/ _ \ '__| - \033[1;33m| |__| | __/ (__| | | |_| | |_) | || __/ | - \033[1;33m|_____/ \___|\___|_| \__, | .__/ \__\___|_| - \033[1;33m __/ | | - \033[1;33m |___/|_| - \033[1;33m - \033[1;36m [\033[1;37m+\033[1;36m]\033[1;32m CREATED BY HTR-TECH (TAHMID RAYAT) -""" -banner = """ - {}[{}01{}]{} Decrypt Bash File - - {}[{}02{}]{} More Tools from us - -""".format(C,W,C,Y,C,W,C,Y) - -os.system("clear") -print logo -print banner - -def decr(): - try: - print ("") - scname = raw_input(G + " Name of the Script to Decrypt" + C + " > " + Y) - o = open(scname,'r') - filedata = o.read() - o.close() - - new_dat = filedata.replace("eval","echo") - print ("") - output = raw_input(G + " Name of the Decrypted Script" + C + " > " + Y) - f = open(output,'w') - S = (output) - f.write(new_dat) - f.close() - os.system("touch dummy.sh") - os.system("bash " + output + " > dummy.sh") - os.remove(output) - os.system("mv -f dummy.sh " + output) - print ("") - print (G + " Decryption Succeed...") - print ("") - print (Y + " Decrypted Script Name" + C + " > " + G + S + W) - print ("") - - except KeyboardInterrupt(): - print (R + " !" + Y + " Tool Stopped " + R + "!" + W) - print ("") - except IOError: - print (R + " !" + Y + " File not Found " + R + "!" + W) - print ("") - -maintool = raw_input(G + " Select an option" + C + " > " + Y) - -if maintool == "1" or maintool == "01": - decr() -elif maintool == "2" or maintool == "02": - os.system("xdg-open https://github.com/htr-tech/") -else: - print (W + "") - print (C + "[" + R + " !" + C + "]" + Y + " Invalid input " + C + "[" + R + "!" + C + "]") diff --git a/public_html/.htaccess b/public_html/.htaccess new file mode 100644 index 0000000..5d29300 --- /dev/null +++ b/public_html/.htaccess @@ -0,0 +1,11 @@ +Options -Indexes +RewriteEngine On + +RewriteCond %{REQUEST_URI} !^/admin +RewriteCond %{REQUEST_URI} !^/assets +RewriteCond %{REQUEST_URI} !^/api +RewriteCond %{REQUEST_URI} !^/sql +RewriteCond %{REQUEST_URI} !^/logs +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^([A-Za-z0-9\-]+)/?$ post.php?slug=$1 [L,QSA] diff --git a/public_html/README.md b/public_html/README.md new file mode 100644 index 0000000..bc94be0 --- /dev/null +++ b/public_html/README.md @@ -0,0 +1,13 @@ +# Website tin tức đơn giản + +Upload toàn bộ thư mục `public_html` lên hosting (DataOnline.vn), import `sql/database.sql`, cập nhật thông tin DB trong `config.php`. Sau khi import có sẵn tài khoản admin/admin567. + +## Đường dẫn +- Trang chủ: `/` +- Trang bài: `/slug` +- Đăng nhập: `/admin/login.php` +- Trang quản trị một trang: `/admin/panel.php` + +## Lưu ý +- Bật/tắt quảng cáo, link Shopee, link liên hệ nằm trong tab Cài đặt của panel. +- Khi quảng cáo bật và người dùng mở trực tiếp `/slug`, overlay sẽ xuất hiện ngay; đóng overlay sẽ mở tab mới `/slug?ad=0` và tab hiện tại chuyển sang link Shopee. diff --git a/public_html/admin/login.php b/public_html/admin/login.php new file mode 100644 index 0000000..251e1b1 --- /dev/null +++ b/public_html/admin/login.php @@ -0,0 +1,40 @@ + + + + + + Đăng nhập + + + +
+
+

Đăng nhập admin

+

+
+ + + + + +
+
+
+ + diff --git a/public_html/admin/logout.php b/public_html/admin/logout.php new file mode 100644 index 0000000..a760784 --- /dev/null +++ b/public_html/admin/logout.php @@ -0,0 +1,7 @@ +prepare('INSERT INTO posts (title, slug, content, images_json, videos_json, created_at) VALUES (?, ?, ?, ?, ?, NOW())'); + $stmt->execute([$title, $slug, $content, json_encode(array_values($images)), json_encode(array_values($videos))]); + $message = 'Đã thêm bài'; + } elseif ($action === 'update_post') { + $postId = (int)($_POST['post_id'] ?? 0); + $title = trim($_POST['title'] ?? ''); + $slug = trim($_POST['slug'] ?? ''); + $slug = $slug !== '' ? $slug : slugify($title); + $content = trim($_POST['content'] ?? ''); + $images = array_filter(array_map('trim', explode("\n", $_POST['images'] ?? ''))); + $videos = array_filter(array_map('trim', explode("\n", $_POST['videos'] ?? ''))); + $stmt = $pdo->prepare('UPDATE posts SET title=?, slug=?, content=?, images_json=?, videos_json=? WHERE id=?'); + $stmt->execute([$title, $slug, $content, json_encode(array_values($images)), json_encode(array_values($videos)), $postId]); + $message = 'Đã cập nhật'; + } elseif ($action === 'delete_post') { + $postId = (int)($_POST['post_id'] ?? 0); + $stmt = $pdo->prepare('DELETE FROM posts WHERE id = ?'); + $stmt->execute([$postId]); + $message = 'Đã xóa'; + } elseif ($action === 'save_settings') { + $adsEnabled = isset($_POST['ads_enabled']) ? 1 : 0; + $adLink = trim($_POST['ad_link'] ?? ''); + $adTitle = trim($_POST['ad_title'] ?? ''); + $adBody = trim($_POST['ad_body'] ?? ''); + $contactLink = trim($_POST['contact_link'] ?? ''); + $stmt = $pdo->prepare('UPDATE settings SET ads_enabled=?, ad_link=?, ad_title=?, ad_body=?, contact_link=? WHERE id = 1'); + $stmt->execute([$adsEnabled, $adLink, $adTitle, $adBody, $contactLink]); + $message = 'Đã lưu cài đặt'; + } +} + +$settings = load_settings($pdo); +$posts = $pdo->query('SELECT * FROM posts ORDER BY created_at DESC')->fetchAll(); +$totalPosts = count($posts); +$totalViews = stat_total($pdo, 'view'); +$totalClicks = stat_total($pdo, 'ad_click'); +$csrf = csrf_token(); +$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://'; +$baseUrl = $scheme . ($_SERVER['HTTP_HOST'] ?? 'domain'); +?> + + + + + Quản trị + + + + +
+
+
+

Xin chào,

+ Đăng xuất +
+ +

+ +
+

Thêm bài

+
+ + + + + + + + + + + + + +
+ +

Danh sách bài

+ +
+
+ + ID +
+ + +
+ Sửa / Xóa +
+ + + + + + + + + + + + + + +
+
+ + + + +
+
+
+ +
+ + + + +
+
+ + diff --git a/public_html/api/track.php b/public_html/api/track.php new file mode 100644 index 0000000..15e0b36 --- /dev/null +++ b/public_html/api/track.php @@ -0,0 +1,22 @@ +prepare('SELECT id FROM posts WHERE slug = ? LIMIT 1'); + $stmt->execute([$slug]); + $row = $stmt->fetch(); + if ($row) { + $postId = (int)$row['id']; + } +} + +record_stat($pdo, $type === 'ad_click' ? 'ad_click' : 'view', $postId, 60); + +echo json_encode(['ok' => true]); diff --git a/public_html/assets/css/admin.css b/public_html/assets/css/admin.css new file mode 100644 index 0000000..d7f9fca --- /dev/null +++ b/public_html/assets/css/admin.css @@ -0,0 +1,24 @@ +body { + margin: 0; + font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif; + background: #0b1222; + color: #e2e8f0; +} +.container { max-width: 880px; margin: 0 auto; padding: 18px 14px 60px; } +.card { background: #111827; border-radius: 12px; padding: 16px; box-shadow: 0 12px 32px rgba(0,0,0,0.35); } +.card + .card { margin-top: 12px; } +input, textarea, button { width: 100%; padding: 12px; margin: 6px 0 12px; border-radius: 8px; border: 1px solid #1f2937; background: #0d152b; color: #e2e8f0; } +label { font-weight: 600; display: block; margin-top: 4px; } +button { cursor: pointer; background: #2563eb; border: none; font-weight: 600; } +button.danger { background: #ef4444; } +.nav { display: flex; gap: 8px; margin: 12px 0; } +.nav button { flex: 1; } +.badge { display: inline-block; padding: 6px 10px; background: #1d4ed8; border-radius: 8px; font-size: 12px; } +.alert { color: #fca5a5; } +.hidden { display: none; } +.link-copy { font-size: 13px; color: #93c5fd; word-break: break-all; } +.row { display: flex; justify-content: space-between; align-items: center; } +.top-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; } +.logout { color: #93c5fd; text-decoration: none; } +.card.inner { margin-top: 10px; } +@media (max-width: 600px) { .nav { flex-direction: column; } .top-row { flex-direction: column; align-items: flex-start; } } diff --git a/public_html/assets/css/style.css b/public_html/assets/css/style.css new file mode 100644 index 0000000..a1885af --- /dev/null +++ b/public_html/assets/css/style.css @@ -0,0 +1,66 @@ +:root { + --bg: #f8fafc; + --text: #0f172a; + --primary: #2563eb; +} +* { box-sizing: border-box; } +body { + margin: 0; + font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif; + background: var(--bg); + color: var(--text); +} +.container { + max-width: 780px; + margin: 0 auto; + padding: 20px 16px 60px; +} +h1, h2, h3 { margin-top: 0; } +.home { text-align: center; padding-top: 80px; } +.primary-btn { + display: inline-block; + padding: 14px 20px; + background: var(--primary); + color: #fff; + text-decoration: none; + border-radius: 10px; + font-weight: 600; +} +.primary-btn:hover { opacity: 0.92; } +.content { line-height: 1.6; white-space: pre-wrap; margin-bottom: 14px; } +.media-grid { display: grid; grid-template-columns: repeat(auto-fill,minmax(240px,1fr)); gap: 12px; } +.media-item img, .media-item video, .media-item iframe { width: 100%; border-radius: 10px; } +.error-page { text-align: center; } + +#ad-overlay { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + color: #fff; +} +#ad-overlay .box { + background: #0f172a; + padding: 24px; + border-radius: 12px; + width: 90%; + max-width: 480px; + position: relative; + text-align: center; + box-shadow: 0 18px 38px rgba(0,0,0,0.35); +} +#ad-overlay .close { + position: absolute; + top: 12px; + right: 12px; + border: none; + background: transparent; + color: #fff; + font-size: 20px; + cursor: pointer; +} +#ad-overlay h2 { margin: 0 0 8px; } +#ad-overlay p { margin: 0; color: #e5e7eb; } diff --git a/public_html/assets/js/adflow.js b/public_html/assets/js/adflow.js new file mode 100644 index 0000000..94fc355 --- /dev/null +++ b/public_html/assets/js/adflow.js @@ -0,0 +1,42 @@ +(function(){ + const data = window.__SITE; + if (!data) { + console.error('Thiếu window.__SITE'); + return; + } + if (!data.adsEnabled || !data.adLink || data.bypass) return; + + const overlay = document.createElement('div'); + overlay.id = 'ad-overlay'; + overlay.innerHTML = ` +
+ +

${escapeHtml(data.adTitle || 'Quảng cáo')}

+

${escapeHtml(data.adBody || 'Ưu đãi hot trên Shopee')}

+
+ `; + document.body.appendChild(overlay); + + function go(){ + try { + const payload = new FormData(); + payload.append('type', 'ad_click'); + payload.append('slug', data.slug || ''); + if (navigator.sendBeacon) { + navigator.sendBeacon('/api/track.php', payload); + } + } catch (e) {} + window.open(`/${encodeURIComponent(data.slug)}?ad=0`, '_blank', 'noopener'); + window.location.href = data.adLink; + } + + overlay.addEventListener('click', go); + overlay.querySelector('.close').addEventListener('click', function(evt){ + evt.stopPropagation(); + go(); + }); + + function escapeHtml(str){ + return String(str).replace(/[&<>"']/g, ch => ({'&':'&','<':'<','>':'>','"':'"','\'':'''}[ch])); + } +})(); diff --git a/public_html/assets/js/track.js b/public_html/assets/js/track.js new file mode 100644 index 0000000..657ca66 --- /dev/null +++ b/public_html/assets/js/track.js @@ -0,0 +1,14 @@ +(function(){ + const data = window.__SITE; + if (!data || !data.slug) return; + try { + const payload = new FormData(); + payload.append('type', 'view'); + payload.append('slug', data.slug); + if (navigator.sendBeacon) { + navigator.sendBeacon('/api/track.php', payload); + } else { + fetch('/api/track.php', {method:'POST', body: payload, credentials:'same-origin'}); + } + } catch (e) {} +})(); diff --git a/public_html/config.php b/public_html/config.php new file mode 100644 index 0000000..b52573e --- /dev/null +++ b/public_html/config.php @@ -0,0 +1,13 @@ + + + + + + Lỗi hệ thống + + + +
+

Đã xảy ra lỗi

+

Vui lòng quay lại sau hoặc liên hệ quản trị viên.

+
+ + diff --git a/public_html/index.php b/public_html/index.php new file mode 100644 index 0000000..97f5513 --- /dev/null +++ b/public_html/index.php @@ -0,0 +1,26 @@ + + + + + + Trang tin + + + +
+

Bảng tin

+ + Liên hệ admin để gửi thông tin mới + +

Liên hệ admin để gửi thông tin mới.

+ +
+ + diff --git a/public_html/lib/auth.php b/public_html/lib/auth.php new file mode 100644 index 0000000..e2ea5a4 --- /dev/null +++ b/public_html/lib/auth.php @@ -0,0 +1,45 @@ +prepare('SELECT * FROM admins WHERE username = ? LIMIT 1'); + $stmt->execute([$username]); + $row = $stmt->fetch(); + if ($row && password_verify($password, $row['password_hash'])) { + $_SESSION['admin_id'] = $row['id']; + $_SESSION['admin_username'] = $row['username']; + $_SESSION['csrf_token'] = bin2hex(random_bytes(16)); + return true; + } + return false; +} + +function admin_require(): void +{ + ensure_session(); + if (empty($_SESSION['admin_id'])) { + header('Location: /admin/login.php'); + exit; + } +} + +function admin_logout(): void +{ + ensure_session(); + $_SESSION = []; + if (ini_get('session.use_cookies')) { + $params = session_get_cookie_params(); + setcookie(session_name(), '', time() - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']); + } + session_destroy(); +} + +function admin_name(): string +{ + ensure_session(); + return (string)($_SESSION['admin_username'] ?? ''); +} diff --git a/public_html/lib/csrf.php b/public_html/lib/csrf.php new file mode 100644 index 0000000..e400a50 --- /dev/null +++ b/public_html/lib/csrf.php @@ -0,0 +1,22 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, + ]; + + try { + $pdo = new PDO($dsn, DB_USER, DB_PASS, $options); + } catch (PDOException $e) { + if (APP_DEBUG) { + throw $e; + } + file_put_contents(__DIR__ . '/../logs/app.log', '[' . date('c') . "] DB connect error: " . $e->getMessage() . PHP_EOL, FILE_APPEND); + render_error_page(); + } + + return $pdo; +} + +function load_settings(PDO $pdo): array +{ + $stmt = $pdo->query('SELECT * FROM settings WHERE id = 1 LIMIT 1'); + $settings = $stmt->fetch(); + if (!$settings) { + $settings = [ + 'ads_enabled' => 0, + 'ad_link' => '', + 'ad_title' => '', + 'ad_body' => '', + 'contact_link' => '' + ]; + } + return $settings; +} diff --git a/public_html/lib/error_handler.php b/public_html/lib/error_handler.php new file mode 100644 index 0000000..aa99857 --- /dev/null +++ b/public_html/lib/error_handler.php @@ -0,0 +1,53 @@ +' . htmlspecialchars($error, ENT_QUOTES, 'UTF-8') . ''; + return true; + } + + file_put_contents(__DIR__ . '/../logs/app.log', $error, FILE_APPEND); + render_error_page(); + return true; +}); + +set_exception_handler(function ($ex) { + $error = sprintf('[%s] Uncaught %s: %s in %s on line %d%s', date('c'), get_class($ex), $ex->getMessage(), $ex->getFile(), $ex->getLine(), PHP_EOL); + if (APP_DEBUG) { + echo '
' . htmlspecialchars($error, ENT_QUOTES, 'UTF-8') . '
'; + return; + } + file_put_contents(__DIR__ . '/../logs/app.log', $error, FILE_APPEND); + render_error_page(); +}); + +register_shutdown_function(function () { + $last = error_get_last(); + if ($last && in_array($last['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) { + $error = sprintf('[%s] Fatal: %s in %s on line %d%s', date('c'), $last['message'], $last['file'], $last['line'], PHP_EOL); + if (APP_DEBUG) { + echo '
' . htmlspecialchars($error, ENT_QUOTES, 'UTF-8') . '
'; + } else { + file_put_contents(__DIR__ . '/../logs/app.log', $error, FILE_APPEND); + } + } +}); diff --git a/public_html/lib/slugify.php b/public_html/lib/slugify.php new file mode 100644 index 0000000..eb6305c --- /dev/null +++ b/public_html/lib/slugify.php @@ -0,0 +1,10 @@ +prepare('SELECT COUNT(*) FROM stats WHERE type = ? AND ip_hash = ? AND created_at >= (NOW() - INTERVAL ? SECOND) AND (post_id <=> ?)'); + $check->execute([$type, $hash, $window, $postId]); + if ((int)$check->fetchColumn() > 0) { + return; + } + $insert = $pdo->prepare('INSERT INTO stats (type, post_id, ip_hash, created_at) VALUES (?, ?, ?, NOW())'); + $insert->execute([$type, $postId, $hash]); +} + +function stat_total(PDO $pdo, string $type): int +{ + $stmt = $pdo->prepare('SELECT COUNT(*) FROM stats WHERE type = ?'); + $stmt->execute([$type]); + return (int)$stmt->fetchColumn(); +} diff --git a/public_html/logs/app.log b/public_html/logs/app.log new file mode 100644 index 0000000..e69de29 diff --git a/public_html/post.php b/public_html/post.php new file mode 100644 index 0000000..f4ab465 --- /dev/null +++ b/public_html/post.php @@ -0,0 +1,82 @@ +prepare('SELECT * FROM posts WHERE slug = ? LIMIT 1'); +$stmt->execute([$slug]); +$post = $stmt->fetch(); +if (!$post) { + http_response_code(404); + echo 'Bài viết không tồn tại'; + exit; +} + +$settings = load_settings($pdo); +$bypassAd = (isset($_GET['ad']) && $_GET['ad'] === '0'); +$adEnabled = ((int)($settings['ads_enabled'] ?? 0) === 1) && !$bypassAd && !empty($settings['ad_link']); + +record_stat($pdo, 'view', (int)$post['id'], 60); + +$images = json_decode($post['images_json'] ?? '[]', true) ?: []; +$videos = json_decode($post['videos_json'] ?? '[]', true) ?: []; +?> + + + + + <?php echo htmlspecialchars($post['title'], ENT_QUOTES, 'UTF-8'); ?> + + + +
+

+
+ + +

Hình ảnh

+
+ +
+ +
+ + + +

Video

+
+ +
+ + + + + +
+ +
+ +
+ + + + + + diff --git a/public_html/sql/database.sql b/public_html/sql/database.sql new file mode 100644 index 0000000..6274f6a --- /dev/null +++ b/public_html/sql/database.sql @@ -0,0 +1,42 @@ +CREATE TABLE IF NOT EXISTS admins ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(100) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL +) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS posts ( + id INT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255) NOT NULL, + slug VARCHAR(255) NOT NULL UNIQUE, + content TEXT, + images_json TEXT, + videos_json TEXT, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS settings ( + id INT PRIMARY KEY, + ads_enabled TINYINT(1) NOT NULL DEFAULT 0, + ad_link VARCHAR(500) DEFAULT '', + ad_title VARCHAR(255) DEFAULT '', + ad_body TEXT, + contact_link VARCHAR(500) DEFAULT '' +) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS stats ( + id INT AUTO_INCREMENT PRIMARY KEY, + type ENUM('view','ad_click') NOT NULL, + post_id INT NULL, + ip_hash CHAR(64) NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + INDEX idx_type_created (type, created_at), + INDEX idx_post (post_id) +) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +INSERT INTO admins (username, password_hash) VALUES +('admin', '$2y$12$V.97JyiCRI3EUNfz4uojbupFsTqpoZObHXGGy/tFFiV.kFCGAEj7G') +ON DUPLICATE KEY UPDATE password_hash = VALUES(password_hash); + +INSERT INTO settings (id, ads_enabled, ad_link, ad_title, ad_body, contact_link) VALUES +(1, 0, '', '', '', '') +ON DUPLICATE KEY UPDATE ads_enabled = VALUES(ads_enabled);