├── .github └── workflows │ └── publish_docker.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── cmd └── main │ └── main.go ├── docs ├── de │ └── README.md ├── es │ └── README.md ├── fr │ └── README.md ├── it │ └── README.md ├── ja │ └── README.md ├── ko │ └── README.md ├── pt │ └── README.md ├── ru │ └── README.md ├── zhs │ └── README.md └── zht │ └── README.md ├── go.mod ├── go.sum ├── internal ├── envs │ └── env_key.go ├── handles │ ├── index_handle.go │ ├── list_language_handle.go │ ├── openapi_handle.go │ └── text_handle.go ├── searchs │ ├── browser_support.go │ ├── browser_support_types.go │ ├── list_language.go │ └── search_text.go ├── server.go ├── types │ ├── responses.go │ └── types.go └── utils │ ├── env_util.go │ ├── file_util.go │ └── response_util.go ├── resource ├── openapi.json └── stealth.min.js ├── test └── test.http └── version.go /.github/workflows/publish_docker.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker Image 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10, v0.0.1-beta 7 | 8 | env: 9 | REGISTRY: docker.io 10 | IMAGE_NAME: lessapi/lessapi-duckduckgo 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | permissions: 17 | contents: read 18 | packages: write 19 | 20 | steps: 21 | - name: Read Tag 22 | id: read_tag 23 | run: echo ::set-output name=tag::${GITHUB_REF/refs\/tags\//} 24 | 25 | - name: Checkout repository 26 | uses: actions/checkout@v4 27 | 28 | - name: Log in to the Container registry 29 | uses: docker/login-action@v3 30 | with: 31 | registry: ${{ env.REGISTRY }} 32 | username: ${{ secrets.LESSAPI_DOCKER_HUB_USERNAME }} 33 | password: ${{ secrets.LESSAPI_DOCKER_HUB_ACCESS_TOKEN }} 34 | 35 | - name: Extract metadata (tags, labels) for Docker 36 | id: meta 37 | uses: docker/metadata-action@v5 38 | with: 39 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 40 | 41 | - name: Build and push Docker image 42 | uses: docker/build-push-action@v5 43 | with: 44 | context: . 45 | push: true 46 | tags: ${{ steps.meta.outputs.tags }} 47 | labels: ${{ steps.meta.outputs.labels }} 48 | 49 | - name: Logout from DockerHub 50 | run: docker logout -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | 24 | /.idea 25 | /Dockerfile-local -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Modules caching 2 | FROM golang:1.21 as modules 3 | COPY go.mod go.sum /modules/ 4 | WORKDIR /modules 5 | RUN go mod download 6 | 7 | # Stage 2: Build 8 | FROM golang:1.21 as builder 9 | COPY --from=modules /go/pkg /go/pkg 10 | COPY . /workdir 11 | WORKDIR /workdir 12 | RUN GOOS=linux GOARCH=amd64 go build -o /bin/lessapi-duckduckgo ./cmd/main 13 | 14 | # Stage 3: Final 15 | FROM lessapi/base-env-ubuntu-playwright-go:ubuntu22.04-pwgo-v0.4201.1 16 | COPY --from=builder /bin/lessapi-duckduckgo / 17 | COPY ./resource /resource 18 | RUN chmod +x /lessapi-duckduckgo 19 | CMD ["/lessapi-duckduckgo"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present, Rebilly, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LessAPI-DuckDuckGo 2 | 3 | [![GitHub](https://img.shields.io/github/license/lessapi-dev/lessapi-duckduckgo?style=for-the-badge)](https://github.com/lessapi-dev/lessapi-duckduckgo) 4 | [![Docker](https://img.shields.io/docker/pulls/lessapi/lessapi-duckduckgo?style=for-the-badge)](https://hub.docker.com/r/lessapi/lessapi-duckduckgo) 5 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Flessapi-dev%2Flessapi-duckduckgo.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Flessapi-dev%2Flessapi-duckduckgo?ref=badge_shield) 6 | 7 | [English](./README.md) | 8 | [简体中文](./docs/zhs/README.md) | 9 | [繁體中文](./docs/zht/README.md) | 10 | [日本語](./docs/ja/README.md) | 11 | [한국어](./docs/ko/README.md) | 12 | [Русский](./docs/ru/README.md) | 13 | [Deutsch](./docs/de/README.md) | 14 | [Français](./docs/fr/README.md) | 15 | [Español](./docs/es/README.md) | 16 | [Italiano](./docs/it/README.md) | 17 | [Português](./docs/pt/README.md) 18 | 19 | ## Introduction 20 | 21 | LessAPI-DuckDuckGo is an API service for a search engine. 22 | 23 | Based on Playwright and DuckDuckGo's search engine, it encapsulates to provide simple API interfaces. 24 | 25 | Simple, lightweight, reliable, Docker deployable, easy to maintain. 26 | 27 | Large Language Model (LLM) Friendly. Support Plain Text Response. 28 | 29 | ## Status 30 | 31 | > Experimentally under development and not recommended for use in production environments. 32 | 33 | ## Deployment 34 | 35 | One command is all it takes to deploy the service to port 8080 using Docker. 36 | 37 | ```shell 38 | docker run -d -p 8080:8080 --restart=unless-stopped --name lessapi-duckduckgo lessapi/lessapi-duckduckgo:v0.0.3 39 | ``` 40 | 41 | ## Usage 42 | 43 | ### OpenAPI Standard Documentation (Swagger 3.0) 44 | 45 | [OpenAPI Standard Documentation (Swagger 3.0)](resource/openapi.json) 46 | 47 | ### Text Search `GET /search/text` 48 | 49 | **Request Parameters:** 50 | 51 | - keyword: Search keyword (required) 52 | - region: Region (optional) such as en-US, fr-FR, zh-CN, ru-RU, etc. Default is en-US 53 | - maxCount: Maximum number of results returned. (optional) Default is 20 54 | - viaProxyUrl: The address of the proxy used by the browser. e.g., http://proxy.server:3000 (optional) Default is empty 55 | - llmStyle: Whether to use Large Language Model (LLM) Friendly style response. e.g. 1, 0 (optional) Default is 0 56 | 57 | **Request Example:** 58 | 59 | ```shell 60 | curl 'http://127.0.0.1:8080/search/text?keyword=lessapi&maxCount=10' 61 | ``` 62 | 63 | ```shell 64 | curl 'http://127.0.0.1:8080/search/text?keyword=lessapi&maxCount=99&viaProxyUrl=http://proxy.server:3000' 65 | ``` 66 | 67 | **Response Example:** 68 | 69 | ```json 70 | { 71 | "code": "success", 72 | "data": { 73 | "results": [ 74 | { 75 | "order": 1, 76 | "title": "Adele - Hello (Official Music Video) - YouTube", 77 | "url": "https://www.youtube.com/watch?v=YQHsXMglC9A", 78 | "description": "Listen to \"Easy On Me\" here: http://Adele.lnk.to/EOMPre-order Adele's new album \"30\" before its release on November 19: https://www.adele.comShop the \"Adele..." 79 | }, 80 | { 81 | "order": 2, 82 | "title": "Hello Definition & Meaning - Merriam-Webster", 83 | "url": "https://www.merriam-webster.com/dictionary/hello", 84 | "description": "Learn the origin, usage, and synonyms of the word hello, an expression or gesture of greeting. See examples of hello in sentences and related words from the dictionary." 85 | } 86 | ] 87 | } 88 | } 89 | ``` 90 | 91 | ## Advanced 92 | 93 | ### Use Environment Variables 94 | 95 | - **LESSAPI_DEFAULT_LANGUAGE**: (optional) Default language, such as en-US, fr-FR, zh-CN, ru-RU, etc. Default is en-US 96 | - **LESSAPI_DEFAULT_VIA_PROXY_URL**: (optional) The address of the proxy used by the browser, 97 | e.g., http://proxy.server:3000 Default is empty 98 | 99 | ## Security 100 | 101 | [![Security Status](https://www.murphysec.com/platform3/v31/badge/1779906127272730624.svg)](https://www.murphysec.com/console/report/1778449242088529920/1779906127272730624) 102 | 103 | ## License 104 | 105 | [MIT](./LICENSE) 106 | 107 | 108 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Flessapi-dev%2Flessapi-duckduckgo.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Flessapi-dev%2Flessapi-duckduckgo?ref=badge_large) -------------------------------------------------------------------------------- /cmd/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/lessapidev/lessapi-duckduckgo/internal" 4 | 5 | func main() { 6 | internal.Run() 7 | } 8 | -------------------------------------------------------------------------------- /docs/de/README.md: -------------------------------------------------------------------------------- 1 | # LessAPI-DuckDuckGo 2 | [![GitHub](https://img.shields.io/github/license/lessapi-dev/lessapi-duckduckgo?style=for-the-badge)](https://github.com/lessapi-dev/lessapi-duckduckgo) 3 | [![Docker](https://img.shields.io/docker/pulls/lessapi/lessapi-duckduckgo?style=for-the-badge)](https://hub.docker.com/r/lessapi/lessapi-duckduckgo) 4 | 5 | [English](./../../README.md) | 6 | [简体中文](./../zhs/README.md) | 7 | [繁體中文](./../zht/README.md) | 8 | [日本語](./../ja/README.md) | 9 | [한국어](./../ko/README.md) | 10 | [Русский](./../ru/README.md) | 11 | [Deutsch](./../de/README.md) | 12 | [Français](./../fr/README.md) | 13 | [Español](./../es/README.md) | 14 | [Italiano](./../it/README.md) | 15 | [Português](./../pt/README.md) 16 | 17 | (Aktuelle Sprachdokumente wurden durch KI übersetzt generiert, im Zweifelsfall bitte auf die Dokumente in vereinfachtem 18 | Chinesisch oder Englisch verweisen.) 19 | 20 | --- 21 | 22 | ## Einführung 23 | 24 | LessAPI-DuckDuckGo ist ein Suchmaschinen-API-Dienst. 25 | Basierend auf playwright und der DuckDuckGo-Suchmaschine, wird nach der Verpackung ein einfacher API-Endpunkt 26 | implementiert. 27 | Einfach, leicht, zuverlässig, Docker-Deployment, einfach zu warten. 28 | 29 | ## Status 30 | 31 | > Experimentell in der Entwicklung, nicht empfohlen für die Verwendung in Produktionsumgebungen. 32 | 33 | ## Nutzung 34 | 35 | ### OpenAPI-Standarddokument (Swagger 3.0) 36 | 37 | [OpenAPI-Standarddokument (Swagger 3.0)](../../resource/openapi.json) 38 | 39 | ### Textsuche `GET /search/text` 40 | 41 | **Anforderungsparameter:** 42 | 43 | - keyword: Suchschlüsselwort (Pflichtfeld) 44 | - region: Region (optional) z.B. en-US, fr-FR, zh-CN, ru-RU, usw. Standardwert ist en-US 45 | - maxCount: Maximale Rückgabeanzahl (optional) Standardwert ist 20 46 | - viaProxyUrl: Adresse des Proxys, der vom Browser verwendet wird (optional) z.B. http://proxy.server:3000 Standardwert 47 | ist leer 48 | 49 | **Anfragebeispiel:** 50 | 51 | ```shell 52 | curl 'http://127.0.0.1:8080/search/text?keyword=hello&maxCount=2' 53 | ``` 54 | 55 | **Antwortbeispiel:** 56 | 57 | ```json 58 | { 59 | "code": "success", 60 | "data": { 61 | "results": [ 62 | { 63 | "order": 1, 64 | "title": "Adele - Hello (Official Music Video) - YouTube", 65 | "url": "https://www.youtube.com/watch?v=YQHsXMglC9A", 66 | "description": "Listen to \"Easy On Me\" here: http://Adele.lnk.to/EOMPre-order Adele's new album \"30\" before its release on November 19: https://www.adele.comShop the \"Adele..." 67 | }, 68 | { 69 | "order": 2, 70 | "title": "Hello Definition & Meaning - Merriam-Webster", 71 | "url": "https://www.merriam-webster.com/dictionary/hello", 72 | "description": "Learn the origin, usage, and synonyms of the word hello, an expression or gesture of greeting. See examples of hello in sentences and related words from the dictionary." 73 | } 74 | ] 75 | } 76 | } 77 | ``` 78 | 79 | ## Lizenz 80 | 81 | [MIT](./../../LICENSE) 82 | -------------------------------------------------------------------------------- /docs/es/README.md: -------------------------------------------------------------------------------- 1 | # LessAPI-DuckDuckGo 2 | 3 | [![GitHub](https://img.shields.io/github/license/lessapi-dev/lessapi-duckduckgo?style=for-the-badge)](https://github.com/lessapi-dev/lessapi-duckduckgo) 4 | [![Docker](https://img.shields.io/docker/pulls/lessapi/lessapi-duckduckgo?style=for-the-badge)](https://hub.docker.com/r/lessapi/lessapi-duckduckgo) 5 | 6 | [English](./../../README.md) | 7 | [简体中文](./../zhs/README.md) | 8 | [繁體中文](./../zht/README.md) | 9 | [日本語](./../ja/README.md) | 10 | [한국어](./../ko/README.md) | 11 | [Русский](./../ru/README.md) | 12 | [Deutsch](./../de/README.md) | 13 | [Français](./../fr/README.md) | 14 | [Español](./../es/README.md) | 15 | [Italiano](./../it/README.md) | 16 | [Português](./../pt/README.md) 17 | 18 | (Este documento actualmente está siendo traducido por AI, por favor, consulta el documento en chino simplificado o 19 | inglés en caso de duda) 20 | 21 | 22 | --- 23 | 24 | ## Introducción 25 | 26 | LessAPI-DuckDuckGo es un servicio de API de motor de búsqueda. 27 | Basado en playwright y DuckDuckGo, se ha encapsulado para implementar una interfaz de API simple. 28 | Es simple, ligero, confiable, se puede desplegar con Docker y fácil de mantener. 29 | 30 | ## Estado 31 | 32 | > Actualmente en desarrollo experimental, no se recomienda usar en entornos de producción. 33 | 34 | ## Uso 35 | 36 | ### Documento estándar OpenAPI (Swagger 3.0) 37 | 38 | [Documento estándar OpenAPI (Swagger 3.0)](../../resource/openapi.json) 39 | 40 | ### Búsqueda de texto `GET /search/text` 41 | 42 | **Parámetros de solicitud:** 43 | 44 | - keyword: Palabra clave de búsqueda (obligatorio) 45 | - region: Región (opcional) como en-US, fr-FR, zh-CN, ru-RU, etc. El valor predeterminado es en-US 46 | - maxCount: Número máximo de resultados devueltos (opcional) El valor predeterminado es 20 47 | - viaProxyUrl: Dirección del proxy utilizado por el navegador (opcional) como http://proxy.server:3000 El valor 48 | predeterminado es vacío 49 | 50 | **Ejemplo de solicitud:** 51 | 52 | ```shell 53 | curl 'http://127.0.0.1:8080/search/text?keyword=hello&maxCount=2' 54 | ``` 55 | 56 | **Ejemplo de respuesta:** 57 | 58 | ```json 59 | { 60 | "code": "success", 61 | "data": { 62 | "results": [ 63 | { 64 | "order": 1, 65 | "title": "Adele - Hello (Official Music Video) - YouTube", 66 | "url": "https://www.youtube.com/watch?v=YQHsXMglC9A", 67 | "description": "Escucha \"Easy On Me\" aquí: http://Adele.lnk.to/EOMPre-order Adele's new album \"30\" before its release on November 19: https://www.adele.comShop the \"Adele..." 68 | }, 69 | { 70 | "order": 2, 71 | "title": "Hello Definition & Meaning - Merriam-Webster", 72 | "url": "https://www.merriam-webster.com/dictionary/hello", 73 | "description": "Aprende el origen, uso y sinónimos de la palabra hello, una expresión o gesto de saludo. Ver ejemplos de hello en frases y palabras relacionadas del diccionario." 74 | } 75 | ] 76 | } 77 | } 78 | ``` 79 | 80 | ## Licencia 81 | 82 | [MIT](./../../LICENSE) 83 | -------------------------------------------------------------------------------- /docs/fr/README.md: -------------------------------------------------------------------------------- 1 | # LessAPI-DuckDuckGo 2 | 3 | [![GitHub](https://img.shields.io/github/license/lessapi-dev/lessapi-duckduckgo?style=for-the-badge)](https://github.com/lessapi-dev/lessapi-duckduckgo) 4 | [![Docker](https://img.shields.io/docker/pulls/lessapi/lessapi-duckduckgo?style=for-the-badge)](https://hub.docker.com/r/lessapi/lessapi-duckduckgo) 5 | 6 | [English](./../../README.md) | 7 | [简体中文](./../zhs/README.md) | 8 | [繁體中文](./../zht/README.md) | 9 | [日本語](./../ja/README.md) | 10 | [한국어](./../ko/README.md) | 11 | [Русский](./../ru/README.md) | 12 | [Deutsch](./../de/README.md) | 13 | [Français](./../fr/README.md) | 14 | [Español](./../es/README.md) | 15 | [Italiano](./../it/README.md) | 16 | [Português](./../pt/README.md) 17 | 18 | (Ce document est généré par une traduction IA ; en cas de doute, veuillez vous référer aux documents en chinois 19 | simplifié ou en anglais.) 20 | 21 | --- 22 | 23 | ## Introduction 24 | 25 | LessAPI-DuckDuckGo est un service d'API de moteur de recherche. 26 | Basé sur playwright et le moteur de recherche DuckDuckGo, il est encapsulé pour fournir une interface API simple. 27 | Simple, léger, fiable, déployable via Docker, facile à maintenir. 28 | 29 | ## État 30 | 31 | > En développement expérimental, pas recommandé pour une utilisation en production. 32 | 33 | ## Utilisation 34 | 35 | ### Documentation OpenAPI (Swagger 3.0) 36 | 37 | [Documentation OpenAPI (Swagger 3.0)](../../resource/openapi.json) 38 | 39 | ### Recherche texte `GET /search/text` 40 | 41 | **Paramètres de la requête:** 42 | 43 | - keyword: Mot-clé de recherche (obligatoire) 44 | - region: Région (facultatif) en-US, fr-FR, zh-CN, ru-RU, etc. La valeur par défaut est en-US 45 | - maxCount: Nombre maximal de résultats retournés (facultatif) La valeur par défaut est 20 46 | - viaProxyUrl: Adresse du proxy utilisé par le navigateur (facultatif) Exemple : http://proxy.server:3000 La valeur par 47 | défaut est vide 48 | 49 | **Exemple de requête:** 50 | 51 | ```shell 52 | curl 'http://127.0.0.1:8080/search/text?keyword=hello&maxCount=2' 53 | ``` 54 | 55 | **Exemple de réponse:** 56 | 57 | ```json 58 | { 59 | "code": "success", 60 | "data": { 61 | "results": [ 62 | { 63 | "order": 1, 64 | "title": "Adele - Hello (Vidéo musicale officielle) - YouTube", 65 | "url": "https://www.youtube.com/watch?v=YQHsXMglC9A", 66 | "description": "Écoutez \"Easy On Me\" ici: http://Adele.lnk.to/EOMPre-commandez le nouvel album d'Adele \"30\" avant sa sortie le 19 novembre: https://www.adele.comBoutique \"Adele..." 67 | }, 68 | { 69 | "order": 2, 70 | "title": "Définition et signification de Hello - Merriam-Webster", 71 | "url": "https://www.merriam-webster.com/dictionary/hello", 72 | "description": "Apprenez l'origine, l'utilisation et les synonymes du mot hello, une expression ou un geste de salutation. Voir des exemples d'utilisation de hello dans des phrases et des mots associés du dictionnaire." 73 | } 74 | ] 75 | } 76 | } 77 | ``` 78 | 79 | ## Licence 80 | 81 | [MIT](./../../LICENSE) 82 | -------------------------------------------------------------------------------- /docs/it/README.md: -------------------------------------------------------------------------------- 1 | # LessAPI-DuckDuckGo 2 | 3 | [![GitHub](https://img.shields.io/github/license/lessapi-dev/lessapi-duckduckgo?style=for-the-badge)](https://github.com/lessapi-dev/lessapi-duckduckgo) 4 | [![Docker](https://img.shields.io/docker/pulls/lessapi/lessapi-duckduckgo?style=for-the-badge)](https://hub.docker.com/r/lessapi/lessapi-duckduckgo) 5 | 6 | [English](./../../README.md) | 7 | [简体中文](./../zhs/README.md) | 8 | [繁體中文](./../zht/README.md) | 9 | [日本語](./../ja/README.md) | 10 | [한국어](./../ko/README.md) | 11 | [Русский](./../ru/README.md) | 12 | [Deutsch](./../de/README.md) | 13 | [Français](./../fr/README.md) | 14 | [Español](./../es/README.md) | 15 | [Italiano](./../it/README.md) | 16 | [Português](./../pt/README.md) 17 | 18 | (questo documento è stato tradotto da un traduttore AI; in caso di dubbi, fare riferimento ai documenti in cinese 19 | semplificato o in inglese) 20 | 21 | --- 22 | 23 | ## Introduzione 24 | 25 | LessAPI-DuckDuckGo è un servizio API di ricerca basato sulla libreria Playwright e sull'indicizzatore DuckDuckGo, che 26 | offre un'interfaccia API semplice, leggera e affidabile, ed è facilmente mantenibile grazie al supporto Docker. 27 | 28 | ## Stato 29 | 30 | > In fase sperimentale di sviluppo, non consigliato per l'uso in produzione. 31 | 32 | ## Utilizzo 33 | 34 | ### Documento OpenAPI standard (Swagger 3.0) 35 | 36 | [Documento OpenAPI standard (Swagger 3.0)](../../resource/openapi.json) 37 | 38 | ### Ricerca testuale `GET /search/text` 39 | 40 | **Parametri della richiesta:** 41 | 42 | - keyword: parola chiave da cercare (obbligatorio) 43 | - region: regione (facoltativo) es. en-US, fr-FR, zh-CN, ru-RU, ecc. Il valore predefinito è en-US 44 | - maxCount: numero massimo di risultati restituiti (facoltativo) Il valore predefinito è 20 45 | - viaProxyUrl: indirizzo del proxy da utilizzare con il browser (facoltativo) ad esempio http://proxy.server:3000 Il 46 | valore predefinito è vuoto 47 | 48 | **Esempio di richiesta:** 49 | 50 | ```shell 51 | curl 'http://127.0.0.1:8080/search/text?keyword=hello&maxCount=2' 52 | ``` 53 | 54 | **Esempio di risposta:** 55 | 56 | ```json 57 | { 58 | "code": "success", 59 | "data": { 60 | "results": [ 61 | { 62 | "order": 1, 63 | "title": "Adele - Hello (Official Music Video) - YouTube", 64 | "url": "https://www.youtube.com/watch?v=YQHsXMglC9A", 65 | "description": "Ascolta \"Easy On Me\" qui: http://Adele.lnk.to/EOMPre-order Adele's new album \"30\" before its release on November 19: https://www.adele.comShop the \"Adele..." 66 | }, 67 | { 68 | "order": 2, 69 | "title": "Hello Definition & Meaning - Merriam-Webster", 70 | "url": "https://www.merriam-webster.com/dictionary/hello", 71 | "description": "Scopri l'origine, l'uso e i sinonimi della parola hello, un'espressione o gesto di saluto. Guarda gli esempi di hello in frase e parole collegate dal dizionario." 72 | } 73 | ] 74 | } 75 | } 76 | ``` 77 | 78 | ## Licenza 79 | 80 | [MIT](./../../LICENSE) 81 | -------------------------------------------------------------------------------- /docs/ja/README.md: -------------------------------------------------------------------------------- 1 | # LessAPI-DuckDuckGo 2 | 3 | [![GitHub](https://img.shields.io/github/license/lessapi-dev/lessapi-duckduckgo?style=for-the-badge)](https://github.com/lessapi-dev/lessapi-duckduckgo) 4 | [![Docker](https://img.shields.io/docker/pulls/lessapi/lessapi-duckduckgo?style=for-the-badge)](https://hub.docker.com/r/lessapi/lessapi-duckduckgo) 5 | 6 | [English](./../../README.md) | 7 | [简体中文](./../zhs/README.md) | 8 | [繁體中文](./../zht/README.md) | 9 | [日本語](./../ja/README.md) | 10 | [한국어](./../ko/README.md) | 11 | [Русский](./../ru/README.md) | 12 | [Deutsch](./../de/README.md) | 13 | [Français](./../fr/README.md) | 14 | [Español](./../es/README.md) | 15 | [Italiano](./../it/README.md) | 16 | [Português](./../pt/README.md) 17 | 18 | (この言語のドキュメントはAIによって翻訳されています。不明な点があれば、簡体字中国語または英語のドキュメントを参照してください) 19 | 20 | --- 21 | 22 | ## はじめに 23 | 24 | LessAPI-DuckDuckGo は、搜索引擎APIサービスです。 25 | playwright と DuckDuckGo 検索エンジンを基に、単純なAPIインターフェースを実装しています。 26 | シンプルで軽量、信頼性が高く、Dockerによるデプロイメントと保守が容易です。 27 | 28 | ## ステータス 29 | 30 | > 実験的に開発中であり、本番環境での使用は推奨されていません。 31 | 32 | ## 使い方 33 | 34 | ### OpenAPI標準ドキュメント (Swagger 3.0) 35 | 36 | [OpenAPI標準ドキュメント (Swagger 3.0)](../../resource/openapi.json) 37 | 38 | ### テキスト検索 `GET /search/text` 39 | 40 | **リクエストパラメータ:** 41 | 42 | - keyword: 検索キーワード(必須) 43 | - region: 地域(任意) en-US, fr-FR, zh-CN, ru-RU, など。デフォルト値は en-US 44 | - maxCount: 最大返信数(任意) デフォルト値は 20 45 | - viaProxyUrl: ブラウザが使用するプロキシのアドレス(任意) 例: http://proxy.server:3000 デフォルト値は空 46 | 47 | **リクエスト例:** 48 | 49 | ```shell 50 | curl 'http://127.0.0.1:8080/search/text?keyword=hello&maxCount=2' 51 | ``` 52 | 53 | **レスポンス例:** 54 | 55 | ```json 56 | { 57 | "code": "success", 58 | "data": { 59 | "results": [ 60 | { 61 | "order": 1, 62 | "title": "Adele - Hello (Official Music Video) - YouTube", 63 | "url": "https://www.youtube.com/watch?v=YQHsXMglC9A", 64 | "description": "Listen to \"Easy On Me\" here: http://Adele.lnk.to/EOMPre-order Adele's new album \"30\" before its release on November 19: https://www.adele.comShop the \"Adele..." 65 | }, 66 | { 67 | "order": 2, 68 | "title": "Hello Definition & Meaning - Merriam-Webster", 69 | "url": "https://www.merriam-webster.com/dictionary/hello", 70 | "description": "Learn the origin, usage, and synonyms of the word hello, an expression or gesture of greeting. See examples of hello in sentences and related words from the dictionary." 71 | } 72 | ] 73 | } 74 | } 75 | ``` 76 | 77 | ## ライセンス 78 | 79 | [MIT](./../../LICENSE) 80 | -------------------------------------------------------------------------------- /docs/ko/README.md: -------------------------------------------------------------------------------- 1 | # LessAPI-DuckDuckGo 2 | 3 | [![GitHub](https://img.shields.io/github/license/lessapi-dev/lessapi-duckduckgo?style=for-the-badge)](https://github.com/lessapi-dev/lessapi-duckduckgo) 4 | [![Docker](https://img.shields.io/docker/pulls/lessapi/lessapi-duckduckgo?style=for-the-badge)](https://hub.docker.com/r/lessapi/lessapi-duckduckgo) 5 | 6 | [English](./../../README.md) | 7 | [简体中文](./../zhs/README.md) | 8 | [繁體中文](./../zht/README.md) | 9 | [日本語](./../ja/README.md) | 10 | [한국어](./../ko/README.md) | 11 | [Русский](./../ru/README.md) | 12 | [Deutsch](./../de/README.md) | 13 | [Français](./../fr/README.md) | 14 | [Español](./../es/README.md) | 15 | [Italiano](./../it/README.md) | 16 | [Português](./../pt/README.md) 17 | 18 | (LessAPI-DuckDuckGo 프로젝트의 한국어 문서는 AI가 번역한 것으로, 문의 사항이 있을 경우 중국어 간체나 영어 문서를 참고하십시오) 19 | 20 | --- 21 | 22 | ## 소개 23 | 24 | LessAPI-DuckDuckGo는 DuckDuckGo 검색 엔진을 기반으로 하는 간단한 API 인터페이스를 제공하는 검색 엔진 API 서비스입니다. playwright를 사용하여 감싸져 있으며, 간단하고 가벼우며 25 | 신뢰度高며 Docker로 배포되어서 쉽게 유지 관리할 수 있습니다. 26 | 27 | ## 상태 28 | 29 | > 실험적으로 개발 중입니다. 프로덕션 환경에서 사용하지 마십시오. 30 | 31 | ## 사용법 32 | 33 | ### OpenAPI 표준 문서 (Swagger 3.0) 34 | 35 | [OpenAPI 표준 문서 (Swagger 3.0)](../../resource/openapi.json) 36 | 37 | ### 텍스트 검색 `GET /search/text` 38 | 39 | **요청 매개 변수:** 40 | 41 | - keyword: 검색 키워드(필수) 42 | - region: 지역(선택) en-US, fr-FR, zh-CN, ru-RU, 등 기본값 en-US 43 | - maxCount: 최대 반환 수(선택) 기본값 20 44 | - viaProxyUrl: 브라우저가 프록시를 사용하는 주소(선택) 예: http://proxy.server:3000 기본값: 빈 문자열 45 | 46 | **요청 예시:** 47 | 48 | ```shell 49 | curl 'http://127.0.0.1:8080/search/text?keyword=hello&maxCount=2' 50 | ``` 51 | 52 | **응답 예시:** 53 | 54 | ```json 55 | { 56 | "code": "success", 57 | "data": { 58 | "results": [ 59 | { 60 | "order": 1, 61 | "title": "Adele - Hello (Official Music Video) - YouTube", 62 | "url": "https://www.youtube.com/watch?v=YQHsXMglC9A", 63 | "description": "Listen to \"Easy On Me\" here: http://Adele.lnk.to/EOMPre-order Adele's new album \"30\" before its release on November 19: https://www.adele.comShop the \"Adele..." 64 | }, 65 | { 66 | "order": 2, 67 | "title": "Hello Definition & Meaning - Merriam-Webster", 68 | "url": "https://www.merriam-webster.com/dictionary/hello", 69 | "description": "Learn the origin, usage, and synonyms of the word hello, an expression or gesture of greeting. See examples of hello in sentences and related words from the dictionary." 70 | } 71 | ] 72 | } 73 | } 74 | ``` 75 | 76 | ## 라이선스 77 | 78 | [MIT](./../../LICENSE) 79 | -------------------------------------------------------------------------------- /docs/pt/README.md: -------------------------------------------------------------------------------- 1 | # LessAPI-DuckDuckGo 2 | 3 | [![GitHub](https://img.shields.io/github/license/lessapi-dev/lessapi-duckduckgo?style=for-the-badge)](https://github.com/lessapi-dev/lessapi-duckduckgo) 4 | [![Docker](https://img.shields.io/docker/pulls/lessapi/lessapi-duckduckgo?style=for-the-badge)](https://hub.docker.com/r/lessapi/lessapi-duckduckgo) 5 | 6 | [English](./../../README.md) | 7 | [简体中文](./../zhs/README.md) | 8 | [繁體中文](./../zht/README.md) | 9 | [日本語](./../ja/README.md) | 10 | [한국어](./../ko/README.md) | 11 | [Русский](./../ru/README.md) | 12 | [Deutsch](./../de/README.md) | 13 | [Français](./../fr/README.md) | 14 | [Español](./../es/README.md) | 15 | [Italiano](./../it/README.md) | 16 | [Português](./../pt/README.md) 17 | 18 | (Este documento em idioma é gerado por AI, caso haja dúvidas, por favor, consulte o documento em Chinês Simplificado ou 19 | Inglês) 20 | 21 | --- 22 | 23 | ## Introdução 24 | 25 | LessAPI-DuckDuckGo é um serviço de API de busca. 26 | Baseado no playwright e no mecanismo de busca DuckDuckGo, é encapsulado para implementar uma interface de API simples. 27 | Simples, leve, confiável, implantado com Docker, fácil de manter. 28 | 29 | ## Status 30 | 31 | > Em desenvolvimento experimental, não recomendado para uso em ambientes de produção. 32 | 33 | ## Uso 34 | 35 | ### Documento padrão OpenAPI (Swagger 3.0) 36 | 37 | [Documento padrão OpenAPI (Swagger 3.0)](../../resource/openapi.json) 38 | 39 | ### Pesquisa de texto `GET /search/text` 40 | 41 | **Parâmetros de solicitação:** 42 | 43 | - keyword: Palavra-chave de pesquisa (obrigatório) 44 | - region: Região (opcional) como en-US, fr-FR, zh-CN, ru-RU, etc. O valor padrão é en-US 45 | - maxCount: Número máximo de resultados retornados (opcional) O valor padrão é 20 46 | - viaProxyUrl: Endereço do proxy usado pelo navegador (opcional) como http://proxy.server:3000 O valor padrão é vazio 47 | 48 | **Exemplo de solicitação:** 49 | 50 | ```shell 51 | curl 'http://127.0.0.1:8080/search/text?keyword=hello&maxCount=2' 52 | ``` 53 | 54 | **Exemplo de resposta:** 55 | 56 | ```json 57 | { 58 | "code": "success", 59 | "data": { 60 | "results": [ 61 | { 62 | "order": 1, 63 | "title": "Adele - Hello (Official Music Video) - YouTube", 64 | "url": "https://www.youtube.com/watch?v=YQHsXMglC9A", 65 | "description": "Escute \"Easy On Me\" aqui: http://Adele.lnk.to/EOMPre-order Adele's new album \"30\" before its release on November 19: https://www.adele.comShop the \"Adele..." 66 | }, 67 | { 68 | "order": 2, 69 | "title": "Hello Definition & Meaning - Merriam-Webster", 70 | "url": "https://www.merriam-webster.com/dictionary/hello", 71 | "description": "Learn the origin, usage, and synonyms of the word hello, an expression or gesture of greeting. See examples of hello in sentences and related words from the dictionary." 72 | } 73 | ] 74 | } 75 | } 76 | ``` 77 | 78 | ## Licença 79 | 80 | [MIT](./../../LICENSE) 81 | -------------------------------------------------------------------------------- /docs/ru/README.md: -------------------------------------------------------------------------------- 1 | # LessAPI-DuckDuckGo 2 | 3 | [![GitHub](https://img.shields.io/github/license/lessapi-dev/lessapi-duckduckgo?style=for-the-badge)](https://github.com/lessapi-dev/lessapi-duckduckgo) 4 | [![Docker](https://img.shields.io/docker/pulls/lessapi/lessapi-duckduckgo?style=for-the-badge)](https://hub.docker.com/r/lessapi/lessapi-duckduckgo) 5 | 6 | [English](./../../README.md) | 7 | [简体中文](./../zhs/README.md) | 8 | [繁體中文](./../zht/README.md) | 9 | [日本語](./../ja/README.md) | 10 | [한국어](./../ko/README.md) | 11 | [Русский](./../ru/README.md) | 12 | [Deutsch](./../de/README.md) | 13 | [Français](./../fr/README.md) | 14 | [Español](./../es/README.md) | 15 | [Italiano](./../it/README.md) | 16 | [Português](./../pt/README.md) 17 | 18 | (Текущий язык документа был сгенерирован с помощью AI, в случае сомнений, пожалуйста, смотрите на документы на简体中文 19 | или английском языке) 20 | 21 | --- 22 | 23 | ## Введение 24 | 25 | LessAPI-DuckDuckGo является сервисом API поисковой системы. 26 | Основан на playwright и DuckDuckGo поисковой системы, после упаковки реализует простой API интерфейс. 27 | Простой, легкий, надежный, установка Docker, легко обслуживать. 28 | 29 | ## Состояние 30 | 31 | > Экспериментально разрабатывается, не рекомендуется использовать в производственной среде. 32 | 33 | ## Использование 34 | 35 | ### OpenAPI стандартный документ (Swagger 3.0) 36 | 37 | [OpenAPI стандартный документ (Swagger 3.0)](../../resource/openapi.json) 38 | 39 | ### Текстовый поиск `GET /search/text` 40 | 41 | **Параметры запроса:** 42 | 43 | - keyword: поисковые ключевые слова (обязательно) 44 | - region: регион (опционально) en-US, fr-FR, zh-CN, ru-RU и т.д. Значение по умолчанию en-US 45 | - maxCount: максимальное количество возвращаемых результатов (опционально) Значение по умолчанию 20 46 | - viaProxyUrl: адрес прокси для браузера (опционально) Например, http://proxy.server:3000 Значение по умолчанию пустое 47 | 48 | **Пример запроса:** 49 | 50 | ```shell 51 | curl 'http://127.0.0.1:8080/search/text?keyword=hello&maxCount=2' 52 | ``` 53 | 54 | **Пример ответа:** 55 | 56 | ```json 57 | { 58 | "code": "success", 59 | "data": { 60 | "results": [ 61 | { 62 | "order": 1, 63 | "title": "Adele - Hello (Official Music Video) - YouTube", 64 | "url": "https://www.youtube.com/watch?v=YQHsXMglC9A", 65 | "description": "Listen to \"Easy On Me\" here: http://Adele.lnk.to/EOMPre-order Adele's new album \"30\" before its release on November 19: https://www.adele.comShop the \"Adele..." 66 | }, 67 | { 68 | "order": 2, 69 | "title": "Hello Definition & Meaning - Merriam-Webster", 70 | "url": "https://www.merriam-webster.com/dictionary/hello", 71 | "description": "Learn the origin, usage, and synonyms of the word hello, an expression or gesture of greeting. See examples of hello in sentences and related words from the dictionary." 72 | } 73 | ] 74 | } 75 | } 76 | ``` 77 | 78 | ## Лицензия 79 | 80 | [MIT](./../../LICENSE) 81 | -------------------------------------------------------------------------------- /docs/zhs/README.md: -------------------------------------------------------------------------------- 1 | # LessAPI-DuckDuckGo 2 | 3 | [![GitHub](https://img.shields.io/github/license/lessapi-dev/lessapi-duckduckgo?style=for-the-badge)](https://github.com/lessapi-dev/lessapi-duckduckgo) 4 | [![Docker](https://img.shields.io/docker/pulls/lessapi/lessapi-duckduckgo?style=for-the-badge)](https://hub.docker.com/r/lessapi/lessapi-duckduckgo) 5 | 6 | [English](./../../README.md) | 7 | [简体中文](./../zhs/README.md) | 8 | [繁體中文](./../zht/README.md) | 9 | [日本語](./../ja/README.md) | 10 | [한국어](./../ko/README.md) | 11 | [Русский](./../ru/README.md) | 12 | [Deutsch](./../de/README.md) | 13 | [Français](./../fr/README.md) | 14 | [Español](./../es/README.md) | 15 | [Italiano](./../it/README.md) | 16 | [Português](./../pt/README.md) 17 | 18 | 19 | --- 20 | 21 | ## 简介 22 | 23 | LessAPI-DuckDuckGo 是一个搜索引擎API服务。 24 | 25 | 基于 playwright 和 DuckDuckGo 搜索引擎,封装后实现简单的API接口。 26 | 27 | 简单、轻量、可靠、Docker部署、易于维护。 28 | 29 | 大型语言模型(LLM)友好。支持纯文本响应。 30 | 31 | ## 状态 32 | 33 | > 实验性地开发中,不建议在生产环境中使用。 34 | 35 | ## 部署 36 | 37 | 使用Docker只需要一个命令即可将服务部署到8080端口。 38 | 39 | ```shell 40 | docker run -d -p 8080:8080 --restart=unless-stopped --name lessapi-duckduckgo lessapi/lessapi-duckduckgo:v0.0.3 41 | ``` 42 | 43 | ## 使用 44 | 45 | ### OpenAPI标准文档 (Swagger 3.0) 46 | 47 | [OpenAPI标准文档 (Swagger 3.0)](../../resource/openapi.json) 48 | 49 | ### 文本搜索 `GET /search/text` 50 | 51 | **请求参数:** 52 | 53 | - keyword: 搜索关键字(必填) 54 | - region: 地区(选填) en-US, fr-FR, zh-CN, ru-RU, 等 默认值 en-US 55 | - maxCount: 最大返回数量(选填) 默认值 20 56 | - viaProxyUrl: 浏览器使用代理的地址(选填) 如 http://proxy.server:3000 默认值 空 57 | - llmStyle: 是否使用大型语言模型(LLM)友好风格响应(选填) 1, 0 默认值 0 58 | 59 | > Tips:位于中国大陆的用户直接访问DuckDuckGo可能遇到网络不稳定的问题,建议使用 viaProxyUrl 参数指定代理地址。 60 | 61 | **请求示例:** 62 | 63 | ```shell 64 | curl 'http://127.0.0.1:8080/search/text?keyword=lessapi&maxCount=10' 65 | ``` 66 | 67 | ```shell 68 | curl 'http://127.0.0.1:8080/search/text?keyword=lessapi&maxCount=99&viaProxyUrl=http://proxy.server:3000' 69 | ``` 70 | 71 | **响应示例:** 72 | 73 | ```json 74 | { 75 | "code": "success", 76 | "data": { 77 | "results": [ 78 | { 79 | "order": 1, 80 | "title": "Adele - Hello (Official Music Video) - YouTube", 81 | "url": "https://www.youtube.com/watch?v=YQHsXMglC9A", 82 | "description": "Listen to \"Easy On Me\" here: http://Adele.lnk.to/EOMPre-order Adele's new album \"30\" before its release on November 19: https://www.adele.comShop the \"Adele..." 83 | }, 84 | { 85 | "order": 2, 86 | "title": "Hello Definition & Meaning - Merriam-Webster", 87 | "url": "https://www.merriam-webster.com/dictionary/hello", 88 | "description": "Learn the origin, usage, and synonyms of the word hello, an expression or gesture of greeting. See examples of hello in sentences and related words from the dictionary." 89 | } 90 | ] 91 | } 92 | } 93 | 94 | ``` 95 | 96 | ## 高级 97 | 98 | ### 使用环境变量 99 | 100 | - **LESSAPI_DEFAULT_LANGUAGE**: (选填) 默认语言, 如 en-US, fr-FR, zh-CN, ru-RU, 等 默认值 en-US 101 | - **LESSAPI_DEFAULT_VIA_PROXY_URL**: (选填) 浏览器默认使用代理的地址, 如 http://proxy.server:3000 默认值 空 102 | 103 | ## 安全 104 | 105 | [![Security Status](https://www.murphysec.com/platform3/v31/badge/1779906127272730624.svg)](https://www.murphysec.com/console/report/1778449242088529920/1779906127272730624) 106 | 107 | ## 许可 108 | 109 | [MIT](./../../LICENSE) 110 | -------------------------------------------------------------------------------- /docs/zht/README.md: -------------------------------------------------------------------------------- 1 | # LessAPI-DuckDuckGo 2 | 3 | [![GitHub](https://img.shields.io/github/license/lessapi-dev/lessapi-duckduckgo?style=for-the-badge)](https://github.com/lessapi-dev/lessapi-duckduckgo) 4 | [![Docker](https://img.shields.io/docker/pulls/lessapi/lessapi-duckduckgo?style=for-the-badge)](https://hub.docker.com/r/lessapi/lessapi-duckduckgo) 5 | 6 | [English](./../../README.md) | 7 | [简体中文](./../zhs/README.md) | 8 | [繁體中文](./../zht/README.md) | 9 | [日本語](./../ja/README.md) | 10 | [한국어](./../ko/README.md) | 11 | [Русский](./../ru/README.md) | 12 | [Deutsch](./../de/README.md) | 13 | [Français](./../fr/README.md) | 14 | [Español](./../es/README.md) | 15 | [Italiano](./../it/README.md) | 16 | [Português](./../pt/README.md) 17 | 18 | (當前語言文檔由AI翻譯生成,如有疑問,請以簡體中文或英文文檔為準) 19 | 20 | --- 21 | 22 | ## 簡介 23 | 24 | LessAPI-DuckDuckGo 是一個搜索引擎API服務。 25 | 26 | 基於 playwright 和 DuckDuckGo 搜索引擎,封裝後實現簡單的API接口。 27 | 28 | 簡單、輕量、可靠、Docker部署、易於維護。 29 | 30 | 大型語言模型(LLM)友好。支持純文本響應。 31 | 32 | ## 狀態 33 | 34 | > 專案正在實驗性地開發中,不建議在生產環境中使用。 35 | 36 | ## 部署 37 | 38 | 使用Docker只需要一個命令即可將服務部署到8080端口。 39 | 40 | ```shell 41 | docker run -d -p 8080:8080 --restart=unless-stopped --name lessapi-duckduckgo lessapi/lessapi-duckduckgo:v0.0.3 42 | ``` 43 | 44 | ## 使用 45 | 46 | ### OpenAPI標準文檔 (Swagger 3.0) 47 | 48 | [OpenAPI標準文檔 (Swagger 3.0)](../../resource/openapi.json) 49 | 50 | ### 文本搜索 `GET /search/text` 51 | 52 | **請求參數:** 53 | 54 | - keyword: 搜索關鍵字(必填) 55 | - region: 地區(選填) en-US, fr-FR, zh-CN, ru-RU, 等 預設值 en-US 56 | - maxCount: 最大返回數量(選填) 預設值 20 57 | - viaProxyUrl: 瀏覽器使用代理的地址(選填) 如 http://proxy.server:3000 預設值 空 58 | - llmStyle: 是否使用大型語言模型(LLM)友好風格響應(選填) 1, 0 預設值 0 59 | 60 | **請求示例:** 61 | 62 | ```shell 63 | curl 'http://127.0.0.1:8080/search/text?keyword=lessapi&maxCount=10' 64 | ``` 65 | 66 | ```shell 67 | curl 'http://127.0.0.1:8080/search/text?keyword=lessapi&maxCount=99&viaProxyUrl=http://proxy.server:3000' 68 | ``` 69 | 70 | **響應示例:** 71 | 72 | ```json 73 | { 74 | "code": "success", 75 | "data": { 76 | "results": [ 77 | { 78 | "order": 1, 79 | "title": "Adele - Hello (Official Music Video) - YouTube", 80 | "url": "https://www.youtube.com/watch?v=YQHsXMglC9A", 81 | "description": "Listen to \"Easy On Me\" here: http://Adele.lnk.to/EOMPre-order Adele's new album \"30\" before its release on November 19: https://www.adele.comShop the \"Adele..." 82 | }, 83 | { 84 | "order": 2, 85 | "title": "Hello Definition & Meaning - Merriam-Webster", 86 | "url": "https://www.merriam-webster.com/dictionary/hello", 87 | "description": "Learn the origin, usage, and synonyms of the word hello, an expression or gesture of greeting. See examples of hello in sentences and related words from the dictionary." 88 | } 89 | ] 90 | } 91 | } 92 | ``` 93 | 94 | ## 許可 95 | 96 | [MIT](./../../LICENSE) 97 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lessapidev/lessapi-duckduckgo 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.9.1 7 | github.com/patrickmn/go-cache v2.1.0+incompatible 8 | github.com/playwright-community/playwright-go v0.4201.1 9 | ) 10 | 11 | require ( 12 | github.com/bytedance/sonic v1.9.1 // indirect 13 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 14 | github.com/deckarep/golang-set/v2 v2.6.0 // indirect 15 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 16 | github.com/gin-contrib/sse v0.1.0 // indirect 17 | github.com/go-jose/go-jose/v3 v3.0.3 // indirect 18 | github.com/go-playground/locales v0.14.1 // indirect 19 | github.com/go-playground/universal-translator v0.18.1 // indirect 20 | github.com/go-playground/validator/v10 v10.14.0 // indirect 21 | github.com/go-stack/stack v1.8.1 // indirect 22 | github.com/goccy/go-json v0.10.2 // indirect 23 | github.com/json-iterator/go v1.1.12 // indirect 24 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 25 | github.com/kr/text v0.2.0 // indirect 26 | github.com/leodido/go-urn v1.2.4 // indirect 27 | github.com/mattn/go-isatty v0.0.19 // indirect 28 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 29 | github.com/modern-go/reflect2 v1.0.2 // indirect 30 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 31 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 32 | github.com/ugorji/go/codec v1.2.11 // indirect 33 | go.uber.org/multierr v1.11.0 // indirect 34 | golang.org/x/arch v0.3.0 // indirect 35 | golang.org/x/crypto v0.22.0 // indirect 36 | golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect 37 | golang.org/x/net v0.23.0 // indirect 38 | golang.org/x/sys v0.19.0 // indirect 39 | golang.org/x/text v0.14.0 // indirect 40 | google.golang.org/protobuf v1.33.0 // indirect 41 | gopkg.in/yaml.v3 v3.0.1 // indirect 42 | ) 43 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 3 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 7 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= 12 | github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= 13 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 14 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 15 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 16 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 17 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 18 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 19 | github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= 20 | github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= 21 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 22 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 23 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 24 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 25 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 26 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 27 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 28 | github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= 29 | github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= 30 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 31 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 32 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 33 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 34 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 35 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 36 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 37 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 38 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 39 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 40 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 41 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 42 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 43 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 44 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 45 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 46 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 47 | github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= 48 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 49 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 50 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 51 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 52 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 53 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 54 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 55 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 56 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 57 | github.com/playwright-community/playwright-go v0.4201.1 h1:fFX/02r3wrL+8NB132RcduR0lWEofxRDJEKuln+9uMQ= 58 | github.com/playwright-community/playwright-go v0.4201.1/go.mod h1:hpEOnUo/Kgb2lv5lEY29jbW5Xgn7HaBeiE+PowRad8k= 59 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 60 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 61 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 62 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 63 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 64 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 65 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 66 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 67 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 68 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 69 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 70 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 71 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 72 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 73 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 74 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 75 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 76 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 77 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 78 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 79 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 80 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 81 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 82 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 83 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 84 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 85 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 86 | golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= 87 | golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 88 | golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= 89 | golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 90 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 91 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 92 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 93 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 94 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 95 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 96 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 97 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 98 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 99 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 100 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 101 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 102 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 103 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 104 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 107 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 108 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 109 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 110 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 111 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 112 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 113 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 114 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 115 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 116 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 117 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 118 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 119 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 120 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 121 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 122 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 123 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 124 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 125 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 126 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 127 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 128 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 129 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 130 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 131 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 132 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 133 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 134 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 135 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 136 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 137 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 138 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 139 | -------------------------------------------------------------------------------- /internal/envs/env_key.go: -------------------------------------------------------------------------------- 1 | package envs 2 | 3 | type EnvKey string 4 | 5 | const ( 6 | LessapiDefaultLanguage EnvKey = "LESSAPI_DEFAULT_LANGUAGE" 7 | LessapiDefaultViaProxyUrl = "LESSAPI_DEFAULT_VIA_PROXY_URL" 8 | ) 9 | -------------------------------------------------------------------------------- /internal/handles/index_handle.go: -------------------------------------------------------------------------------- 1 | package handles 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | 6 | "github.com/lessapidev/lessapi-duckduckgo" 7 | "github.com/lessapidev/lessapi-duckduckgo/internal/utils" 8 | ) 9 | 10 | func IndexHandle(c *gin.Context) { 11 | resp := IndexResponse{ 12 | Server: lessapi_duckduckgo.ServerName, 13 | Github: lessapi_duckduckgo.ProjectGithub, 14 | Version: lessapi_duckduckgo.Version, 15 | } 16 | utils.SendResponse(c, utils.BuildApiSuccessData(resp)) 17 | } 18 | 19 | type IndexResponse struct { 20 | Server string `json:"server"` 21 | Github string `json:"github"` 22 | Version string `json:"version"` 23 | } 24 | 25 | func (i IndexResponse) ToLLMStyle() string { 26 | return "Server: " + i.Server + "\nGithub: " + i.Github + "\nVersion: " + i.Version 27 | } 28 | -------------------------------------------------------------------------------- /internal/handles/list_language_handle.go: -------------------------------------------------------------------------------- 1 | package handles 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/lessapidev/lessapi-duckduckgo/internal/searchs" 7 | "github.com/lessapidev/lessapi-duckduckgo/internal/types" 8 | "github.com/lessapidev/lessapi-duckduckgo/internal/utils" 9 | 10 | "github.com/gin-gonic/gin" 11 | "github.com/patrickmn/go-cache" 12 | ) 13 | 14 | var languagesCache = cache.New(5*time.Minute, 10*time.Minute) 15 | 16 | // ListLanguageHandle list all language types 17 | func ListLanguageHandle(c *gin.Context) { 18 | // parse request params 19 | var payload types.ListLanguagePayload 20 | if err := c.ShouldBindQuery(&payload); err != nil { 21 | utils.SendResponse(c, utils.BuildApiError("invalid_params", "invalid request params")) 22 | return 23 | } 24 | 25 | // get from cache 26 | languages, found := languagesCache.Get("languages") 27 | if found { 28 | resp := types.ListLanguageResponse{ 29 | Languages: languages.([]types.LanguageType), 30 | } 31 | utils.SendResponse(c, utils.BuildApiSuccessData(resp)) 32 | return 33 | } 34 | 35 | // search text 36 | resp, err := searchs.ListLanguage(payload) 37 | if err != nil { 38 | utils.SendResponse(c, utils.BuildApiError("fetch_error", err.Error())) 39 | return 40 | } 41 | 42 | if len(resp.Languages) > 0 { 43 | languagesCache.Set("languages", resp.Languages, cache.DefaultExpiration) 44 | } 45 | 46 | // return response 47 | utils.SendResponse(c, utils.BuildApiSuccessData(resp)) 48 | } 49 | -------------------------------------------------------------------------------- /internal/handles/openapi_handle.go: -------------------------------------------------------------------------------- 1 | package handles 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/gin-gonic/gin" 6 | "github.com/lessapidev/lessapi-duckduckgo/internal/utils" 7 | ) 8 | 9 | // OpenAPIHandle handle OpenAPI request 10 | func OpenAPIHandle(c *gin.Context) { 11 | // Read OpenAPI file from resources 12 | jsonRaw, err := utils.ReadLocalFile("./resource/openapi.json") 13 | if err != nil { 14 | c.JSON(200, utils.BuildApiError("read_openapi_error", err.Error())) 15 | return 16 | } 17 | // Parse JSON & Rewrite "servers" field to point to the current server 18 | jsonObj := map[string]interface{}{} 19 | if err := json.Unmarshal([]byte(jsonRaw), &jsonObj); err != nil { 20 | c.JSON(200, utils.BuildApiError("parse_openapi_error", err.Error())) 21 | return 22 | } 23 | jsonObj["servers"] = []map[string]string{} 24 | jsonObj["servers"] = append(jsonObj["servers"].([]map[string]string), map[string]string{ 25 | "url": "http://" + c.Request.Host, 26 | "description": "LessAPI DuckDuckGo API Server", 27 | }) 28 | // Return OpenAPI JSON 29 | c.JSON(200, jsonObj) 30 | } 31 | -------------------------------------------------------------------------------- /internal/handles/text_handle.go: -------------------------------------------------------------------------------- 1 | package handles 2 | 3 | import ( 4 | "github.com/lessapidev/lessapi-duckduckgo/internal/searchs" 5 | "github.com/lessapidev/lessapi-duckduckgo/internal/types" 6 | "github.com/lessapidev/lessapi-duckduckgo/internal/utils" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | // SearchTextHandle search text 12 | func SearchTextHandle(c *gin.Context) { 13 | // parse request params 14 | var payload types.SearchTextPayload 15 | if err := c.ShouldBindQuery(&payload); err != nil { 16 | utils.SendResponse(c, utils.BuildApiError("invalid_params", "invalid request params")) 17 | return 18 | } 19 | 20 | // search text 21 | resp, err := searchs.SearchText(payload) 22 | if err != nil { 23 | utils.SendResponse(c, utils.BuildApiError("search_error", err.Error())) 24 | return 25 | } 26 | 27 | // return response 28 | utils.SendResponse(c, utils.BuildApiSuccessData(resp)) 29 | } 30 | -------------------------------------------------------------------------------- /internal/searchs/browser_support.go: -------------------------------------------------------------------------------- 1 | package searchs 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/lessapidev/lessapi-duckduckgo/internal/envs" 8 | "github.com/lessapidev/lessapi-duckduckgo/internal/utils" 9 | 10 | "github.com/playwright-community/playwright-go" 11 | ) 12 | 13 | // NewBrowserContextWithOptions creates a new browser context with options 14 | func NewBrowserContextWithOptions(opt BrowserOptions) (playwright.BrowserContext, CloseBrowserHandler, error) { 15 | 16 | // start playwright 17 | pw, err := playwright.Run() 18 | if err != nil { 19 | return nil, nil, fmt.Errorf("could not start playwright: %w", err) 20 | } 21 | 22 | // launch browser 23 | launchOptions := playwright.BrowserTypeLaunchOptions{} 24 | launchOptions.Headless = playwright.Bool(true) 25 | 26 | // set proxy 27 | if opt.ProxyServer != nil && *opt.ProxyServer != "" { 28 | launchOptions.Proxy = &playwright.Proxy{Server: *opt.ProxyServer} 29 | } else if utils.GetEnv(envs.LessapiDefaultViaProxyUrl) != "" { 30 | launchOptions.Proxy = &playwright.Proxy{Server: utils.GetEnv(envs.LessapiDefaultViaProxyUrl)} 31 | } 32 | browser, err := pw.Chromium.Launch(launchOptions) 33 | if err != nil { 34 | return nil, nil, fmt.Errorf("could not launch browser: %w", err) 35 | } 36 | // set locale 37 | pLocate := opt.Language 38 | if pLocate == nil || *pLocate == "" { 39 | pLocate = playwright.String(utils.GetEnvOrDefault(envs.LessapiDefaultLanguage, "en-US")) 40 | } else { 41 | pLocate = playwright.String(strings.ReplaceAll(*pLocate, "_", "-")) 42 | } 43 | // set viewport 44 | pViewport := &playwright.Size{ 45 | Width: 1920, 46 | Height: 1080, 47 | } 48 | if opt.viewportWidth != nil && opt.viewportHeight != nil { 49 | pViewport = &playwright.Size{ 50 | Width: *opt.viewportWidth, 51 | Height: *opt.viewportHeight, 52 | } 53 | } 54 | // create context 55 | context, err := browser.NewContext(playwright.BrowserNewContextOptions{ 56 | Locale: pLocate, 57 | Viewport: pViewport, 58 | }) 59 | if err != nil { 60 | return nil, nil, fmt.Errorf("could not create context: %w", err) 61 | } 62 | 63 | // add init stealth.min.js 64 | if opt.EnableStealthJs != nil && *opt.EnableStealthJs { 65 | stealthJs, err := utils.ReadLocalFile("./resource/stealth.min.js") 66 | if err != nil { 67 | return nil, nil, fmt.Errorf("could not read stealth.min.js: %w", err) 68 | } 69 | stealthJsScript := playwright.Script{ 70 | Content: playwright.String(stealthJs), 71 | } 72 | if err = context.AddInitScript(stealthJsScript); err != nil { 73 | return nil, nil, fmt.Errorf("could not add stealth.min.js: %w", err) 74 | } 75 | } 76 | 77 | // create close handler 78 | closeHandler := func() error { 79 | if err := browser.Close(); err != nil { 80 | return fmt.Errorf("could not close browser: %w", err) 81 | } 82 | if err := pw.Stop(); err != nil { 83 | return fmt.Errorf("could not stop Playwright: %w", err) 84 | } 85 | return nil 86 | } 87 | 88 | return context, closeHandler, nil 89 | 90 | } 91 | -------------------------------------------------------------------------------- /internal/searchs/browser_support_types.go: -------------------------------------------------------------------------------- 1 | package searchs 2 | 3 | // BrowserOptions Playwright browser options 4 | type BrowserOptions struct { 5 | ProxyServer *string 6 | EnableStealthJs *bool 7 | Language *string 8 | viewportWidth *int 9 | viewportHeight *int 10 | } 11 | 12 | type CloseBrowserHandler func() error 13 | -------------------------------------------------------------------------------- /internal/searchs/list_language.go: -------------------------------------------------------------------------------- 1 | package searchs 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/lessapidev/lessapi-duckduckgo/internal/types" 8 | "github.com/playwright-community/playwright-go" 9 | ) 10 | 11 | func ListLanguage(param types.ListLanguagePayload) (*types.ListLanguageResponse, error) { 12 | 13 | // ----------------------------------------------------------- 14 | // open browser 15 | // ----------------------------------------------------------- 16 | 17 | // create new browser context 18 | browserOpt := BrowserOptions{ 19 | ProxyServer: ¶m.ViaProxyUrl, 20 | EnableStealthJs: playwright.Bool(true), 21 | viewportWidth: playwright.Int(1920), 22 | viewportHeight: playwright.Int(1080 - 35), 23 | } 24 | browserCtx, browserClose, err := NewBrowserContextWithOptions(browserOpt) 25 | if err != nil { 26 | return nil, fmt.Errorf("could not create virtual browser context: %w", err) 27 | } 28 | defer (func() { 29 | _ = browserClose() 30 | })() 31 | 32 | // create new page 33 | page, err := browserCtx.NewPage() 34 | if err != nil { 35 | return nil, fmt.Errorf("could not create page: %w", err) 36 | } 37 | 38 | // open search page 39 | if _, err = page.Goto("https://duckduckgo.com/?q=duckduckgo", playwright.PageGotoOptions{Timeout: playwright.Float(10 * 1000)}); err != nil { 40 | return nil, fmt.Errorf("could not goto: %w", err) 41 | } 42 | if err = page.WaitForLoadState(playwright.PageWaitForLoadStateOptions{Timeout: playwright.Float(10 * 1000)}); err != nil { 43 | return nil, fmt.Errorf("could not wait for load: %w", err) 44 | } 45 | 46 | // click settings 47 | settingLocator := page.Locator("div[class='dropdown dropdown--settings'] a") 48 | if err := settingLocator.WaitFor(playwright.LocatorWaitForOptions{Timeout: playwright.Float(5 * 1000)}); err != nil { 49 | return nil, fmt.Errorf("could not locate setting: %w", err) 50 | } 51 | if err := settingLocator.Click(); err != nil { 52 | return nil, fmt.Errorf("could not click: %w", err) 53 | } 54 | 55 | // find language select 56 | selectLocator := page.Locator("select[id=setting_kad]") 57 | if err := selectLocator.WaitFor(playwright.LocatorWaitForOptions{Timeout: playwright.Float(5 * 1000)}); err != nil { 58 | return nil, fmt.Errorf("could not locate select: %w", err) 59 | } 60 | 61 | // get languages 62 | optionLocator := selectLocator.Locator("option") 63 | options, err := optionLocator.All() 64 | if err != nil { 65 | return nil, fmt.Errorf("could not get options: %w", err) 66 | } 67 | l := make([]types.LanguageType, 0) 68 | for _, option := range options { 69 | code, err := option.GetAttribute("value") 70 | if err != nil { 71 | return nil, fmt.Errorf("could not get value: %w", err) 72 | } 73 | name, err := option.TextContent() 74 | if err != nil { 75 | return nil, fmt.Errorf("could not get text content: %w", err) 76 | } 77 | l = append(l, types.LanguageType{ 78 | Code: strings.ReplaceAll(code, "_", "-"), 79 | Name: name, 80 | }) 81 | } 82 | 83 | return &types.ListLanguageResponse{ 84 | Languages: l, 85 | }, nil 86 | } 87 | -------------------------------------------------------------------------------- /internal/searchs/search_text.go: -------------------------------------------------------------------------------- 1 | package searchs 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/lessapidev/lessapi-duckduckgo/internal/types" 7 | 8 | "github.com/playwright-community/playwright-go" 9 | ) 10 | 11 | func SearchText(param types.SearchTextPayload) (*types.SearchTextResponse, error) { 12 | 13 | // validate and set default values 14 | if param.Keyword == "" { 15 | return nil, fmt.Errorf("keyword is required") 16 | } 17 | if param.TimeLimit == "" { 18 | param.TimeLimit = "" 19 | } 20 | if param.MaxCount < 1 { 21 | param.MaxCount = 20 22 | } 23 | 24 | // ----------------------------------------------------------- 25 | // open browser 26 | // ----------------------------------------------------------- 27 | 28 | // create new browser context 29 | browserOpt := BrowserOptions{ 30 | ProxyServer: ¶m.ViaProxyUrl, 31 | EnableStealthJs: playwright.Bool(true), 32 | Language: ¶m.Region, 33 | viewportWidth: playwright.Int(1920), 34 | viewportHeight: playwright.Int(1080 - 35), 35 | } 36 | browserCtx, browserClose, err := NewBrowserContextWithOptions(browserOpt) 37 | if err != nil { 38 | return nil, fmt.Errorf("could not create virtual browser context: %w", err) 39 | } 40 | defer (func() { 41 | _ = browserClose() 42 | })() 43 | 44 | // create new page 45 | page, err := browserCtx.NewPage() 46 | if err != nil { 47 | return nil, fmt.Errorf("could not create page: %w", err) 48 | } 49 | 50 | // ----------------------------------------------------------- 51 | // search text 52 | // ----------------------------------------------------------- 53 | 54 | // open homepage, input keyword and search 55 | if _, err = page.Goto("https://duckduckgo.com/", playwright.PageGotoOptions{Timeout: playwright.Float(10 * 1000)}); err != nil { 56 | return nil, fmt.Errorf("could not goto: %w", err) 57 | } 58 | if err = page.WaitForLoadState(playwright.PageWaitForLoadStateOptions{Timeout: playwright.Float(10 * 1000)}); err != nil { 59 | return nil, fmt.Errorf("could not wait for load: %w", err) 60 | } 61 | inputLocator := page.Locator("input[name=q]") 62 | if err := inputLocator.WaitFor(playwright.LocatorWaitForOptions{Timeout: playwright.Float(10 * 1000)}); err != nil { 63 | return nil, fmt.Errorf("could not locate input: %w", err) 64 | } 65 | if err = inputLocator.Fill(param.Keyword); err != nil { 66 | return nil, fmt.Errorf("could not fill: %w", err) 67 | } 68 | 69 | // goto search result page 70 | if err = page.Keyboard().Press("Enter"); err != nil { 71 | return nil, fmt.Errorf("could not press Enter: %w", err) 72 | } 73 | majorAreaLocator := page.Locator("section[data-testid=mainline]") 74 | if err = majorAreaLocator.WaitFor(playwright.LocatorWaitForOptions{Timeout: playwright.Float(10 * 1000)}); err != nil { 75 | return nil, fmt.Errorf("could not find major div: %w", err) 76 | } 77 | if err = page.WaitForLoadState(playwright.PageWaitForLoadStateOptions{ 78 | State: playwright.LoadStateNetworkidle, 79 | Timeout: playwright.Float(10 * 1000), 80 | }); err != nil { 81 | return nil, fmt.Errorf("could not wait for load result: %w", err) 82 | } 83 | 84 | // ----------------------------------------------------------- 85 | // parse search results & auto next page 86 | // ----------------------------------------------------------- 87 | 88 | // find all search result elements 89 | searchResultList := make([]types.SearchTextResultItem, 0) 90 | for len(searchResultList) < param.MaxCount { 91 | elResultLiList, err := page.Locator("ol[class=react-results--main]").Locator("article[data-testid=result]").All() 92 | if err != nil { 93 | return nil, fmt.Errorf("could not get search results: %w", err) 94 | } 95 | searchResultList = make([]types.SearchTextResultItem, 0) 96 | for i, elResultLi := range elResultLiList { 97 | title, err := elResultLi.Locator("a[data-testid=result-title-a]").InnerText() 98 | if err != nil { 99 | continue 100 | } 101 | href, err := elResultLi.Locator("a[data-testid=result-title-a]").GetAttribute("href") 102 | if err != nil { 103 | continue 104 | } 105 | description, err := elResultLi.Locator("div[data-result=snippet]").InnerText() 106 | if err != nil { 107 | continue 108 | } 109 | 110 | searchResultList = append(searchResultList, types.SearchTextResultItem{ 111 | Order: i + 1, 112 | Title: title, 113 | Url: href, 114 | Description: description, 115 | }) 116 | if len(searchResultList) >= param.MaxCount { 117 | break // enough results 118 | } 119 | } 120 | if len(searchResultList) >= param.MaxCount { 121 | searchResultList = searchResultList[:param.MaxCount] 122 | break // enough results 123 | } 124 | // scroll to bottom 125 | if _, err = page.Evaluate("window.scrollTo(0, document.body.scrollHeight)"); err != nil { 126 | continue 127 | } 128 | // wait for elements to load 129 | if err = page.WaitForLoadState(playwright.PageWaitForLoadStateOptions{ 130 | State: playwright.LoadStateNetworkidle, 131 | Timeout: playwright.Float(10 * 1000), 132 | }); err != nil { 133 | break 134 | } 135 | // auto next page 136 | locator := page.Locator("button[id=more-results]:not([disabled])") 137 | if err = locator.WaitFor(playwright.LocatorWaitForOptions{Timeout: playwright.Float(5 * 1000)}); err != nil { 138 | break 139 | } 140 | exist, err := locator.IsVisible() 141 | if err != nil { 142 | break 143 | } 144 | if exist { 145 | if err = locator.Click(); err != nil { 146 | break 147 | } 148 | // wait for elements to load 149 | if err = page.WaitForLoadState(playwright.PageWaitForLoadStateOptions{ 150 | State: playwright.LoadStateNetworkidle, 151 | Timeout: playwright.Float(10 * 1000), 152 | }); err != nil { 153 | break 154 | } 155 | } else { 156 | break // no more results 157 | } 158 | } 159 | 160 | return &types.SearchTextResponse{ 161 | Results: searchResultList, 162 | }, nil 163 | } 164 | -------------------------------------------------------------------------------- /internal/server.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/gin-gonic/gin" 7 | 8 | "github.com/lessapidev/lessapi-duckduckgo/internal/handles" 9 | ) 10 | 11 | func Run() { 12 | gin.SetMode(gin.ReleaseMode) 13 | 14 | r := gin.Default() 15 | 16 | r.GET("/", handles.IndexHandle) 17 | r.GET("/openapi/openapi.json", handles.OpenAPIHandle) 18 | r.GET("/search/text", handles.SearchTextHandle) 19 | r.GET("/base/list-language", handles.ListLanguageHandle) 20 | 21 | err := r.Run(":8080") 22 | if err != nil { 23 | log.Fatal("Unable to start server: ", err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /internal/types/responses.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "fmt" 4 | 5 | type SearchTextPayload struct { 6 | // Keywords to search (required) 7 | Keyword string `json:"keyword" form:"keyword" validate:"required"` 8 | // Region to search (optional) (default "en-US") 9 | Region string `json:"region" form:"region"` 10 | // Time limit to search (optional) ( "" default all , "d" past day, "w" past week, "m" past month, "y" past year) 11 | TimeLimit string `json:"timeLimit" form:"timeLimit"` 12 | // Max count of search results (optional) (default 20) 13 | MaxCount int `json:"maxCount" form:"maxCount"` 14 | // Proxy url to use (optional) 15 | ViaProxyUrl string `json:"viaProxyUrl" form:"viaProxyUrl"` 16 | } 17 | 18 | type SearchTextResponse struct { 19 | Results []SearchTextResultItem `json:"results"` 20 | } 21 | 22 | func (s SearchTextResponse) ToLLMStyle() string { 23 | var resultStr string 24 | for i, item := range s.Results { 25 | resultStr += fmt.Sprintf("# %d. %s\nURL:%s\nAbstract:%s\n\n", i+1, item.Title, item.Url, item.Description) 26 | } 27 | return resultStr 28 | } 29 | 30 | type SearchTextResultItem struct { 31 | Order int `json:"order"` // order of the result 32 | Title string `json:"title"` // title of the result 33 | Url string `json:"url"` // url of the result 34 | Description string `json:"description"` // description of the result 35 | } 36 | 37 | // ----------------------------------------------------------- 38 | 39 | type LanguageType struct { 40 | Code string `json:"code"` 41 | Name string `json:"name"` 42 | } 43 | 44 | type ListLanguagePayload struct { 45 | // Proxy url to use (optional) 46 | ViaProxyUrl string `json:"viaProxyUrl" form:"viaProxyUrl"` 47 | } 48 | 49 | type ListLanguageResponse struct { 50 | Languages []LanguageType `json:"languages"` 51 | } 52 | 53 | func (l ListLanguageResponse) ToLLMStyle() string { 54 | var resultStr string 55 | for _, item := range l.Languages { 56 | resultStr += fmt.Sprintf("%s (%s)\n", item.Name, item.Code) 57 | } 58 | return resultStr 59 | } 60 | -------------------------------------------------------------------------------- /internal/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type IResponse interface { 4 | ToLLMStyle() string 5 | } 6 | 7 | type ApiResponse struct { 8 | Code string `json:"code"` 9 | Message *string `json:"message,omitempty"` 10 | Data IResponse `json:"data,omitempty"` 11 | } 12 | 13 | type EmptyResponse struct{} 14 | 15 | func (e EmptyResponse) ToLLMStyle() string { 16 | return "success" 17 | } 18 | -------------------------------------------------------------------------------- /internal/utils/env_util.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/lessapidev/lessapi-duckduckgo/internal/envs" 7 | ) 8 | 9 | // GetEnvOrDefault returns the value of the environment variable with the given key. 10 | func GetEnvOrDefault(key envs.EnvKey, defaultValue string) string { 11 | value := os.Getenv(string(key)) 12 | if value == "" { 13 | return defaultValue 14 | } 15 | return value 16 | } 17 | 18 | // GetEnv returns the value of the environment variable with the given key. 19 | func GetEnv(key envs.EnvKey) string { 20 | return os.Getenv(string(key)) 21 | } 22 | -------------------------------------------------------------------------------- /internal/utils/file_util.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | // ReadLocalFile reads the content of the file with the given filename. 9 | func ReadLocalFile(filename string) (string, error) { 10 | file, err := os.Open(filename) 11 | if err != nil { 12 | return "", err 13 | } 14 | defer func() { 15 | _ = file.Close() 16 | }() 17 | 18 | data, err := io.ReadAll(file) 19 | if err != nil { 20 | return "", err 21 | } 22 | return string(data), nil 23 | } 24 | -------------------------------------------------------------------------------- /internal/utils/response_util.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/lessapidev/lessapi-duckduckgo/internal/types" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | // BuildApiSuccessData returns a new ApiResponse with code "success" and the given data. 12 | func BuildApiSuccessData(data types.IResponse) *types.ApiResponse { 13 | return &types.ApiResponse{ 14 | Code: "success", 15 | Message: nil, 16 | Data: data, 17 | } 18 | 19 | } 20 | 21 | // BuildApiError returns a new ApiResponse with the given error code and message. 22 | func BuildApiError(errCode string, errorMessage string) *types.ApiResponse { 23 | return &types.ApiResponse{ 24 | Code: errCode, 25 | Message: &errorMessage, 26 | } 27 | } 28 | 29 | // ----------------------------------------------------------- 30 | 31 | // BuildLLMError returns an error message for LLM. 32 | func BuildLLMError(errCode string, errorMessage string) string { 33 | return fmt.Sprintf("fail with error: %s\n error message is: %s", errCode, errorMessage) 34 | } 35 | 36 | // -------------------------------------------------------- 37 | 38 | func SendResponse(ctx *gin.Context, resp *types.ApiResponse) { 39 | // Check if LLM style is requested 40 | // Support both header and query parameter 41 | llmStyleByHeader := ctx.Request.Header.Get("x-llm-style") 42 | llmStyleByQuery := ctx.Query("llmStyle") 43 | 44 | if llmStyleByHeader == "1" || llmStyleByQuery == "1" { 45 | // LLM style 46 | if resp.Code == "success" { 47 | ctx.String(200, resp.Data.ToLLMStyle()) 48 | } else { 49 | httpCode := 500 50 | if resp.Code == "invalid_params" { 51 | httpCode = 400 52 | } 53 | ctx.String(httpCode, BuildLLMError(resp.Code, *resp.Message)) 54 | } 55 | } else { 56 | // API style 57 | if resp.Code == "success" { 58 | ctx.JSON(200, resp) 59 | } else { 60 | httpCode := 500 61 | if resp.Code == "invalid_params" { 62 | httpCode = 400 63 | } 64 | ctx.JSON(httpCode, resp) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /resource/openapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.1", 3 | "info": { 4 | "title": "lessapi-duckduckgo", 5 | "description": "", 6 | "version": "0.0.1" 7 | }, 8 | "tags": [], 9 | "paths": { 10 | "/search/text": { 11 | "get": { 12 | "summary": "search/text", 13 | "deprecated": false, 14 | "description": "search text by keyword.", 15 | "tags": [], 16 | "parameters": [ 17 | { 18 | "name": "keyword", 19 | "in": "query", 20 | "description": "keywords for query.", 21 | "required": true, 22 | "example": "lessapi", 23 | "schema": { 24 | "type": "string" 25 | } 26 | }, 27 | { 28 | "name": "region", 29 | "in": "query", 30 | "description": "locate region. en-US, fr-FR, zh-CN, ru-RU, etc. Defaults to \"en-US\".", 31 | "required": false, 32 | "schema": { 33 | "type": "string" 34 | } 35 | }, 36 | { 37 | "name": "timeLimit", 38 | "in": "query", 39 | "description": "d (day), w (week), m (month), y (year). Defaults to None.", 40 | "required": false, 41 | "schema": { 42 | "type": "string" 43 | } 44 | }, 45 | { 46 | "name": "maxCount", 47 | "in": "query", 48 | "description": "max number of results. Defaults to 20.", 49 | "required": false, 50 | "schema": { 51 | "type": "string" 52 | } 53 | }, 54 | { 55 | "name": "viaProxyUrl", 56 | "in": "query", 57 | "description": "proxy url to use by browser. Defaults to None.", 58 | "required": false, 59 | "schema": { 60 | "type": "string" 61 | } 62 | } 63 | ], 64 | "responses": { 65 | "200": { 66 | "description": "成功", 67 | "content": { 68 | "application/json": { 69 | "schema": { 70 | "type": "object", 71 | "properties": { 72 | "code": { 73 | "type": "string", 74 | "description": "\"success\" or ErrorCode" 75 | }, 76 | "message": { 77 | "type": "string", 78 | "description": "error message" 79 | }, 80 | "data": { 81 | "type": "object", 82 | "properties": { 83 | "results": { 84 | "type": "array", 85 | "items": { 86 | "$ref": "#/components/schemas/SearchTextResultItem" 87 | }, 88 | "description": "search result items" 89 | } 90 | }, 91 | "description": "success data", 92 | "required": [ 93 | "results" 94 | ] 95 | } 96 | }, 97 | "required": [ 98 | "code" 99 | ] 100 | }, 101 | "examples": { 102 | "1": { 103 | "summary": "成功示例", 104 | "value": { 105 | "code": "success", 106 | "data": { 107 | "results": [ 108 | { 109 | "order": 1, 110 | "title": "Adele - Hello (Official Music Video) - YouTube", 111 | "href": "https://www.youtube.com/watch?v=YQHsXMglC9A", 112 | "description": "Listen to \"Easy On Me\" here: http://Adele.lnk.to/EOMPre-order Adele's new album \"30\" before its release on November 19: https://www.adele.comShop the \"Adele..." 113 | }, 114 | { 115 | "order": 2, 116 | "title": "Hello Definition & Meaning - Merriam-Webster", 117 | "href": "https://www.merriam-webster.com/dictionary/hello", 118 | "description": "Learn the origin, usage, and synonyms of the word hello, an expression or gesture of greeting. See examples of hello in sentences and related words from the dictionary." 119 | }, 120 | { 121 | "order": 3, 122 | "title": "HELLO | definition in the Cambridge English Dictionary", 123 | "href": "https://dictionary.cambridge.org/us/dictionary/english/hello", 124 | "description": "Learn the meaning, pronunciation and usage of hello, a common greeting and phone call in English. Find out how to say hello in different situations and languages with examples and translations." 125 | }, 126 | { 127 | "order": 4, 128 | "title": "Hello - Wikipedia", 129 | "href": "https://en.wikipedia.org/wiki/Hello", 130 | "description": "Hello is a salutation or greeting in the English language. It is first attested in writing from 1826. Early uses. Hello, with that spelling, was used in publications in the U.S. as early as the 18 October 1826 edition of the Norwich Courier of Norwich, Connecticut." 131 | }, 132 | { 133 | "order": 5, 134 | "title": "Adele - Hello (Official Music Video) - YouTube Music", 135 | "href": "https://music.youtube.com/watch?v=YQHsXMglC9A", 136 | "description": "Add similar content to the end of the queue. Autoplay is on. Player bar" 137 | }, 138 | { 139 | "order": 6, 140 | "title": "Adele - Hello | Lyrics | HD - YouTube", 141 | "href": "https://www.youtube.com/watch?v=CaHWq1rheeU", 142 | "description": "\"Hello\" is taken from the new album, 25, out November 20.http://adele.comFollow Adele on: Facebook - https://www.facebook.com/AdeleTwitter - https://twitter...." 143 | }, 144 | { 145 | "order": 7, 146 | "title": "HELLO | meaning - Cambridge Learner's Dictionary", 147 | "href": "https://dictionary.cambridge.org/dictionary/learner-english/hello", 148 | "description": "Learn the meaning and usage of hello, an exclamation to greet someone or start a phone call. Find translations of hello in many languages, such as Chinese, Spanish, French, Arabic, and more." 149 | }, 150 | { 151 | "order": 8, 152 | "title": "The Origin of 'Hello' | Merriam-Webster", 153 | "href": "https://www.merriam-webster.com/wordplay/the-origin-of-hello", 154 | "description": "Learn how hello became the most common word for greeting in English, and how it replaced older terms like hail and ahoy. Discover the history and usage of hello, from its first appearance in the 1800s to its role in the telephone era." 155 | }, 156 | { 157 | "order": 9, 158 | "title": "Adele - Hello Lyrics | Genius Lyrics", 159 | "href": "https://genius.com/Adele-hello-lyrics", 160 | "description": "Hello Lyrics: Hello, it's me / I was wondering if, after all these years, you'd like to meet / To go over everything / They say that time's supposed to heal ya / But I ain't done much healin" 161 | }, 162 | { 163 | "order": 10, 164 | "title": "HELLO | English meaning - Cambridge Dictionary", 165 | "href": "https://dictionary.cambridge.org/dictionary/english/hello", 166 | "description": "Learn the meaning and usage of the word hello in English, with examples from different contexts and sources. Find out how to pronounce hello and how to say it in other languages." 167 | } 168 | ] 169 | } 170 | } 171 | } 172 | } 173 | } 174 | } 175 | } 176 | } 177 | } 178 | } 179 | }, 180 | "components": { 181 | "schemas": { 182 | "SearchTextResultItem": { 183 | "type": "object", 184 | "properties": { 185 | "order": { 186 | "type": "integer" 187 | }, 188 | "title": { 189 | "type": "string" 190 | }, 191 | "url": { 192 | "type": "string" 193 | }, 194 | "description": { 195 | "type": "string" 196 | } 197 | }, 198 | "required": [ 199 | "order", 200 | "title", 201 | "url", 202 | "description" 203 | ] 204 | }, 205 | "ApiResponse": { 206 | "type": "object", 207 | "properties": { 208 | "code": { 209 | "type": "string", 210 | "description": "\"success\" or ErrorCode" 211 | }, 212 | "message": { 213 | "type": "string", 214 | "description": "error message" 215 | }, 216 | "data": { 217 | "type": "string" 218 | } 219 | }, 220 | "required": [ 221 | "code" 222 | ] 223 | } 224 | } 225 | }, 226 | "servers": [ 227 | { 228 | "url": "http://127.0.0.1:8080", 229 | "description": "Local Development Server" 230 | } 231 | ] 232 | } -------------------------------------------------------------------------------- /test/test.http: -------------------------------------------------------------------------------- 1 | ### GET ROOT 2 | GET http://127.0.0.1:8080 3 | Accept: application/json 4 | 5 | 6 | ### Search Text 7 | GET http://127.0.0.1:8080/search/text?keyword=lessapi&maxCount=15 8 | Accept: application/json 9 | 10 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package lessapi_duckduckgo 2 | 3 | const ServerName = "LessAPI-DuckDuckGo" 4 | const ProjectGithub = "https://github.com/lessapidev/lessapi-duckduckgo" 5 | const Version = "0.0.3" 6 | --------------------------------------------------------------------------------