api_docs.php

main 105 lines · 3.6 KB Raw
Alex Alex Commit Initial commit 01/10/2026 09:20
1<?php
2/**
3 * Title: API
4 * Icon: fa-code
5 * Group: Operations
6 * Order: 60
7 * Description: Every public JSON endpoint under /api/, discovered automatically.
8 */
9
10if (!defined('ACP_ROOT')) {
11 http_response_code(403);
12 die('Direct access denied.');
13}
14
15function acp_api_known_endpoints(): array {
16 return array(
17 'index.php' => t_default('acp.api.desc_index', 'Site title, account/player counts, players online (with unique IPs), client version, port.'),
18 'modules/status/online.php' => t_default('acp.api.desc_online', 'The currently online players: name, level, vocation.'),
19 'modules/highscores/topExperience.php' => t_default('acp.api.desc_top_experience', 'Top players by experience. ?rows=N (default 10, max 100).'),
20 'modules/towns/getTownNames.php' => t_default('acp.api.desc_town_names', 'Configured town id => name map.'),
21 'modules/character/info.php' => t_default('acp.api.desc_character_info', 'Character card by name (?name=). Respects hidden characters.'),
22 'modules/highscores/top.php' => t_default('acp.api.desc_highscores_top', 'Highscores by skill type, same data as the site page. ?type=1-9&vocation=id|all&rows=N.'),
23 );
24}
25
26$root = dirname(__DIR__, 2) . '/api';
27$files = array();
28
29if (is_dir($root)) {
30 $files[] = 'index.php';
31
32 $modulesDir = $root . '/modules';
33 if (is_dir($modulesDir)) {
34 $iterator = new RecursiveIteratorIterator(
35 new RecursiveDirectoryIterator($modulesDir, FilesystemIterator::SKIP_DOTS)
36 );
37 foreach ($iterator as $file) {
38 if (!$file->isFile() || $file->getExtension() !== 'php') {
39 continue;
40 }
41 $relative = str_replace('\\', '/', substr($file->getPathname(), strlen($root) + 1));
42 if (strpos($relative, '/class/') !== false) {
43 continue;
44 }
45 $files[] = $relative;
46 }
47 }
48}
49
50sort($files);
51
52$known = acp_api_known_endpoints();
53$siteUrl = rtrim((string)($config['site_url'] ?? ''), '/');
54?>
55
56<section class="acp-card">
57 <header class="acp-card-head">
58 <h2><?= t_default('acp.api.title', 'Public JSON endpoints') ?></h2>
59 <p><?= t_default('acp.api.sub', 'Everything under /api/ - read-only, no authentication. Point a Discord bot or external tool at these.') ?></p>
60 </header>
61 <div class="acp-card-body is-flush">
62 <?php if ($files): ?>
63 <div class="acp-table-wrap">
64 <table class="acp-table" data-sortable>
65 <thead>
66 <tr>
67 <th><?= t_default('acp.api.col_endpoint', 'Endpoint') ?></th>
68 <th><?= t_default('acp.api.col_description', 'What it returns') ?></th>
69 <th></th>
70 </tr>
71 </thead>
72 <tbody>
73 <?php foreach ($files as $file): $url = $siteUrl . '/api/' . $file; ?>
74 <tr>
75 <td><code>/api/<?= h($file) ?></code></td>
76 <td><?= h($known[$file] ?? '') ?></td>
77 <td class="is-num">
78 <a class="acp-btn acp-btn--ghost acp-btn--sm" href="<?= h($url) ?>" target="_blank" rel="noopener">
79 <i class="fa fa-external-link"></i> <?= t_default('acp.api.open_btn', 'Open') ?>
80 </a>
81 </td>
82 </tr>
83 <?php endforeach; ?>
84 </tbody>
85 </table>
86 </div>
87 <?php else: ?>
88 <?php acp_empty(t_default('acp.api.empty', 'No API endpoints found.'), 'fa-code'); ?>
89 <?php endif; ?>
90 </div>
91</section>
92
93<section class="acp-card">
94 <header class="acp-card-head">
95 <h2><?= t_default('acp.api.response_title', 'Response shape') ?></h2>
96 </header>
97 <div class="acp-card-body">
98 <p><?= t_default('acp.api.response_text', 'Every endpoint replies with JSON shaped like this - "version" always present, "data" holding the actual payload:') ?></p>
99 <pre style="background:var(--acp-panel-2);padding:10px;border-radius:var(--acp-radius);overflow-x:auto;">{
100 "version": { "znote": "2.0.1", "ot": "TFS_10", "module": 1 },
101 "data": { ... }
102}</pre>
103 </div>
104</section>
105
Top