Skip to content

Commit

Permalink
rework cfclient
Browse files Browse the repository at this point in the history
  • Loading branch information
froehlichA committed Apr 17, 2022
1 parent cbd469d commit 88162af
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 60 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"**/.DS_Store": true,
"**/Thumbs.db": true,
"nimcache/": true,
"testresults/": true
"testresults/": true,
"tests/**/*.exe": true
},
"nim.nimprettyIndent": 2
}
69 changes: 38 additions & 31 deletions src/api/cfclient.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
## Docs for the official API are available at https://docs.curseforge.com.
## Requests to the proxy stay the same, except the base URL is switched out.

import asyncdispatch, json, options, strutils
import std/[asyncdispatch, json, options, strutils]
import uri except Url
import cfcore, http

Expand All @@ -19,51 +19,55 @@ const
## used for retrieving mods by their slug, which isn't possible with the curse api
addonsSlugBaseUrl = "https://curse.nikky.moe/graphql"

type
CfClientError* = object of HttpRequestError

proc fetchAddonsByQuery*(query: string, category: Option[CfAddonGameCategory]): Future[seq[CfAddon]] {.async.} =
## retrieves all addons that match the given `query` search and `category`.
let encodedQuery = encodeUrl(query, usePlus = false)
var url = addonsBaseUrl & "/v1/mods/search?gameId=432&pageSize=50&sortField=6&sortOrder=desc&searchFilter=" & encodedQuery
if category.isSome:
url = url & "&classId=" & $ord(category.get())
try:
return get(url.Url).await.parseJson["data"].addonsFromForgeSvc
except HttpRequestError:
return @[]
return get(url.Url).await.parseJson["data"].addonsFromForgeSvc

proc fetchAddonsByQuery*(query: string): Future[seq[CfAddon]] {.async.} =
return await fetchAddonsByQuery(query, category = none[CfAddonGameCategory]())
proc fetchAddonsByQuery*(query: string, category: CfAddonGameCategory): Future[seq[CfAddon]] =
## retrieves all addons that match the given `query` search and `category`.
return fetchAddonsByQuery(query, category = some(category))

proc fetchAddonsByQuery*(query: string): Future[seq[CfAddon]] =
## retrieves all addons that match the given `query` search.
return fetchAddonsByQuery(query, category = none[CfAddonGameCategory]())

proc fetchAddon*(projectId: int): Future[Option[CfAddon]] {.async.} =
proc fetchAddon*(projectId: int): Future[CfAddon] {.async.} =
## get the addon with the given `projectId`.
let url = addonsBaseUrl & "/v1/mods/" & $projectId
try:
return get(url.Url).await.parseJson["data"].addonFromForgeSvc.some
return get(url.Url).await.parseJson["data"].addonFromForgeSvc
except HttpRequestError:
return none[CfAddon]()
raise newException(CfClientError, "addon with project id '" & $projectId & "' not found.")

proc fetchAddons*(projectIds: seq[int]): Future[seq[CfAddon]] {.async.} =
## get all addons with their given `projectId`
## get all addons with their given `projectId`.
let url = addonsBaseUrl & "/v1/mods/"
let reqBody = %* { "modIds": projectIds }
let body = %* { "modIds": projectIds }
try:
return post(url.Url, body = $reqBody).await.parseJson["data"].addonsFromForgeSvc
let addons = post(url.Url, $body).await.parseJson["data"].addonsFromForgeSvc
if addons.len != projectIds.len:
raise newException(CfClientError, "one of the addons of project ids '" & $projectIds & "' was not found.")
return addons
except HttpRequestError:
return @[]
raise newException(CfClientError, "one of the addons of project ids '" & $projectIds & "' was not found.")

