├── .editorconfig ├── .env ├── .github ├── ISSUE_TEMPLATE │ └── internal-server-error.md └── workflows │ └── okteto.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── Dockerfile ├── IM.json ├── LICENSE ├── README.md ├── app.ts ├── assets ├── goodgle.svg ├── instructions │ ├── brave │ │ ├── 1.avif │ │ ├── 1.png │ │ └── 1.webp │ ├── brave_mobile │ │ ├── 1.avif │ │ ├── 1.png │ │ ├── 1.webp │ │ ├── 2.avif │ │ ├── 2.png │ │ ├── 2.webp │ │ ├── 3.avif │ │ ├── 3.png │ │ └── 3.webp │ ├── chrome │ │ ├── 1.avif │ │ ├── 1.png │ │ └── 1.webp │ ├── chrome_mobile │ │ ├── 1.avif │ │ ├── 1.png │ │ ├── 1.webp │ │ ├── 2.avif │ │ ├── 2.png │ │ └── 2.webp │ ├── edge │ │ ├── 1.avif │ │ ├── 1.png │ │ └── 1.webp │ ├── edge_mobile │ │ ├── 1.avif │ │ ├── 1.png │ │ ├── 1.webp │ │ ├── 2.avif │ │ ├── 2.png │ │ └── 2.webp │ ├── firefox │ │ ├── 1.avif │ │ ├── 1.png │ │ ├── 1.webp │ │ ├── 2.avif │ │ ├── 2.png │ │ └── 2.webp │ ├── firefox_mobile │ │ ├── 1.avif │ │ ├── 1.png │ │ ├── 1.webp │ │ ├── 2.avif │ │ ├── 2.png │ │ ├── 2.webp │ │ ├── 3.avif │ │ ├── 3.png │ │ └── 3.webp │ ├── firefox_proton │ │ ├── 1.avif │ │ ├── 1.png │ │ ├── 1.webp │ │ ├── 2.avif │ │ ├── 2.png │ │ └── 2.webp │ ├── opera │ │ ├── 1.avif │ │ ├── 1.png │ │ └── 1.webp │ └── unknown │ │ ├── 1.avif │ │ ├── 1.png │ │ └── 1.webp ├── no_image.svg ├── opensearch.xml ├── search.svg └── style │ ├── 404.css │ ├── all.css │ ├── home.css │ ├── images.css │ └── videos.css ├── instantAnswers └── finance.hbs ├── okteto-stack.yaml ├── pages ├── 404.hbs ├── all.hbs ├── home.hbs ├── images.hbs └── videos.hbs ├── src ├── all.ts ├── images.ts ├── utils.ts └── videos.ts └── templates ├── base.hbs ├── menu.hbs ├── navigation.hbs └── search.hbs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | max_line_length = off 10 | insert_final_newline = false 11 | 12 | [*.md] 13 | insert_final_newline = true -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | #PORT=8080 2 | 3 | #LANG="en-US" 4 | #ref: https://github.com/unicode-org/cldr-json/blob/master/cldr-json/cldr-core/availableLocales.json -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/internal-server-error.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Internal Server Error 3 | about: Report internal server errors 4 | title: Internal Server Error 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Search terms**: 11 | 12 | 13 | 14 | **Server location**: 15 | 16 | 17 | 18 | 19 | **Instance used:** 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/workflows/okteto.yml: -------------------------------------------------------------------------------- 1 | on: [push] 2 | name: Deploy 3 | jobs: 4 | devflow: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: checkout 8 | uses: actions/checkout@master 9 | - uses: okteto/login@master 10 | with: 11 | token: ${{ secrets.OKTETO_TOKEN }} 12 | - uses: okteto/namespace@master 13 | - name: "Create stack" 14 | uses: okteto/deploy-stack@master 15 | with: 16 | build: "true" 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #For personal testing propose 2 | test.ts 3 | Makefile -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "editorconfig.editorconfig", 4 | "denoland.vscode-deno", 5 | "yzhang.markdown-all-in-one", 6 | "mikebovenlander.formate" 7 | ] 8 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.format.semicolons": "remove", 3 | "typescript.preferences.quoteStyle": "single", 4 | "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false, 5 | "typescript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces": false, 6 | "[typescript]": { 7 | "editor.defaultFormatter": "vscode.typescript-language-features" 8 | }, 9 | "editor.formatOnSave": true, 10 | "formate.verticalAlignProperties": false, 11 | "deno.enable": true, 12 | "deno.unstable": true, 13 | "deno.importMap": "./IM.json", 14 | "html.format.wrapLineLength": 0, 15 | "deno.suggest.imports.hosts": { 16 | "https://deno.land": true 17 | } 18 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM denoland/deno:1.11.1 2 | EXPOSE 8080 3 | COPY . . 4 | CMD deno run --allow-net --allow-read --allow-env --unstable --import-map=IM.json app.ts 5 | #TODO: wait for Leaf (https://github.com/mandarineorg/leaf) to support Deno 1.10.2 (https://github.com/mandarineorg/leaf/pull/10) 6 | # and for deno_dom to have a working navice backend (https://github.com/b-fuze/deno-dom/issues/48) -------------------------------------------------------------------------------- /IM.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "oak": "https://deno.land/x/oak/mod.ts", 4 | "deno_dom": "https://deno.land/x/deno_dom/deno-dom-wasm.ts", 5 | "mustache_ts": "https://deno.land/x/mustache_ts/mustache.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Goodgle

2 |

A metasearch engine based on Google written in Deno 🦕

