index.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. <?php
  2. // Error handling
  3. error_reporting(E_ALL);
  4. ini_set('display_errors', 1);
  5. // Constants and helper functions
  6. const URBAN_DICT_BASE = 'https://www.urbandictionary.com';
  7. const URBAN_API_BASE = 'https://api.urbandictionary.com/v0';
  8. const DOM_OPTIONS = LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD;
  9. function handle_dom($html, $is_pagination = false) {
  10. if (empty($html)) return $html;
  11. $dom = new DOMDocument();
  12. @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), DOM_OPTIONS);
  13. $xpath = new DOMXPath($dom);
  14. // Remove all classes
  15. foreach ($xpath->query('//*[@class]') as $element) {
  16. $element->removeAttribute('class');
  17. }
  18. // Handle pagination
  19. if ($is_pagination && ($div = $xpath->query("//div")[0])) {
  20. $div->setAttribute('class', 'pagination');
  21. // Fix pagination links for subdirectory
  22. foreach ($xpath->query("//a[@href]") as $link) {
  23. $href = $link->getAttribute('href');
  24. if (str_starts_with($href, '/?')) {
  25. $link->setAttribute('href', './' . substr($href, 1));
  26. }
  27. }
  28. }
  29. return $dom->saveHTML();
  30. }
  31. function fetch_url($url) {
  32. static $ch = null;
  33. if (!$ch) {
  34. $ch = curl_init();
  35. curl_setopt_array($ch, [
  36. CURLOPT_RETURNTRANSFER => true,
  37. CURLOPT_FOLLOWLOCATION => true,
  38. CURLOPT_TIMEOUT => 10,
  39. CURLOPT_ENCODING => '',
  40. CURLOPT_USERAGENT => 'Mozilla/5.0 Rural Dictionary',
  41. CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
  42. CURLOPT_TCP_FASTOPEN => 1,
  43. ]);
  44. }
  45. curl_setopt($ch, CURLOPT_URL, $url);
  46. return [
  47. curl_exec($ch),
  48. curl_getinfo($ch, CURLINFO_HTTP_CODE),
  49. curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)
  50. ];
  51. }
  52. function fetch_thumbs($def_ids) {
  53. if (empty($def_ids)) return [];
  54. $response = @file_get_contents(
  55. URBAN_API_BASE . '/uncacheable?ids=' . implode(',', $def_ids),
  56. false,
  57. stream_context_create(['http' => ['timeout' => 5, 'ignore_errors' => true]])
  58. );
  59. $data = $response ? json_decode($response, true) : [];
  60. return isset($data['thumbs']) ? array_column($data['thumbs'], null, 'defid') : [];
  61. }
  62. // Get request info
  63. $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
  64. $query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
  65. $term = $_GET['term'] ?? '';
  66. // Fetch and handle redirects
  67. [$html, $status_code, $final_url] = fetch_url(URBAN_DICT_BASE . $path . ($query ? "?$query" : ''));
  68. if ($final_url !== URBAN_DICT_BASE . $path . ($query ? "?$query" : '')) {
  69. header('Location: ' . parse_url($final_url, PHP_URL_PATH) .
  70. (($q = parse_url($final_url, PHP_URL_QUERY)) ? "?$q" : ''));
  71. exit;
  72. }
  73. // Parse HTML
  74. $dom = new DOMDocument();
  75. @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'), DOM_OPTIONS);
  76. $xpath = new DOMXPath($dom);
  77. $results = [];
  78. $site_description = null;
  79. $pagination = null;
  80. if ($status_code !== 200) {
  81. $similar_words = array_map(
  82. fn($word) => handle_dom($dom->saveHTML($word)),
  83. iterator_to_array($xpath->query("//div[contains(@class, 'try-these')]//li/a"))
  84. );
  85. $template = '404';
  86. } else {
  87. $definitions = $xpath->query("//div[@data-defid]");
  88. $def_ids = array_map(fn($def) => $def->getAttribute('data-defid'), iterator_to_array($definitions));
  89. $thumbs_data = fetch_thumbs($def_ids);
  90. foreach ($definitions as $def) {
  91. $def_id = $def->getAttribute('data-defid');
  92. $word = $xpath->query(".//a[contains(@class, 'word')]", $def)[0]->textContent;
  93. $meaning = handle_dom($dom->saveHTML($xpath->query(".//div[contains(@class, 'meaning')]", $def)[0]));
  94. $example = handle_dom($dom->saveHTML($xpath->query(".//div[contains(@class, 'example')]", $def)[0]));
  95. $contributor = handle_dom($dom->saveHTML($xpath->query(".//div[contains(@class, 'contributor')]", $def)[0]));
  96. if (!$site_description) {
  97. $site_description = preg_replace('/\s+/', ' ', strip_tags($meaning));
  98. }
  99. $thumbs = $thumbs_data[$def_id] ?? [];
  100. $results[] = [
  101. $def_id, $word, $meaning, $example, $contributor,
  102. $thumbs['up'] ?? null, $thumbs['down'] ?? null
  103. ];
  104. }
  105. if ($pagination_node = $xpath->query("//div[contains(@class, 'pagination')]")[0]) {
  106. $pagination = handle_dom($dom->saveHTML($pagination_node), true);
  107. }
  108. $template = 'index';
  109. }
  110. // Set title
  111. $site_title = 'Rural Dictionary' .
  112. ($path === '/' ? ', ' . date('d F') : '') .
  113. ($path === '/random.php' ? ': Random words' : ($term ? ": $term" : ''));
  114. // Output HTML
  115. ?>
  116. <!DOCTYPE html>
  117. <html lang="en">
  118. <head>
  119. <meta charset="UTF-8">
  120. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  121. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  122. <link rel="stylesheet" href="./static/css/main.css">
  123. <link rel="icon" type="image/png" href="./static/img/favicon.png">
  124. <title><?= htmlspecialchars($site_title) ?></title>
  125. <meta name="description" content="<?= htmlspecialchars($site_description ?? '') ?>">
  126. <meta property="og:url" content="<?= htmlspecialchars($_SERVER['REQUEST_URI']) ?>">
  127. <meta property="og:type" content="website">
  128. <meta property="og:title" content="<?= htmlspecialchars($site_title) ?>">
  129. <meta property="og:description" content="<?= htmlspecialchars($site_description ?? '') ?>">
  130. <meta property="twitter:domain" content="<?= htmlspecialchars($_SERVER['HTTP_HOST']) ?>">
  131. <meta property="twitter:url" content="<?= htmlspecialchars($_SERVER['REQUEST_URI']) ?>">
  132. <meta name="twitter:title" content="<?= htmlspecialchars($site_title) ?>">
  133. <meta name="twitter:description" content="<?= htmlspecialchars($site_description ?? '') ?>">
  134. </head>
  135. <body>
  136. <div style="text-align: center">
  137. <a href="./">
  138. <img src="./static/img/logo.png" alt="logo">
  139. </a>
  140. <form id="search" role="search" method="get" action="./define.php">
  141. <input
  142. autocomplete="off"
  143. type="search"
  144. id="term"
  145. name="term"
  146. placeholder="Search"
  147. aria-label="Search"
  148. value="<?= $path === '/define.php' ? htmlspecialchars($term) : '' ?>"
  149. autofocus
  150. >
  151. <button>Go</button>
  152. </form>
  153. <a href="./random.php">Random</a>
  154. <br>
  155. <a href="https://git.qunn.eu/poesty/rural-dict/src/php-port">Source Code</a>
  156. </div>
  157. <br>
  158. <?php if ($template === '404'): ?>
  159. <div style="text-align: center">
  160. <h2>Definition not found: <?= htmlspecialchars($term) ?></h2>
  161. <?php if (!empty($similar_words)): ?>
  162. <?php foreach ($similar_words as $word): ?>
  163. <h3 class="underline-links"><?= $word ?></h3>
  164. <?php endforeach; ?>
  165. <?php else: ?>
  166. <p>There are no similar words. Try correcting your search.</p>
  167. <?php endif; ?>
  168. </div>
  169. <?php else: ?>
  170. <?php foreach ($results as [$definition_id, $word, $meaning, $example, $contributor, $thumbs_up, $thumbs_down]): ?>
  171. <div data-id="<?= htmlspecialchars($definition_id) ?>">
  172. <a href="./define.php?term=<?= urlencode($word) ?>">
  173. <h2><?= htmlspecialchars($word) ?></h2>
  174. </a>
  175. <div class="underline-links">
  176. <p><?= $meaning ?></p>
  177. <p><i><?= $example ?></i></p>
  178. </div>
  179. <div><?= $contributor ?></div>
  180. <?php if ($thumbs_up !== null && $thumbs_down !== null): ?>
  181. <p>
  182. <span title="thumbs up">👍<?= htmlspecialchars($thumbs_up) ?></span>
  183. <span title="thumbs down">👎<?= htmlspecialchars($thumbs_down) ?></span>
  184. </p>
  185. <?php endif; ?>
  186. </div>
  187. <br>
  188. <?php endforeach; ?>
  189. <?php if ($pagination): ?>
  190. <?= $pagination ?>
  191. <?php endif; ?>
  192. <?php endif; ?>
  193. </body>
  194. </html>