proc fetchAddon*(slug: string): Future[Option[CfAddon]] {.async.} =
proc fetchAddon*(slug: string): Future[CfAddon] {.async.} =
## get the addon matching the `slug`.
let reqBody = %* {
"query": "{ addons(slug: \"" & slug & "\") { id }}"
}
let curseProxyInfo = await post(addonsSlugBaseUrl.Url, body = $reqBody)
var projectId: int
try:
let addons = curseProxyInfo.parseJson["data"]["addons"]
if addons.len == 0:
return none[CfAddon]()
projectId = addons[0]["id"].getInt()
except KeyError:
return none[CfAddon]()
let addons = curseProxyInfo.parseJson["data"]["addons"]
if addons.len == 0:
raise newException(CfClientError, "addon with slug '" & slug & "' not found")
let projectId = addons[0]["id"].getInt()
return await fetchAddon(projectId)

proc fetchAddonFiles*(projectId: int): Future[seq[CfAddonFile]] {.async.} =
Expand All @@ -72,21 +76,24 @@ proc fetchAddonFiles*(projectId: int): Future[seq[CfAddonFile]] {.async.} =
try:
return get(url.Url).await.parseJson["data"].addonFilesFromForgeSvc
except HttpRequestError:
return @[]
raise newException(CfClientError, "addon with project id '" & $projectId & "' not found.")

proc fetchAddonFiles*(fileIds: seq[int]): Future[seq[CfAddonFile]] {.async.} =
## get all addon files for the given `fileIds`.
## get all addon files with their given `fileIds`.
let url = addonsBaseUrl & "/v1/mods/files"
let reqBody = %* { "fileIds": fileIds }
let body = %* { "fileIds": fileIds }
try:
return post(url.Url, body = $reqBody).await.parseJson["data"].addonFilesFromForgeSvc
let addonFiles = post(url.Url, $body).await.parseJson["data"].addonFilesFromForgeSvc
if addonFiles.len != fileIds.len:
raise newException(CfClientError, "one of the addon files of file ids '" & $fileIds & "' was not found.")
return addonFiles
except HttpRequestError:
return @[]
raise newException(CfClientError, "one of the addon files of file ids '" & $fileIds & "' was not found.")

proc fetchAddonFile*(projectId: int, fileId: int): Future[Option[CfAddonFile]] {.async.} =
proc fetchAddonFile*(projectId: int, fileId: int): Future[CfAddonFile] {.async.} =
## get the addon file with the given `fileId` & `projectId`.
let url = addonsBaseUrl & "/v1/mods/" & $projectId & "/files/" & $fileId
try:
return get(url.Url).await.parseJson["data"].addonFileFromForgeSvc.some
return get(url.Url).await.parseJson["data"].addonFileFromForgeSvc
except HttpRequestError:
return none[CfAddonFile]()
raise newException(CfClientError, "addon with project & file id '" & $projectId & ':' & $fileId & "' not found.")
90 changes: 63 additions & 27 deletions tests/api/tcfclient.nim
Original file line number Diff line number Diff line change
@@ -1,48 +1,84 @@
discard """
cmd: "nim $target --hints:on -d:testing -d:ssl --nimblePath:tests/deps $options $file"
"""
import std/[asyncdispatch, options, sequtils, strutils, sugar]
import api/[cfclient, cfcore]
import ../tutils

import asyncdispatch, options, sequtils, strutils, sugar
import api/cfclient, api/cfcore

block: # fetch by query
let mods = waitFor(fetchAddonsByQuery("jei"))
asyncBlock: # fetch by query
let mods = await fetchAddonsByQuery("jei")
doAssert mods[0].projectId == 238222
doAssert mods[0].name == "Just Enough Items (JEI)"

block: # fetch mods by query
let mods = waitFor(fetchAddonsByQuery("jei", some(CfAddonGameCategory.Mod)))
asyncBlock: # fetch mods by query
let mods = await fetchAddonsByQuery("jei", some(CfAddonGameCategory.Mod))
doAssert mods.all(m => m.websiteUrl.contains("/mc-mods/"))

