Skip to content

Commit

Permalink
feat(credentials): added support for credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
AtoraSuunva committed Jul 28, 2022
1 parent 206389e commit 76199a8
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 33 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,27 @@
# booru Changelog

## 2.6.0

- Added credential support
- Support is fairly basic and only support query param auth
- The credential object provided will be serialized into query params
- Previously credentials did nothing, now they do something
- Updated some types to return more useful type unions (like AnySite being a union of all site domains)
- `Booru#getSearchUrl` now properly accepts no parameters

```js
const booru = require('booru')
const gelbooru = booru.forSite('gb', { api_key: 'key', user_id: 'id' })
gelbooru.getSearchUrl()
// https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1&tags=&limit=100&pid=1&api_key=key&user_id=id

// or
booru.search('gb', [], {
credentials: { api_key: 'key', user_id: 'id' },
})
// Uses the same search URL as above
```

## 2.5.9

- Fix gelbooru returning wrong creation date ([thanks respektive](https://github.com/AtoraSuunva/booru/pull/89))
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "booru",
"version": "2.5.9",
"version": "2.6.0",
"description": "Search (and do other things) on a bunch of different boorus!",
"author": "AtoraSuunva (https://github.com/AtoraSuunva/)",
"license": "MIT",
Expand Down
38 changes: 30 additions & 8 deletions src/Constants.ts
Expand Up @@ -4,13 +4,28 @@
*/

import { RequestInit } from 'undici'
import { BooruCredentials } from './boorus/Booru'
import siteJson from './sites.json'
import Site from './structures/Site'
import SiteInfo from './structures/SiteInfo'
import { querystring } from './Utils'

export interface SMap<V> {
[key: string]: V
}
export type AnySite =
| 'e621.net'
| 'e926.net'
| 'hypnohub.net'
| 'danbooru.donmai.us'
| 'konachan.com'
| 'konachan.net'
| 'yande.re'
| 'gelbooru.com'
| 'rule34.xxx'
| 'safebooru.org'
| 'tbib.org'
| 'xbooru.com'
| 'rule34.paheal.net'
| 'derpibooru.org'
| 'realbooru.com'

type gelTags = {
'rating:e': 'rating:explicit'
Expand All @@ -29,7 +44,7 @@ const expandedTags: gelTags = {
/**
* A map of site url/{@link SiteInfo}
*/
export const sites = siteJson as unknown as SMap<SiteInfo>
export const sites = siteJson as Record<AnySite, SiteInfo>

/**
* Custom error type for when the boorus error or for user-side error, not my code (probably)
Expand Down Expand Up @@ -78,19 +93,26 @@ function expandTags(tags: string[]): string[] {
* @param {string[]} [tags=[]] The tags to search for
* @param {number} [limit=100] The limit for images to return
* @param {number} [page=0] The page to get
* @param {BooryCredentials} [credentials] The credentials to use for the search, appended to the querystring
*/
export function searchURI(
site: Site,
tags: string[] = [],
limit = 100,
page: number,
page = 0,
credentials: BooruCredentials = {},
): string {
const query = querystring({
[site.tagQuery]: expandTags(tags).join(site.tagJoin),
limit,
[site.paginate]: page,
...credentials,
})

return (
`http${site.insecure ? '' : 's'}://` +
`${site.domain}${site.api.search}` +
`${site.tagQuery}=${expandTags(tags).join(site.tagJoin)}` +
`&limit=${limit}` +
`&${site.paginate}=${page}`
query
)
}

Expand Down
49 changes: 43 additions & 6 deletions src/Utils.ts
Expand Up @@ -3,7 +3,7 @@
* @module Utils
*/

import { BooruError, sites } from './Constants'
import { AnySite, BooruError, sites } from './Constants'

import { XMLParser } from 'fast-xml-parser'

Expand All @@ -13,20 +13,20 @@ import { XMLParser } from 'fast-xml-parser'
* @param {String} domain The site to resolveSite
* @return {String?} null if site is not supported, the site otherwise
*/
export function resolveSite(domain: string): string | null {
export function resolveSite(domain: string): AnySite | null {
if (typeof domain !== 'string') {
return null
}

domain = domain.toLowerCase()

for (const site in sites) {
for (const [site, info] of Object.entries(sites)) {
if (
site === domain ||
sites[site].domain === domain ||
sites[site].aliases.includes(domain)
info.domain === domain ||
info.aliases.includes(domain)
) {
return site
return site as AnySite
}
}

Expand Down Expand Up @@ -189,3 +189,40 @@ export function compareArrays(arr1: string[], arr2: string[]): string[] {
arr2.some((e2) => e1.toLowerCase() === e2.toLowerCase()),
)
}

type URIEncodable = string | number | boolean
type QueryValue = URIEncodable | URIEncodable[]

/**
* Turns an object into a query string, correctly encoding uri components
*
* @example
* const options = { page: 10, limit: 100 }
* const query = querystring(options) // 'page=10&limit=100'
* console.log(`https://example.com?${query}`)
*
* @param query An object with key/value pairs that will be turned into a string
* @returns A string that can be appended to a url (after `?`)
*/
export function querystring(query: Record<string, QueryValue>): string {
return Object.entries(query)
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIQueryValue(value)}`,
)
.join('&')
}

/**
* Encodes a single value or an array of values to be usable in as a URI component,
* joining array elements with '+'
* @param value The value to encode
* @returns An encoded value that can be passed to a querystring
*/
export function encodeURIQueryValue(value: QueryValue): string {
if (Array.isArray(value)) {
return value.map(encodeURIComponent).join('+')
} else {
return encodeURIComponent(value)
}
}
9 changes: 3 additions & 6 deletions src/boorus/Booru.ts
Expand Up @@ -20,10 +20,7 @@ declare const window: any
const resolvedFetch: typeof fetch =
typeof window !== 'undefined' ? window.fetch.bind(window) : fetch

// WIP, will use implement later
export interface BooruCredentials {
token: string
}
export type BooruCredentials = Record<string, string>

interface SearchUrlParams {
tags: string[]
Expand Down Expand Up @@ -226,8 +223,8 @@ export class Booru {
tags = [],
limit = 100,
page = 1,
}: Partial<SearchUrlParams>): string {
return searchURI(this.site, tags, limit, page)
}: Partial<SearchUrlParams> = {}): string {
return searchURI(this.site, tags, limit, page, this.credentials)
}

/**
Expand Down
22 changes: 14 additions & 8 deletions src/index.ts
Expand Up @@ -3,7 +3,7 @@
* @module Index
*/

import { BooruError, sites, SMap } from './Constants'
import { AnySite, BooruError, sites } from './Constants'

import { deprecate } from 'util'
import Booru, { BooruCredentials } from './boorus/Booru'
Expand All @@ -20,7 +20,7 @@ const BooruTypes: Record<string, typeof Booru> = {
xml: XmlBooru,
}

const booruCache: SMap<Booru> = {}
const booruCache: Partial<Record<AnySite, Booru>> = {}

/**
* Create a new booru, if special type, use that booru, else use default Booru
Expand All @@ -42,10 +42,10 @@ function booruFrom(booruSite: Site, credentials?: BooruCredentials): Booru {
*
* @constructor
* @param {String} site The {@link Site} domain (or alias of it) to create a booru from
* @param {*} credentials The credentials to use on this booru
* @param {BooruCredentials} credentials The credentials to use on this booru
* @return {Booru} A booru to use
*/
function booruForSite(site: string, credentials: any = null): Booru {
function booruForSite(site: string, credentials?: BooruCredentials): Booru {
const rSite = resolveSite(site)

if (!rSite) throw new BooruError('Site not supported')
Expand All @@ -59,6 +59,10 @@ function booruForSite(site: string, credentials: any = null): Booru {
export { booruForSite as forSite }
export default booruForSite

export interface BooruSearch extends SearchParameters {
credentials?: BooruCredentials
}

/**
* Searches a site for images with tags and returns the results
* @param {String} site The site to search
Expand All @@ -77,9 +81,9 @@ export default booruForSite
export function search(
site: string,
tags: string[] | string = [],
{ limit = 1, random = false, page = 0, credentials }: SearchParameters = {},
{ limit = 1, random = false, page = 0, credentials = {} }: BooruSearch = {},
): Promise<SearchResults> {
const rSite: string | null = resolveSite(site)
const rSite = resolveSite(site)

if (typeof limit === 'string') {
limit = parseInt(limit, 10)
Expand All @@ -100,10 +104,12 @@ export function search(
const booruSite = new Site(sites[rSite])

if (!booruCache[rSite]) {
booruCache[rSite] = booruFrom(booruSite)
booruCache[rSite] = booruFrom(booruSite, credentials)
}

return booruCache[rSite].search(tags, { limit, random, page, credentials })
// This is ugly and a hack, I know this
booruCache[rSite]!.credentials = credentials
return booruCache[rSite]!.search(tags, { limit, random, page })
}

// eslint-disable-next-line no-empty,@typescript-eslint/no-empty-function
Expand Down
4 changes: 0 additions & 4 deletions src/structures/SearchParameters.ts
Expand Up @@ -3,8 +3,6 @@
* @module Structures
*/

import { BooruCredentials } from '../boorus/Booru'

/**
* Just an interface for {@link Booru}'s search params :)
*/
Expand All @@ -15,8 +13,6 @@ export default interface SearchParameters {
random?: boolean
/** Which page of results to fetch */
page?: number
/** The credentials to use to auth with the booru */
credentials?: BooruCredentials
/** Return unavailable posts (ie. banned/deleted posts) */
showUnavailable?: boolean
}

0 comments on commit 76199a8

Please sign in to comment.