Procházet zdrojové kódy

Version 1.4

- NOTICE: config.default.php has changed, re-create your config.php!!
- [fix] Footer no longer overlaps results
- [fix] Search navigation no longer bunched up on smaller displays
- [fix] Double search type when searching from start page
- [new] Filter for additional/different headers per cURL request
- [new] Image search via Openverse API (Access token and cronjob required, see installation instructions)
- [new] Image search via Qwant API
- [new] Web (recent news) search via Qwant API
- [tweak] Merged 'cache' option into 'cache-type', see config.default.php for details
- [tweak] Better filtering for duplicate web results
- [tweak] File size formatting for images more uniform
- [tweak] Optimized curl_multi_exec handling
- [tweak] Improved SEO headers
- [tweak] Layout tweaks and optimizations for search results, header and footer
- [tweak] Removed redundant HTML, CSS and some PHP
- [tweak] MagnetDL search disabled by default because of Cloudflare (Will probably be removed in future version)
- [tweak] Removed non-functional magnet trackers
- [tweak] Added 15 extra public magnet trackers
- [change] Removed Ecosia support
- [change] Removed Reddit support
- [change] Removed 1337x support
- [change] Removed MagnetDL support
Arnan de Gans před 1 rokem
rodič
revize
32c27924f5

+ 148 - 151
assets/css/styles.css

@@ -9,8 +9,7 @@
 *  liability that might arise from its use.
 ------------------------------------------------------------------------------------ */
 