block: # fetch resource packs by query
let mods = waitFor(fetchAddonsByQuery("jei", some(CfAddonGameCategory.Resourcepack)))
asyncBlock: # fetch resource packs by query
let mods = await fetchAddonsByQuery("jei", some(CfAddonGameCategory.Resourcepack))
doAssert mods.all(m => m.websiteUrl.contains("/texture-packs/"))

block: # fetch mod by id
let mcMod = waitFor(fetchAddon(220318)).get()
asyncBlock: # fetch non-existing by query
let mods = await fetchAddonsByQuery("---------------------------")
doAssert mods.len == 0

asyncBlock: # fetch mod by id
let mcMod = await fetchAddon(220318)
doAssert mcMod.projectId == 220318
doAssert mcMod.name == "Biomes O' Plenty"

## Skip failing test because the curse.nikky.moe api doesn't update anymore
#[
block: # fetch mod by slug
var mcMod = waitFor(fetchAddon("appleskin")).get()
asyncBlock: # fetch mod by non-existing id
doAssertRaises(CfClientError):
discard await fetchAddon(99999999)

asyncBlock: # fetch mods by id
let mcMods = await fetchAddons(@[220318, 238222])
doAssert mcMods.any((x) => x.projectId == 220318)
doAssert mcMods.any((x) => x.projectId == 238222)

asyncBlock: # fetch mods by non-existing id
doAssertRaises(CfClientError):
discard await fetchAddons(@[220318, 99999999])

asyncBlock: # fetch mod by slug
var mcMod = await fetchAddon("appleskin")
doAssert mcMod.projectId == 248787
mcMod = waitFor(fetchAddon("dtbop")).get()
mcMod = await fetchAddon("dtbop")
doAssert mcMod.projectId == 289529
mcMod = waitFor(fetchAddon("dtphc")).get()
mcMod = await fetchAddon("dtphc")
doAssert mcMod.projectId == 307560
]#

block: # fetch mod files
let modFiles = waitFor(fetchAddonFiles(248787))
asyncBlock: # fetch mod by non-existing slug
doAssertRaises(CfClientError):
discard await fetchAddon("abcdefghijklmnopqrstuvwxyz")

asyncBlock: # fetch mod files by project id
let modFiles = await fetchAddonFiles(248787)
doAssert modFiles.any((x) => x.fileId == 3035787)

block: # fetch mod file
let modFile = waitFor(fetchAddonFile(306770, 2992184)).get()
asyncBlock: # fetch mod files by non-existing project id
doAssertRaises(CfClientError):
discard await fetchAddonFiles(99999999)

asyncBlock: # fetch mod files by file ids
let modFiles = await fetchAddonFiles(@[2992184, 3098571])
doAssert modFiles.any((x) => x.fileId == 2992184)
doAssert modFiles.any((x) => x.fileId == 3098571)

asyncBlock: # fetch mod files by non-existing file ids
doAssertRaises(CfClientError):
discard await fetchAddonFiles(@[2992184, 99999999])

asyncBlock: # fetch mod file by project & file id
let modFile = await fetchAddonFile(306770, 2992184)
doAssert modFile.fileId == 2992184
doAssert modFile.name == "Patchouli-1.0-21.jar"

block: # check if dependencies are tracked
let modFile = waitFor(fetchAddonFile(243121, 3366626)).get()
asyncBlock: # fetch mod files by non-existing project & file id
doAssertRaises(CfClientError):
discard await fetchAddonFile(306770, 99999999)
doAssertRaises(CfClientError):
discard await fetchAddonFile(99999999, 2992184)

asyncBlock: # check if dependencies are tracked
let modFile = await fetchAddonFile(243121, 3366626)
doAssert modFile.dependencies == @[250363]

runTests()
3 changes: 2 additions & 1 deletion tests/nim.cfg
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
--path:"../src/"
--path:"../src/"
-d:ssl

0 comments on commit 88162af

Please sign in to comment.