Skip to content

Commit

Permalink
feat: update implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeSiu committed Apr 23, 2024
1 parent 0f653b2 commit b2907b8
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 247 deletions.
61 changes: 35 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,32 @@ import * as XenoCanto from "xeno-canto-api-ts";
You can pass a string query to the `search` method like this:

```ts
const result = await XenoCanto.search("Owl");
const result = await XenoCanto.search({ query: "Owl" });
// Do something with result
```

or

```ts
XenoCanto.search({query: "Owl"}).then((result) => {
XenoCanto.search({ query: "Owl" }).then((result) => {
// Do something with result
});
```

If the search is successful, the `search` method will return an object with the following properties:

* `url`: The query URL used for the search
* `rawResponse`: The original Response object from the fetch
* `xrResponse`: An `XCResponse` object that contains the fetched data
- `url`: The query URL used for the search
- `rawResponse`: The original Response object from the fetch
- `xrResponse`: An `XCResponse` object that contains the fetched data

You can access the data like this:

```ts
console.log(result.rawResponse.status) //Response status code, e.g., 200
console.log(result.xcResponse.numRecordings) // Total number of recordings
console.log(result.xcResponse.recordings[0].file) // The first recording result's sound file download URL
console.log(result.rawResponse.status); //Response status code, e.g., 200
console.log(result.xcResponse.numRecordings); // Total number of recordings
console.log(result.xcResponse[XCResponseKey.numRecordings]); // Same as above, but use the defined enum as key
console.log(result.xcResponse.recordings[0].file); // The first recording result's sound file download URL
console.log(result.xcResponse[XCResponseKey.recordings][0][XCRecordingKey.file]); // Same as above, but use the defined enum as key
```

### Advanced Search
Expand All @@ -64,17 +66,17 @@ You can pass a `XCQueryOption` object to the `search` method like this:
```ts
// Create options
const options: XenoCanto.XCQueryOption = {
query: "Eagle",
grp: "birds",
cnt: "United States",
query: "Eagle", // Required
grp: "birds", // Optional
cnt: "United States", // Optional
// ...
};

const result = await XenoCanto.search(options);
```

* Some of the `XCQueryOption` properties accepts operators such as `=`, `>`, `<` or `-`. For example, the recording length property `len` can accept `10`, `">120"` or `"=19.8"`.
* The options list can accept additional properties that are not specified in the current API documentation in case of future updates. Note that the API will disregard any non-existing query parmeters.
- Some of the `XCQueryOption` properties accepts operators such as `=`, `>`, `<` or `-`. For example, the recording length property `len` can accept `10`, `">120"` or `"=19.8"`.
- The options list can accept additional properties that are not specified in the current API documentation in case of future updates. Note that the API will disregard any non-existing query parmeters.

#### Multiple Pages

Expand All @@ -100,13 +102,13 @@ console.log(result.xcResponse.recordings[0]);
await new Promise((resolve) => setTimeout(resolve, 1000));