-html { font-size: 16px; }
-body { position: relative; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; color: #222; background-color: #ffffff; line-height: 1.2; }
+body { margin: 0; padding: 0; background-color: #fff; font-family: Arial, Helvetica, sans-serif; font-size: 16px; color: #222; line-height: 1.2; }
 div { margin: 0; padding: 0; border: 0; vertical-align: baseline; }
 article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; }
 h2, h3, h4, h5, h6 { font-weight: normal; }
@@ -22,110 +21,108 @@ p { font-size: 18px; color: #494949; }
 a { text-decoration: none; color: #4495d4; }
 small, sub, sup { padding: 5px 0; color: #666; font-size: 12px; }
 sub, sup { font-style: italic; }
+pre, code { word-wrap: break-word; overflow-wrap: break-word; }
 a:hover { text-decoration: underline; }
-input[type="text"]:invalid ~ input[type="submit"] { opacity: 0.5; pointer-events: none; }
-input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23777'><path d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>"); }
+input[type="text"]: invalid ~ input[type="submit"] { opacity: 0.5; pointer-events: none; }
+input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; -webkit-mask-image: url("data: image/svg+xml;utf8,<svg xmlns='http: //www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23777'><path d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>"); }
 
 /* Page structure */
-.wrap { min-height: 100vh; overflow: hidden; }
-.header-wrap { display: block; padding-top: 16px; width: 100%; }
-.results-wrap { position: relative; display: flex; margin: 0 158px 50px 158px; }
-.footer-wrap { position: absolute; bottom: 0; display: block; margin-top: 15px; padding: 0; width: 100%; }
-
-/* Main page */
-body.main { background-color: #1f242b; color: #f0f6fc; }
-.search-box-main, .password-generator { text-align: center; margin-top: 10%; }
-.search-box-main h1 { font-size: 4rem; }
-.search-box-main .search, .password-generator .password { padding: 10px 20px; width: 600px; color: #f0f6fc; background-color: #333333; font-size: 2rem; font-family: sans-serif; border: 1px solid #3C4043; border-radius: 10px; }
-.search-box-main .search[type="search"]::-webkit-search-cancel-button { background-size: 28px 28px; height: 28px; width: 28px; background-color: #f0f6fc; }
-.search-box-buttons button { margin: 30px 20px 10px 20px; padding: 13px 10px 13px 10px; min-width: 130px; color: #f0f6fc; background-color: #333333; font-size: 0.8rem; border: 1px solid #3C4043; border-radius: 6px; }
-.search-box-buttons button:hover { border: 1px solid #5f6368; }
+body { min-height: 100vh; display: flex; flex-direction: column; }
+.header { padding-top: 16px; width: 100%; }
+.content { margin: 0 158px; padding:  15px 0; }
+.footer { bottom: 0; margin-top: auto; padding: 0; width: 100%; }
+
+/* Start page */
+.startpage { background-color: #1f242b; color: #f0f6fc; }
+.startpage-search, .password-generator { text-align: center; margin-top: 10%; }
+.startpage-search h1 { font-size: 4rem; }
+.startpage-search .search, .password-generator .password { padding: 10px 20px; width: 600px; color: #f0f6fc; background-color: #333333; font-size: 2rem; font-family: sans-serif; border: 1px solid #3C4043; border-radius: 10px; }
+.startpage-search .search[type="search"]::-webkit-search-cancel-button { background-size: 28px 28px; height: 28px; width: 28px; background-color: #f0f6fc; }
+.search-buttons button { margin: 30px 20px 10px 20px; padding: 13px 10px 13px 10px; min-width: 130px; color: #f0f6fc; background-color: #333333; font-size: 1rem; border: 1px solid #3C4043; border-radius: 6px; }
+.startpage-search .search:focus, .password-generator .password:focus, .search-buttons button:hover { border: 1px solid #5f6368; }
 
 .password-generator { margin: 30px auto; padding: 0; }
 .password-generator .password { margin: 10px auto; width: 300px; text-align: center; font-size: 0.8rem; }
 
-/* Search Results - Header */
-.header-wrap { background-color: #1f242b; color: #f0f6fc; border-bottom: 2px solid #1fa4d1; }
-.header-wrap .search, .header-wrap .button { position: relative; height: 40px; color: #f0f6fc; font-size: 1rem; font-weight: 400; }
-.header-wrap .search { margin: 28px 0 28px 158px; padding: 5px 5px 5px 15px; width: 580px; background-color: #1f242b; border: 1px solid #303842; border-radius: 10px 0 0 10px; }
-.header-wrap .search[type="search"]::-webkit-search-cancel-button { background-size: 20px 20px; height: 20px; width: 20px; background-color: #f0f6fc; }
-.header-wrap .button { margin: 28px 10px 28px 0; padding: 5px 20px 5px 15px; background-color: #1fa4d1; border: none; border-radius: 0 10px 10px 0; }
-
-/* Search results - Header Navigation */
-.navigation-header { margin-left: 165px; margin-bottom: 10px; }
-.navigation-header img { margin-right: 5px; height: 16px; vertical-align: middle; }
-.navigation-header a { margin-right: 20px; border: none; font-size: 1rem; cursor: pointer; text-decoration: none; }
-.navigation-header a:hover { color: #ebf3fa; }
-.navigation-header a:visited { color: #1fa4d1; }
-.navigation-header .active { padding-bottom: 8px; border-bottom: 4px #1fa4d1 solid; }
-
-/* Search results */
-.main-column { width: 100%; }
-.main-column ol .meta { margin: .75rem 0 .05rem 0; padding: .5rem 10px 0 10px; }
-.main-column ol .sources { margin: .05rem 0 .75rem 0; padding: 0 10px .5rem 10px; font-size: 0.75rem; color: #666; }
-
-.main-column ol .special-result { margin: .75rem 0 .25rem 0; padding: .5rem 10px; }
-.main-column ol .result { margin: .50rem 0 .50rem 0; padding: 0; }
-
-.main-column ol li article { padding: .5rem 10px; border: 1px solid #fefefe; border-radius: 8px; }
-.main-column ol li article div.url:first-child, .main-column ol li.special-result article div.source:first-child { flex-grow: 0; }
-.main-column ol li article div.url { display: inline-block; margin: 0; max-width: 100%; color: #666; font-size: 1rem; line-height: 1.6; letter-spacing: .2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
-.main-column ol li article div.url a { margin: 0; color: #3f6e35; cursor: pointer; text-decoration: none; }
-.main-column ol li article div.title, .main-column ol li.special-result article div.title { margin-bottom: .29rem; }
-.main-column ol li article div.title h2 { margin: 0; padding: 0; position: relative; font-size: 1.46rem; letter-spacing: -.01px; }
-.main-column ol li article div.title h2:hover { text-decoration: underline; }
-.main-column ol li article div.title a { margin: 0; display: block; cursor: pointer; }
-.main-column ol li article div.title a:visited { color: #6d59a3; }
-.main-column ol li article div.description { margin: 0; line-height: 1.4; font-size: 1rem; color: #494949; }
-.main-column ol li article div.engine { padding: 2px 0; font: 12px italic; color: #666; }
-.main-column ol li article div.description .seeders { color: #518257; }
-.main-column ol li article div.description .leechers { color: #c00; }
-
-/* Image results */
-.main-column .image-wrapper { width: 100%; margin: .75rem 0 .75rem 0; }
-@supports not (display: grid) {
-	.image-grid > * { max-width: 8rem; margin-left: auto; margin-right: auto; }
-	.image-grid li.result { display: inline-block; margin: .75rem; width: 12.5%; }
-	.image-grid > * + * { margin-top: 1rem; }
-}
+/* oAUTH page*/
+.oauthpage { background-color: #1f242b; color: #f0f6fc; }
+.oauth-form { text-align: center; margin-top: 20px; }
+.oauth-form h1 { font-size: 4rem; }
+.oauth-form p, .oauth-form small { margin-bottom: 15px; color: #f0f6fc; }
+.oauth-form .field { padding: 5px 10px; width: 300px; color: #f0f6fc; background-color: #333333; font-size: 1rem; font-family: sans-serif; border: 1px solid #3C4043; border-radius: 10px; }
+.oauth-buttons button { margin: 30px 20px 10px 20px; padding: 13px 10px 13px 10px; min-width: 130px; color: #f0f6fc; background-color: #333333; font-size: 1rem; border: 1px solid #3C4043; border-radius: 6px; }
+.oauth-buttons button:hover { border: 1px solid #5f6368; }
+
+/* Results page header */
+.header { background-color: #1f242b; color: #f0f6fc; border-bottom: 2px solid #1fa4d1; }
+.header .search, .header .button { position: relative; height: 40px; color: #f0f6fc; font-size: 1rem; font-weight: 400; }
+.header .search { margin: 28px 0 28px 158px; padding: 5px 5px 5px 15px; width: 580px; background-color: #1f242b; border: 1px solid #5f6368; border-radius: 10px 0 0 10px; }
+.header .search[type="search"]::-webkit-search-cancel-button { background-size: 20px 20px; height: 20px; width: 20px; background-color: #f0f6fc; }
+.header .button { margin: 28px 10px 28px 0; padding: 5px 20px 5px 15px; background-color: #1fa4d1; border: none; border-radius: 0 10px 10px 0; }
+
+.navigation { margin-left: 158px; margin-bottom: 10px; }
+.navigation img { margin-right: 5px; height: 16px; vertical-align: middle; }
+.navigation a { margin-right: 20px; border: none; font-size: 1rem; cursor: pointer; text-decoration: none; }
+.navigation a:hover { color: #ebf3fa; }
+.navigation a: visited { color: #1fa4d1; }
+.navigation .active { padding-bottom: 8px; border-bottom: 4px solid #1fa4d1; }
+
+/* Search results spacing */
+.content .meta { padding: 10px 0 0 0; }
+.content .sources { padding: 0 0 10px 0; font-size: 0.75rem; color: #666; }
+.content .suggestion { padding: 15px 0; }
+.content .result { margin: 0 0 25px 0; }
+.content .result-special { margin: 15px 0 25px 0; padding: 10px; }
+.content .result.image { margin: 0; }
+
+/* Search results (web, image and magnet) */
+.content .result { border: 1px solid #fefefe; border-radius: 8px; }
+.content .result div.url, .content .result-special div.source { max-width: 100%; font-size: 1rem; line-height: 1.6; letter-spacing: .2px; white-space: nowrap; overflow: hidden; }
+.content .result div.url a, .content .result-special div.source a { color: #3f6e35; cursor: pointer; text-decoration: none; }
+.content .result div.title, .content .result-special div.title { margin-bottom: 5px; }
+.content .result div.title h2 { padding: 0; position: relative; font-size: 1.46rem; letter-spacing: -.01px; }
+.content .result div.title h2:hover { text-decoration: underline; }
+.content .result div.title a { display: block; cursor: pointer; }
+.content .result div.title a: visited { color: #6d59a3; }
+.content .result div.description { line-height: 1.4; font-size: 1rem; color: #494949; }
+.content .result div.engine { padding: 2px 0; font: 12px italic; color: #666; }
+.content .result div.description .seeders { color: #518257; }
+.content .result div.description .leechers { color: #c00; }
+
+.content .result-special { position: relative; background-color: #fefefe; overflow: hidden; border: 1px solid #aeaeae; border-radius: 8px; color: #222; }
+.content .result-special div.title h2 { padding: 0; font-size: 1.5rem; font-weight: 600; word-wrap: break-word; color: #222; }
+.content .result-special div.title h2:hover { text-decoration: none; }
+.content .result-special div.title a { display: block; color: #6c00a2; cursor: pointer; }
+.content .result-special div.title a: visited { color: #6d59a3; }
+.content .result-special div.text, .content .result-special div.source { position: relative; font-style: normal; }
+
+/* Grids (image and magnet highlights) */
 @supports (display: grid) {
-	.main-column ol.image-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); grid-gap: 1rem; }
+	.image-grid ol, .magnet-grid ol { display: grid; grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); grid-gap: 1rem; }
 }
-.main-column ol.image-grid .result .image-box { position: relative; }
-.main-column ol.image-grid .result .image-box::after { display: block; padding-bottom: 100%; content: ""; }
-.main-column ol.image-grid .result .image-box img { position: absolute; object-fit: cover; width: 100%; height: 100%; border-radius: 10px; }
-.main-column ol.image-grid .result .image-box img:hover { outline: none; border-color: #3C4043; box-shadow: 0 0 10px #3C4043; }
-.main-column ol.image-grid .result span { padding: 5px 0 0 0; color: #666; font-size: 0.75rem; }
-
-/* Special results - Main column */
-.main-column ol .special-result { background-color: #fefefe; }
-.main-column ol li.special-result article { padding: .5rem 10px; position: relative; overflow: hidden; border: 1px solid #aeaeae; border-radius: 8px; color: #222; }
-.main-column ol li.special-result article div.title h2 { margin: 0; padding: 0; font-size: 1.5rem; font-weight: 600; word-wrap: break-word; color: #222; }
-.main-column ol li.special-result article div.title h2:hover { text-decoration: none; }
-.main-column ol li.special-result article div.title a { margin: 0; display: block; color: #6c00a2; cursor: pointer; }
-.main-column ol li.special-result article div.title a:visited { color: #6d59a3; }
-.main-column ol li.special-result article div.text, .main-column ol li article div.source { position: relative; font-style: normal; }
-.main-column ol li.special-result article div.text img { padding: 0 0 10px 10px; }
-.main-column ol li.special-result article div.source { display: inline-block; margin: 0; max-width: 100%; color: #666; font-size: 1rem; line-height: 1.6; letter-spacing: .2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-decoration: inherit; }
-.main-column ol li.special-result article div.source a { margin: 0; color: #3f6e35; text-decoration: none; }
-
-/* Magnet highlights */
-.main-column .magnet-wrapper { width: 100%; margin: .75rem 0 .75rem 0; }
 @supports not (display: grid) {
-	.magnet-grid > * { max-width: 8rem; margin-left: auto; margin-right: auto; }
-	.magnet-grid li.result { display: inline-block; margin: .75rem; width: 12.5%; }
-	.magnet-grid > * + * { margin-top: 1rem; }
-}
-@supports (display: grid) {
-	.main-column ol.magnet-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); grid-gap: 1rem; padding: .5rem 10px; }
+	.image-grid ol > *, .magnet-grid ol > * { max-width: 8rem; margin-left: auto; margin-right: auto; }
+	.image-grid ol .result, .magnet-grid ol .result { display: inline-block; margin: .75rem; width: 12.5%; }
+	.image-grid ol > * + *, .magnet-grid ol > * + * { margin-top: 1rem; }
 }
-.main-column ol.magnet-grid .result .magnet-box { position: relative; }
-.main-column ol.magnet-grid .result .magnet-box::after { display: block; content: ""; }
-.main-column ol.magnet-grid .result .magnet-box img { width: 100%; height: 100%; border-radius: 10px; }
-.main-column ol.magnet-grid .result .magnet-box p { visibility: hidden; opacity: 0; position: absolute; top: 0; bottom: 0; left: 0; right: 0; background: #1f242b; transition: opacity .1s, visibility .1s; padding: 10px; line-height: 1.4; text-align: center; font-size: 0.8rem; color: #f0f6fc; }
-.main-column ol.magnet-grid .result .magnet-box a { display: block; position: relative; margin: 5px 0 5px 0; padding: 5px 20px; background-color: #1fa4d1; border: 1px solid #466f82; border-radius: 10px; color: #f0f6fc; font-weight: 800; text-align: center; }
-.main-column ol.magnet-grid .result .magnet-box a:hover { text-decoration: none; }
-.main-column ol.magnet-grid .result .magnet-box:hover p { visibility: visible; opacity: .90; outline: none; border-color: #3C4043; border-radius: 10px; box-shadow: 0 0 10px #3C4043; }
+
+/* Image results */
+.image-grid { width: 100%; margin: 25px 0; }
+.image-grid ol .result .image-box { position: relative; }
+.image-grid ol .result .image-box::after { display: block; padding-bottom: 100%; content: ""; }
+.image-grid ol .result .image-box img { position: absolute; object-fit: cover; width: 100%; height: 100%; border-radius: 10px; }
+.image-grid ol .result .image-box img:hover { outline: none; border-color: #3C4043; box-shadow: 0 0 10px #3C4043; }
+.image-grid ol .result span { padding: 5px 0 0 0; color: #666; font-size: 0.75rem; }
+
+/* Magnet highlights */
+.magnet-grid { width: 100%; margin: 25px 0; }
+.magnet-grid ol .result .magnet-box { position: relative; }
+.magnet-grid ol .result .magnet-box::after { display: block; content: ""; }
+.magnet-grid ol .result .magnet-box img { width: 100%; height: 100%; border-radius: 10px; }
+.magnet-grid ol .result .magnet-box p { visibility: hidden; opacity: 0; position: absolute; top: 0; bottom: 0; left: 0; right: 0; background: #1f242b; transition: opacity .1s, visibility .1s; padding: 10px; line-height: 1.4; text-align: center; font-size: 0.8rem; color: #f0f6fc; }
+.magnet-grid ol .result .magnet-box a { display: block; position: relative; margin: 5px 0 5px 0; padding: 5px 20px; background-color: #1fa4d1; border: 1px solid #466f82; border-radius: 10px; color: #f0f6fc; font-weight: 800; text-align: center; }
+.magnet-grid ol .result .magnet-box a:hover { text-decoration: none; }
+.magnet-grid ol .result .magnet-box:hover p { visibility: visible; opacity: .90; outline: none; border-color: #3C4043; border-radius: 10px; box-shadow: 0 0 10px #3C4043; }
 
 /* Misc */
 .logo { position: absolute; margin: 28px 18px; }
@@ -133,104 +130,104 @@ body.main { background-color: #1f242b; color: #f0f6fc; }
 .no-decoration, .no-decoration:hover { text-decoration: none; }
 .hide { display: none; }
 .G { color: #1fa4d1; }
-.warning { position: relative; overflow: hidden; margin: 1rem 0 1rem 0; padding: .5rem 10px; color: #db9900; background-color: #ffffe0; border: 1px solid #e6db55; border-radius: 10px; }
-.error { position: relative; overflow: hidden; margin: 1rem 0 1rem 0; padding: .5rem 10px; color: #c00; background-color: #ffebe8; border: 1px solid #c00; border-radius: 10px; }
-.auth-error { margin-top: 15%; font-size: 2rem; text-align: center; color: #eaeaea; }
+.warning { position: relative; overflow: hidden; margin: 20px 0; padding: 10px; color: #db9900; background-color: #ffffe0; border: 1px solid #e6db55; border-radius: 10px; }
+.error { position: relative; overflow: hidden; margin: 20px 0; padding: 10px; color: #c00; background-color: #ffebe8; border: 1px solid #c00; border-radius: 10px; }
+.auth-success { margin-top: 15%; font-size: 1rem; text-align: center; color: #f0f6fc; }
+.auth-error { margin-top: 15%; font-size: 2rem; text-align: center; color: #f0f6fc; }
+a.update { color: #c90; font-weight: bold; }
 
-/* Footer bar */
-.footer-wrap { background-color: #161616; color: #f0f6fc; border-top: 2px solid #303134; }
-.footer { padding: 10px; }
+/* Footer */
+.footer { background-color: #161616; color: #f0f6fc;  border-top: 1px solid #1fa4d1; }
 .footer a { color: #f0f6fc; }
-a.update { color: #c90; font-weight: bold; }
+.footer-left, .footer-right { display: inline-block; padding: 10px; }
+.footer-right { float: right; }
 
-@media only screen and (max-width:960px)  { /* tablet, landscape iPad, lo-res laptops ands desktops */ 
+@media only screen and (max-width: 960px)  { /* tablet, landscape iPad, lo-res/smaller laptops */ 
 	/* Page structure */
-	.results-wrap { position: relative; display: flex; margin: 0 48px 20px 48px; }
+	.content { position: relative; margin: 0 48px; }
+	.footer-left, .footer-right { display: block; padding: 5px 5px 0 5px; text-align: center; }
+	.footer-right { float: none; padding: 0px 5px 5px 5px; }
 
-	/* Main page */
-	.search-box-main { margin-top: 10%; }
-	.search-box-main input { width: 80%; }
-	.search-box-buttons button { display: table-row; margin: 30px 0px 0px 0px; width: 80%; }
+	/* Start page */
+	.startpage-search { margin-top: 10%; }
+	.startpage-search .search { width: 80%; }
+	.search-buttons button { display: table-row; margin: 30px 0px 0px 0px; width: 80%; }
 
 	/* Search Results - Header */
-	.header-wrap { margin-left: auto; margin-right: auto; text-align: center; }
-	.header-wrap .search { margin: 10px 0px 28px 48px; width: 400px; }
-	.header-wrap .search, .header-wrap .button { margin: 10px 0px 28px 0px; }
+	.header { margin-left: auto; margin-right: auto; text-align: center; }
+	.header .search { margin: 10px 0px 28px 48px; width: 400px; }
+	.header .search, .header .button { margin: 10px 0px 28px 0px; }
 	
 	/* Search results - Header Navigation */
-	.navigation-header { display: flex; margin: 0; padding: 0; align-items: baseline; }
-	.navigation-header a { margin: 0 auto; padding: 0; }
+	.navigation { margin: 0 auto 10px auto; padding: 0; align-items: baseline; }
 
 	/* Misc */
 	.logo { position: relative; display: block;margin: 0 auto; float: none; padding: 10px; font-size: 1.75rem; }
 }
 
-@media only screen and (max-width:640px)  { /* portrait tablets, portrait iPad, landscape e-readers, landscape 800x480 or 854x480 phones */ 
+@media only screen and (max-width: 640px)  { /* portrait tablets, portrait iPad, landscape e-readers, landscape 800x480 or 854x480 phones */ 
 	/* Page structure */
-	.results-wrap { position: relative; display: flex; margin: 0 10px 10px 10px; }
+	.content { position: relative; margin: 0 10px; }
+	.footer-left, .footer-right { display: block; padding: 5px 5px 0 5px; text-align: center; }
+	.footer-right { float: none; padding: 0px 5px 5px 5px; }
 
-	/* Main page */
-	.search-box-main { margin-top: 10%; }
-	.search-box-main input { width: 80%; }
-	.search-box-buttons button { display: table-row; margin: 30px 0px 0px 0px; width: 80%; }
+	/* Start page */
+	.startpage-search { margin-top: 10%; }
+	.startpage-search .search { width: 80%; }
+	.search-buttons button { display: table-row; margin: 30px 0px 0px 0px; width: 80%; }
 
 	/* Search Results - Header */
-	.header-wrap { margin-left: auto; margin-right: auto; text-align: center; }
-	.header-wrap .search, .header-wrap .button { margin: 0px 0px 10px 0px; width: 80%; border-radius: 25px; }
+	.header { margin-left: auto; margin-right: auto; text-align: center; }
+	.header .search, .header .button { margin: 0px 0px 10px 0px; width: 80%; border-radius: 25px; }
 	
 	/* Search results - Header Navigation */
-	.navigation-header { display: flex; margin: 0; padding: 0; align-items: baseline; }
-	.navigation-header a { margin: 0 auto; padding: 0; }
+	.navigation { margin: 0 auto 10px auto; padding: 0; align-items: baseline; }
 
 	/* Misc */
 	.logo { position: relative; display: block; float: none; margin: 0 auto; padding: 10px; font-size: 1.75rem; }
 }
 
-@media only screen and (max-width:480px)  { /* portrait e-readers (Nook/Kindle), smaller tablets @ 600 or @ 640 wide. */ 
+@media only screen and (max-width: 480px)  { /* portrait e-readers (Nook/Kindle), smaller tablets @ 600 or @ 640 wide. */ 
 	/* Page structure */
-	.results-wrap { position: relative; display: flex; margin: 0 10px 10px 10px; }
+	.content { position: relative; margin: 0 10px; }
+	.footer-left, .footer-right { display: block; padding: 5px 5px 0 5px; text-align: center; }
+	.footer-right { float: none; padding: 0px 5px 5px 5px; }
 
-	/* Main page */
-	.search-box-main { margin-top: 10%; }
-	.search-box-main input { width: 80%; }
-	.search-box-main h1 { font-size: 2.5rem; }
-	.search-box-buttons button { display: table-row; margin: 30px 0px 0px 0px; width: 80%; }
+	/* Start page */
+	.startpage-search { margin-top: 10%; }
+	.startpage-search h1 { font-size: 2.5rem; }
+	.startpage-search .search { width: 80%; }
+	.search-buttons button { display: table-row; margin: 30px 0px 0px 0px; width: 80%; }
 
 	/* Search Results - Header */
-	.header-wrap { margin-left: auto; margin-right: auto; text-align: center; }
-	.header-wrap .search, .header-wrap .button { margin: 0px 0px 10px 0px; width: 80%; border-radius: 25px; }
+	.header { margin-left: auto; margin-right: auto; text-align: center; }
+	.header .search, .header .button { margin: 0px 0px 10px 0px; width: 80%; border-radius: 25px; }
 	
 	/* Search results - Header Navigation */
-	.navigation-header { display: flex; margin: 0; padding: 0; align-items: baseline; }
-	.navigation-header a { margin: 0 auto; padding: 0; }
-
-	/* Magnet highlights */
-	.main-column .magnet-wrapper { display: none; }
+	.navigation { margin: 0 auto 10px auto; padding: 0; align-items: baseline; }
 
 	/* Misc */
 	.logo { position: relative; display: block; float: none; margin: 0 auto; padding: 10px; font-size: 1.75rem; }
 }
 
-@media only screen and (max-width:320px)  { /* smartphones, iPhone, portrait 480x320 phones */ 
+@media only screen and (max-width: 320px)  { /* smartphones, iPhone, portrait 480x320 phones, split screen devices */ 
 	/* Page structure */
-	.results-wrap { position: relative; display: flex; margin: 0 10px 10px 10px; }
+	.content { position: relative; margin: 0 10px; }
+	.footer-left, .footer-right { display: block; padding: 5px 5px 0 5px; text-align: center; }
+	.footer-right { float: none; padding: 0px 5px 5px 5px; }
 
-	/* Main page */
-	.search-box-main { margin-top: 40px; }
-	.search-box-main input { width: 80%; }
-	.search-box-main h1 { font-size: 2.5rem; }
-	.search-box-buttons button { display: table-row; margin: 20px 0px 0px 0px; width: 80%; }
+	/* Start page */
+	.startpage-search { margin-top: 40px; }
+	.startpage-search h1 { font-size: 2.5rem; }
+	.startpage-search .search { width: 80%; }
+	.search-buttons button { display: table-row; margin: 20px 0px 0px 0px; width: 80%; }
 
 	/* Search Results - Header */
-	.header-wrap { margin-left: auto; margin-right: auto; text-align: center; }
-	.header-wrap .search, .header-wrap .button { margin: 0px 0px 10px 0px; width: 80%; border-radius: 25px; }
+	.header { margin-left: auto; margin-right: auto; text-align: center; }
+	.header .search, .header .button { margin: 0px 0px 10px 0px; width: 80%; border-radius: 25px; }
 	
 	/* Search results - Header Navigation */
-	.navigation-header { display: flex; margin: 0; padding: 0; align-items: baseline; }
-	.navigation-header a { margin: 0 auto; padding: 0; }
-
-	/* Magnet highlights */
-	.main-column .magnet-wrapper { display: none; }
+	.navigation { margin: 0 auto 10px auto; padding: 0; align-items: baseline; }
 
 	/* Misc */
 	.logo { position: relative; float: none; display: block; margin: 0 auto; padding: 10px; font-size: 1.75rem; }

binární
assets/images/goosle.webp


+ 81 - 110
config.default.php

@@ -11,154 +11,106 @@
 ------------------------------------------------------------------------------------ */
 
 /* ------------------------------------------------------------------------------------
+SITEURL:
+	Set the base domain name for your Goosle setup (ex. example.com, something.example.com, example.com/something/) so that internal links will work correctly.
+
 HASH:
-	A simple lowercase passphrase (something simple like: j9fg-i2du-er6m or 1846) used for caching results. This helps to differentiate between instances on the same server.
-	You can also add it to your url/bookmark as a simple passphrase to keep unwanted users out.
+	A simple lowercase passphrase, something simple like: j9fg-i2du-er6m or 1846.
+	Used for caching results and optionally for accessing Goosle (See HASH_AUTH option).
 
-HASH AUTH:
+HASH_AUTH:
 	Use the above hash as a simple passphrase.
-	Using it as a passphrase lets you host Goosle on a public server without providing a public service.
+	Using it as a passphrase lets you host Goosle on a public facing server without providing a public service.
+	This is useful for if just you and some friends or family should be able to use Goosle from anywhere.
+
+	"off" Don't use the hash as a password.
+	"on" Use the hash as a password.
 
 	Usage: https://example.com/?a=1234567890
-	Disclaimer: This is not meant to 'hack proof' or truly secure the setup. Just a simple token to keep surface level prying eyes out.
-	
-CACHE:
-	It is highly recommended to enable caching as it'll speed up repeat searches by a lot.
+	Disclaimer: This is not meant to 'hack proof' or truly secure the setup. Just a simple token to keep surface level prying eyes out.	
 
 CACHE_TYPE:
-	Choose how to cache results. The cache is NOT unique per user but shared between all users. Different users searching for the exact same thing get the same results.
-	Default caching method is APCu. Alternatively, you can store the results in text files in the /cache/ folder.
-	Ignored if above 'cache' option is set to off.
-	"apcu" (Recommended) faster, utilize memory.
-	"file" Store results in text files.
+	It is highly recommended to enable caching as it will speed up repeat searches by a lot.
+	The cache is NOT unique per user but shared between all users. Different users searching for the exact same thing get the same results.
+
+	Caching can be done in memory with APCu or as temporary files in the /cache/ folder.
+
+	"off" No caching.
+	"file" Store results in text files (Default).
+	"apcu" Faster, utilizes memory.
 
 CACHE_TIME:
 	Minutes the result should be cached. Accepts a numeric value between 1 and 720.
-	APCu stores in memory, using a longer time takes up more of it. It is recommended to not exceed 30 minutes for it.
+	APCu stores in memory, using a longer time takes up more of it. It is recommended to not exceed 30 minutes for APCu.
 	The file cache is only limited by your hosting storage space and can safely be much much longer if you want.
 	To not show outdated results the 'limit' is 720 minutes, which equals 12 hours.
-	Ignored if above 'cache' option is set to off.
-
-
-
-ENABLE IMAGE SEARCH:
-	Enable or disable image searches - Search results are provided by Yahoo! Images.
-
-ENABLE MAGNET SEARCH:
-	Enable or disable searching for magnet links on torrent websites.
-
-ENABLE SEARCH ENGINES:
-	Enable or disable search engines.
-
-ENABLE MAGNET CRAWLERS:
-	Enable or disable crawlers to pull magnet links from.
-
-
-
+	Ignored if above 'CACHE_TYPE' option is set to off.
+/* ------------------------------------------------------------------------------------
 LANGUAGE:
-	DuckDuckGo, Google and Ecosia are language agnostic. But they DO profile you for your locale.
-	For example: Me searching with english terms has me seeing Spanish results because I live in Mexico. This setting should minimize that for supported engines.
-
-	DuckDuckGo uses language regions as opposed to a simpler language choice. See if your region is available - https://duckduckgo.com/duckduckgo-help-pages/settings/params/.
-	Google's language option breaks reasonable results and other options like verbatim mode and some other settings. So you'll have to rely on Google picking up on the query language.
+	DuckDuckGo and Google are mostly language agnostic.
+	
+	To not fit the USA mold, Goosle defaults to the United Kingdom for english results.
+	
+	Google has no language setting because as soon as you specify it all 'anonymous' settings stop working.
+	DuckDuckGo uses language regions and defaults to the United Kingdom. To change it see if your region is available - https://duckduckgo.com/duckduckgo-help-pages/settings/params/.
 	Wikipedia needs to be told which language you want. This changes the search url. Use any of their supported languages (en, es, fr, nl, etc.)
+	Qwant uses a locale similar to DuckDuckGo. Available locales are: bg_bg, br_fr, ca_ad, ca_es, ca_fr, co_fr, cs_cz, cy_gb, da_dk, de_at, de_ch, de_de, ec_ca, el_gr, en_au, en_ca, en_gb, en_ie, en_my, en_nz, en_us, es_ad, es_ar, es_cl, es_co, es_es, es_mx, es_pe, et_ee, eu_es, eu_fr, fc_ca, fi_fi, fr_ad, fr_be, fr_ca, fr_ch, fr_fr, gd_gb, he_il, hu_hu, it_ch, it_it, ko_kr, nb_no, nl_be, nl_nl, pl_pl, pt_ad, pt_pt, ro_ro, sv_se, th_th, zh_cn, zh_hk.
 
 SOCIAL MEDIA RELEVANCE:
 	Show social media results lower in the combined results if you don't value such results.
-	Downranked results include: Facebook, Instagram, Twitter, Snapchat, TikTok, LinkedIn and Reddit.
-	!!CAREFUL!! This is a blanket setting, if what you're searching for primarily has social media links then less relevant results may show first.
-	Accepts a numeric value between 1 and 10. With 10 having *NO* effect on the rank, and 0 not ranking the link at all (shows very very low in the results)
-
-SHOW SEARCH SOURCE:
-	Show which search engine(s) came up with the result.
-
-SHOW SEARCH RANK:
-	When search source is enabled, show the rank Goosle gave the result.
-
-IMDB ID SEARCH:
-	Highlight imdb results if it's a tv-show or movie.
-	Handy for finding better results for specific tv-shows through EZTV and The Pirate Bay.
-
-PASSWORD GENERATOR
-	Show a password generator on the Goosle home page.
-
-
-		
-SPECIAL:
-	Enable or disable special searches that show up before search results.
-
-
-
-SHOW ZERO SEEDERS:
-	Set to "on" to include results with 0 seeders (slow or stale downloads). Off to exclude these results.
-
-YTS HIGHLIGHT:
-	If you've enabled the YTS special search, you can also choose what it should show. The 8 most [insert choice] movies.
-	"date_added" = Newest movies (Default).
-	"rating" = Highest rated movies as per imdb.
-	"download_count" = Most downloaded movies.
-
-BLOCK 1337x CATEGORIES:
-	Add category IDs of 1337x categories, check /engines/magnet/1337x.php for a list of known categories.
-	Accepts a basic numeric array, comma separated.
-
-BLOCK PIRATEBAY CATEGORIES:
-	Add category IDs of Pirate Bay categories, check /engines/magnet/thepiratebay.php for a list of known categories.
-	Accepts a basic numeric array, comma separated.
-
-BLOCK YTS CATEGORIES:
-	Add category names as keywords, eg; "thriller", "war".
-	Movies can be in multiple categories, if a movie is in 5 categories it only has to match one to be filtered out.
-	Accepts a basic array of keywords, comma separated.
-
-
-
+	Downranked results include websites like Facebook, Instagram, Twitter, Snapchat, TikTok, LinkedIn and Reddit.
+	!! CAREFUL !! This is a blanket setting, if what (or who) you're searching for primarily has social media links then less relevant results may show first.
+	Accepts a numeric value between 1 and 10. With 10 having *NO* effect on the rank, and 0 not ranking the link at all (shows very very low in the results).
+/* ------------------------------------------------------------------------------------
 USER AGENTS:
-	Add more or less user agents to the list. Keep at least one!
+	Add more or less user agents to the list but keep at least one!
 	On every search Goosle picks one at random to identify as.
 	Keep them generic to prevent profiling, but also so that the request comes off as a generic boring browser and not as a server/crawler.
 	
-	Safari, Firefox and Internet Explorer/Edge should be safe to use.
+	Safari, Firefox and Internet Explorer (Yes that's old!) should be safe to use.
 	Chrome may attract attention because of the lack of Chrome information (tracking) aside from the user agent. The search engine may know something is 'weird'.
 	Opera/Edge/Brave and many others use Chrome under the hood and are not a good pick for that reason.
-	Mobile agents may work, but some services like Wikipedia are a bit picky when it comes to answering API calls. Mobile users generally do not use APIs, so they may block your search.
+	Mobile user agents may work, but some services like Wikipedia are a bit picky when it comes to answering API calls. 
+	Mobile users generally do not use APIs, so they may block your search or show a trimmed version of results.
 
 MAGNET TRACKERS:
-	Only used for The Pirate Bay, LimeTorrents and YTS.
+	These are added to the magnet links Goosle creates by itself. 
 	Generally you do not need to change these.
-	These are added to the magnet links Goosle creates. You can add more or replace the existing ones if you know what you're doing.
-	Accepts a basic array of strings (tracker urls), comma separated.
+	Currently only The Pirate Bay, LimeTorrents and YTS use generated magnet links.
+
+	You can add more or replace the existing ones if you know what you're doing. But keep at least one, preferably 3-5+.
 ------------------------------------------------------------------------------------ */
 
 return (object) array(
-	"hash" => "j9fg-i2du-er6m",
+	// ALL OPTIONS ARE REQUIRED, EVEN IF YOU DO NOT USE THE FEATURE. EMPTY VALUES OR MISSING SETTINGS CAUSE ISSUES!!!
+	"siteurl" => "example.com", // Make sure this is accurate
+	"hash" => "j9fg-i2du-er6m", // Some kind of alphanumeric password-like string, used for caching and optionally for access to Goosle
     "hash_auth" => "off", // Default: off
-    "cache" => "off", // Default: off
-    "cache_type" => "apcu", // Default: apcu
+    "cache_type" => "file", // Default: file
     "cache_time" => 30, // Default: 30 (Minutes)
 
-    "enable_image_search" => "on", // Default: on
-    "enable_magnet_search" => "on", // Default: on
     "enable_duckduckgo" => "on", // Default: on
     "enable_google" => "on", // Default: on
-    "enable_reddit" => "on", // Default: on
+    "enable_qwantnews" => "on", // Default: on
     "enable_wikipedia" => "on", // Default: on
-    "enable_ecosia" => "off", // Default: on	
-    	// Site uses some kind of bot detector preventing crawler from working reliably since Feb 1, 2024, remove support in future release?)
 
+    "enable_image_search" => "on", // Default: on (Disables all image search regardless of settings for individual engines)
+    "enable_yahooimages" => "on", // Default: on
+    "enable_openverse" => "off", // Default: off (Requires API token, see readme for details)
+    "enable_qwant" => "on", // Default: on
+
+    "enable_magnet_search" => "on", // Default: on (Disables all image search regardless of settings for individual engines)
+    "enable_eztv" => "on", // Default: on
     "enable_limetorrents" => "on", // Default: on
+    "enable_nyaa" => "on", // Default: on	
     "enable_piratebay" => "on", // Default: on
     "enable_yts" => "on", // Default: on
-    "enable_magnetdl" => "on", // Default: on	
-    "enable_nyaa" => "on", // Default: on	
-    "enable_eztv" => "on", // Default: on
-    "enable_l33tx" => "off", // Default: off
-    	// Site now uses cloudflare preventing crawler from working since Jan 20, 2024, remove support in future release?)
 
     "duckduckgo_language" => "uk-en", // Default: uk-en (United Kingdom)
     "wikipedia_language" => "en", // Default: en (English)
+	"qwant_language" => "en_gb", // Default: en_gb (United Kingdom)
+
     "social_media_relevance" => 8, // Default: 8
-    "show_reddit_nsfw" => "on", // Default: on
     "show_search_source" => "on", // Default: on
     "show_search_rank" => "off", // Default: off
 	"imdb_id_search" => "off", // Default: off
@@ -174,11 +126,15 @@ return (object) array(
 
     "show_zero_seeders" => "on", // Default: on
     "yts_highlight" => "date_added", // Default: "date_added"
-    "leetx_categories_blocked" => array(3, 7, 47), // Default: 3, 7, 47
     "piratebay_categories_blocked" => array(206, 210), // Default: 206, 210
     "yts_categories_blocked" => array("horror"), // Default: "horror"
 
 	"user_agents" => array(
+		"Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1", // Linux, Lynx browser 2.8.5
+		"Lynx/2.8.9rel.1 libwww-FM/2.14 SSL-MM/1.4.1", // Linux, Lynx browser 2.8.9
+		"Lynx/2.8.6rel.4 libwww-FM/2.14 SSL-MM/1.4.1", // Linux, Lynx browser 2.8.6
+		"TinyBrowser/2.0 (TinyBrowser Comment; rv:1.9.1a2pre) Gecko/20201231", // Linux, Tinybrowser 2
+		"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) DuckDuckGo/7 Safari/605.1.15", // macOS 10.15, DuckDuckGo 7
 		"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15) Gecko/20100101 Firefox/119.0", // macOS 10.15, Firefox 119
 		"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/116.0", // Windows 10, Firefox 116
 		"Mozilla/5.0 (X11; Ubuntu; Linux x86_64) Gecko/20100101 Firefox/83.0", // Linux Ubuntu, Firefox 83
@@ -187,11 +143,26 @@ return (object) array(
 
     "magnet_trackers" => array(
     	"http://nyaa.tracker.wf:7777/announce", 
-		"http://tracker.openbittorrent.com:80/announce",
-    	"udp://tracker.opentrackr.org:1337/announce", 
-    	"udp://exodus.desync.com:6969/announce", 
+    	"udp://tracker.opentrackr.org:1337/announce",
+    	"udp://exodus.desync.com:6969/announce",
     	"udp://tracker.torrent.eu.org:451/announce",
     	"udp://opentracker.i2p.rocks:6969/announce",
+    	"udp://open.demonii.com:1337/announce", 
+		"udp://open.stealth.si:80/announce", 
+		"udp://tracker.moeking.me:6969/announce", 
+		"udp://explodie.org:6969/announce", 
+		"udp://tracker1.bt.moack.co.kr:80/announce", 
+		"udp://tracker.theoks.net:6969/announce", 
+		"udp://tracker-udp.gbitt.info:80/announce", 
+		"https://tracker.tamersunion.org:443/announce", 
+		"https://tracker.gbitt.info:443/announce", 
+		"udp://tracker.tiny-vps.com:6969/announce", 	
+		"udp://tracker.dump.cl:6969/announce", 
+		"udp://tamas3.ynh.fr:6969/announce", 
+		"udp://retracker01-msk-virt.corbina.net:80/announce", 
+		"udp://open.free-tracker.ga:6969/announce", 
+		"udp://epider.me:6969/announce", 
+		"udp://bt2.archive.org:6969/announce",
     )
 );
-?>
+?>

+ 111 - 0
engines/image/openverse.php

@@ -0,0 +1,111 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class OpenverseRequest extends EngineRequest {
+	public function get_request_url() {
+
+	    $query_terms = substr($this->query, 0, 200);
+
+		// Safe search override
+		$safe = "0"; // No mature results
+		if(strpos($query_terms[0], "safe") !== false) {
+			$switch = explode(":", $query_terms[0]);
+
+			if(!is_numeric($switch[1])) {
+				$safe = (strtolower($switch[1]) == "off") ? "1" : "0";
+				$this->query = implode(" ", array_slice($query_terms, 1));
+			}
+		}
+
+		// q = query
+		// format = json
+		// mature = 1 / 0
+		// page_size = 80 (int)
+
+		$args = array("q" => $query_terms, "format" => "json", "mature" => $safe, "page_size" => 50);
+        $url = "https://api.openverse.org/v1/images/?".http_build_query($args);
+
+        unset($query_terms, $switch, $safe, $max_results, $args);
+
+        return $url;
+	}
+
+    public function get_request_headers() {
+		$token_file = ABSPATH.'cache/token.data';
+		$token = unserialize(file_get_contents($token_file));
+		
+		return array(
+			'Accept' => 'application/json, */*;q=0.8',
+			'Content-type' => 'application/x-www-form-urlencoded',
+			'Authorization' => 'Bearer '.$token['openverse']['access_token'],
+			'Accept-Language' => null,
+			'Accept-Encoding' => null,
+			'Connection' => null,
+			'Sec-Fetch-Dest' => null,
+			'Sec-Fetch-Mode' => null,
+			'Sec-Fetch-Site' => null
+		);
+	}
+
+	public function parse_results($response) {
+		$results = array();
+		$json_response = json_decode($response, true);
+
+		// No response
+		if(empty($json_response)) return $results;
+		
+		// Set base rank and result amound
+		$rank = $results['amount'] = count($json_response['results']);
+
+		// Nothing found
+		if($results['amount'] == 0) return $results;
+
+		// Use API result
+		foreach ($json_response['results'] as $result) {
+			// Deal with optional or missing data
+			$dimensions_w = (!empty($result['width'])) ? sanitize($result['width']) : "";
+			$dimensions_h = (!empty($result['height'])) ? sanitize($result['height']) : "";
+			$filesize = (!empty($result['filesize'])) ? sanitize($result['filesize']) : "";
+			$link = (!empty($result['url'])) ? sanitize($result['url']) : "";
+
+			$image_full = (!empty($result['foreign_landing_url'])) ? sanitize($result['foreign_landing_url']) : "";
+			$image_thumb = (!empty($result['thumbnail'])) ? sanitize($result['thumbnail']) : $image_full;
+			$alt = (!empty($result['title'])) ? sanitize($result['title']) : "";
+
+			// Add attribution to alt text?
+			$creator = (!empty($result['creator'])) ? " by ".sanitize($result['creator']) : "";
+			$alt = (!empty($creator)) ? $alt.$creator : $alt;
+
+			// Process result
+			$filesize = intval(preg_replace('/[^0-9]/', '', $filesize));
+
+			// filter duplicate IMAGE urls/results
+            if(!empty($results['search'])) {
+                if(in_array($image_full, array_column($results['search'], "image_full"))) continue;
+            }
+
+			$results['search'][] = array ("id" => uniqid(rand(0, 9999)), "source" => "Openverse", "image_thumb" => $image_thumb, "alt" => $alt, "image_full" => $image_full, "width" => $dimensions_w, "height" => $dimensions_h, "filesize" => $filesize, "webpage_url" => $link, "engine_rank" => $rank);
+			$rank -= 1;
+			unset($url_data, $usable_data, $dimensions_w, $dimensions_h, $filesize, $link, $image_full, $alt, $image_thumb);
+		}
+		unset($json_response, $rank);
+
+		// Add error if there are no search results
+		if(empty($results['search'])) {
+			$results['error'] = array(
+				"message" => "No results found. Please try with less or different keywords!"
+			);
+		}
+		
+		return $results;
+	}
+}
+?>

+ 106 - 0
engines/image/qwant.php

@@ -0,0 +1,106 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class QwantImageRequest extends EngineRequest {
+	public function get_request_url() {
+		// Split the query
+	    $query_terms = explode(" ", strtolower($this->query));
+
+		// Safe search override
+		$safe = "1"; // Moderate results
+		if(strpos($query_terms[0], "safe") !== false) {
+			$switch = explode(":", $query_terms[0]);
+
+			if(!is_numeric($switch[1])) {
+				$safe = (strtolower($switch[1]) == "off") ? "0" : "2";
+				$this->query = implode(" ", array_slice($query_terms, 1));
+			}
+		}
+
+		$language = (strlen($this->opts->qwant_language) > 0 && strlen($this->opts->qwant_language < 6)) ? $this->opts->qwant_language : "en_gb";
+
+		// q = query
+		// t = Type of search, Images
+		// count = Up-to how many images to return (Max 50)
+		// locale = In which language should the search be done
+		// device = What kind of device are we searching from?
+		// safesearch = Safe search filter (0 = off, 1 = normal, 2 = strict)
+
+		$args = array("q" => $this->query, "t" => 'images', 'count' => 50, 'locale' => $language, 'device' => 'desktop', 'safesearch' => $safe);
+        $url = "https://api.qwant.com/v3/search/images?".http_build_query($args);
+
+        unset($query_terms, $switch, $safe, $language, $args);
+
+        return $url;
+	}
+
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'application/json, */*;q=0.8',
+			'Accept-Language' => null,
+			'Accept-Encoding' => null,
+			'Connection' => null,
+			'Sec-Fetch-Dest' => null,
+			'Sec-Fetch-Mode' => null,
+			'Sec-Fetch-Site' => null
+		);
+	}
+
+	public function parse_results($response) {
+		$results = array();
+		$json_response = json_decode($response, true);
+
+		// No response
+		if(empty($json_response)) return $results;
+
+		// Nothing found
+        if($json_response["status"] != "success") return $results;
+
+		// Set base rank and result amound
+		$rank = $results['amount'] = $json_response["data"]["result"]["total"];
+
+		// Use API result
+		foreach ($json_response["data"]["result"]["items"] as $result) {
+			// Deal with optional or missing data
+			$dimensions_w = (!empty($result['width'])) ? sanitize($result['width']) : "";
+			$dimensions_h = (!empty($result['height'])) ? sanitize($result['height']) : "";
+			$filesize = (!empty($result['size'])) ? sanitize($result['size']) : "";
+			$link = (!empty($result['url'])) ? sanitize($result['url']) : "";
+
+			$image_full = (!empty($result['media'])) ? sanitize($result['media']) : "";
+			$image_thumb = (!empty($result['thumbnail'])) ? sanitize($result['thumbnail']) : $image_full;
+			$alt = (!empty($result['title'])) ? sanitize($result['title']) : "";
+
+			// Process result
+			$filesize = intval(preg_replace('/[^0-9]/', '', $filesize));
+
+			// filter duplicate IMAGE urls/results
+            if(!empty($results['search'])) {
+                if(in_array($image_full, array_column($results['search'], "image_full"))) continue;
+            }
+
+			$results['search'][] = array ("id" => uniqid(rand(0, 9999)), "source" => "Qwant", "image_thumb" => $image_thumb, "alt" => $alt, "image_full" => $image_full, "width" => $dimensions_w, "height" => $dimensions_h, "filesize" => $filesize, "webpage_url" => $link, "engine_rank" => $rank);
+			$rank -= 1;
+			unset($url_data, $usable_data, $dimensions_w, $dimensions_h, $filesize, $link, $image_full, $alt, $image_thumb);
+		}
+		unset($json_response, $rank);
+
+		// Add error if there are no search results
+		if(empty($results['search'])) {
+			$results['error'] = array(
+				"message" => "No results found. Please try with less or different keywords!"
+			);
+		}
+		
+		return $results;
+	}
+}
+?>

+ 26 - 18
engines/image/yahoo.php

@@ -38,11 +38,17 @@ class YahooImageRequest extends EngineRequest {
 		$args = array("p" => $this->query, "imgsz" => $size);
         $url = "https://images.search.yahoo.com/search/images?".http_build_query($args);
 
-        unset($query_terms, $switch, $args, $size);
+        unset($query_terms, $switch, $size, $args);
 
         return $url;
 	}
 
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'text/html, application/xhtml+xml, application/xml;q=0.8, */*;q=0.7',
+		);
+	}
+
 	public function parse_results($response) {
 		$results = array();
 		$xpath = get_xpath($response);
@@ -62,15 +68,19 @@ class YahooImageRequest extends EngineRequest {
 		// Scrape the results
 //		$scrape = $xpath->query("//li[contains(@class, 'ld') and not(contains(@class, 'slotting'))][position() < 101]");
 		$scrape = $xpath->query("//li[contains(@class, 'ld') and not(contains(@class, 'ignore'))][position() < 101]");
+
+		// Set base rank and result amound
 		$rank = $results['amount'] = count($scrape);
 
+		// Nothing found
+		if($results['amount'] == 0) return $results;
 
         foreach($scrape as $result) {
-			$image = $xpath->evaluate(".//img/@src", $result)[0];
-			if($image == null) continue;
+			$image_thumb = $xpath->evaluate(".//img/@src", $result)[0];
+			if(is_null($image_thumb)) continue;
 			
 			$url_data = $xpath->evaluate(".//a/@href", $result)[0];
-			if($url_data == null) continue;
+			if(is_null($url_data)) continue;
 			
 			// Get and prepare meta data
 			// -- Relevant $url_data (there is more, but unused by Goosle)
@@ -92,27 +102,25 @@ class YahooImageRequest extends EngineRequest {
 			}
 
 			// Deal with optional or missing data
-			$dimensions_w = (!array_key_exists('w', $usable_data)) ? "" : htmlspecialchars($usable_data['w']);
-			$dimensions_h = (!array_key_exists('h', $usable_data)) ? "" : htmlspecialchars($usable_data['h']);
-			$link = (!array_key_exists('imgurl', $usable_data)) ? "" : "//".htmlspecialchars($usable_data['imgurl']);
-			$url = (!array_key_exists('rurl', $usable_data)) ? "" : htmlspecialchars($usable_data['rurl']);
-			$filesize = (!array_key_exists('size', $usable_data)) ? "" : htmlspecialchars($usable_data['size']);
-			$alt = (!array_key_exists('tt', $usable_data)) ? "" : htmlspecialchars($usable_data['tt']);
+			$dimensions_w = (!array_key_exists('w', $usable_data)) ? "" : sanitize($usable_data['w']);
+			$dimensions_h = (!array_key_exists('h', $usable_data)) ? "" : sanitize($usable_data['h']);
+			$image_full = (!array_key_exists('imgurl', $usable_data)) ? "" : "//".sanitize($usable_data['imgurl']);
+			$link = (!array_key_exists('rurl', $usable_data)) ? "" : sanitize($usable_data['rurl']);
+			$filesize = (!array_key_exists('size', $usable_data)) ? "" : sanitize($usable_data['size']);
+			$alt = (!array_key_exists('tt', $usable_data)) ? "" : sanitize($usable_data['tt']);
 
 			// Process result
-			$image = htmlspecialchars($image->textContent);
+			$image_thumb = sanitize($image_thumb->textContent);
+			$filesize = intval(preg_replace('/[^0-9.]/', '', $filesize) * 1000);
 
-			// filter duplicate urls/results
+			// filter duplicate IMAGE urls/results
             if(!empty($results['search'])) {
-		        $result_urls = array_column($results['search'], "direct_link");
-                if(in_array($link, $result_urls)) continue;
+                if(in_array($image_full, array_column($results['search'], "image_full"))) continue;
             }
 
-			$id = uniqid(rand(0, 9999));
-
-			$results['search'][] = array ("id" => $id, "source" => "Yahoo! Images", "image" => $image, "alt" => $alt, "url" => $url, "width" => $dimensions_w, "height" => $dimensions_h, "filesize" => $filesize, "direct_link" => $link, "engine_rank" => $rank);
+			$results['search'][] = array ("id" => uniqid(rand(0, 9999)), "source" => "Yahoo! Images", "image_thumb" => $image_thumb, "alt" => $alt, "image_full" => $image_full, "width" => $dimensions_w, "height" => $dimensions_h, "filesize" => $filesize, "webpage_url" => $link, "engine_rank" => $rank);
 			$rank -= 1;
-			unset($url_data, $usable_data, $dimensions_w, $dimensions_h, $filesize, $link, $url, $alt, $image);
+			unset($url_data, $usable_data, $dimensions_w, $dimensions_h, $filesize, $link, $image_full, $alt, $image_thumb);
 		}
 		unset($response, $xpath, $scrape, $rank);
 

+ 0 - 147
engines/magnet/1337x.php

@@ -1,147 +0,0 @@
-<?php
-/* ------------------------------------------------------------------------------------
-*  Goosle - A meta search engine for private and fast internet fun.
-*
-*  COPYRIGHT NOTICE
-*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
-*
-*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
-*  By using this code you agree to indemnify Arnan de Gans from any 
-*  liability that might arise from its use.
------------------------------------------------------------------------------------- */
-class LeetxRequest extends EngineRequest {
-	public function get_request_url() {
-		$url = "https://1337x.to/search/".urlencode($this->query)."/1/";
-
-        return $url;
-
-	}
-	
-	public function parse_results($response) {
-		$results = array();
-		$xpath = get_xpath($response);
-		
-		// Failed to load page
-		if(!$xpath) return $results;
-		
-		$categories = array(
-			1 => "DVD",
-			2 => "Divx/Xvid",
-			3 => "SVCD/VCD",
-			4 => "Dubs/Dual Audio",
-			5 => "DVD",
-			6 => "Divx/Xvid",
-			7 => "SVCD/VCD",
-			9 => "Documentary",
-
-			10 => "PC Game",
-			11 => "PS2",
-			12 => "PSP",
-			13 => "Xbox",
-			14 => "Xbox360",
-			15 => "PS1",
-			16 => "Dreamcast",
-			17 => "Other (Gaming)",
-			18 => "PC Software",
-			19 => "Mac Software",
-
-			20 => "Linux Software",
-			21 => "Other (Software)",
-			22 => "MP3",
-			23 => "Lossless Audio",
-			24 => "DVD (Music)",
-			25 => "Music Video",
-			26 => "Radio",
-			27 => "Other (Audio)",
-			28 => "Anime",
-
-			33 => "Emulation",
-			34 => "Tutorials",
-			35 => "Sounds",
-			36 => "E-Books",
-			37 => "Images",
-			38 => "Mobile Phone",
-			39 => "Comics",
-
-			40 => "Other",
-			41 => "HD (Video)",
-			42 => "HD (Video)",
-			43 => "PS3",
-			44 => "Wii",
-			45 => "DS",
-			46 => "GameCube",
-			47 => "Nulled Script",
-			48 => "Video",
-			49 => "Picture",
-
-			50 => "Magazine",
-			51 => "Hentai",
-			52 => "Audiobook",
-			53 => "Album (Music)",
-			54 => "h.264/x264",
-			55 => "Mp4",
-			56 => "Android",
-			57 => "iOS",
-			58 => "Box Set (Music)",
-			59 => "Discography",
-
-			60 => "Single (Music)",
-			66 => "3D",
-			67 => "Games",
-			68 => "Concerts",
-			69 => "AAC (Music)",
-
-			70 => "HEVC/x265",
-			71 => "HEVC/x265",
-			72 => "3DS",
-			73 => "Bollywood",
-			74 => "Cartoon",
-			75 => "SD (Video)",
-			76 => "UHD",
-			77 => "PS4",
-			78 => "Dual Audio (Video)",
-			79 => "Dubbed (Video)",
-
-			80 => "Subbed",
-			81 => "Raw",
-			82 => "Switch",
-		);
-
-		// Scrape the page
-		foreach($xpath->query("//table/tbody/tr") as $result) {
-			$name = sanitize($xpath->evaluate(".//td[@class='coll-1 name']/a", $result)[1]->textContent);
-			$url = "https://1337x.to".sanitize($xpath->evaluate(".//td[@class='coll-1 name']/a/@href", $result)[1]->textContent);
-			$magnet = "./engines/magnet/magnetize_1337x.php?url=".$url;
-			$seeders = sanitize($xpath->evaluate(".//td[@class='coll-2 seeds']", $result)[0]->textContent);
-			$leechers = sanitize($xpath->evaluate(".//td[@class='coll-3 leeches']", $result)[0]->textContent);
-			$size_unformatted = explode(" ", sanitize($xpath->evaluate(".//td[contains(@class, 'coll-4 size')]", $result)[0]->textContent));
-			$size = $size_unformatted[0] . " " . preg_replace("/[0-9]+/", "", $size_unformatted[1]);
-			
-			// Ignore results with 0 seeders?
-			if($this->opts->show_zero_seeders == "off" AND $seeders == 0) continue;
-			
-			// Get extra data
-			$category = explode("/", sanitize($xpath->evaluate(".//td[@class='coll-1 name']/a/@href", $result)[0]->textContent));
-			$category = $category[2];
-			
-			// Block these categories
-			if(in_array($category, $this->opts->leetx_categories_blocked)) continue;
-			
-			// Filter episodes
-			if(!is_season_or_episode($this->query, $name)) continue;
-			
-			$id = uniqid(rand(0, 9999));
-			
-			$results[] = array (
-				// Required
-				"id" => $id, "source" => "1337x.to", "name" => $name, "magnet" => $magnet, "hash" => null, "seeders" => $seeders, "leechers" => $leechers, "size" => $size,
-				// Extra
-				"category" => $categories[$category], "url" => $url
-			);
-		}
-		unset($response, $xpath);
-		
-		return $results;
-	}
-}
-?>

+ 13 - 3
engines/magnet/eztv.php

@@ -23,6 +23,18 @@ class EZTVRequest extends EngineRequest {
         return $url;
 	}
 
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'application/json, */*;q=0.8',
+			'Accept-Language' => null,
+			'Accept-Encoding' => null,
+			'Connection' => null,
+			'Sec-Fetch-Dest' => null,
+			'Sec-Fetch-Mode' => null,
+			'Sec-Fetch-Site' => null
+		);
+	}
+
 	public function parse_results($response) {
 		$results = array();
 		$json_response = json_decode($response, true);
@@ -61,11 +73,9 @@ class EZTVRequest extends EngineRequest {
 				}
 			}
 			
-			$id = uniqid(rand(0, 9999));
-			
 			$results[] = array (
 				// Required
-				"id" => $id, "source" => "EZTV", "name" => $name, "magnet" => $magnet, "hash" => $hash, "seeders" => $seeders, "leechers" => $leechers, "size" => human_filesize($size),
+				"id" => uniqid(rand(0, 9999)), "source" => "EZTV", "name" => $name, "magnet" => $magnet, "hash" => $hash, "seeders" => $seeders, "leechers" => $leechers, "size" => human_filesize($size),
 				// Extra
 				"quality" => $quality, "codec" => $codec, "date_added" => $date_added
 			);

+ 7 - 3
engines/magnet/lime.php

@@ -17,6 +17,12 @@ class LimeRequest extends EngineRequest {
         return $url;
 	}
 	
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'text/html, application/xhtml+xml, application/xml;q=0.8, */*;q=0.7',
+		);
+	}
+
 	public function parse_results($response) {
 		$results = array();
 		$xpath = get_xpath($response);
@@ -46,11 +52,9 @@ class LimeRequest extends EngineRequest {
 			// Filter episodes
 			if(!is_season_or_episode($this->query, $name)) continue;
 			
-			$id = uniqid(rand(0, 9999));
-			
 			$results[] = array (
 				// Required
-				"id" => $id, "source" => "limetorrents.lol", "name" => $name, "magnet" => $magnet, "hash" => $hash, "seeders" => $seeders, "leechers" => $leechers, "size" => $size,
+				"id" => uniqid(rand(0, 9999)), "source" => "limetorrents.lol", "name" => $name, "magnet" => $magnet, "hash" => $hash, "seeders" => $seeders, "leechers" => $leechers, "size" => $size,
 				// Extra
 				"category" => $category, "url" => $url
 			);

+ 0 - 64
engines/magnet/magnetdl.php

@@ -1,64 +0,0 @@
-<?php
-/* ------------------------------------------------------------------------------------
-*  Goosle - A meta search engine for private and fast internet fun.
-*
-*  COPYRIGHT NOTICE
-*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
-*
-*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
-*  By using this code you agree to indemnify Arnan de Gans from any 
-*  liability that might arise from its use.
------------------------------------------------------------------------------------- */
-class MagnetDLRequest extends EngineRequest {
-	public function get_request_url() {
-        $url = "https://www.magnetdl.com/".substr($this->query, 0, 1)."/".str_replace(' ', '-', $this->query);
-
-        return $url;
-	}
-	
-	public function parse_results($response) {
-		$results = array();
-		$xpath = get_xpath($response);
-		
-		// Failed to load page
-		if(!$xpath) return $results;
-		
-		// Scrape the page
-		foreach($xpath->query("//table[@class='download']/tbody/tr") as $result) {			
-			// Skip page navigation and incompatible rows
-			if(is_null($xpath->evaluate(".//td[2]", $result)[0])) continue;
-
-			$name = sanitize($xpath->evaluate(".//td[2]/a/@title", $result)[0]->textContent);
-			$magnet = sanitize($xpath->evaluate(".//td[1]/a/@href", $result)[0]->textContent);
-			$hash = parse_url($magnet, PHP_URL_QUERY);
-			parse_str($hash, $hash_parameters);
-			$hash = strtolower(str_replace("urn:btih:", "", $hash_parameters['xt']));
-			$seeders = sanitize($xpath->evaluate(".//td[7]", $result)[0]->textContent);
-			$leechers = sanitize($xpath->evaluate(".//td[8]", $result)[0]->textContent);
-			$size = sanitize($xpath->evaluate(".//td[6]", $result)[0]->textContent);
-
-			// Ignore results with 0 seeders?
-			if($this->opts->show_zero_seeders == "off" AND $seeders == 0) continue;
-			
-			// Get extra data
-			$category = sanitize($xpath->evaluate(".//td[4]", $result)[0]->textContent);
-			$url = "https://www.magnetdl.com".sanitize($xpath->evaluate(".//td[2]//a/@href", $result)[0]->textContent);
-			
-			// Filter episodes
-			if(!is_season_or_episode($this->query, $name)) continue;
-			
-			$id = uniqid(rand(0, 9999));
-			
-			$results[] = array (
-				// Required
-				"id" => $id, "source" => "magnetdl.com", "name" => $name, "magnet" => $magnet, "hash" => $hash, "seeders" => $seeders, "leechers" => $leechers, "size" => $size,
-				// Extra
-				"category" => $category, "url" => $url
-			);
-		}
-		unset($response, $xpath);
-
-		return $results;
-	}
-}
-?>

+ 0 - 29
engines/magnet/magnetize_1337x.php

@@ -1,29 +0,0 @@
-<?php
-require "../../functions/tools.php";
-$opts = require "../../config.php";
-
-/* ------------------------------------------------------------------------------------
-*  Goosle - A meta search engine for private and fast internet fun.
-*
-*  COPYRIGHT NOTICE
-*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
-*
-*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
-*  By using this code you agree to indemnify Arnan de Gans from any 
-*  liability that might arise from its use.
------------------------------------------------------------------------------------- */
-
-$ch = curl_init();
-set_curl_options($ch, $_REQUEST["url"], $opts->user_agents);
-$response = curl_exec($ch);
-curl_close($ch);
-
-$xpath = get_xpath($response);
-
-// No results
-if(!$xpath) die();
-
-$magnet = trim($xpath->query("//main/div/div/div/div/div/ul/li/a/@href")[0]->textContent);
-
-header("Location: $magnet")
-?>

+ 7 - 3
engines/magnet/nyaa.php

@@ -19,6 +19,12 @@ class NyaaRequest extends EngineRequest {
         return $url;
 	}
 	
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'text/html, application/xhtml+xml, application/xml;q=0.8, */*;q=0.7',
+		);
+	}
+
 	public function parse_results($response) {
 		$results = array();
 		$xpath = get_xpath($response);
@@ -53,11 +59,9 @@ class NyaaRequest extends EngineRequest {
 			// Filter episodes
 			if(!is_season_or_episode($this->query, $name)) continue;
 			
-			$id = uniqid(rand(0, 9999));
-			
 			$results[] = array (
 				// Required
-				"id" => $id, "source" => "nyaa.si", "name" => $name, "magnet" => $magnet, "hash" => $hash, "seeders" => $seeders, "leechers" => $leechers, "size" => $size,
+				"id" => uniqid(rand(0, 9999)), "source" => "nyaa.si", "name" => $name, "magnet" => $magnet, "hash" => $hash, "seeders" => $seeders, "leechers" => $leechers, "size" => $size,
 				// Extra
 				"category" => $category, "url" => $url, "date_added" => $date_added
 			);

+ 23 - 13
engines/magnet/thepiratebay.php

@@ -19,6 +19,18 @@ class PirateBayRequest extends EngineRequest {
         return $url;
 	}
 
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'application/json, */*;q=0.8',
+			'Accept-Language' => null,
+			'Accept-Encoding' => null,
+			'Connection' => null,
+			'Sec-Fetch-Dest' => null,
+			'Sec-Fetch-Mode' => null,
+			'Sec-Fetch-Site' => null
+		);
+	}
+
 	public function parse_results($response) {
 		$results = array();
 		$json_response = json_decode($response, true);
@@ -89,24 +101,24 @@ class PirateBayRequest extends EngineRequest {
 		);
 
 		// Use API result
-		foreach($json_response as $response) {
+		foreach($json_response as $result) {
 			// Nothing found
-			if($response['name'] == "No results returned") break;
+			if($result['name'] == "No results returned") break;
 			
-			$name = sanitize($response['name']);
-			$hash = strtolower(sanitize($response['info_hash']));
+			$name = sanitize($result['name']);
+			$hash = strtolower(sanitize($result['info_hash']));
 			$magnet = "magnet:?xt=urn:btih:".$hash."&dn=".urlencode($name)."&tr=".implode("&tr=", $this->opts->magnet_trackers);
-			$seeders = sanitize($response['seeders']);
-			$leechers = sanitize($response['leechers']);
-			$size = sanitize($response['size']);
+			$seeders = sanitize($result['seeders']);
+			$leechers = sanitize($result['leechers']);
+			$size = sanitize($result['size']);
 			
 			// Ignore results with 0 seeders?
 			if($this->opts->show_zero_seeders == "off" AND $seeders == 0) continue;
 			
 			// Get extra data
-			$category = sanitize($response['category']);
-			$url = "https://thepiratebay.org/description.php?id=".sanitize($response['id']);
-			$date_added = sanitize($response['added']);
+			$category = sanitize($result['category']);
+			$url = "https://thepiratebay.org/description.php?id=".sanitize($result['id']);
+			$date_added = sanitize($result['added']);
 			
 			// Block these categories
 			if(in_array($category, $this->opts->piratebay_categories_blocked)) continue;
@@ -114,11 +126,9 @@ class PirateBayRequest extends EngineRequest {
 			// Filter episodes
 			if(!is_season_or_episode($this->query, $name)) continue;
 			
-			$id = uniqid(rand(0, 9999));
-			
 			$results[] = array(
 				// Required
-				"id" => $id, "source" => "thepiratebay.org", "name" => $name, "magnet" => $magnet, "hash" => $hash, "seeders" => $seeders, "leechers" => $leechers, "size" => human_filesize($size),
+				"id" => uniqid(rand(0, 9999)), "source" => "thepiratebay.org", "name" => $name, "magnet" => $magnet, "hash" => $hash, "seeders" => $seeders, "leechers" => $leechers, "size" => human_filesize($size),
 				// Extra
 				"category" => $categories[$category], "url" => $url, "date_added" => $date_added,
  			);

+ 14 - 3
engines/magnet/yts.php

@@ -16,9 +16,21 @@ class YTSRequest extends EngineRequest {
         return $url;
 	}
 
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'application/json, */*;q=0.8',
+			'Accept-Language' => null,
+			'Accept-Encoding' => null,
+			'Connection' => null,
+			'Sec-Fetch-Dest' => null,
+			'Sec-Fetch-Mode' => null,
+			'Sec-Fetch-Site' => null
+		);
+	}
+
 	public function parse_results($response) {
 		$results = array();
-		$response = curl_multi_getcontent($this->ch);
+//		$response = curl_multi_getcontent($this->ch);
 		$json_response = json_decode($response, true);
 		
 		// No response
@@ -60,11 +72,10 @@ class YTSRequest extends EngineRequest {
 				// Get extra data
 				$quality = sanitize($download['quality']);
 				$codec = sanitize($download['video_codec']);
-				$id = uniqid(rand(0, 9999));
 			
 				$results[] = array (
 					// Required
-					"id" => $id, "source" => "yts.mx", "name" => $name, "magnet" => $magnet, "hash" => $hash, "seeders" => $seeders, "leechers" => $leechers, "size" => $size,
+					"id" => uniqid(rand(0, 9999)), "source" => "yts.mx", "name" => $name, "magnet" => $magnet, "hash" => $hash, "seeders" => $seeders, "leechers" => $leechers, "size" => $size,
 					// Extra
 					"quality" => $quality, "codec" => $codec, "year" => $year, "category" => $category, "runtime" => $runtime, "url" => $url, "date_added" => $date_added
 				);

+ 35 - 20
engines/search-image.php

@@ -13,17 +13,27 @@ class ImageSearch extends EngineRequest {
 	protected $requests;
 	
 	public function __construct($opts, $mh) {
-		require ABSPATH."engines/image/yahoo.php";
+		$this->requests = array();
 		
-		$this->requests = array(
-			new YahooImageRequest($opts, $mh),
-		);
+		if($opts->enable_yahooimages == "on") {
+			require ABSPATH."engines/image/yahoo.php";
+			$this->requests[] = new YahooImageRequest($opts, $mh);	
+		}
+
+		if($opts->enable_openverse == "on") {
+			require ABSPATH."engines/image/openverse.php";
+			$this->requests[] = new OpenverseRequest($opts, $mh);	
+		}
+
+		if($opts->enable_qwant == "on") {
+			require ABSPATH."engines/image/qwant.php";
+			$this->requests[] = new QwantImageRequest($opts, $mh);	
+		}
 	}
 
     public function parse_results($response) {
         $results = array();
 
-		// Merge all results together
         foreach($this->requests as $request) {
 			if($request->request_successful()) {
 				$engine_result = $request->get_results();
@@ -41,26 +51,28 @@ class ImageSearch extends EngineRequest {
 						// Merge duplicates and apply relevance scoring
 						foreach($engine_result['search'] as $result) {
 							if(array_key_exists('search', $results)) {
-								$result_urls = array_column($results['search'], "direct_link", "id");
-								$found_key = array_search($result['direct_link'], $result_urls);
+								$result_urls = array_column($results['search'], "webpage_url", "id");
+								$found_id = array_search($result['webpage_url'], $result_urls);
 							} else {
-								$found_key = false;
+								$found_id = false;
 							}
 
-							if($found_key !== false) {
+							if($found_id !== false) {
 								// Duplicate result from another source, merge and rank accordingly
-								$results['search'][$found_key]['goosle_rank'] += $result['engine_rank'];
+								$results['search'][$found_id]['goosle_rank'] += $result['engine_rank'];
 							} else {
 								// First find, rank and add to results
 								$query_terms = explode(" ", preg_replace("/[^a-z0-9 ]+/", "", strtolower($request->query)));
 								$match_rank = match_count($result['alt'], $query_terms);
+//								$match_rank += match_count($result['url'], $query_terms);
+								$match_rank += match_count($result['webpage_url'], $query_terms);
 
 								$result['goosle_rank'] = $result['engine_rank'] + $match_rank;
 
 								$results['search'][$result['id']] = $result;
 							}
 	
-							unset($result, $result_urls, $found_key, $social_media_multiplier, $goosle_rank, $match_rank);
+							unset($result, $result_urls, $found_id, $social_media_multiplier, $goosle_rank, $match_rank);
 						}
 					}
 				}
@@ -115,35 +127,38 @@ echo '</pre>';
 			echo "<li class=\"meta\">Fetched ".$number_of_results." results in ".$results['time']." seconds.</li>";
 
 			// Format sources
-	        search_sources($results['sources']);
+			echo "<li class=\"sources\">Includes ".search_sources($results['sources'])."</li>";
 
 			// Did you mean/Search suggestion
-			search_suggestion($opts, $results);
+			if(array_key_exists("did_you_mean", $results)) {
+				echo "<li class=\"suggestion\">Did you mean <a href=\"./results.php?q=".urlencode($results['did_you_mean'])."&t=".$opts->type."&a=".$opts->hash."\">".$results['did_you_mean']."</a>?".search_suggestion($opts, $results)."</li>";
+			}
 
 			echo "</ol>";
 
 			// Search results
-			echo "<div class=\"image-wrapper\">";
-			echo "<ol class=\"image-grid\">";
+			echo "<div class=\"image-grid\">";
+			echo "<ol>";
 	
 	        foreach($results['search'] as $result) {
 				// Extra data
 				$meta = $links = array();
 				if(!empty($result['height']) && !empty($result['width'])) $meta[] = $result['width']."&times;".$result['height'];
-				if(!empty($result['filesize'])) $meta[] = $result['filesize'];
+				if(!empty($result['filesize'])) $meta[] = human_filesize($result['filesize']);
 
-				$links[] = "<a href=\"".$result['url']."\" target=\"_blank\">Website</a>";
-				if(!empty($result['direct_link'])) $links[] = "<a href=\"".$result['direct_link']."\" target=\"_blank\">Image</a>";
+				$links[] = "<a href=\"".$result['webpage_url']."\" target=\"_blank\">Website</a>";
+				if(!empty($result['image_full'])) $links[] = "<a href=\"".$result['image_full']."\" target=\"_blank\">Image</a>";
 
 				// Put result together
-				echo "<li class=\"result\"><div class=\"image-box\">";
-				echo "<a href=\"".$result['url']."\" target=\"_blank\" title=\"".$result['alt']."\"><img src=\"".$result['image']."\" alt=\"".$result['alt']."\" /></a>";
+				echo "<li class=\"result image rs-".$result['goosle_rank']." id-".$result['id']."\"><div class=\"image-box\">";
+				echo "<a href=\"".$result['webpage_url']."\" target=\"_blank\" title=\"".$result['alt']."\"><img src=\"".$result['image_thumb']."\" alt=\"".$result['alt']."\" /></a>";
 				echo "</div><span>".implode(" - ", $meta)."<br />".implode(" - ", $links)."</span>";
 				echo "</li>";
 	        }
 
 	        echo "</ol>";
 	        echo "</div>";
+			echo "<center><small>Goosle does not store or distribute image files.</small></center>";
 		}
 
 		// No results found

+ 9 - 20
engines/search-magnet.php

@@ -30,11 +30,6 @@ class MagnetSearch extends EngineRequest {
 			$this->requests[] = new YTSRequest($opts, $mh);
 		}
 
-		if($opts->enable_magnetdl == "on") {
-			require ABSPATH."engines/magnet/magnetdl.php";
-			$this->requests[] = new MagnetDLRequest($opts, $mh);
-		}
-
 		if($opts->enable_nyaa == "on") {
 			require ABSPATH."engines/magnet/nyaa.php";
 			$this->requests[] = new NyaaRequest($opts, $mh);
@@ -46,11 +41,6 @@ class MagnetSearch extends EngineRequest {
 				$this->requests[] = new EZTVRequest($opts, $mh);
 			}
 		}
-
-		if($opts->enable_l33tx == "on") {
-			require ABSPATH."engines/magnet/1337x.php";
-			$this->requests[] = new LeetxRequest($opts, $mh);
-		}
 		
 		// Special search
 		$this->special_request = special_magnet_request($opts, $mh);
@@ -81,7 +71,7 @@ class MagnetSearch extends EngineRequest {
 
 							$results_temp[$found_id]['combo_source'][] = $result['source'];
 						} else {
-							// First find - rank and add to results
+							// First find - rank (by combo_seeders instead of internal ranking) and add to results
 							$result['combo_seeders'] = intval($result['seeders']);
 							$result['combo_leechers'] = intval($result['leechers']);
 							$result['combo_source'][] = $result['source'];
@@ -121,8 +111,7 @@ class MagnetSearch extends EngineRequest {
 			$results['search'] = array_slice($results_temp, 0, 50);
 
 			// Count results per source
-			$sources = array_count_values(array_column($results['search'], 'source'));
-			if(count($sources) > 0) $results['sources'] = $sources;
+			$results['sources'] = array_count_values(array_column($results['search'], 'source'));
 
 			unset($sources);
 		} else {
@@ -150,13 +139,13 @@ echo '</pre>';
 
 		// Special results
 		if(array_key_exists("special", $results)) {
-			echo "<div class=\"magnet-wrapper\">";
+			echo "<div class=\"magnet-grid\">";
 			if(array_key_exists("yts", $results['special'])) {
 				if($opts->yts_highlight == "date_added") echo "<h2>Latest releases from YTS</h2>";
 				if($opts->yts_highlight == "rating") echo "<h2>Highest rated on YTS</h2>";
 				if($opts->yts_highlight == "download_count") echo "<h2>Most downloaded from YTS</h2>";
 				if($opts->yts_highlight == "seeds") echo "<h2>Most seeded on YTS</h2>";
-				echo "<ol class=\"magnet-grid\">";
+				echo "<ol>";
 		
 				foreach($results['special']['yts'] as $highlight) {
 					echo "<li class=\"result yts\">";
@@ -181,7 +170,7 @@ echo '</pre>';
 
 			if(array_key_exists("eztv", $results['special'])) {
 				echo "<h2>Latest releases from EZTV</h2>";
-				echo "<ol class=\"magnet-grid\">";
+				echo "<ol>";
 		
 				foreach($results['special']['eztv'] as $highlight) {
 					echo "<li class=\"result eztv\">";
@@ -213,7 +202,7 @@ echo '</pre>';
 			echo "<li class=\"meta\">Fetched ".$number_of_results." results in ".$results['time']." seconds.</li>";
 
 			// Format sources
-	        search_sources($results['sources']);
+			echo "<li class=\"sources\">Includes ".search_sources($results['sources'])."</li>";
 
 			// Search results
 			foreach($results['search'] as $result) {
@@ -230,11 +219,11 @@ echo '</pre>';
 				$url = (array_key_exists('url', $result)) ? " - <a href=\"".$result['url']."\" target=\"_blank\" title=\"Careful - Site may contain intrusive popup ads and malware!\">torrent page</a>" : "";
 	
 				// Put result together
-				echo "<li class=\"result\"><article>";
+				echo "<li class=\"result magnet id-".$result['id']."\">";
 				echo "<div class=\"title\"><a href=\"".$result['magnet']."\"><h2>".stripslashes($result['name'])."</h2></a></div>";
 				echo "<div class=\"description\"><strong>Seeds:</strong> <span class=\"seeders\">".$result['combo_seeders']."</span> - <strong>Peers:</strong> <span class=\"leechers\">".$result['combo_leechers']."</span> - <strong>Size:</strong> ".$result['size']."<br />".implode(" - ", $meta)."</div>";
-				if($opts->show_search_source == "on") echo "<div class=\"description\"><strong>Found on:</strong> ".replace_last_comma(implode(", ", $result['combo_source'])).$url."</div>";
-				echo "</article></li>";
+				if($opts->show_search_source == "on") echo "<div class=\"description\"><strong>Found on:</strong> ".replace_last_comma(implode(", ", $result['combo_source'])).'.'.$url."</div>";
+				echo "</li>";
 
 				unset($result, $meta, $url);
 			}

+ 19 - 23
engines/search.php

@@ -25,20 +25,15 @@ class Search extends EngineRequest {
 			$this->requests[] = new GoogleRequest($opts, $mh);	
 		}
 
-		if($opts->enable_reddit == "on") {
-			require ABSPATH."engines/search/reddit.php";
-			$this->requests[] = new RedditRequest($opts, $mh);	
+		if($opts->enable_qwantnews == "on") {
+			require ABSPATH."engines/search/qwantnews.php";
+			$this->requests[] = new QwantNewsRequest($opts, $mh);	
 		}
-
+		
 		if($opts->enable_wikipedia == "on") {
 			require ABSPATH."engines/search/wikipedia.php";
 			$this->requests[] = new WikiRequest($opts, $mh);	
 		}
-
-		if($opts->enable_ecosia == "on") {
-			require ABSPATH."engines/search/ecosia.php";
-			$this->requests[] = new EcosiaRequest($opts, $mh);	
-		}
 		
 		// Special search
 		$this->special_request = special_search_request($opts);
@@ -65,18 +60,18 @@ class Search extends EngineRequest {
 						foreach($engine_result['search'] as $result) {
 							if(array_key_exists('search', $results)) {
 								$result_urls = array_column($results['search'], "url", "id");
-								$found_key = array_search($result['url'], $result_urls);
+								$found_id = array_search($result['url'], $result_urls);
 							} else {
-								$found_key = false;
+								$found_id = false;
 							}
 
 							$social_media_multiplier = (is_social_media($result['url'])) ? ($request->opts->social_media_relevance / 10) : 1;
 							$goosle_rank = floor($result['engine_rank'] * floatval($social_media_multiplier));
 	
-							if($found_key !== false) {
+							if($found_id !== false) {
 								// Duplicate result from another source, merge and rank accordingly
-								$results['search'][$found_key]['goosle_rank'] += $goosle_rank;
-								$results['search'][$found_key]['combo_source'][] = $result['source'];
+								$results['search'][$found_id]['goosle_rank'] += $goosle_rank;
+								$results['search'][$found_id]['combo_source'][] = $result['source'];
 							} else {
 								// First find, rank and add to results
 								$query_terms = explode(" ", preg_replace("/[^a-z0-9 ]+/", "", strtolower($request->query)));
@@ -90,7 +85,7 @@ class Search extends EngineRequest {
 								$results['search'][$result['id']] = $result;
 							}
 	
-							unset($result, $result_urls, $found_key, $social_media_multiplier, $goosle_rank, $match_rank);
+							unset($result, $result_urls, $found_id, $social_media_multiplier, $goosle_rank, $match_rank);
 						}
 					}
 				}
@@ -124,8 +119,7 @@ class Search extends EngineRequest {
 			array_multisort($keys, SORT_DESC, $results['search']);
 
 			// Count results per source
-			$sources = array_count_values(array_column($results['search'], 'source'));
-			if(count($sources) > 0) $results['sources'] = $sources;
+			$results['sources'] = array_count_values(array_column($results['search'], 'source'));
 			
 			unset($keys);
 		} else {
@@ -157,20 +151,22 @@ echo '</pre>';
 			echo "<li class=\"meta\">Fetched ".$number_of_results." results in ".$results['time']." seconds.</li>";
 
 			// Format sources
-	        search_sources($results['sources']);
+			echo "<li class=\"sources\">Includes ".search_sources($results['sources'])."</li>";
 
 			// Did you mean/Search suggestion
-			search_suggestion($opts, $results);
+			if(array_key_exists("did_you_mean", $results)) {
+				echo "<li class=\"suggestion\">Did you mean <a href=\"./results.php?q=".urlencode($results['did_you_mean'])."&t=".$opts->type."&a=".$opts->hash."\">".$results['did_you_mean']."</a>?".search_suggestion($opts, $results)."</li>";
+			}
 
 			// Special results
 			if(array_key_exists("special", $results)) {
-				echo "<li class=\"special-result\"><article>";
+				echo "<li class=\"result-special\">";
 				echo "<div class=\"title\"><h2>".$results['special']['title']."</h2></div>";
 				echo "<div class=\"text\">".$results['special']['text']."</div>";
 				if(array_key_exists("source", $results['special'])) {
 					echo "<div class=\"source\"><a href=\"".$results['special']['source']."\" target=\"_blank\">".$results['special']['source']."</a></div>";
 				}
-				echo "</article></li>";
+				echo "</li>";
 			}
 		
 			// Search results
@@ -181,7 +177,7 @@ echo '</pre>';
 					}
 				}
 
-				echo "<li class=\"result rs-".$result['goosle_rank']." id-".$result['id']."\"><article>";
+				echo "<li class=\"result web rs-".$result['goosle_rank']." id-".$result['id']."\">";
 				echo "<div class=\"url\"><a href=\"".$result['url']."\" target=\"_blank\">".get_formatted_url($result['url'])."</a></div>";
 				echo "<div class=\"title\"><a href=\"".$result['url']."\" target=\"_blank\"><h2>".$result['title']."</h2></a></div>";
 				echo "<div class=\"description\">".$result['description']."</div>";
@@ -193,7 +189,7 @@ echo '</pre>';
 					echo "</div>";
 				}
 
-				echo "</article></li>";
+				echo "</li>";
 	        }
 
 			echo "</ol>";

+ 17 - 7
engines/search/duckduckgo.php

@@ -49,6 +49,12 @@ class DuckDuckGoRequest extends EngineRequest {
         return $url;
     }
 
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'text/html, application/xhtml+xml, application/xml;q=0.8, */*;q=0.7',
+		);
+	}
+
     public function parse_results($response) {
 		$results = array();
 		$xpath = get_xpath($response);
@@ -70,19 +76,23 @@ class DuckDuckGoRequest extends EngineRequest {
 		$rank = $results['amount'] = count($scrape);
 		foreach($scrape as $result) {
 			$url = $xpath->evaluate(".//h2[@class='result__title']//a/@href", $result)[0];
-			if($url == null) continue;
+			if(is_null($url)) continue;
 
 			$title = $xpath->evaluate(".//h2[@class='result__title']", $result)[0];
-			if($title == null) continue;
+			if(is_null($title)) continue;
 			
 			$description = $xpath->evaluate(".//a[@class='result__snippet']", $result)[0];
-			$description = ($description == null) ? "No description was provided for this site." : htmlspecialchars(trim($description->textContent));
+			$description = (is_null($description)) ? "No description was provided for this site." : sanitize($description->textContent);
 
-			$url = htmlspecialchars(trim($url->textContent));
-			$title = htmlspecialchars(trim($title->textContent));
-			$id = uniqid(rand(0, 9999));
+			$url = sanitize($url->textContent);
+			$title = sanitize($title->textContent);
 			
-			$results['search'][] = array ("id" => $id, "source" => "DuckDuckGo", "title" => $title, "url" => $url, "description" => $description, "engine_rank" => $rank);
+			// filter duplicate urls/results
+            if(!empty($results['search'])) {
+                if(in_array($url, array_column($results['search'], "url"))) continue;
+            }
+
+			$results['search'][] = array("id" => uniqid(rand(0, 9999)), "source" => "DuckDuckGo", "title" => $title, "url" => $url, "description" => $description, "engine_rank" => $rank);
 			$rank -= 1;
 		}
 		unset($response, $xpath, $scrape, $rank);

+ 0 - 51
engines/search/ecosia.php

@@ -1,51 +0,0 @@
-<?php
-/* ------------------------------------------------------------------------------------
-*  Goosle - A meta search engine for private and fast internet fun.
-*
-*  COPYRIGHT NOTICE
-*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
-*
-*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
-*  By using this code you agree to indemnify Arnan de Gans from any 
-*  liability that might arise from its use.
------------------------------------------------------------------------------------- */
-class EcosiaRequest extends EngineRequest {
-    public function get_request_url() {
-		$args = array("q" => $this->query, "method" => "index", "addon" => "opensearch");
-        $url = "https://www.ecosia.org/search/?".http_build_query($args);
-
-        return $url;
-    }
-
-    public function parse_results($response) {
-		$results = array();
-		$xpath = get_xpath($response);
-
-		if(!$xpath) return $results;
-
-		// Scrape the results
-		$scrape = $xpath->query("//article[@class='result web-result mainline__result']");
-		$rank = $results['amount'] = count($scrape);
-		foreach($scrape as $result) {
-			$url = $xpath->evaluate(".//a[@class='result__link']/@href", $result)[0];
-			if($url == null) continue;
-
-			$title = $xpath->evaluate(".//h2[@class='result-title__heading']", $result)[0];
-			if($title == null) continue;
-			
-			$description = $xpath->evaluate(".//p[@class='web-result__description']", $result)[0];
-			$description = ($description == null) ? "No description was provided for this site." : htmlspecialchars(trim($description->textContent));
-
-			$url = htmlspecialchars(trim($url->textContent));
-			$title = htmlspecialchars(trim($title->textContent));
-			$id = uniqid(rand(0, 9999));
-			
-			$results['search'][] = array ("id" => $id, "source" => "Ecosia", "title" => $title, "url" => $url, "description" => $description, "engine_rank" => $rank);
-			$rank -= 1;
-		}
-		unset($response, $xpath, $scrape, $rank);
-
-		return $results;
-    }
-}
-?>

+ 17 - 7
engines/search/google.php

@@ -44,6 +44,12 @@ class GoogleRequest extends EngineRequest {
         return $url;
     }
 
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'text/html, application/xhtml+xml, application/xml;q=0.8, */*;q=0.7',
+		);
+	}
+
     public function parse_results($response) {
 		$results = array();
         $xpath = get_xpath($response);
@@ -66,19 +72,23 @@ class GoogleRequest extends EngineRequest {
 		$rank = $results['amount'] = count($scrape);
         foreach($scrape as $result) {
 			$url = $xpath->evaluate(".//div[@class='yuRUbf']//a/@href", $result)[0];
-			if($url == null) continue;
+			if(is_null($url)) continue;
 			
 			$title = $xpath->evaluate(".//h3", $result)[0];
-			if($title == null) continue;
+			if(is_null($title)) continue;
 			
 			$description = $xpath->evaluate(".//div[contains(@class, 'VwiC3b')]", $result)[0];
-			$description = ($description == null) ? "No description was provided for this site." : htmlspecialchars(trim($description->textContent));
+			$description = (is_null($description)) ? "No description was provided for this site." : sanitize($description->textContent);
 
-			$url = htmlspecialchars(trim($url->textContent));
-			$title = htmlspecialchars(trim($title->textContent));
-			$id = uniqid(rand(0, 9999));
+			$url = sanitize($url->textContent);
+			$title = sanitize($title->textContent);
 			
-			$results['search'][] = array("id" => $id, "source" => "Google", "title" => $title, "url" => $url, "description" => $description, "engine_rank" => $rank);
+			// filter duplicate urls/results
+            if(!empty($results['search'])) {
+                if(in_array($url, array_column($results['search'], "url"))) continue;
+            }
+
+			$results['search'][] = array("id" => uniqid(rand(0, 9999)), "source" => "Google", "title" => $title, "url" => $url, "description" => $description, "engine_rank" => $rank);
 			$rank -= 1;
         }
 		unset($response, $xpath, $scrape, $rank);

+ 84 - 0
engines/search/qwantnews.php

@@ -0,0 +1,84 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class QwantNewsRequest extends EngineRequest {
+    public function get_request_url() {
+		// Split the query
+	    $query_terms = explode(" ", strtolower($this->query));
+
+		// Safe search override
+		$safe = "1"; // Moderate results
+		if(strpos($query_terms[0], "safe") !== false) {
+			$switch = explode(":", $query_terms[0]);
+
+			if(!is_numeric($switch[1])) {
+				$safe = (strtolower($switch[1]) == "off") ? "0" : "2";
+				$this->query = implode(" ", array_slice($query_terms, 1));
+			}
+		}
+
+		$language = (strlen($this->opts->qwant_language) > 0 && strlen($this->opts->qwant_language < 6)) ? $this->opts->qwant_language : "en_gb";
+
+		// q = query
+		// t = Type of search, Images
+		// locale = In which language should the search be done
+		// source = Where to get the news from (All)
+		// freshness = How old may the article be? (1 month)
+		// device = What kind of device are we searching from?
+		// safesearch = Safe search filter (0 = off, 1 = normal, 2 = strict)
+
+		$args = array("q" => $this->query, "t" => 'news', 'locale' => $language, 'source' => 'all', 'freshness' => 'month', 'device' => 'desktop', 'safesearch' => $safe);
+        $url = "https://api.qwant.com/v3/search/news?".http_build_query($args);
+
+        unset($query_terms, $switch, $safe, $language, $args);
+
+        return $url;
+    }
+
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'application/json, */*;q=0.8',
+			'Accept-Language' => null,
+			'Accept-Encoding' => null,
+			'Connection' => null,
+			'Sec-Fetch-Dest' => null,
+			'Sec-Fetch-Mode' => null,
+			'Sec-Fetch-Site' => null
+		);
+	}
+
+	public function parse_results($response) {
+		$results = array();
+		$json_response = json_decode($response, true);
+
+		// No response
+		if(empty($json_response)) return $results;
+
+		// Nothing found
+        if($json_response["status"] != "success") return $results;
+
+		// Set base rank and result amound
+		$rank = $results['amount'] = $json_response["data"]["result"]["total"];
+
+		foreach ($json_response["data"]["result"]["items"] as $result) {
+			$title = sanitize($result['title']);
+			$url = sanitize($result['url']);
+			$description = date('M d, Y H:i', sanitize($result['date']))." &sdot; ".sanitize($result['desc']);
+
+			$results['search'][] = array ("id" => uniqid(rand(0, 9999)), "source" => "Qwant News", "title" => $title, "url" => $url, "description" => $description, "engine_rank" => $rank);
+			$rank -= 1;
+		}
+		unset($response, $json_response, $rank);
+		
+		return $results;
+	}
+}
+?>

+ 0 - 60
engines/search/reddit.php

@@ -1,60 +0,0 @@
-<?php
-/* ------------------------------------------------------------------------------------
-*  Goosle - A meta search engine for private and fast internet fun.
-*
-*  COPYRIGHT NOTICE
-*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
-*
-*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
-*  By using this code you agree to indemnify Arnan de Gans from any 
-*  liability that might arise from its use.
------------------------------------------------------------------------------------- */
-class RedditRequest extends EngineRequest {
-    public function get_request_url() {
-		$args = array("q" => $this->query, "type" => "link", "sort" => "top", "t" => "year");
-        $url = "https://www.reddit.com/search.json?".http_build_query($args);
-
-        return $url;
-    }
-
-	public function parse_results($response) {
-		$results = array();
-		$json_response = json_decode($response, true);
-		
-		if(empty($json_response)) return $results;
-		
-		// No results
-		if($json_response['data']['dist'] == 0) return $results;
-		
-		$rank = $results['amount'] = count($json_response['data']['children']);
-		foreach($json_response['data']['children'] as $result) {
-			$result = $result['data'];
-
-			$nsfw = sanitize($result['over_18']);
-			
-			// Ignore NSFW results
-			if($this->opts->show_reddit_nsfw == "off" && $nsfw === 1) continue;
-			
-			$title = trim($result['title']);
-			$url = "https://www.reddit.com".sanitize($result['permalink']);
-			
-			$postdate = date('M d, Y', sanitize($result['created']));
-			$author = sanitize($result['author']);
-			$reddit = sanitize($result['subreddit']);
-			$votes = sanitize($result['score']);
-			$comments = sanitize($result['num_comments']);
-			$nsfw = ($nsfw === 1) ? "<span style=\"color:#cc0033;\">[NSFW 18+] Caution, this result contains mature content!</span><br />" : "";
-			
-			$description = $nsfw."In <a href=\"https://www.reddit.com/r/".$reddit."\" target=\"_blank\">r/".$reddit."</a> &sdot; ".$postdate." &sdot; <a href=\"https://www.reddit.com/user/".$author."\" target=\"_blank\">".$author."</a> &sdot; ".$votes." votes &sdot; ".$comments." comments";
-
-			$id = uniqid(rand(0, 9999));
-		
-			$results['search'][] = array ("id" => $id, "source" => "Reddit", "title" => $title, "url" => $url, "description" => $description, "engine_rank" => $rank);
-			$rank -= 1;
-		}
-		unset($response, $json_response, $rank);
-		
-		return $results;
-	}
-}
-?>

+ 16 - 5
engines/search/wikipedia.php

@@ -17,6 +17,18 @@ class WikiRequest extends EngineRequest {
         return $url;
     }
 
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'application/json, */*;q=0.8',
+			'Accept-Language' => null,
+			'Accept-Encoding' => null,
+			'Connection' => null,
+			'Sec-Fetch-Dest' => null,
+			'Sec-Fetch-Mode' => null,
+			'Sec-Fetch-Site' => null
+		);
+	}
+
 	public function parse_results($response) {
 		$results = array();
 		$json_response = json_decode($response, true);
@@ -28,12 +40,11 @@ class WikiRequest extends EngineRequest {
 		
 		$rank = $results['amount'] = count($json_response['query']['search']);
 		foreach($json_response['query']['search'] as $result) {
-			$title = htmlspecialchars(trim($result['title']));
-			$url = "https://".$this->opts->wikipedia_language.".wikipedia.org/wiki/".htmlspecialchars(str_replace(" ", "_", trim($result['title'])));
-			$description = htmlspecialchars(strip_tags(trim($result['snippet'])));
-			$id = uniqid(rand(0, 9999));
+			$title = sanitize($result['title']);
+			$url = "https://".$this->opts->wikipedia_language.".wikipedia.org/wiki/".sanitize(str_replace(" ", "_", $result['title']));
+			$description = sanitize(strip_tags($result['snippet']));
 		
-			$results['search'][] = array ("id" => $id, "source" => "Wikipedia", "title" => $title, "url" => $url, "description" => $description, "engine_rank" => $rank);
+			$results['search'][] = array ("id" => uniqid(rand(0, 9999)), "source" => "Wikipedia", "title" => $title, "url" => $url, "description" => $description, "engine_rank" => $rank);
 			$rank -= 1;
 		}
 		unset($response, $json_response, $rank);

+ 12 - 0
engines/special/currency.php

@@ -14,6 +14,18 @@ class CurrencyRequest extends EngineRequest {
         return "https://cdn.moneyconvert.net/api/latest.json";
     }
     
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'application/json, */*;q=0.8',
+			'Accept-Language' => null,
+			'Accept-Encoding' => null,
+			'Connection' => null,
+			'Sec-Fetch-Dest' => null,
+			'Sec-Fetch-Mode' => null,
+			'Sec-Fetch-Site' => null
+		);
+	}
+
     public function parse_results($response) {
         $json_response = json_decode($response, true);
 

+ 12 - 0
engines/special/definition.php

@@ -19,6 +19,18 @@ class DefinitionRequest extends EngineRequest {
 		return "https://api.dictionaryapi.dev/api/v2/entries/en/".$query_terms[1];
 	}
 
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'application/json, */*;q=0.8',
+			'Accept-Language' => null,
+			'Accept-Encoding' => null,
+			'Connection' => null,
+			'Sec-Fetch-Dest' => null,
+			'Sec-Fetch-Mode' => null,
+			'Sec-Fetch-Site' => null
+		);
+	}
+
 	public function parse_results($response) {
 		$json_response = json_decode($response, true);
 

+ 12 - 0
engines/special/eztv_highlights.php

@@ -15,6 +15,18 @@ class eztvhighlights extends EngineRequest {
         return $url;
     }
     
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'application/json, */*;q=0.8',
+			'Accept-Language' => null,
+			'Accept-Encoding' => null,
+			'Connection' => null,
+			'Sec-Fetch-Dest' => null,
+			'Sec-Fetch-Mode' => null,
+			'Sec-Fetch-Site' => null
+		);
+	}
+
     public function parse_results($response) {
 		$results = array();
 		$json_response = json_decode($response, true);

+ 6 - 0
engines/special/php.php

@@ -16,6 +16,12 @@ class PHPnetRequest extends EngineRequest {
 		return "https://www.php.net/manual/function.".urlencode($this->query);
 	}
 	
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'text/html, application/xhtml+xml, application/xml;q=0.8, */*;q=0.7',
+		);
+	}
+
 	public function parse_results($response) {
         $results = array();
         $xpath = get_xpath($response);

+ 14 - 2
engines/special/yts_highlights.php

@@ -11,13 +11,25 @@
 ------------------------------------------------------------------------------------ */
 class ytshighlights extends EngineRequest {
     public function get_request_url() {
-        $url = "https://yts.mx/api/v2/list_movies.json?".http_build_query(array("limit" => "20", "sort_by" => $this->opts->yts_highlight));
+        $url = "https://yts.mx/api/v2/list_movies.json?".http_build_query(array("limit" => "16", "sort_by" => $this->opts->yts_highlight));
         return $url;
     }
     
+    public function get_request_headers() {
+		return array(
+			'Accept' => 'application/json, */*;q=0.8',
+			'Accept-Language' => null,
+			'Accept-Encoding' => null,
+			'Connection' => null,
+			'Sec-Fetch-Dest' => null,
+			'Sec-Fetch-Mode' => null,
+			'Sec-Fetch-Site' => null
+		);
+	}
+
     public function parse_results($response) {
 		$results = array();
-		$response = curl_multi_getcontent($this->ch);
+//		$response = curl_multi_getcontent($this->ch);
 		$json_response = json_decode($response, true);
 		
 		// No response

+ 59 - 0
functions/oauth-functions.php

@@ -0,0 +1,59 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+
+/*--------------------------------------
+// Curl requests for oAUTH
+--------------------------------------*/
+function oath_curl_request($url, $user_agent, $method, $header, $post) {
+	$ch = curl_init();
+	
+	curl_setopt($ch, CURLOPT_URL, $url);
+	if($method == "post") {
+		curl_setopt($ch, CURLOPT_POST, 1);
+		curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
+	} else {
+		curl_setopt($ch, CURLOPT_HTTPGET, 1);
+	}
+	curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
+	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+	curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
+	curl_setopt($ch, CURLOPT_ENCODING, "gzip,deflate");
+	curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge(array('Accept: application/json, */*;q=0.8'), $header));
+	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+	curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
+	curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
+	curl_setopt($ch, CURLOPT_TIMEOUT, 3);
+	curl_setopt($ch, CURLOPT_VERBOSE, false);
+	
+	$response = curl_exec($ch);
+	curl_close($ch);
+	
+	return json_decode($response, true);
+}
+
+/*--------------------------------------
+// Store generated tokens
+--------------------------------------*/
+function oath_store_token($token_file, $connect, $token) {
+	if(!is_file($token_file)){
+		// Create token file
+	    file_put_contents($token_file, serialize(array($connect => $token)));
+	} else {
+		// Update token file
+		$tokens = unserialize(file_get_contents($token_file));
+		$tokens[$connect] = $token;
+	    file_put_contents($token_file, serialize($tokens));
+	}
+}		
+
+?>

+ 123 - 0
functions/oauth.php

@@ -0,0 +1,123 @@
+<?php 
+if(!defined('ABSPATH')) define('ABSPATH', $_SERVER['DOCUMENT_ROOT'] . '/');
+
+require ABSPATH."functions/tools.php";
+require ABSPATH."functions/oauth-functions.php";
+
+$opts = load_opts();
+$auth = (isset($_GET['a'])) ? sanitize($_GET['a']) : $opts->user_auth;
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+?>
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <title>Goosle Search oAUTH</title>
+
+	<meta charset="utf-8" />
+	<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
+	<meta name="robots" content="noodp,noydir" />
+    <meta name="referrer" content="no-referrer"/>
+	<meta name="description" content="Get your Goosle on! - The best meta search engine for private and fast internet fun!" />
+
+	<link rel="icon" href="../favicon.ico" />
+	<link rel="apple-touch-icon" href="../apple-touch-icon.png" />
+	<link rel="canonical" href="<?php echo get_base_url($opts->siteurl); ?>/functions/oauth.php" />
+
+    <link rel="stylesheet" type="text/css" href="../assets/css/styles.css"/>
+</head>
+
+<body class="oauthpage">
+<?php
+if(verify_hash($opts, $auth)) {
+
+$connect = (isset($_REQUEST['oa'])) ? sanitize($_REQUEST['oa']) : "";
+// Openverse
+$email = (isset($_REQUEST['oae'])) ? sanitize($_REQUEST['oae']) : "";
+$client_id = (isset($_REQUEST['oaid'])) ? sanitize($_REQUEST['oaid']) : "";
+$client_secret = (isset($_REQUEST['oacs'])) ? sanitize($_REQUEST['oacs']) : "";
+
+if(empty($connect)) {
+?>
+
+<div class="oauth-form">
+	<h1><span class="G">G</span>oosle</h1>
+	<p>Use this page to set up an authorization token for Openverse.<br />
+	Fill in the relevant fields and click the button at the bottom to continue.</p>
+    
+	<form action="oauth.php" method="get" autocomplete="off">
+		<h2>(re)New registration</h2>
+		<p>Email address:<br /><input tabindex="10" type="text" class="field" name="oae" /><br /><small>(Required for verification)</small></p>
+
+		<h2>Recovering a previous registration?</h2>
+		<p>Client ID:<br /><input tabindex="20" type="text" class="field" name="oaid" /></p>
+		<p>Client Secret:<br /><input tabindex="30" type="text" class="field" name="oacs" /></p>
+	
+		<input type="hidden" name="a" value="<?php echo $opts->hash; ?>"/>
+	
+		<div class="oauth-buttons">
+			<button tabindex="100" name="oa" value="openverse" type="submit">Connect to Openverse</button>
+		</div>
+	</form>
+</div>
+
+<?php
+} else {
+	$token_file = ABSPATH.'cache/token.data';
+	
+	if(empty($client_id) AND empty($client_secret) AND !empty($email)) {
+		$registration = oath_curl_request(
+			'https://api.openverse.org/v1/auth_tokens/register/', // Where?
+			$opts->user_agents[0], // Who?
+			'post', // post/get
+			array(), // Additional headers
+			array('name' => 'Goosle Meta Search '.md5(get_base_url($opts->siteurl)), 'description' => 'Goosle Meta Search for '.get_base_url($opts->siteurl), 'email' => $email) // Payload
+		);
+
+		// Site already exists, get new token
+		if(stristr($registration['name'][0], 'this name already exists')) {
+			if(is_file($token_file)) {
+				$tokens = unserialize(file_get_contents($token_file));
+				$registration = $tokens['openverse'];
+			} else {
+				echo "<div class=\"auth-error\">Error - Token file is missing. Please recover your registration with the Client ID and Client Secret.<br /><a href=\"/functions/oauth.php?a=".$opts->hash."\">Try again</a></div>";
+				exit;
+			}
+		}
+	} else {
+		$registration = array('client_id' => $client_id, 'client_secret' => $client_secret);
+	}
+
+	$new_token = oath_curl_request(
+		'https://api.openverse.org/v1/auth_tokens/token/', // Where?
+		$opts->user_agents[0], // Who?
+		'post', // post/get
+		array('Authorization: Bearer'.$registration['client_id']), // Additional headers
+		'grant_type=client_credentials&client_id='.$registration['client_id'].'&client_secret='.$registration['client_secret'] // Payload
+	);
+
+	$new_token['expires_in'] = time() + ($new_token['expires_in'] - 3600);
+
+	oath_store_token($token_file, $connect, array("client_id" => $registration['client_id'], "client_secret" => $registration['client_secret'], "access_token" => $new_token['access_token'], "expires" => $new_token['expires_in']));
+
+	echo "<div class=\"auth-success\">SUCCESS!<br />Goosle is now authorized and you can enable Openverse in your config.php if you haven't already!<br />If this is your first time authorizing with this email address you'll get an email from Openverse in a few moments with a verification link that you need to click.<br /><br />To be able to recover your registration save these values:<br />Used Email Address: ".$email."<br />Client ID: ".$registration['client_id']."<br />Client Secret: ".$registration['client_secret']."<br /><br /><a href=\"/results.php?a=".$opts->hash."&q=goose&t=1\">Continue to Goosle</div>";
+
+	unset($registration, $new_token);
+}
+?>
+
+<?php 
+} else {
+	echo "<div class=\"auth-error\">Goosle</div>";
+} 
+?>
+</body>
+</html>

+ 50 - 52
functions/search_engine.php

@@ -10,7 +10,7 @@
 *  liability that might arise from its use.
 ------------------------------------------------------------------------------------ */
 abstract class EngineRequest {
-    protected $url, $query, $opts, $mh, $ch;
+    protected $query, $ch, $mh, $opts, $url, $headers;
 
 	function __construct($opts, $mh) {
 		$this->query = $opts->query;
@@ -23,11 +23,52 @@ abstract class EngineRequest {
 		if(!$this->url) return;
 		
 		// Skip if there is a cached result (from earlier search)
-		if($this->opts->cache == "on" && has_cached_results($this->opts->cache_type, $this->opts->hash, $this->url, (intval($this->opts->cache_time) * 60))) return;
+		if($this->opts->cache_type !== "off" && has_cached_results($this->opts->cache_type, $this->opts->hash, $this->url, (intval($this->opts->cache_time) * 60))) return;
+
+		// Default headers for the curl request
+		$default_headers = array(
+			'Accept' => 'text/html, application/xhtml+xml, application/json;q=0.9, application/xml;q=0.8, */*;q=0.7',
+			'Accept-Language' => 'en-US,en;q=0.5',
+			'Accept-Encoding' => 'gzip, deflate',
+// 			'Connection' => 'keep-alive',
+			'Upgrade-Insecure-Requests' => '1',
+			'User-Agent' => $this->opts->user_agents[array_rand($this->opts->user_agents)],
+			'Sec-Fetch-Dest' => 'document',
+			'Sec-Fetch-Mode' => 'navigate',
+			'Sec-Fetch-Site' => 'none'
+		);
+
+		// Override or remove headers per curl request
+		$extra_headers = $this->get_request_headers();
+		if(count($extra_headers) > 0) {
+			$headers = array_filter(array_replace($default_headers, $extra_headers));
+
+			foreach($headers as $key => $value) {
+				$this->headers[] = $key.': '.$value;
+			}
+
+			unset($key, $value);
+		} else {
+			$this->headers = $default_headers;
+		}
+
+		unset($default_headers, $extra_headers, $key, $value);
 
 		// Curl
 		$this->ch = curl_init();
-		set_curl_options($this->ch, $this->url, $this->opts->user_agents);
+		
+		curl_setopt($this->ch, CURLOPT_URL, $this->url);
+		curl_setopt($this->ch, CURLOPT_HTTPGET, 1); // Redundant? Probably...
+		curl_setopt($this->ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
+		curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, false);
+		curl_setopt($this->ch, CURLOPT_ENCODING, "gzip,deflate");
+		curl_setopt($this->ch, CURLOPT_HTTPHEADER, $this->headers);
+		curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
+		curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true);
+		curl_setopt($this->ch, CURLOPT_MAXREDIRS, 5);
+		curl_setopt($this->ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
+		curl_setopt($this->ch, CURLOPT_TIMEOUT, 3);
+		curl_setopt($this->ch, CURLOPT_VERBOSE, false);
 
 		if($mh) curl_multi_add_handle($mh, $this->ch);
 	}
@@ -63,7 +104,7 @@ abstract class EngineRequest {
 		$ttl = intval($this->opts->cache_time) * 60;
 
 		// If there is a cached result from an earlier search use that instead
-		if($this->opts->cache == "on" && has_cached_results($this->opts->cache_type, $this->opts->hash, $this->url, $ttl)) {
+		if($this->opts->cache_type !== "off" && has_cached_results($this->opts->cache_type, $this->opts->hash, $this->url, $ttl)) {
 			return fetch_cached_results($this->opts->cache_type, $this->opts->hash, $this->url);
 		}
 
@@ -75,10 +116,12 @@ abstract class EngineRequest {
 		$response = ($this->mh) ? curl_multi_getcontent($this->ch) : curl_exec($this->ch);
 
 		$results = $this->parse_results($response) ?? array();
-	
+
 		// Cache last request
-		if($this->opts->cache == "on") {
+		if($this->opts->cache_type !== "off") {
 			if(!empty($results)) store_cached_results($this->opts->cache_type, $this->opts->hash, $this->url, $results, $ttl);
+
+			// Maybe delete old file cache
 			if($this->opts->cache_type == "file") delete_cached_results($ttl);
 		}
 
@@ -88,52 +131,6 @@ abstract class EngineRequest {
 	public static function print_results($results, $opts) {}
 }
 
-/*--------------------------------------
-// Try to get some search results
---------------------------------------*/
-function fetch_search_results($opts) {
-    $start_time = microtime(true);
-
-	echo "<section class=\"main-column\">";
-
-	if(!empty($opts->query)) {
-		// Curl
-	    $mh = curl_multi_init();
-
-		// Load search script
-	    if($opts->type == 0) {
-	        require ABSPATH."engines/search.php";
-	        $search = new Search($opts, $mh);
-		} else if($opts->type == 1) {
-		    require ABSPATH."engines/search-image.php";
-	        $search = new ImageSearch($opts, $mh);
-		} else if($opts->type == 9) {
-		    require ABSPATH."engines/search-magnet.php";
-	        $search = new MagnetSearch($opts, $mh);
-	    }
-	
-	    $running = null;
-	
-	    do {
-	        curl_multi_exec($mh, $running);
-	    } while ($running);
-	
-	    $results = $search->get_results();
-
-		curl_multi_close($mh);
-	
-		// Add elapsed time to results
-		$results['time'] = number_format(microtime(true) - $start_time, 5, '.', '');
-	
-		// Echoes results and special searches
-	    $search->print_results($results, $opts);
-	} else {
-		echo "<div class=\"warning\">Search query can not be empty!<br />Not sure what went wrong? Learn more about <a href=\"./help.php?a=".$opts->hash."\">how to use Goosle</a>.</div>";
-    }
-
-	echo "</section>";
-}
-
 /*--------------------------------------
 // Process special searches
 --------------------------------------*/
@@ -176,6 +173,7 @@ function special_magnet_request($opts, $mh) {
         $special_request['yts'] = new ytshighlights($opts, $mh);
 	}
 
+	// Latest additions to eztv
 	if($opts->special['eztv'] == "on") {
         require ABSPATH."engines/special/eztv_highlights.php";
         $special_request['eztv'] = new eztvhighlights($opts, $mh);

+ 49 - 60
functions/tools.php

@@ -31,8 +31,8 @@ function load_opts() {
 	$opts->user_auth = (isset($_REQUEST['a'])) ? sanitize($_REQUEST['a']) : "";
 	
 	// Force a few defaults and safeguards
-	if($opts->cache_type == "file" && !is_dir(ABSPATH.'cache/')) $opts->cache = "off";
-	if($opts->cache_type == "apcu" && !function_exists("apcu_exists")) $opts->cache = "off";
+	if($opts->cache_type == "file" && !is_dir(ABSPATH.'cache/')) $opts->cache_type = "off";
+	if($opts->cache_type == "apcu" && !function_exists("apcu_exists")) $opts->cache_type = "off";
 	if($opts->enable_image_search == "off" && $opts->type == 1) $opts->type = 0;
 	if($opts->enable_magnet_search == "off" && $opts->type == 9) $opts->type = 0;
 	if(!is_numeric($opts->cache_time) || ($opts->cache_time > 720 || $opts->cache_time < 1)) $opts->cache_time = 30;
@@ -44,34 +44,6 @@ function load_opts() {
 	return $opts;
 }
 
-/*--------------------------------------
-// Set curl options
---------------------------------------*/
-function set_curl_options($curl, $url, $user_agents) {
-	curl_setopt($curl, CURLOPT_URL, $url);
-	curl_setopt($curl, CURLOPT_HTTPGET, 1); // Redundant? Probably...
-	curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
-	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
-	curl_setopt($curl, CURLOPT_USERAGENT, $user_agents[array_rand($user_agents)]);
-	curl_setopt($curl, CURLOPT_ENCODING, "gzip,deflate");
-	curl_setopt($curl, CURLOPT_HTTPHEADER, array(
-	    'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
-	    'Accept-Language: en-US,en;q=0.5',
-	    'Accept-Encoding: gzip, deflate',
-	    'Connection: keep-alive',
-	    'Upgrade-Insecure-Requests: 1',
-	    'Sec-Fetch-Dest: document',
-		'Sec-Fetch-Mode: navigate',
-		'Sec-Fetch-Site: none'
-	));
-	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
-	curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
-	curl_setopt($curl, CURLOPT_MAXREDIRS, 5);
-	curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
-	curl_setopt($curl, CURLOPT_TIMEOUT, 3);
-	curl_setopt($curl, CURLOPT_VERBOSE, false);
-}
-
 /*--------------------------------------
 // Load pages into a DOM
 --------------------------------------*/
@@ -97,6 +69,16 @@ function get_formatted_url($url) {
 	return $formatted_url;
 }
 
+/*--------------------------------------
+// Get websites url/page
+--------------------------------------*/
+function get_base_url($siteurl) {
+	// Figure out server protocol
+	$protocol = empty($_SERVER['HTTPS']) ? 'http' : 'https';	
+	
+	return $protocol.'://'.$siteurl;
+}
+
 /*--------------------------------------
 // Result Caching
 --------------------------------------*/
@@ -106,7 +88,7 @@ function has_cached_results($cache_type, $hash, $url, $ttl) {
 	}
 
 	if($cache_type == "file") {
-		$cache_file = ABSPATH.'cache/'.md5("$hash:$url").'.data';
+		$cache_file = ABSPATH.'cache/'.md5("$hash:$url").'.result';
 		if(is_file($cache_file)) {
 			if(filemtime($cache_file) >= (time() - $ttl)) {
 				return true;
@@ -123,7 +105,7 @@ function store_cached_results($cache_type, $hash, $url, $results, $ttl) {
 	}
 
 	if($cache_type == "file") {
-		$cache_file = ABSPATH.'cache/'.md5("$hash:$url").'.data';
+		$cache_file = ABSPATH.'cache/'.md5("$hash:$url").'.result';
 		file_put_contents($cache_file, serialize($results));
 	}
 }
@@ -134,7 +116,7 @@ function fetch_cached_results($cache_type, $hash, $url) {
 	}
 
 	if($cache_type == "file") {
-		$cache_file = ABSPATH.'cache/'.md5("$hash:$url").'.data';
+		$cache_file = ABSPATH.'cache/'.md5("$hash:$url").'.result';
 		if(is_file($cache_file)) {
 			return unserialize(file_get_contents($cache_file));
 		}
@@ -147,7 +129,7 @@ function delete_cached_results($ttl) {
 	$folder = opendir(ABSPATH.'cache/');	
 	while($file_name = readdir($folder)) {
 		$extension = pathinfo($file_name, PATHINFO_EXTENSION);
-		if($file_name == "." OR $file_name == ".." OR $extension != "data") continue; 
+		if($file_name == "." OR $file_name == ".." OR $extension != "result") continue; 
 	
 		if(is_file($folder.$file_name)) {
 			if(filemtime($folder.$file_name) < (time() - $ttl)) {
@@ -230,6 +212,7 @@ function is_social_media($string) {
 	
 	// Borrowed from https://github.com/lorey/social-media-profiles-regexs
 	if(preg_match("/(?:https?:)?\/\/(?:www\.)?(?:facebook|fb)\.com\/(?P<profile>(?![A-z]+\.php)(?!marketplace|gaming|watch|me|messages|help|search|groups)[A-z0-9_\-\.]+)\/?/", $string)
+		|| preg_match("/(?:https?:)?\/\/(?:www\.)facebook.com\/(?:profile.php\?id=)?(?P<id>[0-9]+)/", $string)
 		|| preg_match("/(?:https?:)?\/\/(?:www\.)?(?:instagram\.com|instagr\.am)\/(?P<username>[A-Za-z0-9_](?:(?:[A-Za-z0-9_]|(?:\.(?!\.))){0,28}(?:[A-Za-z0-9_]))?)/", $string)
 		|| preg_match("/(?:https?:)?\/\/(?:[A-z]+\.)?twitter\.com\/@?(?P<username>[A-z0-9_]+)\/status\/(?P<tweet_id>[0-9]+)\/?/", $string)
 		|| preg_match("/(?:https?:)?\/\/(?:[A-z]+\.)?twitter\.com\/@?(?!home|share|privacy|tos)(?P<username>[A-z0-9_]+)\/?/", $string)
@@ -239,6 +222,9 @@ function is_social_media($string) {
 		|| preg_match("/(?:https?:)?\/\/(?:[\w]+\.)?linkedin\.com\/(?P<company_type>(company)|(school))\/(?P<company_permalink>[A-z0-9-À-ÿ\.]+)\/?/", $string)
 		|| preg_match("/(?:https?:)?\/\/(?:[\w]+\.)?linkedin\.com\/feed\/update\/urn:li:activity:(?P<activity_id>[0-9]+)\/?/", $string)
 		|| preg_match("/(?:https?:)?\/\/(?:[\w]+\.)?linkedin\.com\/in\/(?P<permalink>[\w\-\_À-ÿ%]+)\/?/", $string)
+//		|| preg_match("/(?:https?:)?\/\/(?:[A-z]+\.)?youtube.com\/(?:c(?:hannel)?)\/(?P<id>[A-z0-9-\_]+)\/?/", $string)
+		|| preg_match("/(?:https?:)?\/\/(?:[A-z]+\.)?youtube.com\/(?:u(?:ser)?)\/(?P<username>[A-z0-9]+)\/?/", $string)
+//		|| preg_match("/(?:https?:)?\/\/(?:(?:www\.)?youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)(?P<id>[A-z0-9\-\_]+)/", $string)
 	) return true;
 
     return false;
@@ -248,29 +234,23 @@ function is_social_media($string) {
 // Search suggestions
 --------------------------------------*/
 function search_suggestion($opts, $results) {
-	if(array_key_exists("did_you_mean", $results)) {
-		$specific_result = $specific_result2 = "";
-
-		if(array_key_exists("search_specific", $results)) {
-			if($opts->type == 3 && count($results['search_specific']) > 1) {
-				// Format query url
-				$search_specific_url2 = "./results.php?q=".urlencode($results['search_specific'][1])."&t=".$opts->type."&a=".$opts->hash;
-				$specific_result2 = " or <a href=\"".$search_specific_url2."\">".$results['search_specific'][1]."</a>";
-			}
-
-			// Format query url			
-			$search_specific_url = "./results.php?q=".urlencode($results['search_specific'][0])."&t=".$opts->type."&a=".$opts->hash;
-			$specific_result = "<br /><small>Or instead search for <a href=\"".$search_specific_url."\">".$results['search_specific'][0]."</a>".$specific_result2.".</small>";
+	$specific_result = $specific_result2 = "";
 
-			unset($search_specific, $search_specific_url, $search_specific2, $search_specific_url2);
+	if(array_key_exists("search_specific", $results)) {
+		if($opts->type == 3 && count($results['search_specific']) > 1) {
+			// Format query url
+			$search_specific_url2 = "./results.php?q=".urlencode($results['search_specific'][1])."&t=".$opts->type."&a=".$opts->hash;
+			$specific_result2 = " or <a href=\"".$search_specific_url2."\">".$results['search_specific'][1]."</a>";
 		}
 
-		$didyoumean_url = "./results.php?q=".urlencode($results['did_you_mean'])."&t=".$opts->type."&a=".$opts->hash;
+		// Format query url			
+		$search_specific_url = "./results.php?q=".urlencode($results['search_specific'][0])."&t=".$opts->type."&a=".$opts->hash;
+		$specific_result = "<br /><small>Or instead search for <a href=\"".$search_specific_url."\">".$results['search_specific'][0]."</a>".$specific_result2.".</small>";
 
-		echo "<li class=\"meta\">Did you mean <a href=\"".$didyoumean_url."\">".$results['did_you_mean']."</a>?".$specific_result."</li>";
-
-		unset($didyoumean_url, $specific_result, $specific_result2);
+		unset($search_specific, $search_specific_url, $search_specific2, $search_specific_url2, $specific_result2);
 	}
+
+	return $specific_result;
 }
 
 /*--------------------------------------
@@ -283,11 +263,7 @@ function search_sources($results) {
 		$sources[] = $amount." ".$plural." from ".$source;
 	}
 
-    $sources = replace_last_comma(implode(', ', $sources));
-
-	echo "<li class=\"sources\">Includes ".$sources.".</li>";
-	
-	unset($sources);
+    return $sources = replace_last_comma(implode(', ', $sources)).'.';
 }
 
 /*--------------------------------------
@@ -336,10 +312,10 @@ function string_generator() {
 // Show version in footer and do periodic update check
 --------------------------------------*/
 function show_version() {
-	$cache_file = dirname(__DIR__).'/version.data';
+	$cache_file = ABSPATH.'cache/version.data';
 	
 	// Currently installed version
-	$current_version = "1.3";
+	$current_version = "1.4b3";
 
 	// Format current version for footer
 	$show_version = "<a href=\"https://github.com/adegans/Goosle/\" target=\"_blank\">Goosle ".$current_version."</a>.";
@@ -356,7 +332,20 @@ function show_version() {
 	// Update check, every week
 	if($version['checked'] < time() - 604800) {
 		$ch = curl_init();
-		set_curl_options($ch, "https://api.github.com/repos/adegans/goosle/releases/latest", array("goosle/".$current_version.";"));
+
+		curl_setopt($ch, CURLOPT_URL, 'https://api.github.com/repos/adegans/goosle/releases/latest');
+		curl_setopt($ch, CURLOPT_HTTPGET, 1); // Redundant? Probably...
+		curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
+		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+		curl_setopt($ch, CURLOPT_ENCODING, "gzip,deflate");
+		curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json, */*;q=0.7', 'User-Agent: goosle/'.$current_version.';'));
+		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+		curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
+		curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
+		curl_setopt($ch, CURLOPT_TIMEOUT, 3);
+		curl_setopt($ch, CURLOPT_VERBOSE, false);
+
 		$response = curl_exec($ch);
 		curl_close($ch);
 		

+ 34 - 12
goosle-cron.php

@@ -1,5 +1,5 @@
 <?php
-if(!defined('ABSPATH')) define('ABSPATH', dirname(__FILE__) . '/');
+if(!defined('ABSPATH')) define('ABSPATH', $_SERVER['DOCUMENT_ROOT'] . '/');
 
 require ABSPATH."functions/tools.php";
 
@@ -17,25 +17,47 @@ $auth = (isset($_GET['a'])) ? sanitize($_GET['a']) : $opts->user_auth;
 ---------------------------------------------------------------------------------------
 * Includes:
 * - Clearing out old cached results when using the file cache.
----------------------------------------------------------------------------------------
-* Execute this file with a cron once or twice a day.
-* If you've enabled the access hash, don't forget to include ?a=YOUR_HASH to the url.
-* 
-* Example for 5 minutes past every midnight and noon: 
-* 		5 0,12 * * * wget -qO - https://example.com/goosle-cron.php?a=YOUR_HASH
-*
-* Example for every midnight: 
-* 		0 0 * * * wget -qO - https://example.com/goosle-cron.php?a=YOUR_HASH
+* - Renewing access token for Openverse (Expires every 12 hours)
 ------------------------------------------------------------------------------------ */
 
 if(verify_hash($opts, $auth)) {
 	// Clear out old cached files?
-	if($opts->cache == "on" && $opts->cache_type == "file") {
+	if($opts->cache_type == "file") {
 		$ttl = intval($opts->cache_time) * 60;
+
 		delete_cached_results($ttl);
+		
+		echo "Cache deleted!<br />";
 	}
 
-	echo "Done!";
+	// Possibly renew the Openverse access token
+	if($opts->enable_image_search == "on" && $opts->enable_openverse == "on") {
+		require ABSPATH."functions/oauth-functions.php";
+
+		$token_file = ABSPATH.'cache/token.data';
+
+		if(is_file($token_file)) {
+			$tokens = unserialize(file_get_contents($token_file));
+			$registration = $tokens['openverse'];
+	
+			if($registration['expires'] < time()) {
+				// Is the token expired?
+				$new_token = oath_curl_request(
+					'https://api.openverse.org/v1/auth_tokens/token/', // Where?
+					$opts->user_agents[0], // Who?
+					'post', // post/get
+					array('Authorization: Bearer'.$registration['client_id']), // Additional headers
+					'grant_type=client_credentials&client_id='.$registration['client_id'].'&client_secret='.$registration['client_secret'] // Payload
+				);
+				
+				$new_token['expires_in'] = time() + ($new_token['expires_in'] - 3600);
+		
+				oath_store_token($token_file, 'openverse', array("client_id" => $registration['client_id'], "client_secret" => $registration['client_secret'], "access_token" => $new_token['access_token'], "expires" => $new_token['expires_in']));
+	
+				echo "New Openverse token stored!<br />";
+			}
+		}
+	}
 } else {
 	echo "Unauthorized!";
 } 

+ 99 - 97
help.php

@@ -1,8 +1,7 @@
 <?php
-if(!defined('ABSPATH')) define('ABSPATH', dirname(__FILE__) . '/');
+if(!defined('ABSPATH')) define('ABSPATH', $_SERVER['DOCUMENT_ROOT'] . '/');
 
 require ABSPATH."functions/tools.php";
-require ABSPATH."functions/search_engine.php";
 
 $opts = load_opts();
 $auth = (isset($_GET['a'])) ? sanitize($_GET['a']) : $opts->user_auth;
@@ -20,116 +19,119 @@ $auth = (isset($_GET['a'])) ? sanitize($_GET['a']) : $opts->user_auth;
 <!DOCTYPE html>
 <html lang="en">
 <head>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <meta charset="UTF-8"/>
-    <meta name="description" content="Goosle - The best meta search engine for private and fast internet fun!"/>
+	<title>Goosle Search Help</title>
+
+	<meta charset="utf-8" />
+	<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
+	<meta name="robots" content="noodp,noydir" />
     <meta name="referrer" content="no-referrer"/>
-	<link rel="apple-touch-icon" href="apple-touch-icon.png">
-	<link rel="icon" href="favicon.ico" type="image/x-icon"/>
+	<meta name="description" content="Get your Goosle on! - The best meta search engine for private and fast internet fun!" />
+
+	<link rel="icon" href="favicon.ico" />
+	<link rel="apple-touch-icon" href="apple-touch-icon.png" />
+	<link rel="canonical" href="<?php echo get_base_url($opts->siteurl); ?>/help.php" />
+
     <link rel="stylesheet" type="text/css" href="assets/css/styles.css"/>
-	<title>Goosle Search Help</title>
 </head>
+
 <body>
 <?php
 if(verify_hash($opts, $auth)) {
 ?>
-<div class="wrap">
-	<div class="header-wrap">
-		<form action="results.php" method="get" autocomplete="off">
-		    <h1 class="logo"><a class="no-decoration" href="./?a=<?php echo $opts->hash; ?>"><span class="G">G</span>oosle</a></h1>        
-		    <input tabindex="1" class="search" type="search" value="<?php echo (strlen($opts->query) > 0) ? $opts->query : "" ; ?>" name="q" /><input tabindex="2" class="button" type="submit" value="Search" />
-		
-	        <input type="hidden" name="t" value="<?php echo $opts->type; ?>"/>
-		    <input type="hidden" name="a" value="<?php echo $opts->hash; ?>">
-	 
-	        <div class="navigation-header">
-		        <a <?php echo ($opts->type == "0") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=0"><img src="assets/images/search.png" alt="Search results" />Search</a>
-		        <?php if($opts->enable_image_search == "on") { ?>
-		        <a <?php echo ($opts->type == "1") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=1"><img src="assets/images/image.png" alt="Image results" />Images</a>
-		        <?php } ?>
-		        <?php if($opts->enable_magnet_search == "on") { ?>
-		        <a <?php echo ($opts->type == "9") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=9"><img src="assets/images/magnet.png" alt="Magnet results" />Magnet links</a>
-		        <?php } ?>
-			</div>
-		</form>
-	</div>
+<div class="header">
+	<form action="results.php" method="get" autocomplete="off">
+	    <h1 class="logo"><a class="no-decoration" href="./?a=<?php echo $opts->hash; ?>"><span class="G">G</span>oosle</a></h1>        
+	    <input tabindex="1" class="search" type="search" value="<?php echo (strlen($opts->query) > 0) ? $opts->query : "" ; ?>" name="q" /><input tabindex="2" class="button" type="submit" value="Search" />
 	
-	<div class="results-wrap">
-		<section class="main-column">
-			<h2>How to use Goosle</h2>
-			<p>DuckDuckGo, Google and Ecosia are mostly language agnostic and will try to figure out on their own what language to use based on your search query.</p>
-			<p>Searching defaults to Moderate Safe mode. To override the safe mode, prefix your search with <strong>safe:on</strong> or <strong>safe:off</strong>.<br /><strong>On</strong> will use 'Strict' mode, while <strong>off</strong> will disable safe searching, this may yield results that are unsuitable for work or minors.</p>
-			<?php if($opts->enable_google == "on") { ?>
-			<p>Google results are not personalized by default, using Google's own option for it.</p>
-			<?php } ?>
-			<?php if($opts->enable_duckduckgo == "on") { ?>
-			<p>DuckDuckGo search bangs are not supported and the <strong>!</strong> to trigger them is stripped out to prevent issues.</em></p>
-			<?php } ?>
-			<?php if($opts->enable_reddit == "on") { ?>
-			<p>Reddit results will find 'Top Posts' in any subreddit posted in the last year. NSFW or mature content will be visibly marked. Alternatively Goosle has an option to ignore Reddit content labelled as NSFW.</p>
-			<?php } ?>
-			<?php if($opts->enable_wikipedia == "on") { ?>
-			<p>Goosle can search directly on Wikipedia. This will yield results linking to Wikipedia pages in the language of your choice. Configure your language in config.php or have it default to english.</p>
-			<?php } ?>
+        <input type="hidden" name="t" value="<?php echo $opts->type; ?>"/>
+	    <input type="hidden" name="a" value="<?php echo $opts->hash; ?>">
+ 
+        <div class="navigation">
+	        <a <?php echo ($opts->type == "0") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=0"><img src="assets/images/search.png" alt="Search results" />Search</a>
 
-			<h3>Result ranking</h3>
-			<p>To try and provide the best results first, if a website is found through multiple search engines it will show higher in the results on Goosle. Also the amount of matching words in the title and SEO description is considered. A better match will show higher in the results.</p>
-			<p>Result ranking is applied to web search and image search.</p>
+	        <?php if($opts->enable_image_search == "on") { ?>
+	        <a <?php echo ($opts->type == "1") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=1"><img src="assets/images/image.png" alt="Image results" />Images</a>
+	        <?php } ?>
 
-			<?php if($opts->enable_image_search == "on") { ?>
-				<h2>Image Search</h2>
-				<p>The number of results is not limited but generally yields around 60 to 80 images.</p>
-				<p>Contrary to Google Image Search which opens a popup/slider with more information. Goosle Image Search links directly to the page where the image is displayed, but also to the actual image itself. You'll see the links for it below each image.</p>
-				<p>You can search for images in a general size by adding <strong>size:small</strong>, <strong>size:medium</strong> or <strong>size:large</strong> to your search query.</p>
-			<?php } ?>
-			
-			<h2>Special Searches</h2>
-			<?php if($opts->special['currency'] == "on") { ?>
-				<h3>Currency converter</h3>
-				<p>Convert currency with a simple query.<br />
-				For example: Search for <strong>20 EUR in HKD</strong> or <strong>14 USD to MXN</strong> and Goosle will search for it, but also a local conversion is done in a highlighted result.</p>
-			<?php } ?>
-			
-			<?php if($opts->special['phpnet'] == "on") { ?>
-				<h3>PHP.net Search</h3>
-				<p>Prefix your search with <strong>php</strong> to search on php.net for a PHP function.<br />
-				For example: Searching for <strong>php in_array</strong> or <strong>php trim</strong> will show you a brief description, compatible PHP versions and the basic syntax for that function.</p>
-			<?php } ?>
-			
-			<?php if($opts->special['definition'] == "on") { ?>
-				<h3>Word Definition</h3>
-				<p>You can easily look up the meaning of single words. Prefix the word you want to look up with any of the following keywords; <strong>d</strong>, <strong>define</strong>, <strong>mean</strong> or <strong>meaning</strong>.<br />
-				For example: Searching for <strong>define search</strong> will search for that as normal, but also show the dictionary definition highlighted above the search results.</p>
-				<p><em><strong>Note:</strong> Special Searches do not work for image and magnet search.</em></p>
-			<?php } ?>
+	        <?php if($opts->enable_magnet_search == "on") { ?>
+	        <a <?php echo ($opts->type == "9") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=9"><img src="assets/images/magnet.png" alt="Magnet results" />Magnet links</a>
+	        <?php } ?>
+		</div>
+	</form>
+</div>
+	
+<div class="content">
+	<h2>How to use Goosle</h2>
+	<p>DuckDuckGo and Google are mostly language agnostic and will try to figure out on their own what language to use based on your search query.</p>
+	<p>Searching defaults to Moderate Safe mode. To override the safe mode, prefix your search with <strong>safe:on</strong> or <strong>safe:off</strong>.<br /><strong>On</strong> will use 'Strict' mode, while <strong>off</strong> will disable safe searching, this may yield results that are unsuitable for work or minors.</p>
+	<?php if($opts->enable_google == "on") { ?>
+	<p>Google results are not personalized by default, using Google's own option for it.</p>
+	<?php } ?>
+	<?php if($opts->enable_duckduckgo == "on") { ?>
+	<p>DuckDuckGo search bangs are not supported and the <strong>!</strong> to trigger them is stripped out to prevent issues.</em></p>
+	<?php } ?>
+	<?php if($opts->enable_wikipedia == "on") { ?>
+	<p>Goosle can search directly on Wikipedia. This will yield results linking to Wikipedia pages in the language of your choice. Configure your language in config.php or have it default to english.</p>
+	<?php } ?>
+
+	<h3>Result ranking</h3>
+	<p>To try and provide the best results first, if a website is found through multiple search engines it will show higher in the results on Goosle. Also the amount of matching words in the title and SEO description is considered. A better match will show higher in the results.</p>
+	<p>Result ranking is applied to web search and image search.</p>
+
+	<?php if($opts->enable_image_search == "on") { ?>
+		<h2>Image Search</h2>
+		<p>The number of results is not limited but typically yields about 60 to 80 images. If you've enabled Openverse you can set a limit for Openverse images of up to 200 images per search.</p>
+		<p>Contrary to regular image search such as Google Image Search which opens a popup/slider with more information. Goosle Image Search links directly to the page where the image is displayed, but also to the actual image itself. You'll see the links for it below each image.</p>
+		<p>You can search for images in a general size by adding <strong>size:small</strong>, <strong>size:medium</strong> or <strong>size:large</strong> to your search query.</p>
+	<?php } ?>
+	
+	<h2>Special Searches</h2>
+	<?php if($opts->special['currency'] == "on") { ?>
+		<h3>Currency converter</h3>
+		<p>Convert currency with a simple query.<br />
+		For example: Search for <strong>20 EUR in HKD</strong> or <strong>14 USD to MXN</strong> and Goosle will search for it, but also a local conversion is done in a highlighted result.</p>
+	<?php } ?>
+	
+	<?php if($opts->special['phpnet'] == "on") { ?>
+		<h3>PHP.net Search</h3>
+		<p>Prefix your search with <strong>php</strong> to search on php.net for a PHP function.<br />
+		For example: Searching for <strong>php in_array</strong> or <strong>php trim</strong> will show you a brief description, compatible PHP versions and the basic syntax for that function.</p>
+	<?php } ?>
+	
+	<?php if($opts->special['definition'] == "on") { ?>
+		<h3>Word Definition</h3>
+		<p>You can easily look up the meaning of single words. Prefix the word you want to look up with any of the following keywords; <strong>d</strong>, <strong>define</strong>, <strong>mean</strong> or <strong>meaning</strong>.<br />
+		For example: Searching for <strong>define search</strong> will search for that as normal, but also show the dictionary definition highlighted above the search results.</p>
+		<p><em><strong>Note:</strong> Special Searches do not work for image and magnet search.</em></p>
+	<?php } ?>
+	
+	<?php if($opts->enable_magnet_search == "on") { ?>
+		<h2>Magnet Search</h2>
+		<p>Search for anything torrent sites have on offer the direct search result should give you the magnet link.<br />Results are gathered from 1337x, Nyaa, The Pirate Bay, EZTV and YTS and are ranked by most seeders. The number of results is limited to 50.</p>
+		<p>The search scripts will try to provide useful data which may include; Seeders/Leechers, A link to the torrent page, Download Category, Release year, Movie quality (720p, 4K etc.), Movie Runtime and the Download Size. Not every website makes this available and all results take a best effort approach.</p>
+		<?php if($opts->imdb_id_search == "on") { ?>
+			<h3>Searching TV Shows</h3>
+			<p>To do a specific search on The Pirate Bay and EZTV you can search for IMDb Title IDs. These are numeric IDs prefixed with <strong>tt</strong>. This kind of search is useful when you're looking for a tv show that doesn't have a unique name, or simply if you want to use a specialized tracker for tv shows.</p>
+			<p>	If you already know the Title ID you can enter it directly in the Magnet Link search as your search query.<br />
+			If you don't know the Title ID you can do a regular search for <strong>imdb [tv show name]</strong>, for example <strong>imdb Jack Ryan</strong>.<br />
+			Goosle will detect the IMDb ID from the search results and include a highlight in the result that offers you to search for downloads through a magnet link search.</p>
 			
-			<?php if($opts->enable_magnet_search == "on") { ?>
-				<h2>Magnet Search</h2>
-				<p>Search for anything torrent sites have on offer the direct search result should give you the magnet link.<br />Results are gathered from 1337x, Nyaa, The Pirate Bay, EZTV and YTS and are ranked by most seeders. The number of results is limited to 50.</p>
-				<p>The search scripts will try to provide useful data which may include; Seeders/Leechers, A link to the torrent page, Download Category, Release year, Movie quality (720p, 4K etc.), Movie Runtime and the Download Size. Not every website makes this available and all results take a best effort approach.</p>
-				<?php if($opts->imdb_id_search == "on") { ?>
-					<h3>Searching TV Shows</h3>
-					<p>To do a specific search on The Pirate Bay and EZTV you can search for IMDb Title IDs. These are numeric IDs prefixed with <strong>tt</strong>. This kind of search is useful when you're looking for a tv show that doesn't have a unique name, or simply if you want to use a specialized tracker for tv shows.</p>
-					<p>	If you already know the Title ID you can enter it directly in the Magnet Link search as your search query.<br />
-					If you don't know the Title ID you can do a regular search for <strong>imdb [tv show name]</strong>, for example <strong>imdb Jack Ryan</strong>.<br />
-					Goosle will detect the IMDb ID from the search results and include a highlight in the result that offers you to search for downloads through a magnet link search.</p>
-					
-				<?php } ?>
-				<h3>Filtering TV Show episodes</h3>
-				<p>To help you narrow down results you can search for specific seasons and episodes. For example: If you search for <strong>tt5057054 S02</strong> or <strong>Jack Ryan S02</strong> you'll get filtered results for Jack Ryan Season 2. Searching for <strong>tt5057054 S02E03</strong> or <strong>Jack Ryan S02E03</strong> should find Season 2 Episode 3 and so on.</p>
-				<p>Likewise if you want a specific quality of a movie or tv show you can add that directly in your search. For example: If you search for <strong>Arrietty 720p</strong> you should primarily find that movie in 720p quality if it's available. Common screensizes are 480p, 720p, 1080p, 2160p (4K) and terms like HD-DVD, FULLHD, etc..</p>
-				<p><em><strong>Note:</strong> If you like, or found a use for, what you downloaded, you should buy a legal copy of it.</em></p>
-			<?php } ?>
+		<?php } ?>
+		<h3>Filtering TV Show episodes</h3>
+		<p>To help you narrow down results you can search for specific seasons and episodes. For example: If you search for <strong>tt5057054 S02</strong> or <strong>Jack Ryan S02</strong> you'll get filtered results for Jack Ryan Season 2. Searching for <strong>tt5057054 S02E03</strong> or <strong>Jack Ryan S02E03</strong> should find Season 2 Episode 3 and so on.</p>
+		<p>Likewise if you want a specific quality of a movie or tv show you can add that directly in your search. For example: If you search for <strong>Arrietty 720p</strong> you should primarily find that movie in 720p quality if it's available. Common screensizes are 480p, 720p, 1080p, 2160p (4K) and terms like HD-DVD, FULLHD, etc..</p>
+		<p><em><strong>Note:</strong> If you like, or found a use for, what you downloaded, you should buy a legal copy of it.</em></p>
+	<?php } ?>
 
-			<p><small><strong>Acknowledgements:</strong><br />Goosle started as a fork of LibreY, and takes some design cues from DuckDuckGo.com. Goosle is created by <a href="https://ajdg.solutions/" target="_blank">Arnan de Gans</a> with the intent to make search fun and productive.</small></p>
-		</section>
-	</div>
+	<p><small><strong>Acknowledgements:</strong><br />Goosle started as a fork of LibreY, and takes some design cues from DuckDuckGo.com. Goosle is created by <a href="https://ajdg.solutions/" target="_blank">Arnan de Gans</a> with the intent to make search fun and productive.</small></p>
 </div>
 
-<div class="footer-wrap">
-	<div class="footer">
+<div class="footer">
+	<div class="footer-left">
 		&copy; <?php echo date('Y'); ?> <?php echo show_version(); ?> By <a href="https://ajdg.solutions/" target="_blank">Arnan de Gans</a>.
-		<span style="float:right;"><a href="./?a=<?php echo $opts->hash; ?>">Start</a> - <a href="./help.php?a=<?php echo $opts->hash; ?>">Help</a> - Your IP: <?php echo $_SERVER["REMOTE_ADDR"]; ?></span>
+	</div>
+	<div class="footer-right">
+		<a href="./?a=<?php echo $opts->hash; ?>">Start</a> - <a href="./help.php?a=<?php echo $opts->hash; ?>">Help</a>
 	</div>
 </div>
 

+ 41 - 37
index.php

@@ -1,8 +1,7 @@
 <?php 
-if(!defined('ABSPATH')) define('ABSPATH', dirname(__FILE__) . '/');
+if(!defined('ABSPATH')) define('ABSPATH', $_SERVER['DOCUMENT_ROOT'] . '/');
 
 require ABSPATH."functions/tools.php";
-require ABSPATH."functions/search_engine.php";
 
 $opts = load_opts();
 $auth = (isset($_GET['a'])) ? sanitize($_GET['a']) : $opts->user_auth;
@@ -20,50 +19,55 @@ $auth = (isset($_GET['a'])) ? sanitize($_GET['a']) : $opts->user_auth;
 <!DOCTYPE html>
 <html lang="en">
 <head>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <meta charset="UTF-8"/>
-    <meta name="description" content="Goosle - The best meta search engine for private and fast internet fun!"/>
+    <title>Goosle Search</title>
+
+	<meta charset="utf-8" />
+	<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
+	<meta name="robots" content="noodp,noydir" />
     <meta name="referrer" content="no-referrer"/>
-	<link rel="apple-touch-icon" href="apple-touch-icon.png">
-	<link rel="icon" href="favicon.ico" type="image/x-icon"/>
+	<meta name="description" content="Get your Goosle on! - The best meta search engine for private and fast internet fun!" />
+
+	<link rel="icon" href="favicon.ico" />
+	<link rel="apple-touch-icon" href="apple-touch-icon.png" />
+	<link rel="canonical" href="<?php echo get_base_url($opts->siteurl); ?>" />
+
     <link rel="stylesheet" type="text/css" href="assets/css/styles.css"/>
-    <title>Goosle Search</title>
 </head>
-<body class="main">
+
+<body class="startpage">
 <?php
 if(verify_hash($opts, $auth)) {
 ?>
-<div class="wrap">
-	<div class="search-box-main">
-	    <form action="results.php" method="get" autocomplete="off">
-	        <h1><span class="G">G</span>oosle</h1>
-	
-	        <input tabindex="10" type="search" class="search" name="q" autofocus />
 
-	        <input type="hidden" name="t" value="0"/>
-	        <input type="hidden" name="a" value="<?php echo $opts->hash; ?>"/>
-	
-	        <div class="search-box-buttons">
-		        <button tabindex="20" name="t" value="0" type="submit">Web search</button>
-		        <?php if($opts->enable_image_search == "on") { ?>
-		        <button tabindex="40" name="t" value="1" type="submit">Image search</button>
-		        <?php } ?>
-		        <?php if($opts->enable_magnet_search == "on") { ?>
-		        <button tabindex="50" name="t" value="9" type="submit">Magnet search</button>
-		        <?php } ?>
-	        </div>
-	
-	    </form>
-	</div>
+<div class="startpage-search">
+	<h1><span class="G">G</span>oosle</h1>
+    
+    <form action="results.php" method="get" autocomplete="off">
+        <input tabindex="10" type="search" class="search" name="q" autofocus />
+        <input type="hidden" name="a" value="<?php echo $opts->hash; ?>"/>
 
-	<?php if($opts->password_generator == "on") { ?>
-	<div class="password-generator">
-		<form method="get" action="./" autocomplete="off">
-			Password Generator:<br/><input class="password" type="text" name="pw" maxlength="27" value="<?php echo string_generator(); ?>" autocomplete="0" />
-		</form>
-	</div>
-	<?php } ?>
+        <div class="search-buttons">
+	        <button tabindex="20" name="t" value="0" type="submit">Web search</button>
+
+	        <?php if($opts->enable_image_search == "on") { ?>
+	        <button tabindex="40" name="t" value="1" type="submit">Image search</button>
+	        <?php } ?>
+
+	        <?php if($opts->enable_magnet_search == "on") { ?>
+	        <button tabindex="50" name="t" value="9" type="submit">Magnet search</button>
+	        <?php } ?>
+        </div>
+    </form>
 </div>
+
+<?php if($opts->password_generator == "on") { ?>
+<div class="password-generator">
+	<form method="get" action="./" autocomplete="off">
+		Password Generator<br/><input class="password" type="text" name="pw" maxlength="27" value="<?php echo string_generator(); ?>" autocomplete="0" />
+	</form>
+</div>
+<?php } ?>
+
 <?php 
 } else {
 	echo "<div class=\"auth-error\">Goosle</div>";

+ 75 - 21
readme.md

@@ -2,13 +2,13 @@
 <h2><center>The best Meta Search Engine to find everything.</center></h2>
 
 Goosle is a fast, privacy oriented search tool that just works. \
-It's kept simple so everyone can use it and to make sure it works on most (basic) webservers.
+It's kept simple so everyone can use it and to make sure it works on most webservers.
 
-Replace Google search, replace DuckDuckGo and Ecosia but do not give up on it's search results! Goosle uses it all and shows you the most relevant results through a neat, clean interface. Goosle has **no** distractions, **no** trackers, **no** cookies and **no** javascript or other things to slow you down. 
+If you're tired of traditional results from one site like Google search or DuckDuckGo and want to see more at once, Goosle has your back! Goosle searches on several search engine at the same time and shows you the most relevant results through a neat, clean interface. Goosle has **no** distractions, **no** trackers, **no** cookies and **no** javascript or other things to slow you down. 
 
-On top of that, Goosle has a basic Image search tab which for now shows image results from Yahoo! Image Search.
+Goosle does Image search which shows results from Yahoo! Images and Openverse.
 
-And, also very useful, a safe and clean magnet link search tab. Find anything you like in seconds without malware, ads or other site-breaking nonsense that would otherwise require a VPN to safely use Torrent sites. Results are sourced from some of the largest torrent providers, compiled and ordered in by the most seeders.
+On top of that, Goosle provides a safe and clean Magnet Link search tab. Find anything you like in seconds without malware, ads or other site-breaking nonsense that would otherwise require a VPN to safely use Torrent sites. Results are sourced from some of the largest torrent providers, compiled and ordered by the most seeders.
 
 Host for yourself and friends, with a access hash key. Or set up a public search website.
 
@@ -20,13 +20,12 @@ After-all, finding things should be easy and not turn into a chore.
 - Works on **any** hosting package that does PHP7.4 or newer
 - Get search results from DuckDuckGo
 - Get search results from Google
-- Get search results from Reddit
+- Get search results from Qwant
 - Get search results from Wikipedia
-- Get search results from Ecosia (Bing)
-- Image search through Yahoo! Images
-- Algorithm for ranking search results on the results page
-- Option to down-rank the biggest social media sites such as facebook, instagram, twitter, tiktok, snapchat and some others.
+- Image search through Yahoo! Images, Qwant and Openverse
 - Search for magnet links on popular Torrent sites
+- Algorithm for ranking search results on the results page
+- Option to down-rank the biggest social media sites such as facebook, instagram, twitter, tiktok, reddit, snapchat and a few others.
 - Special searches for; Currency conversion, Dictionary and php.net
 - Randomized user-agents for to prevent profiling by search providers
 - Non-personalized Google results without instant results or other non-sense
@@ -58,38 +57,93 @@ Tested to work on Apache with PHP8.0.24 and 8.2.x.
 ## Installation
 1. Download and unzip Goosle.
 2. In the main directory. Copy config.default.php to config.php.
-3. Edit config.php file and set your preferences.
-4. Upload all files to your webserver, for example to the root folder of a subdomain (eg. example.com or search.example.com or a sub-folder such as example.com/search/)
-5. Rename goosle.htaccess to .htaccess
-6. Load the site in your browser. If you've enabled the access hash add *?a=YOURHASH* to the url.
+3. Edit the config.php file and set your preferences.
+4. Upload all files to your webserver. (eg. example.com or search.example.com or a sub-folder such as example.com/search/)
+5. Rename goosle.htaccess to .htaccess or add its contents to your existing .htaccess file.
+6. Load Goosle in your browser. If you've enabled the access hash, add *?a=YOUR_HASH* to the url.
 7. Let me know where you installed Goosle :-)
 
 ## Updating Goosle to a newer version
 1. Download and unzip the latest release of Goosle.
 2. Check your config.php file and compare it to config.default.php. Go over your preferences. Make sure any new settings or changed values are present in your config.php. (Or reconfigure Goosle with a new copy from config.default.php)
 3. Upload all files to your webserver, overwriting all files except perhaps config.php.
-4. Load the site in your browser. If you've enabled the access hash don't forget to add *?a=YOURHASH* to the url.
+4. Load Goosle in your browser. If you've enabled the access hash don't forget to add *?a=YOUR_HASH* to the url.
 5. Enjoy your updated search experience!
 
+## Authorizing access to the Openverse search API
+This is required to use Openverse Image Search.
+In your browser navigate to your goosle setup and add /functions/oauth.php to the url (ex. example.com/functions/oauth.php or example.com/functions/oauth.php?a=YOUR_HASH). \
+Follow the onscreen prompts to register Goosle with Openverse.
+
+At the end, please save the Client ID and Client Secret somewhere on your computer, in a note or something. Should the token file Goosle creates get lost you'll need those strings to continue using Openverse.
+
+This procedure generates an access token, this token expires every 12 hours. Yeah, annoying! \
+To automatically renew the token you can set up the Goosle cronjob as described below.
+
+## Setting up a cronjob
+For a number of background tasks like clearing up the file cache and/or renewing your Openverse access token you need to set up a cronjob. \
+Execute this cronjob a couple of times per day, recommended is every couple of hours.
+
+Without it, Openverse access will expire and you have to generate a new key every day.
+For low traffic setups or if you do not use Openverse a longer interval of two or three times a day is fine.
+
+If you've enabled the access hash as a password, don't forget to include ?a=YOUR_HASH to the url.
+
+Example for 10 minutes past every 3 hours \
+`10 */3 * * * wget -qO - https://example.com/goosle-cron.php?a=YOUR_HASH`
+
+Example for 5 minutes past every 8 hours (I use this on my Goosle) \
+`5 */8 * * * wget -qO - https://example.com/goosle-cron.php?a=YOUR_HASH`
+
+Example for every midnight \
+`0 0 * * * wget -qO - https://example.com/goosle-cron.php?a=YOUR_HASH`
+
 ### Notes:
-- If you use file caching you can set up a cron job to execute goosle-cron.php every 12 or 24 hours. Check that file for details and usage examples.
-- The .htaccess file has a redirect to force HTTPS as well as browser caching rules ready to go.
+- When using file caching you should set up a cronjob to execute goosle-cron.php every few hours. This deletes 'old' results.
+- When you use Openverse for your image searches you should set up a cron job to execute goosle-cron.php every 11 hours or less. This will automagically renew the access token.
+- The .htaccess file has a redirect to force HTTPS, catch 404 errors with a redirect as well as browser caching rules ready to go.
 - The robots.txt has a rule to tell all crawlers to not crawl Goosle. But keep in mind that not every crawler obeys this file.
 - The access hash is NOT meant as a super secure measure and only works for surface level prying eyes.
 
-Have fun finding things! And tell your friends!
+Have fun finding things! Tell your friends!
 
 ## Support
 Goosle comes with limited support. \
-You can post your questions on Github Discussions or on my support forum on [ajdg.solutions](https://ajdg.solutions/support/?mtm_campaign=goosle_readme). \
-Or say hi on [Mastodon](https://mas.to/@arnan) or [Telegram](https://t.me/arnandegans).
+You can post your questions on Github Discussions or say hi on [Mastodon](https://mas.to/@arnan) or [Telegram](https://t.me/arnandegans).
+
+### Known "issues"
+- Duckduckgo sometimes returns a 202 header and no results. I'm not sure what causes that but suspect it's something to do with quotas or service limitation on their end.
+- Some crawlers for Magnet searches may return empty results. These are likely quota limits on their end.
 
 ## Changelog
+1.4 - May 16, 2024
+- NOTICE: config.default.php has changed, re-create your config.php!!
+- [fix] Footer no longer overlaps results
+- [fix] Search navigation no longer bunched up on smaller displays
+- [fix] Double search type when searching from start page
+- [new] Filter for additional/different headers per cURL request
+- [new] Image search via Openverse API (Access token and cronjob required, see installation instructions)
+- [new] Image search via Qwant API
+- [new] Web (recent news) search via Qwant API
+- [tweak] Merged 'cache' option into 'cache-type', see config.default.php for details
+- [tweak] Better filtering for duplicate web results
+- [tweak] File size formatting for images more uniform
+- [tweak] Optimized curl_multi_exec handling
+- [tweak] Improved SEO headers
+- [tweak] Layout tweaks and optimizations for search results, header and footer
+- [tweak] Removed redundant HTML, CSS and some PHP
+- [tweak] MagnetDL search disabled by default because of Cloudflare (Will probably be removed in future version)
+- [tweak] Removed non-functional magnet trackers
+- [tweak] Added 15 extra public magnet trackers
+- [change] Removed Ecosia support
+- [change] Removed Reddit support
+- [change] Removed 1337x support
+- [change] Removed MagnetDL support
+
 1.3 - April 11, 2024
 - [fix] Image search crawler filters out non-image results better
 - [new] Crawler for results from magnetdl.com
 - [new] Direct Reddit.com search, search for 'Top Posts' created in the past year
-- [new] Added NSFW filter for Reddit results in config.default.php
 - [new] YTS movie highlights now link to YTS website when clicking the title
 - [new] Placeholder image for missing eztv highlight thumbnails
 - [tweak] Better hash matching for duplicate magnet results
@@ -188,4 +242,4 @@ Goosle started as a fork of LibreY, and ended up as a rewrite and something diff
 Search results take design cues from DuckDuckGo and the magnet search has been modified to show more useful information where possible. \
 Goosle does not index, store or distribute torrent files. If you like, or found a use for, what you downloaded, you should probably buy a legal copy of it.
 
-The name Goosle is my last name with an L added in. Translate it from Dutch. Not in any way a derivation of Google and DuckDuckGo combined :wink:
+The name Goosle comes from my last name with an L added in. Translate it from Dutch.

+ 81 - 35
results.php

@@ -1,11 +1,12 @@
 <?php
-if(!defined('ABSPATH')) define('ABSPATH', dirname(__FILE__) . '/');
+if(!defined('ABSPATH')) define('ABSPATH', $_SERVER['DOCUMENT_ROOT'] . '/');
 
 require ABSPATH."functions/tools.php";
 require ABSPATH."functions/search_engine.php";
 
 $opts = load_opts();
 $auth = (isset($_GET['a'])) ? sanitize($_GET['a']) : $opts->user_auth;
+$start_time = microtime(true);
 /* ------------------------------------------------------------------------------------
 *  Goosle - A meta search engine for private and fast internet fun.
 *
@@ -20,50 +21,95 @@ $auth = (isset($_GET['a'])) ? sanitize($_GET['a']) : $opts->user_auth;
 <!DOCTYPE html>
 <html lang="en">
 <head>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <meta charset="UTF-8"/>
-    <meta name="description" content="Goosle - The best meta search engine for private and fast internet fun!"/>
+	<title><?php echo $opts->query; ?> - Goosle Search Results</title>
+
+	<meta charset="utf-8" />
+	<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
+	<meta name="robots" content="noodp,noydir" />
     <meta name="referrer" content="no-referrer"/>
-	<link rel="apple-touch-icon" href="apple-touch-icon.png">
-	<link rel="icon" href="favicon.ico" type="image/x-icon"/>
+	<meta name="description" content="Get your Goosle on! - The best meta search engine for private and fast internet fun!" />
+
+	<link rel="icon" href="favicon.ico" />
+	<link rel="apple-touch-icon" href="apple-touch-icon.png" />
+	<link rel="canonical" href="<?php echo get_base_url($opts->siteurl); ?>/results.php" />
+
     <link rel="stylesheet" type="text/css" href="assets/css/styles.css"/>
-	<title><?php echo $opts->query; ?> - Goosle Search Results</title>
 </head>
+
 <body>
 <?php
 if(verify_hash($opts, $auth)) {
 ?>
-<div class="wrap">
-	<div class="header-wrap">
-		<form action="results.php" method="get" autocomplete="off">
-		    <h1 class="logo"><a class="no-decoration" href="./?a=<?php echo $opts->hash; ?>"><span class="G">G</span>oosle</a></h1>        
-		    <input tabindex="1" class="search" type="search" value="<?php echo (strlen($opts->query) > 0) ? $opts->query : "" ; ?>" name="q" /><input tabindex="2" class="button" type="submit" value="Search" />
-			
-
-	        <input type="hidden" name="t" value="<?php echo $opts->type; ?>"/>
-		    <input type="hidden" name="a" value="<?php echo $opts->hash; ?>">
-	 
-	        <div class="navigation-header">
-		        <a <?php echo ($opts->type == "0") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=0"><img src="assets/images/search.png" alt="Search results" />Search</a>
-		        <?php if($opts->enable_image_search == "on") { ?>
-		        <a <?php echo ($opts->type == "1") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=1"><img src="assets/images/image.png" alt="Image results" />Images</a>
-		        <?php } ?>
-		        <?php if($opts->enable_magnet_search == "on") { ?>
-		        <a <?php echo ($opts->type == "9") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=9"><img src="assets/images/magnet.png" alt="Magnet results" />Magnet links</a>
-		        <?php } ?>
-	       </div>
-		</form>
-	</div>
-	
-	<div class="results-wrap">
-    <?php fetch_search_results($opts); ?>
-	</div>
+<div class="header">
+	<form action="results.php" method="get" autocomplete="off">
+	    <h1 class="logo"><a class="no-decoration" href="./?a=<?php echo $opts->hash; ?>"><span class="G">G</span>oosle</a></h1>        
+	    <input tabindex="1" class="search" type="search" value="<?php echo (strlen($opts->query) > 0) ? $opts->query : "" ; ?>" name="q" /><input tabindex="2" class="button" type="submit" value="Search" />
+		
+        <input type="hidden" name="t" value="<?php echo $opts->type; ?>"/>
+	    <input type="hidden" name="a" value="<?php echo $opts->hash; ?>">
+ 
+        <div class="navigation">
+	        <a <?php echo ($opts->type == "0") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=0"><img src="assets/images/search.png" alt="Search results" />Search</a>
+
+	        <?php if($opts->enable_image_search == "on") { ?>
+	        <a <?php echo ($opts->type == "1") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=1"><img src="assets/images/image.png" alt="Image results" />Images</a>
+	        <?php } ?>
+
+	        <?php if($opts->enable_magnet_search == "on") { ?>
+	        <a <?php echo ($opts->type == "9") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=9"><img src="assets/images/magnet.png" alt="Magnet results" />Magnet links</a>
+	        <?php } ?>
+       </div>
+	</form>
 </div>
 
-<div class="footer-wrap">
-	<div class="footer">
+<div class="content">
+<?php
+if(!empty($opts->query)) {
+	// Curl
+    $mh = curl_multi_init();
+
+	// Load search script
+    if($opts->type == 0) {
+        require ABSPATH."engines/search.php";
+        $search = new Search($opts, $mh);
+	} else if($opts->type == 1) {
+	    require ABSPATH."engines/search-image.php";
+        $search = new ImageSearch($opts, $mh);
+	} else if($opts->type == 9) {
+	    require ABSPATH."engines/search-magnet.php";
+        $search = new MagnetSearch($opts, $mh);
+    }
+
+    $running = null;
+
+    do {
+        $status = curl_multi_exec($mh, $running);
+	    if($running) {
+	        curl_multi_select($mh);
+	    }
+    } while ($running && $status == CURLM_OK);
+
+    $results = $search->get_results();
+
+	curl_multi_close($mh);
+
+	// Add elapsed time to results
+	$results['time'] = number_format(microtime(true) - $start_time, 5, '.', '');
+
+	// Echoes results and special searches
+    $search->print_results($results, $opts);
+} else {
+	echo "<div class=\"warning\">Search query can not be empty!<br />Not sure what went wrong? Learn more about <a href=\"./help.php?a=".$opts->hash."\">how to use Goosle</a>.</div>";
+}
+?>
+</div>
+
+<div class="footer">
+	<div class="footer-left">
 		&copy; <?php echo date('Y'); ?> <?php echo show_version(); ?> By <a href="https://ajdg.solutions/" target="_blank">Arnan de Gans</a>.
-		<span style="float:right;"><a href="./?a=<?php echo $opts->hash; ?>">Start</a> - <a href="./help.php?a=<?php echo $opts->hash; ?>">Help</a> - Your IP: <?php echo $_SERVER["REMOTE_ADDR"]; ?></span>
+	</div>
+	<div class="footer-right">
+		<a href="./?a=<?php echo $opts->hash; ?>">Start</a> - <a href="./help.php?a=<?php echo $opts->hash; ?>">Help</a>
 	</div>
 </div>