3 | 4 | --- 5 | 6 | ![Goodgle Demo](https://github.com/Hunam6/Goodgle/assets/38606542/544a5d81-4f43-45ad-aadc-251366cf5942) 7 | 8 | ### ⚠️ Current state of the project ⚠️ 9 | 10 | I haven't been posting updates since some days because I'm working on a V2 written in [V](https://github.com/vlang/v)! The current project is pretty usable but, the speed isn't good enough. For a casual website that's good but for a search engine it isn't satisfying enough. I'm also currently blocked by a [bug/feature in V itself](https://github.com/vlang/v/issues/10683). 11 | 12 | ### *This project is at an early stage!* 13 | 14 | ## Goals 15 | 16 | - Based on Google to get best results 17 | - Beautiful UI and innovative UX 18 | - Privacy respecting (no cookies, no link-tracking, random user-agent, etc.) 19 | - No ads 20 | - Proxy support (maybe Tor too in the future) 21 | - Optional customization 22 | 23 | Non-goal: support old, or even not so old, browsers 24 | 25 | ## Usage 26 | 27 | ### Quickly test it 28 | 29 | You can quickly test it on my instances, just remember to only use it for testing proposes as they may be unavailable during some dev sessions: 30 | 31 | - 32 | - 33 | 34 | ### Locally 35 | 36 | git clone https://github.com/Hunam6/Goodgle & cd Goodgle 37 | deno run --allow-net --allow-read --allow-env --unstable --import-map=IM.json app.ts 38 | 39 | ### On Railway 40 | 41 | Click on [![Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2FHunam6%2FGoodgle&envs=PORT&PORTDefault=8080) 42 | 43 | ### On Okteto 44 | 45 | Click on [![Okteto](https://okteto.com/develop-okteto.svg)](https://cloud.okteto.com/deploy?repository=https://github.com/Hunam6/Goodgle) 46 | 47 | ## Contributing 48 | 49 | Hi and welcome! Contributing is very welcome! Every type of contribution is welcomed, just remember to follow the [GitHub Community Guidelines](https://docs.github.com/articles/github-community-guidelines). 50 | 51 | - Report `Internal Server Error`s when performing searches, don't forget to provide the search terms and the geographic location of the server (or the instance used). 52 | 53 | - Submit an issue. 54 | 55 | - Submit a PR: please follow the styling rules indicated in `.editorconfig` and `.vscode/settings.json`. 56 | 57 | *__PS__: if you don't know where to start look at all the "TODO" comments (I recommend [Todo Tree](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.todo-tree) for VSCode).* 58 | 59 | ## License 60 | 61 | This project is licensed under the [Mozilla Public License 2.0](./LICENSE). 62 | 63 | ## Credits 64 | 65 | [Whoogle](https://github.com/benbusby/whoogle-search) by [@benbusby](https://github.com/benbusby): the original idea 66 | 67 | [Oak](https://github.com/oakserver/oak) by [@kitsonk](https://github.com/kitsonk): the web framework/middleware 68 | 69 | [Deno DOM](https://github.com/b-fuze/deno-dom) by [@b-fuze](https://github.com/b-fuze): the DOM parser 70 | 71 | [Dotenv](https://github.com/pietvanzoen/deno-dotenv) by [@bpietvanzoen](https://github.com/pietvanzoen): the .env file handler 72 | 73 | [Mustache.ts](https://github.com/fabrv/mustache.ts) by [@fabrv](https://github.com/fabrv): the template engine 74 | -------------------------------------------------------------------------------- /app.ts: -------------------------------------------------------------------------------- 1 | import {Application, Router, helpers, send} from 'oak' 2 | import {DOMParser} from 'deno_dom' 3 | import {exists} from 'https://deno.land/std/fs/mod.ts' 4 | import 'https://deno.land/x/dotenv/load.ts' 5 | import {getRdmUA, fetchURL, getFinanceData, rendSearch, rendPage} from './src/utils.ts' 6 | import {all} from './src/all.ts' 7 | import {images} from './src/images.ts' 8 | import {videos} from './src/videos.ts' 9 | 10 | function getLang(params: Record = {}): string { 11 | let lang = '' 12 | if ((Deno.env.get('LANG') || params.lang) != undefined) { 13 | if ((Deno.env.get('LANG') && params.lang) != undefined || params.lang != undefined) lang = params.lang 14 | else lang = Deno.env.get('LANG')!.toString() 15 | } 16 | return lang 17 | } 18 | 19 | async function search(params: Record) { 20 | //TODO: proxy support (maybe use https://proxyscrape.com/free-proxy-list) 21 | //TODO: add tests like with no results etc 22 | //TODO: blacklist sites 23 | 24 | let URL = 'https://google.com/search?q=' + params.q 25 | if (params.page != undefined) URL += '&start=' + (parseInt(params.page) - 1) + '0' //Handle page 26 | URL += '&hl=' + getLang(params) 27 | if (params.trueSpelling != undefined) URL += '&nfpr=' + params.trueSpelling //Handle force good spelling 28 | const getDoc = async (URL: string) => new DOMParser().parseFromString(await fetchURL(URL).then(res => res.text()), 'text/html')! 29 | 30 | //Handle tabs 31 | switch (params.tab) { 32 | case 'images': 33 | URL += '&tbm=isch' 34 | return images(await getDoc(URL), getLang(params)) 35 | case 'videos': 36 | URL += '&tbm=vid' 37 | return videos(await getDoc(URL), getLang(params)) 38 | case 'news': 39 | URL += '&tbm=nws' 40 | //TODO: news 41 | break 42 | case 'shopping': 43 | URL += '&tbm=shop' 44 | //TODO: shopping 45 | break 46 | case 'books': 47 | URL += '&tbm=bks' 48 | //TODO: books 49 | break 50 | case 'maps': 51 | //TODO: maps 52 | break 53 | case 'flights': 54 | //TODO: flights 55 | break 56 | case 'finance': 57 | //TODO: finance 58 | break 59 | default: 60 | return all(await getDoc(URL), getLang(params)) 61 | } 62 | } 63 | 64 | const app = new Application() 65 | const router = new Router() 66 | app.addEventListener('listen', ({secure, hostname, port}) => console.log('Listening on: ' + (secure ? 'https://' : 'http://') + (hostname ?? 'localhost') + ':' + port)) 67 | router 68 | .get('/', async ctx => { 69 | ctx.response.body = await rendPage('home', {...await rendSearch()}, getLang()) 70 | }) 71 | .get('/search', async ctx => { 72 | ctx.response.body = await search(helpers.getQuery(ctx)) 73 | }) 74 | .get('/autocomplete', async ctx => { 75 | ctx.response.body = await fetchURL('https://suggestqueries.google.com/complete/search?client=toolbar&q=' + helpers.getQuery(ctx).q) 76 | .then(res => res.text()) 77 | .then(res => { 78 | const suggestions: [string, string[]] = [helpers.getQuery(ctx).q, res.split('"').filter((_, b) => b % 2 !== 0)] 79 | suggestions[1].shift() 80 | suggestions[1].forEach((el, i) => suggestions[1][i] = el.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(dec))) 81 | return suggestions 82 | }) 83 | }) 84 | .get('/opensearch.xml', async ctx => { 85 | ctx.response.headers.set('Content-Type', 'application/opensearchdescription+xml') 86 | ctx.response.body = (await Deno.readTextFile('./assets/opensearch.xml')).replaceAll('{URL}', ctx.request.url.origin) 87 | }) 88 | .get('/cors', async ctx => { 89 | ctx.response.body = await fetch(helpers.getQuery(ctx).url, { 90 | headers: { 91 | 'user-agent': getRdmUA() 92 | } 93 | }).then(res => res.blob()) 94 | ctx.response.headers.set('Access-Control-Allow-Origin', '*') 95 | }).get('/finance', async ctx => { 96 | ctx.response.body = await getFinanceData(helpers.getQuery(ctx).period, helpers.getQuery(ctx).id) 97 | }) 98 | 99 | app.use(router.routes()) 100 | app.use(router.allowedMethods()) 101 | 102 | app.use(async ctx => { 103 | if (await exists('./assets' + ctx.request.url.pathname)) await send(ctx, ctx.request.url.pathname, {root: './assets'}) 104 | else { 105 | ctx.response.body = await rendPage('404', {}, getLang()) 106 | ctx.response.status = 404 107 | } 108 | }) 109 | 110 | await app.listen({port: Deno.env.get('PORT') == undefined ? 8080 : parseInt(Deno.env.get('PORT')!)}) 111 | 112 | //TODO: Deno Deploy support 113 | //TODO: Qovery support -------------------------------------------------------------------------------- /assets/goodgle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/instructions/brave/1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/brave/1.avif -------------------------------------------------------------------------------- /assets/instructions/brave/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/brave/1.png -------------------------------------------------------------------------------- /assets/instructions/brave/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/brave/1.webp -------------------------------------------------------------------------------- /assets/instructions/brave_mobile/1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/brave_mobile/1.avif -------------------------------------------------------------------------------- /assets/instructions/brave_mobile/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/brave_mobile/1.png -------------------------------------------------------------------------------- /assets/instructions/brave_mobile/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/brave_mobile/1.webp -------------------------------------------------------------------------------- /assets/instructions/brave_mobile/2.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/brave_mobile/2.avif -------------------------------------------------------------------------------- /assets/instructions/brave_mobile/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/brave_mobile/2.png -------------------------------------------------------------------------------- /assets/instructions/brave_mobile/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/brave_mobile/2.webp -------------------------------------------------------------------------------- /assets/instructions/brave_mobile/3.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/brave_mobile/3.avif -------------------------------------------------------------------------------- /assets/instructions/brave_mobile/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/brave_mobile/3.png -------------------------------------------------------------------------------- /assets/instructions/brave_mobile/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/brave_mobile/3.webp -------------------------------------------------------------------------------- /assets/instructions/chrome/1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/chrome/1.avif -------------------------------------------------------------------------------- /assets/instructions/chrome/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/chrome/1.png -------------------------------------------------------------------------------- /assets/instructions/chrome/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/chrome/1.webp -------------------------------------------------------------------------------- /assets/instructions/chrome_mobile/1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/chrome_mobile/1.avif -------------------------------------------------------------------------------- /assets/instructions/chrome_mobile/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/chrome_mobile/1.png -------------------------------------------------------------------------------- /assets/instructions/chrome_mobile/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/chrome_mobile/1.webp -------------------------------------------------------------------------------- /assets/instructions/chrome_mobile/2.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/chrome_mobile/2.avif -------------------------------------------------------------------------------- /assets/instructions/chrome_mobile/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/chrome_mobile/2.png -------------------------------------------------------------------------------- /assets/instructions/chrome_mobile/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/chrome_mobile/2.webp -------------------------------------------------------------------------------- /assets/instructions/edge/1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/edge/1.avif -------------------------------------------------------------------------------- /assets/instructions/edge/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/edge/1.png -------------------------------------------------------------------------------- /assets/instructions/edge/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/edge/1.webp -------------------------------------------------------------------------------- /assets/instructions/edge_mobile/1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/edge_mobile/1.avif -------------------------------------------------------------------------------- /assets/instructions/edge_mobile/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/edge_mobile/1.png -------------------------------------------------------------------------------- /assets/instructions/edge_mobile/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/edge_mobile/1.webp -------------------------------------------------------------------------------- /assets/instructions/edge_mobile/2.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/edge_mobile/2.avif -------------------------------------------------------------------------------- /assets/instructions/edge_mobile/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/edge_mobile/2.png -------------------------------------------------------------------------------- /assets/instructions/edge_mobile/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/edge_mobile/2.webp -------------------------------------------------------------------------------- /assets/instructions/firefox/1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox/1.avif -------------------------------------------------------------------------------- /assets/instructions/firefox/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox/1.png -------------------------------------------------------------------------------- /assets/instructions/firefox/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox/1.webp -------------------------------------------------------------------------------- /assets/instructions/firefox/2.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox/2.avif -------------------------------------------------------------------------------- /assets/instructions/firefox/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox/2.png -------------------------------------------------------------------------------- /assets/instructions/firefox/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox/2.webp -------------------------------------------------------------------------------- /assets/instructions/firefox_mobile/1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_mobile/1.avif -------------------------------------------------------------------------------- /assets/instructions/firefox_mobile/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_mobile/1.png -------------------------------------------------------------------------------- /assets/instructions/firefox_mobile/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_mobile/1.webp -------------------------------------------------------------------------------- /assets/instructions/firefox_mobile/2.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_mobile/2.avif -------------------------------------------------------------------------------- /assets/instructions/firefox_mobile/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_mobile/2.png -------------------------------------------------------------------------------- /assets/instructions/firefox_mobile/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_mobile/2.webp -------------------------------------------------------------------------------- /assets/instructions/firefox_mobile/3.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_mobile/3.avif -------------------------------------------------------------------------------- /assets/instructions/firefox_mobile/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_mobile/3.png -------------------------------------------------------------------------------- /assets/instructions/firefox_mobile/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_mobile/3.webp -------------------------------------------------------------------------------- /assets/instructions/firefox_proton/1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_proton/1.avif -------------------------------------------------------------------------------- /assets/instructions/firefox_proton/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_proton/1.png -------------------------------------------------------------------------------- /assets/instructions/firefox_proton/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_proton/1.webp -------------------------------------------------------------------------------- /assets/instructions/firefox_proton/2.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_proton/2.avif -------------------------------------------------------------------------------- /assets/instructions/firefox_proton/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_proton/2.png -------------------------------------------------------------------------------- /assets/instructions/firefox_proton/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/firefox_proton/2.webp -------------------------------------------------------------------------------- /assets/instructions/opera/1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/opera/1.avif -------------------------------------------------------------------------------- /assets/instructions/opera/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/opera/1.png -------------------------------------------------------------------------------- /assets/instructions/opera/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/opera/1.webp -------------------------------------------------------------------------------- /assets/instructions/unknown/1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/unknown/1.avif -------------------------------------------------------------------------------- /assets/instructions/unknown/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/unknown/1.png -------------------------------------------------------------------------------- /assets/instructions/unknown/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hunam6/Goodgle/47bfbae1ea9df9a607e86e4ded14928592c07b3c/assets/instructions/unknown/1.webp -------------------------------------------------------------------------------- /assets/no_image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/opensearch.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | Goodgle 5 | Goodgle - The beautiful/private metasearch engine based on Google written in Deno 🦕 6 | goodgle search-engine google privacy oak metasearch metasearch-engine deno 7 | UTF-8 8 | {URL}/goodgle.svg 9 | 10 | 11 | 12 | 13 | 14 | {URL}/search 15 | Hunam (https://github.com/Hunam6) 16 | -------------------------------------------------------------------------------- /assets/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/style/404.css: -------------------------------------------------------------------------------- 1 | body { 2 | position: absolute; 3 | top: 50%; 4 | left: 50%; 5 | transform: translate(-50%, -50%) 6 | } 7 | 8 | div { 9 | background: var(--gray); 10 | border-radius: 25px; 11 | padding: 1rem; 12 | border: .5px solid var(--border); 13 | text-align: center 14 | } 15 | 16 | #home { 17 | margin: 1.5rem 1rem 1rem; 18 | display: block; 19 | font-weight: 700; 20 | background: var(--gray); 21 | border: .5px solid var(--darker); 22 | border-radius: 25px; 23 | padding: .5rem 24 | } -------------------------------------------------------------------------------- /assets/style/all.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 6.5rem 0 0 3 | } 4 | 5 | body>p { 6 | width: 90%; 7 | margin: 0 auto 1.5rem auto; 8 | color: var(--dark); 9 | font-weight: 600 10 | } 11 | 12 | aside { 13 | margin: 0 auto; 14 | background: var(--gray); 15 | border: .5px solid var(--border); 16 | width: 90%; 17 | border-radius: 25px; 18 | padding: .5rem 1rem 19 | } 20 | 21 | .head { 22 | display: flex; 23 | justify-content: space-between 24 | } 25 | 26 | .head>div { 27 | border: none 28 | } 29 | 30 | .head>div>h2 { 31 | margin: 0 32 | } 33 | 34 | .head>div>p { 35 | margin: 0; 36 | font-weight: normal; 37 | color: var(--dark) 38 | } 39 | 40 | .head>img { 41 | padding: 1rem 42 | } 43 | 44 | aside>p>b { 45 | font-weight: 600 46 | } 47 | 48 | aside>p { 49 | color: var(--dark) 50 | } 51 | 52 | aside>div>h3 { 53 | font-weight: bold 54 | } 55 | 56 | aside>div>div { 57 | border-radius: 25px; 58 | border: .5px solid var(--border); 59 | padding: 1rem .5rem 60 | } 61 | 62 | .quote-add-info { 63 | display: block; 64 | margin: 1rem 0 65 | } 66 | 67 | .work-add-info { 68 | display: block; 69 | margin: .5rem 70 | } 71 | 72 | .critic-add-info { 73 | margin: .5rem 74 | } 75 | 76 | .critic-add-info>div { 77 | color: var(--dark) 78 | } 79 | 80 | .critic-add-info>div:nth-child(2) { 81 | height: 1.5rem; 82 | display: flex; 83 | justify-items: center; 84 | margin: 1rem 0 85 | } 86 | 87 | .critic-add-info>div>a { 88 | font-weight: 525 89 | } 90 | 91 | .critic-add-info>div>img { 92 | height: 100%; 93 | margin-right: .5rem; 94 | border-radius: 5px 95 | } 96 | 97 | .normal-add-info { 98 | display: flex; 99 | align-items: center; 100 | justify-content: space-around 101 | } 102 | 103 | .normal-add-info>a { 104 | width: 20%; 105 | text-align: center 106 | } 107 | 108 | .normal-add-info>a>img { 109 | width: 90%; 110 | height: attr(width) 111 | } 112 | 113 | .normal-add-info>a>h4, 114 | .normal-add-info>a>h5 { 115 | font-weight: normal; 116 | color: var(--dark); 117 | margin: 0; 118 | font-size: small; 119 | line-height: 110%; 120 | text-align: center 121 | } 122 | 123 | .normal-add-info>a>h5 { 124 | font-size: smaller 125 | } 126 | 127 | .platform-add-info>div { 128 | display: flex; 129 | align-items: center; 130 | margin: .5rem 131 | } 132 | 133 | .platform-add-info>div>img { 134 | margin-right: .5rem 135 | } 136 | 137 | main { 138 | margin: 0 0 -1rem 139 | } 140 | 141 | #add { 142 | text-align: center 143 | } 144 | 145 | #add>button { 146 | background: var(--dark); 147 | color: var(--white); 148 | padding: .5rem 1rem; 149 | border: none; 150 | border-radius: 100vw; 151 | font-weight: bold; 152 | font-size: .75rem; 153 | cursor: pointer 154 | } 155 | 156 | article, 157 | #add { 158 | background: var(--gray); 159 | width: 90%; 160 | border-radius: 25px; 161 | padding: 1rem; 162 | margin: 1.5rem auto; 163 | border: .5px solid var(--border) 164 | } 165 | 166 | .link-title, 167 | .quick-answers>p, 168 | #add>p { 169 | font-weight: 600 170 | } 171 | 172 | .link-title:visited, 173 | .link:visited { 174 | color: var(--visited) 175 | } 176 | 177 | .link { 178 | color: var(--dark); 179 | font-weight: 600 180 | } 181 | 182 | .link-text, 183 | .link-text>a { 184 | color: var(--dark) 185 | } 186 | 187 | .link-text>a { 188 | font-weight: 600 189 | } 190 | 191 | .sub-results { 192 | display: none 193 | } 194 | 195 | .sub-results>article { 196 | margin: 1rem; 197 | max-width: 25% 198 | } 199 | 200 | .quick-answers { 201 | display: grid; 202 | grid-template-columns: 1.5rem 1fr; 203 | grid-gap: .25rem; 204 | padding: 1rem .125rem 205 | } 206 | 207 | .quick-answers>button { 208 | background: none; 209 | border: none; 210 | cursor: pointer; 211 | font-size: x-large; 212 | width: 1.5rem; 213 | height: 1.5rem; 214 | position: relative; 215 | top: 50%; 216 | transform: translateY(-50%); 217 | line-height: 0 218 | } 219 | 220 | .quick-answers>p { 221 | display: inline; 222 | margin: 0; 223 | cursor: pointer 224 | } 225 | 226 | .quick-answers>div { 227 | grid-column: 1 / 3; 228 | border: .5px solid var(--border); 229 | border-radius: 25px 230 | } 231 | 232 | .quick-answers>div * { 233 | font-size: small 234 | } 235 | 236 | .quick-answers>div *:not(b) { 237 | font-weight: normal 238 | } 239 | 240 | .quick-answers>div b { 241 | font-weight: 600 242 | } 243 | 244 | @media (min-width: 1024px) { 245 | aside { 246 | width: 20%; 247 | position: absolute; 248 | right: 2vw; 249 | top: 6.5rem 250 | } 251 | 252 | .normal-add-info>a>img { 253 | width: 70% 254 | } 255 | 256 | #add { 257 | width: 20%; 258 | position: fixed; 259 | left: 2vw; 260 | top: 6.5rem; 261 | margin: 0 262 | } 263 | 264 | main { 265 | margin: 0 0 2rem 266 | } 267 | 268 | body>p, 269 | article { 270 | width: 50% 271 | } 272 | 273 | .sub-results { 274 | display: flex 275 | } 276 | 277 | .quick-answers { 278 | padding: 1rem .5rem 279 | } 280 | 281 | .quick-answers>p { 282 | user-select: none 283 | } 284 | 285 | .quick-answers>div * { 286 | font-size: unset 287 | } 288 | } -------------------------------------------------------------------------------- /assets/style/home.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | code { 6 | padding: .125rem .5rem; 7 | border-radius: 10px; 8 | border: .5px solid var(--border) 9 | } 10 | 11 | #add { 12 | z-index: 3; 13 | position: absolute; 14 | left: 50%; 15 | transform: translateX(-50%); 16 | padding: 1.5rem; 17 | text-align: center 18 | } 19 | 20 | #add>div { 21 | display: flex; 22 | flex-direction: column; 23 | } 24 | 25 | #add>div>div { 26 | background: var(--gray); 27 | padding: .5rem; 28 | margin: .5rem auto; 29 | border-radius: 25px; 30 | border: .5px solid var(--border) 31 | } 32 | 33 | #add>div>div>p { 34 | font-size: 1rem; 35 | margin: .5rem; 36 | line-height: 150%; 37 | } 38 | 39 | picture>img { 40 | width: 80vw 41 | } 42 | 43 | #add>button { 44 | width: 70%; 45 | margin: .5rem auto 1rem auto; 46 | background: var(--dark); 47 | color: var(--white); 48 | padding: .5rem 1rem; 49 | border: none; 50 | border-radius: 100vw; 51 | font-weight: bold; 52 | font-size: .75rem; 53 | cursor: pointer 54 | } 55 | 56 | #logo { 57 | width: 30%; 58 | position: fixed; 59 | top: 30%; 60 | left: 50%; 61 | transform: translateX(-50%) 62 | } 63 | 64 | form, 65 | #autocomplete { 66 | top: 50% !important 67 | } 68 | 69 | @media (min-width: 1024px) { 70 | #add { 71 | left: unset; 72 | right: 1rem; 73 | transform: unset 74 | } 75 | 76 | #add>div>div { 77 | width: 22.5vw 78 | } 79 | 80 | picture>img { 81 | width: 80% 82 | } 83 | 84 | #add>button { 85 | width: 50%; 86 | padding: .5rem 0; 87 | font-size: 1rem 88 | } 89 | 90 | #logo { 91 | width: 7.5vw 92 | } 93 | } -------------------------------------------------------------------------------- /assets/style/images.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 6.5rem 0 0 3 | } 4 | 5 | body>p { 6 | width: 90%; 7 | margin: 0 auto 1.5rem auto; 8 | color: var(--dark); 9 | font-weight: 600 10 | } 11 | 12 | main { 13 | display: flex; 14 | flex-wrap: wrap; 15 | justify-content: space-around; 16 | align-items: center 17 | } 18 | 19 | main>img { 20 | margin: .5rem .25rem; 21 | cursor: pointer; 22 | max-width: 40%; 23 | height: auto 24 | } 25 | 26 | aside { 27 | display: none; 28 | position: fixed; 29 | bottom: 0; 30 | z-index: 1; 31 | background: var(--gray); 32 | border-radius: 2.5rem 2.5rem 0 0; 33 | border: 0.5px var(--border); 34 | border-style: solid none solid solid; 35 | padding: 2.5rem 1rem; 36 | text-align: center; 37 | width: 100%; 38 | height: 85vh 39 | } 40 | 41 | figure { 42 | border-radius: 1rem; 43 | border: 0.5px solid var(--border); 44 | margin: 0 auto; 45 | width: max-content; 46 | max-width: 80% 47 | } 48 | 49 | figure>a>img { 50 | border-radius: 1rem 1rem 0 0; 51 | max-width: 100%; 52 | max-height: 50vh 53 | } 54 | 55 | figure>a>p { 56 | font-weight: 600; 57 | margin: .5rem 0 58 | } 59 | 60 | aside>div { 61 | height: 1rem; 62 | width: 1rem; 63 | border: solid var(--darker); 64 | border-width: 0 0 .2rem .2rem; 65 | position: absolute; 66 | top: .25rem; 67 | left: 50%; 68 | transform: translateX(-50%) rotate(-45deg); 69 | cursor: pointer 70 | } 71 | 72 | @media (min-width: 1024px) { 73 | body>p { 74 | width: unset; 75 | margin: 0 1rem 1.5rem 76 | } 77 | 78 | main { 79 | margin: 0 1rem 2rem 80 | } 81 | 82 | main>img { 83 | max-width: unset 84 | } 85 | 86 | aside { 87 | z-index: unset; 88 | bottom: unset; 89 | right: 0; 90 | top: 50%; 91 | transform: translateY(-50%); 92 | border-radius: 2.5rem 0 0 2.5rem; 93 | width: 32.5%; 94 | height: 75vh 95 | } 96 | 97 | aside>div { 98 | top: 50%; 99 | left: 1rem; 100 | transform: translateX(-50%) rotate(-135deg) 101 | } 102 | } -------------------------------------------------------------------------------- /assets/style/videos.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 6.5rem 0 0 3 | } 4 | 5 | body>p { 6 | width: 90%; 7 | margin: 0 auto 1.5rem auto; 8 | color: var(--dark); 9 | font-weight: 600 10 | } 11 | 12 | main { 13 | margin: 0 0 -1rem 14 | } 15 | 16 | article { 17 | background: var(--gray); 18 | width: 90%; 19 | border-radius: 25px; 20 | padding: 1rem; 21 | margin: 1.5rem auto; 22 | border: .5px solid var(--border) 23 | } 24 | 25 | article>div { 26 | margin-top: .75rem; 27 | display: grid; 28 | grid-template-columns: 1fr auto 29 | } 30 | 31 | .title { 32 | font-weight: 600 33 | } 34 | 35 | .title:visited { 36 | color: var(--visited) 37 | } 38 | 39 | article>div>a { 40 | display: flex; 41 | align-items: center; 42 | } 43 | 44 | div>a>img { 45 | width: calc(116*0.075rem); 46 | height: calc(65*0.075rem); 47 | border-radius: .5rem 48 | } 49 | 50 | .infos, 51 | .desc { 52 | color: var(--dark) 53 | } 54 | 55 | .infos { 56 | margin-left: .5rem; 57 | font-size: .85rem 58 | } 59 | 60 | .desc { 61 | font-size: .95rem; 62 | grid-column: 1/3; 63 | margin: .5rem 0 64 | } 65 | 66 | @media (min-width: 1024px) { 67 | 68 | main { 69 | margin: 0 0 2rem 70 | } 71 | 72 | body>p, 73 | article { 74 | width: 50% 75 | } 76 | 77 | article>div { 78 | grid-template-columns: auto 1fr 79 | } 80 | 81 | article>div>a { 82 | margin-right: 1rem; 83 | grid-row: 1/3 84 | } 85 | 86 | .infos { 87 | margin: 0 88 | } 89 | 90 | .desc { 91 | grid-column: unset 92 | } 93 | } -------------------------------------------------------------------------------- /instantAnswers/finance.hbs: -------------------------------------------------------------------------------- 1 | {{#hasFinance}} 2 |
3 |
4 |
5 |

{{finance.legend.price}}

6 |

{{finance.legend.currency}}

7 |

{{finance.legend.priceEvolution}}

8 |
9 |

{{finance.legend.exchange}}

10 |
11 |
12 | {{#finance.legend.btns}} 13 | 14 | {{/finance.legend.btns}} 15 |
16 |
17 | 18 |
19 |
20 | {{#finance.details}} 21 |

{{title}}: {{content}}

22 | {{/finance.details}} 23 |
24 |
25 | {{/hasFinance}} 26 | 27 | 28 | 117 | 118 | -------------------------------------------------------------------------------- /okteto-stack.yaml: -------------------------------------------------------------------------------- 1 | name: goodgle 2 | services: 3 | goodgle: 4 | public: true 5 | build: . 6 | image: okteto.dev/goodgle:dev 7 | ports: 8 | - 8080 9 | -------------------------------------------------------------------------------- /pages/404.hbs: -------------------------------------------------------------------------------- 1 |
2 |

Mhhh..

3 |

You shouldn't be there.. SO:

4 |
    5 |
  • You're experimenting with something 👀
  • 6 |
  • You don't know how you got there, if you think it's an issue please report it 🙏
  • 7 |
8 | Go back home 🏠 9 |
10 | -------------------------------------------------------------------------------- /pages/all.hbs: -------------------------------------------------------------------------------- 1 |

{{{proposition}}}

2 | {{{search}}} 3 | {{#hasKnwlPanel}} 4 | 81 | {{/hasKnwlPanel}} 82 |
83 | {{{finance}}} 84 | 99 | {{#hasQuickAnswers}} 100 |
101 | {{#quickAnswers}} 102 | 103 |

{{question}}

104 |
{{{answer}}}
105 | {{/quickAnswers}} 106 |
107 | {{/hasQuickAnswers}} 108 | {{#results}} 109 | 115 | {{/results}} 116 |
117 | {{{menu}}} 118 | {{{navigation}}} 119 | 120 | -------------------------------------------------------------------------------- /pages/home.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{{search}}} 3 | 4 | -------------------------------------------------------------------------------- /pages/images.hbs: -------------------------------------------------------------------------------- 1 |

{{{proposition}}}

2 | {{{search}}} 3 |
4 | {{#IMGs}} 5 | {{desc}} 6 | {{/IMGs}} 7 |
8 | 9 | {{{menu}}} 10 | 11 | -------------------------------------------------------------------------------- /pages/videos.hbs: -------------------------------------------------------------------------------- 1 |

{{{proposition}}}

2 | {{{search}}} 3 |
4 | {{#results}} 5 | 15 | {{/results}} 16 |
17 | {{{menu}}} 18 | {{{navigation}}} 19 | 20 | -------------------------------------------------------------------------------- /src/all.ts: -------------------------------------------------------------------------------- 1 | //deno-lint-ignore-file no-explicit-any 2 | import {DOMParser} from 'deno_dom' 3 | import {getBigFatJS, getQuery, rendSearch, rendMenu, rendPage, rendNavigation, rendFinance} from '../src/utils.ts' 4 | import type {Document} from 'deno_dom' 5 | export const all = async (doc: Document, lang: string) => { 6 | let data: any = { 7 | query: getQuery(doc), 8 | proposition: '', 9 | firstResult: {}, 10 | firstResults: [], 11 | hasQuickAnswers: true, 12 | quickAnswers: [], 13 | results: [], 14 | hasKnwlPanel: true, 15 | knwlPanel: { 16 | title: '', 17 | subtitle: '', 18 | desc: '', 19 | infos: [], 20 | additionalInfos: [] 21 | }, 22 | IMGs: [], 23 | stringedIMGs: '' 24 | } 25 | 26 | let noFirstResult = false 27 | //TODO: error 500 with q=& 28 | 29 | //Spell check / No results 30 | //eg: q=minecraftg 31 | if (doc.querySelector('#fprs')) data.proposition = 32 | doc.querySelector('.gL9Hy')!.textContent 33 | + ' ' 36 | + doc.querySelectorAll('.gL9Hy')[1].textContent 37 | + ', ' 38 | + doc.querySelector('.spell_orig')!.innerHTML.split('<')[0].toLocaleLowerCase() 39 | + ' ' 42 | + data.query 43 | + '' 44 | //eg: q=minecraft+fdsfdsfsffdgfdgdfgdsqdsqdsqdsq+dsqd+sd+sqd+sqd+sd+sq+sqdq 45 | if (doc.querySelector('.gqLncc') && doc.querySelector('.uzjuFc')) { 46 | noFirstResult = true 47 | const divParts = doc.querySelector('.gqLncc')!.textContent.split(':') 48 | data.firstResult = { 49 | title: doc.querySelector('.v3jTId')!.textContent, 50 | desc: 51 | divParts[0] 52 | + ': ' 55 | + divParts[1] 56 | + '
' 57 | + doc.querySelector('.Cy9gW')!.textContent.replace('.', '.
') 58 | } 59 | } 60 | //eg: q=minecraft+fdsfdsfsffdgfdgdfgdsqdsqdsqdsq+dsqd+sd+sqd+sqd+sd+sq 61 | if (!doc.querySelector('.gqLncc') && doc.querySelector('.uzjuFc')) { 62 | noFirstResult = true 63 | data.firstResult = { 64 | title: doc.querySelector('.v3jTId')!.textContent, 65 | desc: doc.querySelector('.Cy9gW')!.textContent 66 | } 67 | } 68 | //eg: q=minecraft+hhjhjhjhjhjhjh 69 | if (doc.querySelector('.gqLncc') && !doc.querySelector('.uzjuFc')) data.proposition = 70 | doc.querySelector('.gL9Hy')!.textContent 71 | + ' ' 74 | + doc.querySelectorAll('.gL9Hy')![1].textContent 75 | + '' 76 | //eg: q=minecraft+fdsfdsfsffdgfdgdfgdsqdsqdsqdsq+dsqdsqhdsqhdhsqbdshqbdsqdbsqhdb 77 | if (doc.querySelector('.card-section>ul')) { 78 | noFirstResult = true 79 | data.firstResult = { 80 | title: doc.querySelector('.card-section')!.textContent.split('(')[0] 81 | } 82 | doc.querySelectorAll('ul')![3].textContent.split('.').forEach((el, i) => { 83 | if (i === 0) data.firstResult.desc = el + ', ' 84 | if (i === 1) data.firstResult.desc += el.toLocaleLowerCase() + ', ' 85 | if (i === 2) data.firstResult.desc += el.toLocaleLowerCase() + '.' 86 | }) 87 | } 88 | //eg: q=abc+dsqddsqddsqdsqdsdsqdsqddsqsqdsqdsqdsqdsqd 89 | if (doc.querySelector('#result-stats')) if (doc.querySelector('#result-stats')!.textContent.split(/\s/g)[1] === '0') { 90 | noFirstResult = true 91 | data.firstResult = { 92 | title: '0 ' + doc.querySelector('#result-stats')!.textContent.split(/\s/g)[2] 93 | } 94 | } 95 | 96 | //Results 97 | doc.querySelectorAll('.LC20lb.DKV0Md').forEach((el, i) => (data.results[i] = {title: el.textContent})) //results title 98 | doc.querySelectorAll('.IsZvec').forEach((el, i) => (data.results[i].desc = el.children[0].innerHTML)) //results description 99 | doc.querySelectorAll('.TbwUpd.NJjxre').forEach((el, i) => (data.results[i].shownLink = el.textContent)) //results shown link 100 | doc.querySelectorAll('.yuRUbf').forEach((el, i) => (data.results[i].link = el.children[0].getAttribute('href')!)) //results link 101 | if (!noFirstResult) data.firstResult = data.results.shift() //first result 102 | if (doc.querySelectorAll('.st').length !== 1) { 103 | doc.querySelectorAll('.l').forEach((el, i) => (data.firstResults[i] = {title: el.textContent})) //first results title 104 | doc.querySelectorAll('.usJj9c').forEach((el, i) => (data.firstResults[i].desc = el.textContent)) //first results description 105 | doc.querySelectorAll('.l').forEach((el, i) => (data.firstResults[i].link = el.parentElement!.children[0].getAttribute('href')!)) //first results link 106 | data.firstResults = data.firstResults.slice(0, 4) //limit to 4 first results 107 | } 108 | 109 | //Quick answers 110 | if (doc.querySelector('.JolIg')) { 111 | let i = 0 112 | getBigFatJS(doc) 113 | .split(');}') 114 | .filter(el => (el.includes('iOBnre') || el.includes('wDYxhc')) && !el.includes('Ya0K2e')) 115 | .map(el => JSON.parse('"' + el.split("'")[3].replaceAll('\\x', '\\u00') + '"')) 116 | .forEach(el => { 117 | const doc = new DOMParser().parseFromString(el, 'text/html')! 118 | if (el.includes('iOBnre')) { 119 | data.quickAnswers.push({ 120 | question: doc.querySelector('.iOBnre')!.textContent.split(': ')[1] 121 | }) 122 | i++ 123 | } 124 | else data.quickAnswers[i - 1] != undefined ? data.quickAnswers[i - 1].answer = doc.querySelector('.wDYxhc')!.innerHTML : null 125 | }) 126 | } else data.hasQuickAnswers = false 127 | 128 | //Knowledge panel 129 | if (doc.querySelector('.liYKde.g.VjDLd')) { 130 | data.knwlPanel.title = doc.querySelector('.qrShPb')!.textContent //title 131 | if (doc.querySelector('.wwUB2c')) data.knwlPanel.subtitle = doc.querySelector('.wwUB2c')!.textContent //subtitle 132 | else if (doc.querySelector('.YhemCb')) data.knwlPanel.subtitle = doc.querySelector('.YhemCb')!.textContent 133 | if (doc.querySelector('.kno-rdesc')) data.knwlPanel.desc = doc.querySelector('.kno-rdesc')!.children[1].textContent //description 134 | if (doc.querySelector('.ivg-i')) { //image 135 | const base = doc.querySelector('.ivg-i')!.children[0] 136 | if (doc.querySelector('.ivg-i')) data.knwlPanel.IMG = { 137 | IMG: base.getAttribute('id'), 138 | height: base.getAttribute('height'), 139 | width: base.getAttribute('width') 140 | } 141 | } 142 | 143 | //Infos 144 | doc.querySelectorAll('.wDYxhc').forEach(element => { 145 | const addInfo = (title: string, content: string) => data.knwlPanel.infos.push({ 146 | title: title, 147 | content: content 148 | }) 149 | const createLink = (link: string, shown?: string) => '' + (shown ? shown : link) + '' 150 | 151 | if (element.children[0]) { 152 | const el = element.children[0].parentElement! 153 | const attr = el.getAttribute('data-attrid')! 154 | if (el.hasAttribute('data-attrid')) { 155 | if ( 156 | el.children[0].children[0] && 157 | !el.children[0].classList.contains('kpS1Ac') && 158 | !el.children[0].classList.contains('Ob2kfd') && 159 | !el.querySelector('g-scrolling-carousel') && 160 | !attr.includes('edit') && 161 | !el.querySelector('.Ss2Faf') && 162 | !el.querySelector('.P7hEAd') && 163 | !el.querySelector('.qd9Mkf') && //TODO: like percentage (eg: a film) 164 | !el.querySelector('.PQbOE') 165 | ) { 166 | if (attr === 'kc:/local:scalable_attributes_group') { 167 | //security 168 | const base = el.querySelector('.Wowtd')!.parentElement! 169 | addInfo( 170 | base.children[0].textContent, 171 | base.textContent.slice(base.children[0].textContent.length, -base.children[1].textContent.length - 3) 172 | ) 173 | } else if (attr === 'kc:/collection/knowledge_panels/has_phone:phone') { 174 | //phone 175 | const tel = el.children[0].children[0].children[1].textContent 176 | addInfo( 177 | el.children[0].children[0].children[0].textContent, 178 | createLink('tel: ' + tel, tel) 179 | ) 180 | } else if (attr === 'kc:/location/location:hours') { 181 | //hours 182 | addInfo( 183 | el.querySelector('.GRkHZd')!.textContent, 184 | el.querySelector('.h-n')!.textContent 185 | ) 186 | } else if (attr === 'kc:/location/location:address') { 187 | //address 188 | const link = el.children[0].children[0].children[1].textContent 189 | addInfo( 190 | el.children[0].children[0].children[0].textContent, 191 | createLink('https://www.google.com/maps?q=' + link, link) 192 | ) 193 | } else if (attr.includes('website')) { 194 | //website 195 | const link = el.querySelector('.Eq0J8')!.children[0] 196 | addInfo( 197 | el.querySelector('.GRkHZd')!.textContent, 198 | createLink(link.getAttribute('href')!, link.textContent) 199 | ) 200 | } else if (el.querySelector('.Eq0J8')) { 201 | //list of links 202 | addInfo( 203 | el.querySelector('.GRkHZd')!.textContent, 204 | el.querySelector('.Eq0J8')!.textContent 205 | ) 206 | } else if (el.querySelector('.xFAlBc')) { 207 | //appointment 208 | addInfo( 209 | el.querySelector('b')!.textContent + ': ', 210 | createLink(el.querySelector('.xFAlBc')!.getAttribute('href')!.split('/?utm')[0], el.querySelector('.xFAlBc')!.textContent) 211 | ) 212 | } else if (el.querySelector('c-wiz')) { 213 | //some special cases 214 | const title = el.querySelector('.d2aWRb')!.textContent 215 | addInfo( 216 | title, 217 | el.querySelector('.d2aWRb')!.parentElement!.textContent.substring(title.length) 218 | ) 219 | } else if (el.querySelector('.w8qArf')) { 220 | //normal 221 | addInfo( 222 | el.children[0].children[0].children[0].textContent, 223 | el.children[0].children[0].children[1].textContent 224 | ) 225 | } 226 | } 227 | } 228 | } 229 | }) 230 | 231 | //Additional infos 232 | if (doc.querySelector('.Ss2Faf:not(.ellip)')) { 233 | let index = 0 234 | doc.querySelectorAll('.Ss2Faf:not(.ellip)').forEach(el => { 235 | const next = el.parentElement!.children[1] 236 | //check if not an ad (eg: q=minecraft+dungeons) 237 | if (el.parentElement!.children[0].className.includes('qLYAZd') && !el.parentElement!.className.includes('ellip') && (next.className !== 'JeEise fBkrHb') && (next.getAttribute('style') !== 'display:none')) { 238 | //title 239 | if (el.children[0]) { 240 | if (el.children[0].children[0]) data.knwlPanel.additionalInfos[index] = {title: el.children[0].children[0].textContent} 241 | else data.knwlPanel.additionalInfos[index] = {title: el.textContent} 242 | } 243 | else data.knwlPanel.additionalInfos[index] = {title: el.textContent} 244 | //content 245 | if (next.tagName === 'WEB-QUOTE-CONTAINER') { 246 | //quotes 247 | data.knwlPanel.additionalInfos[index].isQuotes = true 248 | data.knwlPanel.additionalInfos[index].quotes = [] 249 | next.querySelectorAll('.kssN8d').forEach(el => data.knwlPanel.additionalInfos[index].quotes.push(el.textContent)) 250 | } else if (next.tagName === 'CRITIC-REVIEWS-CONTAINER') { 251 | //critic reviews 252 | data.knwlPanel.additionalInfos[index].isCritics = true 253 | data.knwlPanel.additionalInfos[index].critics = [] 254 | next.children[0].children[0].childNodes.forEach(el => data.knwlPanel.additionalInfos[index].critics.push({ 255 | critic: el.children[0].children[0].children[0].textContent, 256 | link: el.children[0].children[0].children[1].getAttribute('href'), 257 | name: el.children[0].children[1].children[1].querySelector('a')!.textContent, 258 | icon: el.children[0].children[1].children[0].children[0].getAttribute('id') 259 | })) 260 | } else if (next.className === 'tpa-cc') { 261 | //musical platforms 262 | data.knwlPanel.additionalInfos[index].isPlatforms = true 263 | data.knwlPanel.additionalInfos[index].platforms = [] 264 | next.querySelectorAll('tr').forEach(el => data.knwlPanel.additionalInfos[index].platforms.push({ 265 | platform: el.textContent, 266 | link: el.children[0].children[0].getAttribute('href'), 267 | icon: el.children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].getAttribute('id') 268 | })) 269 | } else if (next.className === 'AxJnmb gIcqHd') { 270 | //artworks 271 | data.knwlPanel.additionalInfos[index].isWorks = true 272 | data.knwlPanel.additionalInfos[index].works = [] 273 | next.childNodes.forEach(el => data.knwlPanel.additionalInfos[index].works.push({ 274 | work: el.textContent, 275 | link: '/search?q=' + el.textContent 276 | })) 277 | } else if (next.className === 'idWF4b') { 278 | //TODO: handle "Audience reviews" eg: q=minecraft+dungeons 279 | //TODO: fix detecting Audience rating summary 280 | } else if (next.className === 'jYcvae kY5Gde') { 281 | //TODO: handle "Audience rating summary" eg: q=minecraft+dungeons 282 | } else { 283 | //normal 284 | data.knwlPanel.additionalInfos[index].isNormal = true 285 | data.knwlPanel.additionalInfos[index].elements = [] 286 | next.childNodes.forEach((el, j) => { 287 | if (j < 4) data.knwlPanel.additionalInfos[index].elements.push({ 288 | title: el.children[0].children[1] ? el.children[0].children[1].textContent : el.textContent, //additional info title 289 | subtitle: el.children[0].children[2] ? el.children[0].children[2].textContent : null, //additional info subtitle 290 | link: el.parentElement!.children[j].querySelector('a')!.getAttribute('href')!.startsWith('/') ? el.parentElement!.children[j].querySelector('a')!.getAttribute('href')!.split('&')[0] : el.parentElement!.children[j].querySelector('a')!.getAttribute('href'), //additional info link 291 | ID: el.children[0].querySelector('img')!.getAttribute('id') //additional info image ID 292 | }) 293 | }) 294 | } 295 | index++ 296 | } 297 | }) 298 | } 299 | } else data.hasKnwlPanel = false 300 | 301 | //Get images 302 | const IMGs: string[][] = [] 303 | doc.querySelectorAll('script').forEach(el => { 304 | if (el.textContent.includes('{var s')) IMGs.push([ 305 | el.textContent.slice(19).split("'")[0].replaceAll('\\x3d', '='), //Image base64 306 | el.textContent.split("['")[1].split("'")[0] //Image ID 307 | ]) 308 | }) 309 | data.IMGs = JSON.stringify(IMGs) 310 | 311 | data = { 312 | ...data, 313 | ...await rendSearch(doc), 314 | ...await rendMenu(doc), 315 | ...await rendNavigation(), 316 | ...await rendFinance(doc) 317 | } 318 | 319 | return rendPage('all', data, lang) 320 | } -------------------------------------------------------------------------------- /src/images.ts: -------------------------------------------------------------------------------- 1 | //deno-lint-ignore-file no-explicit-any 2 | import {rendSearch, rendMenu, rendPage} from '../src/utils.ts' 3 | import type {Document} from 'deno_dom' 4 | 5 | export const images = async (doc: Document, lang: string) => { 6 | let data: any = { 7 | proposition: '', 8 | IMGs: [], 9 | stringedIMGs: '', 10 | stringedAspectRatio: '' 11 | } 12 | 13 | //Spell check / No results 14 | //eg: q=minecraftg 15 | if (doc.querySelector('.hWrGN')) { 16 | data.proposition = 17 | doc.querySelector('.WxYNlf')!.innerHTML.split('<')[0] 18 | + ' ' 21 | + data.query 22 | + ', ' 23 | if (doc.querySelector('.KtvGCc>.TADXpd')) data.proposition += 24 | doc.querySelector('.KtvGCc')!.innerHTML.split('<')[0].toLocaleLowerCase() 25 | + ' ' 28 | + doc.querySelectorAll('.TADXpd')[1].textContent 29 | + '' 30 | else data.proposition += 31 | doc.querySelector('.KtvGCc')!.textContent.charAt(0).toLocaleLowerCase() 32 | + doc.querySelector('.KtvGCc')!.textContent.slice(1) 33 | } 34 | //eg: q=minecraft+hhjhjhjhjhjhjh 35 | if (doc.querySelector('.G0nQ7b')) data.proposition = 36 | doc.querySelector('.G0nQ7b')!.textContent 37 | + ' ' 40 | + doc.querySelector('.TADXpd')!.textContent 41 | + '' 42 | if (doc.querySelector('.eUYiW')) { 43 | //eg: q=q+dsqdsqhdsqhdh 44 | data.proposition = 45 | '' 48 | + doc.querySelector('.M5HqZb>strong')!.textContent 49 | + '' 50 | + doc.querySelector('.M5HqZb')!.innerHTML.split('>')[2] 51 | + '
' 52 | + doc.querySelector('.eUYiW')!.children[1].textContent 53 | + ' ' 54 | doc.querySelectorAll('.pvadze>li').forEach((el, i) => i < 3 ? data.proposition += el.textContent.toLocaleLowerCase().slice(0, -1) + ', ' : data.proposition += el.textContent.toLocaleLowerCase()) 55 | } else { 56 | //Get images 57 | const baseImgs = JSON.parse(doc.querySelector('#wiz_jd')!.previousElementSibling!.textContent.slice(68, -21).split('"GRID_STATE0",null,')[1].split(',"","","')[0]) 58 | baseImgs.forEach((el: any[], i: number) => (el[0] !== 1 ? baseImgs.splice(i, 1) : null)) 59 | baseImgs.forEach((el: any[], i: number) => { 60 | data.IMGs[i] = { 61 | color: el[1][6], 62 | height: el[1][2][1], 63 | width: el[1][2][2], 64 | resized: el[1][2][0], 65 | original: el[1][3][0], 66 | desc: el[1][9]['2003'][3], 67 | URL: el[1][9]['2003'][2], 68 | title: el[1][9]['2003'][12] 69 | } 70 | }) 71 | data.stringedIMGs = JSON.stringify(data.IMGs.map(({resized}: {resized: number}) => resized)) 72 | data.stringedAspectRatio = JSON.stringify(data.IMGs.map(({height, width}: {height: number, width: number}) => [height, width])) 73 | } 74 | 75 | //TODO: find a way to load additional images 76 | 77 | data = { 78 | ...data, 79 | ...await rendSearch(doc), 80 | ...await rendMenu(doc, true) 81 | } 82 | 83 | return rendPage('images', data, lang) 84 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | //deno-lint-ignore-file no-explicit-any 2 | import {fromUnixTime, format} from 'https://raw.githubusercontent.com/pr0ggy/deno-date-fns/deno_bundle_fixes/index.js' 3 | import {renderFile} from 'mustache_ts' 4 | import type {Document} from 'deno_dom' 5 | 6 | export const getQuery = (doc: Document): string => doc.querySelector('title')!.textContent.split(' - ')[0] 7 | 8 | export function getBigFatJS(doc: Document): string { 9 | let bigFatJS = '' 10 | doc.querySelectorAll('script').forEach(el => el.textContent.includes('(function(){var u=') ? bigFatJS = el.textContent : null) 11 | return bigFatJS 12 | } 13 | 14 | export function getRdmUA() { 15 | const rdmStr = () => Date.now().toString(36) + Math.random().toString(36).substring(2) 16 | const OSs = ['Windows NT;', 'Macintosh', 'X11; Linux x86_64', 'X11; Linux i686', 'X11; CrOS i686', 'X11; OpenBSD i386', 'X11; NetBSD'] 17 | return `Mozilla/5.0 (${OSs[~~(Math.random() * OSs.length)]} ${rdmStr()}) AppleWebKit/${rdmStr()} (KHTML, like Gecko) Chrome/${Math.floor(Math.random() * (500 - 50)) + 49} Safari/${rdmStr()} OPR/${rdmStr()}` 18 | } 19 | 20 | export function fetchURL(URL: string) { 21 | return fetch(URL, { 22 | headers: { 23 | 'user-agent': getRdmUA(), 24 | 'Origin': 'https://google.com' 25 | } 26 | }) 27 | } 28 | 29 | export async function getFinanceData(period: string, id: string) { 30 | let interval = '' 31 | if (period === '1d') interval = '300' 32 | else if (period === '5d') interval = '1800' 33 | else if (period === ('5Y' || '40Y')) interval = '604800' 34 | else interval = '86400' 35 | const URL = 'https://www.google.com/async/finance_wholepage_chart?async=mid_list:/' 36 | + id 37 | + ',period:' 38 | + period 39 | + ',interval:' 40 | + interval 41 | + ',_fmt:pc' 42 | return await fetchURL(URL) 43 | .then(res => res.text()) 44 | .then(res => { 45 | //console.log('---------------------------------------------------------') 46 | //console.log(JSON.parse(res.substring(res.split('[[["')[0].length))[0][0][1]) 47 | const out: Record = { 48 | values: [], 49 | labels: [] 50 | } 51 | JSON.parse(JSON.parse(res.substring(res.split('[[["')[0].length))[0][0][1])[0][3][0][0][0][0].forEach((el: any) => { 52 | out.values.push(el[2][0][0]) 53 | out.labels.push(format(fromUnixTime(el[5] * 60), 'dd/MM/yyyy HH:mm', {})) //TODO: better tooltip (relative from today for short period, no year for current year) 54 | }) 55 | return out 56 | }) 57 | } 58 | 59 | export async function rendPage(page: string, data: Record, lang: string): Promise { 60 | return await renderFile(Deno.cwd() + '/templates/base.hbs', { 61 | page: await renderFile(Deno.cwd() + '/pages/' + page + '.hbs', data), 62 | lang: lang || 'en', 63 | style: page 64 | }) 65 | } 66 | 67 | export async function rendMenu(doc: Document, imagesTab = false): Promise> { 68 | const data: any = { 69 | shownMenu: [], 70 | hiddenMenu: [] 71 | } 72 | 73 | if (imagesTab) { 74 | doc.querySelectorAll('.m3kSL').forEach((el, i) => { 75 | const rawID = el.parentElement!.getAttribute('href')! 76 | let ID = '' 77 | if (rawID == null) ID = 'images' 78 | else if (rawID.includes('//maps')) ID = 'maps' 79 | else if (i === 0) ID = 'all' 80 | else ID = { 81 | 'vid': 'videos', 82 | 'nws': 'news', 83 | 'shop': 'shopping', 84 | 'bks': 'books', 85 | 'flm': 'flights', 86 | 'fin': 'finance' 87 | }[rawID.split('tbm=')[1].split('&')[0]]! 88 | if (i < 5) data.shownMenu.push({ 89 | id: ID, 90 | value: el.parentElement!.textContent 91 | }) 92 | else data.hiddenMenu.push({ 93 | id: ID, 94 | value: el.parentElement!.textContent 95 | }) 96 | }) 97 | } else { 98 | const raw = getBigFatJS(doc).split('var m=[')[1].split(';')[0] 99 | const menuIDs = ['WEB', 'IMAGES', 'VIDEOS', 'NEWS', 'SHOPPING', 'BOOKS', 'MAPS', 'FLIGHTS', 'FINANCE'] 100 | const baseMenu: any[] = [] 101 | menuIDs.forEach((_, i) => baseMenu.push([ 102 | raw.indexOf(menuIDs[i]), 103 | raw.split(menuIDs[i])[0].split('\\x22')[raw.split(menuIDs[i])[0].split('\\x22').length - 3].split('\\x22')[0], 104 | menuIDs[i] !== 'WEB' ? menuIDs[i].toLowerCase() : 'all' 105 | ])) 106 | baseMenu.sort((a, b) => a[0] > b[0] ? 1 : -1) 107 | baseMenu.forEach((el, i) => 108 | i < 5 ? 109 | data.shownMenu.push({ 110 | id: el[2], 111 | value: el[1] 112 | }) 113 | : data.hiddenMenu.push({ 114 | id: el[2], 115 | value: el[1] 116 | })) 117 | } 118 | return { 119 | menu: await renderFile(Deno.cwd() + '/templates/menu.hbs', data) 120 | } 121 | } 122 | 123 | export async function rendSearch(doc?: Document): Promise> { 124 | return { 125 | search: await renderFile(Deno.cwd() + '/templates/search.hbs', doc ? {query: getQuery(doc!)} : {}) 126 | } 127 | } 128 | 129 | export async function rendNavigation(): Promise> { 130 | return { 131 | navigation: await renderFile(Deno.cwd() + '/templates/navigation.hbs', {}) 132 | } 133 | } 134 | 135 | export async function rendFinance(doc: Document): Promise> { 136 | const data: any = {} 137 | 138 | if (doc.querySelector('#knowledge-finance-wholepage__entity-summary')) { 139 | data.hasFinance = true 140 | data.finance = { 141 | ID: getBigFatJS(doc).split("'[[\\x22/")[1].split('\\')[0], 142 | positive: false, 143 | data: '', 144 | legend: { 145 | price: '', 146 | currency: '', 147 | priceEvolution: '', 148 | btns: [], 149 | exchange: '' 150 | }, 151 | details: [] 152 | } 153 | 154 | if (doc.querySelector('.fw-price-up')) data.finance.positive = true //positive 155 | await getFinanceData('1d', data.finance.ID).then(res => data.finance.data = JSON.stringify(res)) //data 156 | data.finance.legend.price = doc.querySelector('.NprOob')!.textContent //price 157 | data.finance.legend.currency = doc.querySelector('.knFDje')!.textContent //currency 158 | data.finance.legend.priceEvolution = //price evolution 159 | doc.querySelector('.WlRRw')!.children[0].textContent 160 | + doc.querySelector('.jBBUv')!.textContent 161 | + (data.finance.positive ? ' ▲ ' : ' ▼ ') 162 | + doc.querySelector('.jdUcZd')!.children[0].textContent 163 | doc.querySelectorAll('.qUjgX').forEach(el => data.finance.legend.btns.push(el.textContent)) //buttons 164 | data.finance.legend.exchange = doc.querySelector('.HfMth')!.textContent //exchange 165 | doc.querySelectorAll('.JgXcPd').forEach(el => data.finance.details.push({ //details 166 | title: el.textContent, 167 | content: el.parentElement!.children[1].textContent 168 | })) 169 | } 170 | //TODO: implement "Previous close" 171 | 172 | return { 173 | finance: await renderFile(Deno.cwd() + '/instantAnswers/finance.hbs', data) 174 | } 175 | } -------------------------------------------------------------------------------- /src/videos.ts: -------------------------------------------------------------------------------- 1 | //deno-lint-ignore-file no-explicit-any 2 | import {rendSearch, rendMenu, rendPage, rendNavigation} from '../src/utils.ts' 3 | import type {Document} from 'deno_dom' 4 | 5 | export const videos = async (doc: Document, lang: string) => { 6 | let data: any = { 7 | proposition: '', 8 | results: [], 9 | IMGs: [], 10 | stringedIMGs: '' 11 | } 12 | 13 | //Spell check / No results 14 | //eg: q=minecraftg 15 | if (doc.querySelector('#fprs')) data.proposition = 16 | doc.querySelector('.gL9Hy')!.textContent 17 | + ' ' 20 | + doc.querySelectorAll('.spell_orig')[1].textContent 21 | + ', ' 22 | + doc.querySelector('.spell_orig')!.textContent 23 | + ' ' 26 | + data.query 27 | + '' 28 | //eg: q=minecraft+hhjhjhjhjhjhjh 29 | if (doc.querySelector('.d2IKib')) data.proposition = 30 | doc.querySelector('.d2IKib')!.textContent 31 | + ' ' 34 | + doc.querySelectorAll('.gL9Hy')[1].textContent 35 | + '' 36 | 37 | //Results 38 | doc.querySelectorAll('.LC20lb').forEach((el, i) => (data.results[i] = {title: el.textContent})) //title 39 | doc.querySelectorAll('.yuRUbf').forEach((el, i) => (data.results[i].link = el.children[0].getAttribute('href'))) //link 40 | doc.querySelectorAll('.PcHvNb').forEach((el, i) => (data.results[i].id = el.children[0].getAttribute('id'))) //IDs 41 | doc.querySelectorAll('.aCOpRe').forEach((el, i) => (data.results[i].desc = el.textContent)) //description 42 | doc.querySelectorAll('.uo4vr').forEach((el, i) => (data.results[i].infos = el.textContent)) //infos 43 | 44 | //Get images 45 | doc.querySelectorAll('script').forEach(el => { 46 | if (el.textContent.includes(' s=')) { 47 | data.IMGs.push([ 48 | el.textContent.slice(19).split("'")[0].replaceAll('\\x3d', '='), //Image base64 49 | el.textContent.split("['")[1].split("'")[0] //Image ID 50 | ]) 51 | } 52 | }) 53 | data.stringedIMGs = JSON.stringify(data.IMGs) 54 | 55 | data = { 56 | ...data, 57 | ...await rendSearch(doc), 58 | ...await rendMenu(doc), 59 | ...await rendNavigation() 60 | } 61 | 62 | return rendPage('videos', data, lang) 63 | } -------------------------------------------------------------------------------- /templates/base.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Goodgle 9 | 10 | 11 | 12 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | {{{page}}} 52 | 53 | 54 | -------------------------------------------------------------------------------- /templates/menu.hbs: -------------------------------------------------------------------------------- 1 | 7 | 8 | {{!-- 9 | TODO: when hover an other menu item then #current "un-hover" the #current element 10 | --}} 11 | 12 | -------------------------------------------------------------------------------- /templates/navigation.hbs: -------------------------------------------------------------------------------- 1 |
2 | 5 | 8 |
9 | 10 | 17 | 18 | -------------------------------------------------------------------------------- /templates/search.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 9 |
10 |
11 | 12 | 118 | 119 | --------------------------------------------------------------------------------