// Check if there are more pages
const numPages = result.xcResponse.numPages;
const totalPages = result.xcResponse.numPages;
if (numPages > 1) {
// Loop through rest of the pages
for (let currentPage = 2; currentPage < numPages; currentPage++) {
for (let currentPage = 2; currentPage < totalPages; currentPage++) {
// Begin next search
console.log(`Fetching page ${page}/${numPages}...`);
const result = await XenoCanto.search({...options, page: currentPage}); // Here we pass the original query and options with a new page
console.log(`Fetching page ${currentPage}/${totalPages}...`);
const result = await XenoCanto.search({ ...options, page: currentPage }); // Here we pass the original query and options with a new page

// Print first recording data from response
console.log(result.xcResponse.recordings[0]);
Expand All @@ -119,7 +121,7 @@ if (numPages > 1) {

### Additional Options

The wrapper also provides additional options by passing a `AdditionalWrapperOption` object to the `search` method
The wrapper also provides additional options by passing a `AdditionalSearchOption` object to the `search` method

#### Change API Base URL

Expand All @@ -128,11 +130,11 @@ For development purpose, the Base URL can be changed as follows:
```ts
// Create options
const options: XenoCanto.XCQueryOption = {
query: "Owl"
query: "Owl",
};
const additionalOptions: XenoCanto.AdditionalSearchOption = {
baseUrl: "https://run.mocky.io/v3/9f08db9a-cfba-4b1d-8c4a-765932f6cf3b", // A custom URL that will return a example JSON data
};
const additionalOptions = {
baseUrl: "https://run.mocky.io/v3/9f08db9a-cfba-4b1d-8c4a-765932f6cf3b", // A fake JSON server URL
} as XenoCanto.AdditionalWrapperOption;

const result = await XenoCanto.search(options, additionalOptions);
```
Expand All @@ -141,15 +143,20 @@ const result = await XenoCanto.search(options, additionalOptions);

#### Custom Data Fetching

If you wish to implement your own data retrieval methods instead of using the Fetch API, you can utilize the `convertJsonToXCResponse` method by passing the JSON response:
If you wish to implement your own data retrieval methods instead of using the default Fetch API, you can utilize the `constructQueryUrl` and `convertJsonToXCResponse` methods:

```ts
const xcResponse = XenoCanto.convertJsonToXCResponse(json);
const options: XenoCanto.XCQueryOption = {
query: "Owl",
};
const customUrl = XenoCanto.constructQueryUrl("/custom-endpoint/", options); // This will returns string `/custom-endpoint/?query="Owl"`
// Your implementation to retrieve the JSON data...
const xcResponse = XenoCanto.convertJsonToXCResponse(json); // If the JSON format is correct, this will convert it to type `XCResponse` which has type hinting
```

#### Query Parameters Names
#### Query Parameters / Response's Key Names

To get the query parameters names / response JSON key names of the Xeno Canto API, you can use the `XCQueryNameDefinition`, `XCResponseNameDefinition` and `XCRecordingNameDefinition` enum, for example, `XCQueryNameDefinition.rec `will return the string `rec`.
To get the query parameters names / response JSON key names of the Xeno Canto API, you can use the `XCQueryNameDefinition`, `XCResponseNameDefinition` and `XCRecordingNameDefinition` enum, for example, `XCQueryNameDefinition.rec`will return the string `rec`.

## Limitation

Expand All @@ -160,3 +167,5 @@ Due to the API limitation, only English queries are supported, and the query sho
Please refer to the [documentation](https://joesiu.github.io/xeno-canto-api-ts/) for details and API references.

To learn more about the Xeno Canto's query parmeters, see [https://xeno-canto.org/explore/api](https://xeno-canto.org/explore/api) and [https://xeno-canto.org/help/search](https://xeno-canto.org/help/search).

To build this package from source, please refer to the [wiki](https://github.com/JoeSiu/xeno-canto-api-ts/wiki) page.
43 changes: 24 additions & 19 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,43 @@
// Import the types and utility functions
import { AdditionalWrapperOption, XCQueryOption, XCResponse } from "./types";
import { constructQueryUrl, convertJsonToXCResponse } from "./utils/utils";
import { XCQueryOption, XCRecordingKey, XCResponse, XCResponseKey } from "./types";
import { AdditionalConvertOption, constructQueryUrl, convertJsonToXCResponse, sanitizeQuery } from "./utils";

// Define the base URL for the API
export const BASE_URL = "https://www.xeno-canto.org/api/2/recordings";

/**
* Represents additional options for the search function.
*/
export interface AdditionalSearchOption extends AdditionalConvertOption {
/**
* Override the default BASE_URL.
*/
baseUrl?: string;
}

/**
* Searches for a query via Fetch API and returns the response and XC response.
*
* @param {XCQueryOption} [options] - Options for the search query.
* @param {AdditionalWrapperOption} [additionalOptions] - Additional options for this wrapper.
* @param {AdditionalSearchOption} [additionalOptions] - Additional search options.
* @return {Promise<{ url: URL, response: Response; xcResponse: XCResponse }>} A promise that resolves to an object containing the query URL, the response from fetch and a XCResponse object.
*/
async function search(
options: XCQueryOption,
additionalOptions?: AdditionalWrapperOption,
additionalOptions?: AdditionalSearchOption,
): Promise<{ url: URL; rawResponse: Response; xcResponse: XCResponse }> {
// If query is empty and options is not provided, throw an error instantly instead of trying to fetch
if (!options.query.trim()) {
return Promise.reject(
new Error(
"Please ensure that the 'query' parameter is not empty or that the 'options' parameter is provided",
),
);
}
try {
// Sanitize the query
options.query = sanitizeQuery(options.query);

// Create the query URL
const url = constructQueryUrl(
additionalOptions?.baseUrl ?? BASE_URL,
options,
);
// Create the query URL
const url = constructQueryUrl(
additionalOptions?.baseUrl ?? BASE_URL,
options,
{ skipSanitizeQuery: additionalOptions?.skipSanitizeQuery || true } // As the query is already sanitized above, we don't need to sanitize it again (unless explicitly set in additionalOptions)
);

// Fetch the response and parse the JSON
try {
// Fetch the response and parse the JSON
const rawResponse = await fetch(url);
const json = await rawResponse.json();
const xcResponse = convertJsonToXCResponse(json);
Expand Down
Loading

0 comments on commit b2907b8

Please sign in to comment.