Compare commits

...

96 Commits

Author SHA1 Message Date
5038fb4240 Merge pull request #139 from InsanusMokrassar/0.12.0
0.12.0
2022-08-06 10:00:46 +06:00
96f520700f Update libs.versions.toml 2022-08-06 08:48:50 +06:00
2a4296ff7d Update gradle-wrapper.properties 2022-08-06 08:47:55 +06:00
2f34ab7ad1 start 0.12.0 2022-08-06 08:47:18 +06:00
798c4097ed Update github_release.gradle 2022-07-22 20:04:50 +06:00
c1df04fd09 Merge pull request #136 from InsanusMokrassar/0.11.1
0.11.1
2022-07-22 19:44:20 +06:00
8cc0426dc5 Update CHANGELOG.md 2022-07-18 17:08:54 +06:00
9cc929adf6 Update dependencies 2022-07-18 17:06:31 +06:00
85567956ed Update gradle.properties 2022-07-18 17:05:41 +06:00
db154f4202 Merge pull request #131 from InsanusMokrassar/0.11.0
0.11.0
2022-05-14 13:47:39 +06:00
bfdbee356a optimize imports 2022-05-14 13:44:44 +06:00
62580f6649 recreate yarn.lock 2022-05-14 13:44:21 +06:00
0b79f27b38 improvements and actualization 2022-05-14 13:43:28 +06:00
8543917da7 Update libs.versions.toml 2022-05-10 10:34:58 +06:00
be858d3e60 Update gradle.properties 2022-05-10 10:33:45 +06:00
90e090ca78 Update gradle-wrapper.properties 2022-04-01 09:38:01 +06:00
66414ff8df Merge pull request #117 from InsanusMokrassar/0.10.1
0.10.1
2022-03-17 13:45:04 +06:00
d0c143dcd6 update dependencies 2022-03-16 12:50:57 +06:00
3ee0d0de7d start 0.10.1 2022-03-16 12:49:32 +06:00
d04ee03043 Merge pull request #116 from InsanusMokrassar/0.10.0
0.10.0
2022-03-11 13:59:37 +06:00
e173bd71e0 migration onto toml and actualizing of publish scripts 2022-03-11 13:54:01 +06:00
11aac1b581 Update build_and_publish.yml 2022-03-10 01:21:59 +06:00
4319c41e44 Update gradle-wrapper.properties 2022-03-10 01:21:19 +06:00
dfd1ba807f start 0.10.0 2022-03-10 01:12:41 +06:00
e926a5422d Update build_and_publish.yml 2021-09-22 20:16:31 +06:00
d631d63490 Merge pull request #108 from InsanusMokrassar/0.9.1
0.9.1
2021-09-21 15:28:38 +06:00
60ae527dd0 small fix 2021-09-21 15:18:48 +06:00
05b8d7738f extension ResultData#urls 2021-09-21 15:10:33 +06:00
b5ceff32bd add several extensions to ResultData: authors, froms, charactersList, titles 2021-09-21 14:47:40 +06:00
64109735f8 update dependencies 2021-09-21 14:44:21 +06:00
d4f902aefd start 0.9.1 2021-09-21 14:42:52 +06:00
2de15a54a1 Merge pull request #104 from InsanusMokrassar/0.9.0
0.9.0
2021-08-31 11:47:54 +06:00
036deeb318 Update README.md 2021-08-31 11:46:59 +06:00
4cc0223fbc update dependencies 2021-08-31 11:29:46 +06:00
be292102aa start 0.9.0 2021-08-31 11:21:41 +06:00
9d473857be Merge pull request #94 from InsanusMokrassar/0.8.2
0.8.2
2021-05-26 22:37:24 +06:00
6ff3f76295 fixes of publish scripts 2021-05-26 22:36:00 +06:00
0ed1a8702b fix deprecations 2021-05-26 22:16:08 +06:00
c800a81a41 update publish scripts 2021-05-26 22:10:53 +06:00
62ceae0066 update klock 2021-05-26 22:02:16 +06:00
8619d020f6 Merge branch 'master' into 0.8.2 2021-05-26 21:58:29 +06:00
affa06905e Create build_and_publish.yml 2021-05-26 21:57:39 +06:00
cbe76b3d95 update gradle wrapper 2021-05-26 21:42:51 +06:00
9f4feeccfc update dependencies 2021-05-26 21:39:42 +06:00
037074cef6 start 0.8.2 2021-05-26 21:37:34 +06:00
dfe29296ab 0.8.1 changelog 2021-04-07 20:09:11 +06:00
ce1d185eff 0.8.1 + dependencies update 2021-04-07 20:00:20 +06:00
a78611a27c Merge pull request #75 from InsanusMokrassar/0.8.0
0.8.0
2021-03-08 00:39:19 +06:00
cba4b2ccdb update publish scripts 2021-03-08 00:38:32 +06:00
6aabcca9d2 update publish scripts 2021-03-08 00:37:31 +06:00
503c4226d7 update dependencies 2021-03-08 00:35:39 +06:00
d82bff2563 start 0.8.0 2021-03-08 00:32:50 +06:00
8391e20dce Merge pull request #64 from InsanusMokrassar/0.7.2
0.7.2
2021-02-01 13:12:58 +06:00
67244683dd update dependencies 2021-02-01 13:11:05 +06:00
948dede0c8 start 0.7.2 2021-02-01 13:06:12 +06:00
6f171c5f6d update readme 2020-12-23 13:59:22 +06:00
6d85ccfffa Merge pull request #54 from InsanusMokrassar/0.7.1
0.7.1
2020-12-23 13:51:01 +06:00
bce508a8c2 update ktor 2020-12-23 13:47:34 +06:00
224f0a6cc0 update versions 2020-12-16 14:29:28 +06:00
61937ed209 start 0.7.1 2020-12-16 14:28:28 +06:00
8e861020d8 Update README.md 2020-12-02 15:09:45 +06:00
c231b956b4 Merge pull request #48 from InsanusMokrassar/0.7.0
0.7.0
2020-12-02 14:46:36 +06:00
c14df69ae1 fix in changelog 2020-12-02 14:45:45 +06:00
f55744a038 update publishing 2020-12-02 14:44:25 +06:00
f27c493771 upmigration 2020-12-02 14:39:54 +06:00
7bc1e822fc update package 2020-12-02 14:32:26 +06:00
eb9456a233 start 0.7.0 2020-12-02 14:30:49 +06:00
8c8a02054d Merge pull request #47 from InsanusMokrassar/0.6.2
0.6.2
2020-12-02 14:23:36 +06:00
f52eb2ad6e update ktor 2020-12-02 14:13:42 +06:00
4fcdb6f728 update versions 2020-12-02 14:08:19 +06:00
18cdf4ffbe update build scripts 2020-12-02 14:02:15 +06:00
33d143ea12 start 0.6.2 2020-12-02 14:01:04 +06:00
e0215dcf8f update github release file 2020-10-08 17:07:37 +06:00
ca4348b5f6 update publication files 2020-10-08 16:54:11 +06:00
249cbba1f7 Merge pull request #31 from InsanusMokrassar/0.6.1
0.6.1
2020-10-08 16:37:59 +06:00
a94b7b8020 add changelog parser 2020-10-08 15:16:41 +06:00
632b534da9 update versions 2020-10-08 14:51:21 +06:00
b30af7c8f9 start 0.6.1 2020-10-08 14:40:22 +06:00
dda2ca17d3 Update README.md 2020-08-30 04:34:19 +06:00
0591f26d0c Merge pull request #21 from InsanusMokrassar/0.6.0
0.6.0
2020-08-29 19:19:36 +06:00
d24e75a452 change repository 2020-08-29 19:16:21 +06:00
6b8d06da70 hotfix 2020-08-29 19:12:47 +06:00
cbd6651c67 hotfix 2020-08-29 19:11:57 +06:00
69bfa56692 hotfix 2020-08-29 19:11:36 +06:00
270be95784 large upgrade 2020-08-29 19:09:54 +06:00
09b4b45b75 update gradle 2020-08-29 18:50:36 +06:00
b80293103e update publication info 2020-08-29 11:46:20 +06:00
fe32aaacb2 upgrade up to multiplatform project 2020-08-22 23:00:07 +06:00
7cbaac8a3e start 0.6.0 2020-08-22 21:58:35 +06:00
6ab2646a03 All known fields were added to ResultData 2020-08-15 18:06:45 +06:00
a54ac4d6a6 start 0.5.1 2020-08-15 16:52:52 +06:00
03e201ae56 Merge pull request #10 from InsanusMokrassar/renovate/junit-junit-4.x
Update dependency junit:junit to v4.13
2020-08-13 11:19:50 +06:00
315d78db11 Merge pull request #11 from InsanusMokrassar/0.5.0
0.5.0
2020-08-13 11:19:31 +06:00
Renovate Bot
46ad249067 Update dependency junit:junit to v4.13 2020-08-12 22:21:51 +00:00
1348abd482 Merge pull request #6 from InsanusMokrassar/renovate/configure
Configure Renovate
2020-08-13 02:12:44 +06:00
Renovate Bot
c7dc76c8df Add renovate.json 2020-08-12 20:04:50 +00:00
52 changed files with 3028 additions and 553 deletions

24
.github/workflows/build_and_publish.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Publish package to GitHub Packages
on: [push]
jobs:
publishing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- name: Rewrite version
run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
cat gradle.properties | sed -e "s/^library_version=\([0-9\.]*\)/library_version=\1-branch_$branch-build${{ github.run_number }}/" > gradle.properties.tmp
rm gradle.properties
mv gradle.properties.tmp gradle.properties
- name: Build
run: ./gradlew build
- name: Publish
run: ./gradlew publishAllPublicationsToGithubPackagesRepository --no-parallel -x signJsPublication -x signJvmPublication -x signKotlinMultiplatformPublication
continue-on-error: true
env:
GITHUBPACKAGES_USER: ${{ github.actor }}
GITHUBPACKAGES_PASSWORD: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,22 +1,128 @@
# SauceNaoAPI Changelog # SauceNaoAPI Changelog
## 0.11.1
* Versions updates:
* `Ktor`: `2.0.1` -> `2.0.3`
* `Coroutines`: `1.6.1` -> `1.6.4`
## 0.11.0
* Versions updates:
* `Kotlin`: `1.6.10` -> `1.6.21`
* `Serialization`: `1.3.2` -> `1.3.3`
* `Klock`: `2.6.3` -> `2.7.0`
* `Ktor`: `1.6.8` -> `2.0.1`
## 0.10.1
* Versions updates:
* `Klock`: `2.6.2` -> `2.6.3`
* `Ktor`: `1.6.7` -> `1.6.8`
## 0.10.0
Migration onto libs versions toml
## 0.9.1
* Versions updates:
* `Kotlin`: `1.5.30` -> `1.5.31`
* `Klock`: `2.4.0` -> `2.4.2`
* `Coroutines`: `1.5.1` -> `1.5.2`
* Add several extensions to `ResultData`: `authors`, `froms`, `charactersList`, `titles`, `urls`
## 0.9.0
* Versions updates:
* `Kotlin`: `1.5.10` -> `1.5.30`
* `Klock`: `2.1.2` -> `2.4.0`
* `Ktor`: `1.5.4` -> `1.6.3`
* `Serialization`: `1.2.1` -> `1.2.2`
* `Coroutines`: `1.5.0` -> `1.5.1`
## 0.8.2
* Versions updates:
* `Kotlin`: `1.4.32` -> `1.5.10`
* `Klock`: `2.0.7` -> `2.1.2`
* `Ktor`: `1.5.3` -> `1.5.4`
* `Serialization`: `1.1.0` -> `1.2.1`
* `Coroutines`: `1.4.3` -> `1.5.0`
## 0.8.1
* Versions updates:
* `Kotlin`: `1.4.31` -> `1.4.32`
* `Klock`: `2.0.6` -> `2.0.7`
* `Ktor`: `1.5.2` -> `1.5.3`
## 0.8.0
* Versions updates:
* `Kotlin`: `1.4.21` -> `1.4.31`
* `Klock`: `2.0.4` -> `2.0.6`
* `Ktor`: `1.5.1` -> `1.5.2`
* `Kotlin Serialisation`: `1.0.1` -> `1.1.0`
* `Kotlin Coroutines`: `1.4.2` -> `1.4.3`
## 0.7.2
* Versions updates:
* `Klock`: `2.0.2` -> `2.0.4`
* `Ktor`: `1.5.0` -> `1.5.1`
## 0.7.1
* Versions updates:
* `Kotlin`: `1.4.20` -> `1.4.21`
* `Klock`: `2.0.0` -> `2.0.2`
* `Ktor`: `1.4.3` -> `1.5.0`
## 0.7.0
**BREAKING CHANGES: PACKAGE HAS BEEN CHANGED FROM `com.insanusmokrassar` to `dev.inmo`**
Migration:
* Packages in the whole project were changed `com.insanusmokrassar.SauceNaoAPI` -> `dev.inmo.saucenaoapi`
* Change implementation in your gradle files: `implementation "com.insanusmokrassar:SauceNaoAPI:*"` ->
`implementation "dev.inmo:saucenaoapi:*"`
## 0.6.2
* Versions updates:
* `Kotlin`: `1.4.10` -> `1.4.20`
* `Kotlin Serialisation`: `1.0.0-RC2` -> `1.0.1`
* `Kotlin Coroutines`: `1.3.9` -> `1.4.2`
* `Klock`: `1.12.1` -> `2.0.0`
* `Ktor`: `1.4.1` -> `1.4.3`
## 0.6.1
* Versions updates:
* `Kotlin`: `1.4.0` -> `1.4.10`
* `Kotlin Serialisation`: `1.0.0-RC` -> `1.0.0-RC2`
* `Klock`: `1.12.0` -> `1.12.1`
* `Ktor`: `1.4.0` -> `1.4.1`
## 0.6.0
**MAIN PACKAGE WAS CHANGED: `com.github.insanusmokrassar` -> `com.insanusmokrassar`**
* All known fields were added to `ResultData`
* Versions updates:
* `Kotlin`: `1.3.72` -> `1.4.0`
* `Coroutines`: `1.3.8` -> `1.3.9`
* `Serialization`: `0.20.0` -> `1.0.0-RC`
* `Klock`: `1.11.14` -> `1.12.0`
* `Ktor`: `1.3.2` -> `1.4.0`
## 0.5.0 ## 0.5.0
* Versions updates * Versions updates
## 0.4.0 ## 0.4.4
* Update libraries versions
* Kotlin `1.3.31` -> `1.3.50`
* Coroutines `1.2.1` -> `1.3.2`
* Serialization `0.11.0` -> `0.13.0`
* Joda Time `2.10.1` -> `2.10.4`
* Ktor `1.1.4` -> `1.2.5`
* Now `SauceNaoAPI` is `Closeable`
* Now `SauceNaoAPI` working with synchronous queue
* `SauceNaoAPI` now will wait for some time when one of limits will be achieved
### 0.4.4
* Uploading of file * Uploading of file
* Updates of versions * Updates of versions
@@ -24,15 +130,15 @@
* `SauceNaoAPI` instances now can return `limitsState` object, which will contains `LimitsState` with currently known * `SauceNaoAPI` instances now can return `limitsState` object, which will contains `LimitsState` with currently known
state of limits state of limits
### 0.4.3 ## 0.4.3
Hotfix for serializer of `SauceNaoAnswer` Hotfix for serializer of `SauceNaoAnswer`
### 0.4.2 ## 0.4.2
Hotfix for autostop for some time when there is no remaining quotas for requests Hotfix for autostop for some time when there is no remaining quotas for requests
### 0.4.1 Managers experiments and row format in answer ## 0.4.1 Managers experiments and row format in answer
* Add `TimeManager` - it will manage work with requests times * Add `TimeManager` - it will manage work with requests times
* Add `RequestQuotaMagager` - it will manage quota for requests and call suspend * Add `RequestQuotaMagager` - it will manage quota for requests and call suspend
@@ -41,6 +147,18 @@ if they will be over
* Now `SauceNaoAnswer` have field `row` which contains `JsonObject` with * Now `SauceNaoAnswer` have field `row` which contains `JsonObject` with
all original answer fields all original answer fields
## 0.4.0
* Update libraries versions
* Kotlin `1.3.31` -> `1.3.50`
* Coroutines `1.2.1` -> `1.3.2`
* Serialization `0.11.0` -> `0.13.0`
* Joda Time `2.10.1` -> `2.10.4`
* Ktor `1.1.4` -> `1.2.5`
* Now `SauceNaoAPI` is `Closeable`
* Now `SauceNaoAPI` working with synchronous queue
* `SauceNaoAPI` now will wait for some time when one of limits will be achieved
## 0.3.0 ## 0.3.0
* Now `results` field of `SauceNaoAnswer` is optional and is empty list by default * Now `results` field of `SauceNaoAnswer` is optional and is empty list by default

View File

@@ -1,9 +1,20 @@
# SauceNaoAPI # SauceNaoAPI
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/saucenaoapi/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/saucenaoapi)
It is wrapper for [SauceNAO](https://saucenao.com/) API. For now, library is It is wrapper for [SauceNAO](https://saucenao.com/) API. For now, library is
in preview state. It can be fully used, but some of info can be unavailable from in preview state. It can be fully used, but some of info can be unavailable from
wrapper classes, but now you can access them via `SauceNaoAnswer#row` field. wrapper classes, but now you can access them via `SauceNaoAnswer#row` field.
## Including
### Gradle
```groovy
implementation "dev.inmo:saucenaoapi:$saucenaoapi_version"
```
## Requester ## Requester
For the requests we are using `SauceNaoAPI` object. Unfortunately, for now it For the requests we are using `SauceNaoAPI` object. Unfortunately, for now it

View File

@@ -1,41 +1,67 @@
project.version = "0.5.0"
project.group = "com.github.insanusmokrassar"
buildscript { buildscript {
repositories { repositories {
mavenLocal() mavenLocal()
jcenter()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath libs.buildscript.kt.gradle
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath libs.buildscript.kt.serialization
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version" classpath libs.buildscript.gh.release
} }
} }
apply plugin: 'kotlin' plugins {
apply plugin: 'kotlinx-serialization' alias(libs.plugins.multiplatform)
alias(libs.plugins.serialization)
}
project.version = "$library_version"
project.group = "dev.inmo"
apply from: "publish.gradle" apply from: "publish.gradle"
apply from: "github_release.gradle"
repositories { repositories {
mavenLocal() mavenLocal()
jcenter()
mavenCentral() mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
maven { url "https://dl.bintray.com/kotlin/ktor" }
} }
dependencies { kotlin {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" jvm()
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" js(IR) {
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$kotlin_serialisation_runtime_version" browser()
implementation "com.soywiz.korlibs.klock:klock:$klock_version" nodejs()
implementation "io.ktor:ktor-client-core:$ktor_version" }
implementation "io.ktor:ktor-client-okhttp:$ktor_version"
// Use JUnit test framework
testImplementation 'junit:junit:4.12' sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
api libs.kt.coroutines
api libs.kt.serialization
api libs.klock
api libs.ktor.client
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.ktor.client.okhttp
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
}
}
}
} }

24
changelog_parser.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
function parse() {
version=$1
while IFS= read -r line && [ -z "`echo $line | grep -e "^#\+ $version"`" ]
do
: # do nothing
done
while IFS= read -r line && [ -z "`echo $line | grep -e "^#\+"`" ]
do
echo "$line"
done
}
version=$1
file=$2
if [ -n "$file" ]; then
parse $version < "$file"
else
parse $version
fi

28
github_release.gradle Normal file
View File

@@ -0,0 +1,28 @@
private String getCurrentVersionChangelog() {
OutputStream changelogDataOS = new ByteArrayOutputStream()
exec {
standardOutput = changelogDataOS
commandLine 'chmod', "+x", './changelog_parser.sh'
commandLine './changelog_parser.sh', "$library_version", 'CHANGELOG.md'
}
return changelogDataOS.toString().trim()
}
if (new File(projectDir, "secret.gradle").exists()) {
apply from: './secret.gradle'
apply plugin: "com.github.breadmoirai.github-release"
githubRelease {
token "${project.property('GITHUB_RELEASE_TOKEN')}"
owner "InsanusMokrassar"
repo "${rootProject.name}"
tagName "v${project.version}"
releaseName "${project.version}"
targetCommitish "${project.version}"
body getCurrentVersionChangelog()
}
}

View File

@@ -1,11 +1,3 @@
kotlin.code.style=official kotlin.code.style=official
kotlin_version=1.3.72
kotlin_coroutines_version=1.3.8
kotlin_serialisation_runtime_version=0.20.0
klock_version=1.11.14
ktor_version=1.3.2
project_public_name=SauceNao API library_version=0.12.0
project_public_description=SauceNao API library
gradle_bintray_plugin_version=1.8.4

30
gradle/libs.versions.toml Normal file
View File

@@ -0,0 +1,30 @@
[versions]
kt = "1.7.10"
kt-serialization = "1.4.0-RC"
kt-coroutines = "1.6.4"
klock = "3.0.0"
ktor = "2.0.3"
gh-release = "2.4.1"
[libraries]
kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" }
kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" }
klock = { module = "com.soywiz.korlibs.klock:klock", version.ref = "klock" }
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
[plugins]
multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kt" }
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kt" }

View File

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip

2058
kotlin-js-store/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,63 +0,0 @@
apply plugin: 'maven-publish'
apply plugin: 'signing'
task sourcesJar(type: Jar) {
from sourceSets.main.allSource
classifier = 'sources'
}
task javadocJar(type: Jar) {
from javadoc
classifier = 'javadoc'
}
publishing {
publications {
maven(MavenPublication) {
from components.java
groupId "${project.group}"
artifactId "${project.name}"
version "${project.version}"
artifact sourcesJar
artifact javadocJar
pom.withXml {
asNode().children().last() + {
resolveStrategy = Closure.DELEGATE_FIRST
name "${project_public_name}"
description "${project_public_description}"
url "https://insanusmokrassar.github.io/${project.name}"
scm {
connection "scm:git:git://github.com/insanusmokrassar/${project.name}.git"
developerConnection "scm:git:[fetch=]https://github.com/insanusmokrassar/${project.name}.git[push=]ssh:git@github.com:insanusmokrassar/${project.name}.git"
url "https://github.com/insanusmokrassar/${project.name}"
}
developers {
developer {
id "InsanusMokrassar"
name "Ovsyannikov Alexey"
email "ovsyannikov.alexey95@gmail.com"
}
}
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/LICENSE'
distribution 'repo'
}
}
}
}
}
}
}
signing {
useGpgCmd()
sign publishing.publications.maven
}

View File

@@ -1,33 +1,79 @@
apply plugin: 'com.jfrog.bintray' apply plugin: 'maven-publish'
ext { task javadocsJar(type: Jar) {
projectBintrayDir = "${project.group}/".replace(".", "/") + "${project.name}/${project.version}" classifier = 'javadoc'
} }
bintray { publishing {
user = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER') publications.all {
key = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY') artifact javadocsJar
publications = ["maven"]
filesSpec { pom {
into "$projectBintrayDir" description = "SauceNao API library"
from("build/libs") { name = "SauceNao API"
include "**/*.asc" url = "https://insanusmokrassar.github.io/${project.name}"
scm {
developerConnection = "scm:git:[fetch=]https://github.com/insanusmokrassar/${project.name}.git[push=]https://github.com/insanusmokrassar/${project.name}.git"
url = "https://github.com/insanusmokrassar/${project.name}.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Ovsyannikov Alexey"
email = "ovsyannikov.alexey95@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://github.com/InsanusMokrassar/SauceNaoAPI/blob/master/LICENSE"
}
}
} }
from("build/publications/maven") { repositories {
rename 'pom-default.xml(.*)', "${project.name}-${project.version}.pom\$1" if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
} maven {
} name = "GithubPackages"
pkg { url = uri("https://maven.pkg.github.com/InsanusMokrassar/SauceNaoAPI")
repo = 'StandardRepository' credentials {
name = "${project.name}" username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
vcsUrl = "https://github.com/InsanusMokrassar/${project.name}" password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
licenses = ['Apache-2.0'] }
version { }
name = "${project.version}" }
released = new Date() if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
vcsTag = name maven {
name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
}
}
}
}
}
}
if (project.hasProperty("signing.gnupg.keyName")) {
apply plugin: 'signing'
signing {
useGpgCmd()
sign publishing.publications
}
task signAll {
tasks.withType(Sign).forEach {
dependsOn(it)
} }
} }
} }
apply from: "maven.publish.gradle"

1
publish.kpsb Normal file
View File

@@ -0,0 +1 @@
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/SauceNaoAPI/blob/master/LICENSE"}],"mavenConfig":{"name":"SauceNao API","description":"SauceNao API library","url":"https://insanusmokrassar.github.io/${project.name}","vcsUrl":"https://github.com/insanusmokrassar/${project.name}.git","developers":[{"id":"InsanusMokrassar","name":"Ovsyannikov Alexey","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/SauceNaoAPI"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}

5
renovate.json Normal file
View File

@@ -0,0 +1,5 @@
{
"extends": [
"config:base"
]
}

View File

@@ -1,18 +1,3 @@
/* rootProject.name = 'saucenaoapi'
* This settings file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
* In a single project build this file can be empty or even removed.
*
* Detailed information about configuring a multi-project build in Gradle can be found
* in the user guide at https://docs.gradle.org/3.4.1/userguide/multi_project_builds.html
*/
/* enableFeaturePreview("VERSION_CATALOGS")
// To declare projects as part of a multi-project build use the 'include' method
include 'shared'
include 'api'
include 'services:webservice'
*/
rootProject.name = 'SauceNaoAPI'

View File

@@ -0,0 +1,17 @@
package dev.inmo.saucenaoapi
sealed class OutputType {
abstract val typeCode: Int
}
object HtmlOutputType : dev.inmo.saucenaoapi.OutputType() {
override val typeCode: Int = 0
}
object XmlOutputType : dev.inmo.saucenaoapi.OutputType() {
override val typeCode: Int = 1
}
object JsonOutputType : dev.inmo.saucenaoapi.OutputType() {
override val typeCode: Int = 2
}

View File

@@ -1,26 +1,20 @@
package com.github.insanusmokrassar.SauceNaoAPI package dev.inmo.saucenaoapi
import com.github.insanusmokrassar.SauceNaoAPI.exceptions.TooManyRequestsException import dev.inmo.saucenaoapi.exceptions.TooManyRequestsException
import com.github.insanusmokrassar.SauceNaoAPI.exceptions.sauceNaoAPIException import dev.inmo.saucenaoapi.exceptions.sauceNaoAPIException
import com.github.insanusmokrassar.SauceNaoAPI.models.* import dev.inmo.saucenaoapi.models.*
import com.github.insanusmokrassar.SauceNaoAPI.utils.* import dev.inmo.saucenaoapi.utils.*
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.call.call import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.features.ClientRequestException
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.request.forms.* import io.ktor.client.request.forms.MultiPartFormDataContent
import io.ktor.client.statement.HttpResponse import io.ktor.client.request.forms.formData
import io.ktor.client.statement.readText import io.ktor.client.statement.bodyAsText
import io.ktor.http.* import io.ktor.http.*
import io.ktor.utils.io.core.Input import io.ktor.utils.io.core.Input
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.Closeable
import java.util.logging.Logger
import kotlin.Result
import kotlin.coroutines.*
private const val API_TOKEN_FIELD = "api_key" private const val API_TOKEN_FIELD = "api_key"
private const val OUTPUT_TYPE_FIELD = "output_type" private const val OUTPUT_TYPE_FIELD = "output_type"
@@ -35,38 +29,47 @@ private const val MINIMAL_SIMILARITY_FIELD = "minsim"
private const val SEARCH_URL = "https://saucenao.com/search.php" private const val SEARCH_URL = "https://saucenao.com/search.php"
val defaultSauceNaoParser = Json {
allowSpecialFloatingPointValues = true
allowStructuredMapKeys = true
ignoreUnknownKeys = true
useArrayPolymorphism = true
}
data class SauceNaoAPI( data class SauceNaoAPI(
private val apiToken: String? = null, private val apiToken: String? = null,
private val outputType: OutputType = JsonOutputType, private val client: HttpClient = HttpClient(),
private val client: HttpClient = HttpClient(OkHttp),
private val searchUrl: String = SEARCH_URL, private val searchUrl: String = SEARCH_URL,
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default) private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
) : Closeable { private val parser: Json = defaultSauceNaoParser
private val logger = Logger.getLogger("SauceNaoAPI") ) : SauceCloseable {
private val requestsChannel = Channel<Pair<CompletableDeferred<SauceNaoAnswer>, HttpRequestBuilder>>(Channel.UNLIMITED)
private val requestsChannel = Channel<Pair<Continuation<SauceNaoAnswer>, HttpRequestBuilder>>(Channel.UNLIMITED) private val subscope = CoroutineScope(scope.coroutineContext + SupervisorJob(scope.coroutineContext.job)).also {
private val timeManager = TimeManager(scope) it.coroutineContext.job.invokeOnCompletion {
private val quotaManager = RequestQuotaManager(scope) requestsChannel.close(it)
}
}
private val timeManager = TimeManager(subscope)
private val quotaManager = RequestQuotaManager(subscope)
val limitsState: LimitsState val limitsState: LimitsState
get() = quotaManager.limitsState get() = quotaManager.limitsState
private val requestsJob = scope.launch { private val requestsJob = subscope.launch {
for ((callback, requestBuilder) in requestsChannel) { for ((callback, requestBuilder) in requestsChannel) {
quotaManager.getQuota() quotaManager.getQuota()
launch { launch {
try { try {
val answer = makeRequest(requestBuilder) val answer = makeRequest(requestBuilder)
callback.resumeWith(Result.success(answer)) callback.complete(answer)
quotaManager.updateQuota(answer.header, timeManager) quotaManager.updateQuota(answer.header, timeManager)
} catch (e: TooManyRequestsException) { } catch (e: TooManyRequestsException) {
logger.warning("Exceed time limit. Answer was:\n${e.answerContent}")
quotaManager.happenTooManyRequests(timeManager, e) quotaManager.happenTooManyRequests(timeManager, e)
requestsChannel.send(callback to requestBuilder) requestsChannel.send(callback to requestBuilder)
} catch (e: Exception) { } catch (e: Exception) {
try { try {
callback.resumeWith(Result.failure(e)) callback.completeExceptionally(e)
} catch (e: IllegalStateException) { // may happen when already resumed and api was closed } catch (e: IllegalStateException) { // may happen when already resumed and api was closed
// do nothing // do nothing
} }
@@ -79,7 +82,7 @@ data class SauceNaoAPI(
url: String, url: String,
resultsCount: Int? = null, resultsCount: Int? = null,
minSimilarity: Float? = null minSimilarity: Float? = null
): SauceNaoAnswer? = makeRequest( ): SauceNaoAnswer = makeRequest(
url.asSauceRequestSubject, url.asSauceRequestSubject,
resultsCount = resultsCount, resultsCount = resultsCount,
minSimilarity = minSimilarity minSimilarity = minSimilarity
@@ -87,21 +90,32 @@ data class SauceNaoAPI(
suspend fun request( suspend fun request(
mediaInput: Input, mediaInput: Input,
mimeType: ContentType = mediaInput.mimeType, mimeType: ContentType,
resultsCount: Int? = null, resultsCount: Int? = null,
minSimilarity: Float? = null minSimilarity: Float? = null
): SauceNaoAnswer? = makeRequest( ): SauceNaoAnswer = makeRequest(
mediaInput.asSauceRequestSubject(mimeType), mediaInput.asSauceRequestSubject(mimeType),
resultsCount = resultsCount, resultsCount = resultsCount,
minSimilarity = minSimilarity minSimilarity = minSimilarity
) )
suspend fun request(
file: MPPFile,
resultsCount: Int? = null,
minSimilarity: Float? = null
): SauceNaoAnswer = request(
file.input,
file.contentType,
resultsCount = resultsCount,
minSimilarity = minSimilarity
)
suspend fun requestByDb( suspend fun requestByDb(
url: String, url: String,
db: Int, db: Int,
resultsCount: Int? = null, resultsCount: Int? = null,
minSimilarity: Float? = null minSimilarity: Float? = null
): SauceNaoAnswer? = makeRequest( ): SauceNaoAnswer = makeRequest(
url.asSauceRequestSubject, url.asSauceRequestSubject,
db = db, db = db,
resultsCount = resultsCount, resultsCount = resultsCount,
@@ -113,7 +127,7 @@ data class SauceNaoAPI(
dbmask: Int, dbmask: Int,
resultsCount: Int? = null, resultsCount: Int? = null,
minSimilarity: Float? = null minSimilarity: Float? = null
): SauceNaoAnswer? = makeRequest( ): SauceNaoAnswer = makeRequest(
url.asSauceRequestSubject, url.asSauceRequestSubject,
dbmask = dbmask, dbmask = dbmask,
resultsCount = resultsCount, resultsCount = resultsCount,
@@ -125,7 +139,7 @@ data class SauceNaoAPI(
dbmaski: Int, dbmaski: Int,
resultsCount: Int? = null, resultsCount: Int? = null,
minSimilarity: Float? = null minSimilarity: Float? = null
): SauceNaoAnswer? = makeRequest( ): SauceNaoAnswer = makeRequest(
url.asSauceRequestSubject, url.asSauceRequestSubject,
dbmaski = dbmaski, dbmaski = dbmaski,
resultsCount = resultsCount, resultsCount = resultsCount,
@@ -136,11 +150,10 @@ data class SauceNaoAPI(
builder: HttpRequestBuilder builder: HttpRequestBuilder
): SauceNaoAnswer { ): SauceNaoAnswer {
return try { return try {
val call = client.request<HttpResponse>(builder) val call = client.request(builder)
val answerText = call.readText() val answerText = call.bodyAsText()
logger.info(answerText)
timeManager.addTimeAndClear() timeManager.addTimeAndClear()
Json.nonstrict.parse( parser.decodeFromString(
SauceNaoAnswerSerializer, SauceNaoAnswerSerializer,
answerText answerText
) )
@@ -156,29 +169,32 @@ data class SauceNaoAPI(
dbmaski: Int? = null, dbmaski: Int? = null,
resultsCount: Int? = null, resultsCount: Int? = null,
minSimilarity: Float? = null minSimilarity: Float? = null
): SauceNaoAnswer? { ): SauceNaoAnswer {
return suspendCoroutine<SauceNaoAnswer> { val deferred = CompletableDeferred<SauceNaoAnswer>()
requestsChannel.offer(
it to HttpRequestBuilder().apply {
url(searchUrl)
apiToken ?.also { parameter(API_TOKEN_FIELD, it) } requestsChannel.trySend(
parameter(OUTPUT_TYPE_FIELD, outputType.typeCode) deferred to HttpRequestBuilder().apply {
db ?.also { parameter(DB_FIELD, it) } url(searchUrl)
dbmask ?.also { parameter(DBMASK_FIELD, it) }
dbmaski ?.also { parameter(DBMASKI_FIELD, it) }
resultsCount ?.also { parameter(RESULTS_COUNT_FIELD, it) }
minSimilarity ?.also { parameter(MINIMAL_SIMILARITY_FIELD, it) }
when (request) { apiToken ?.also { parameter(API_TOKEN_FIELD, it) }
is UrlSauceRequestSubject -> { parameter(OUTPUT_TYPE_FIELD, JsonOutputType.typeCode)
parameter(URL_FIELD, request.url) db ?.also { parameter(DB_FIELD, it) }
} dbmask ?.also { parameter(DBMASK_FIELD, it) }
is InputRequestSubject -> { dbmaski ?.also { parameter(DBMASKI_FIELD, it) }
val mimeType = request.mimeType resultsCount ?.also { parameter(RESULTS_COUNT_FIELD, it) }
minSimilarity ?.also { parameter(MINIMAL_SIMILARITY_FIELD, it) }
method = HttpMethod.Post when (request) {
body = MultiPartFormDataContent(formData { is UrlSauceRequestSubject -> {
parameter(URL_FIELD, request.url)
}
is InputRequestSubject -> {
val mimeType = request.mimeType
method = HttpMethod.Post
setBody(
MultiPartFormDataContent(
formData {
appendInput( appendInput(
FILE_FIELD, FILE_FIELD,
Headers.build { Headers.build {
@@ -197,19 +213,18 @@ data class SauceNaoAPI(
}, },
block = request::input block = request::input
) )
}) }
} )
)
} }
} }
) }
} )
return deferred.await()
} }
override fun close() { override fun close() {
requestsChannel.close() subscope.cancel()
client.close()
requestsJob.cancel()
timeManager.close()
quotaManager.close()
} }
} }

View File

@@ -1,4 +1,4 @@
package com.github.insanusmokrassar.SauceNaoAPI package dev.inmo.saucenaoapi
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.utils.io.core.Input import io.ktor.utils.io.core.Input

View File

@@ -0,0 +1,23 @@
package dev.inmo.saucenaoapi.additional
import dev.inmo.saucenaoapi.additional.header.ResultMetaInfo
import dev.inmo.saucenaoapi.additional.header.adapted
import dev.inmo.saucenaoapi.additional.results.AdaptedResult
import dev.inmo.saucenaoapi.additional.results.adapted
import dev.inmo.saucenaoapi.models.SauceNaoAnswer
val SauceNaoAnswer.adapted: AdaptedAnswer
get() = header.adapted.let { resultMetainfo ->
val adaptedResults = results.map {
it.adapted(resultMetainfo)
}
AdaptedAnswer(
resultMetainfo,
adaptedResults
)
}
data class AdaptedAnswer(
val resultMetaInfo: ResultMetaInfo,
val results: List<AdaptedResult>
)

View File

@@ -1,4 +1,4 @@
package com.github.insanusmokrassar.SauceNaoAPI.additional package dev.inmo.saucenaoapi.additional
import com.soywiz.klock.TimeSpan import com.soywiz.klock.TimeSpan

View File

@@ -1,7 +1,7 @@
package com.github.insanusmokrassar.SauceNaoAPI.additional.header package dev.inmo.saucenaoapi.additional.header
import com.github.insanusmokrassar.SauceNaoAPI.additional.* import dev.inmo.saucenaoapi.additional.*
import com.github.insanusmokrassar.SauceNaoAPI.models.Header import dev.inmo.saucenaoapi.models.Header
val Header.shortLimitStatus: LimitStatus val Header.shortLimitStatus: LimitStatus
get() = LimitStatus( get() = LimitStatus(

View File

@@ -1,6 +1,6 @@
package com.github.insanusmokrassar.SauceNaoAPI.additional.header package dev.inmo.saucenaoapi.additional.header
import com.github.insanusmokrassar.SauceNaoAPI.models.Header import dev.inmo.saucenaoapi.models.Header
data class IndexInfo( data class IndexInfo(
val id: Int, val id: Int,

View File

@@ -1,6 +1,6 @@
package com.github.insanusmokrassar.SauceNaoAPI.additional.header package dev.inmo.saucenaoapi.additional.header
import com.github.insanusmokrassar.SauceNaoAPI.models.Header import dev.inmo.saucenaoapi.models.Header
val Header.queryPreview val Header.queryPreview
get() = QueryResultPreview( get() = QueryResultPreview(

View File

@@ -1,6 +1,6 @@
package com.github.insanusmokrassar.SauceNaoAPI.additional.header package dev.inmo.saucenaoapi.additional.header
import com.github.insanusmokrassar.SauceNaoAPI.models.Header import dev.inmo.saucenaoapi.models.Header
data class ResultMetaInfo( data class ResultMetaInfo(
val accountInfo: AccountInfo = AccountInfo(), val accountInfo: AccountInfo = AccountInfo(),

View File

@@ -1,9 +1,9 @@
package com.github.insanusmokrassar.SauceNaoAPI.additional.results package dev.inmo.saucenaoapi.additional.results
import com.github.insanusmokrassar.SauceNaoAPI.additional.header.IndexInfo import dev.inmo.saucenaoapi.additional.header.IndexInfo
import com.github.insanusmokrassar.SauceNaoAPI.additional.header.ResultMetaInfo import dev.inmo.saucenaoapi.additional.header.ResultMetaInfo
import com.github.insanusmokrassar.SauceNaoAPI.models.Result import dev.inmo.saucenaoapi.models.Result
import com.github.insanusmokrassar.SauceNaoAPI.models.ResultData import dev.inmo.saucenaoapi.models.ResultData
fun Result.adapted( fun Result.adapted(
resultMetaInfo: ResultMetaInfo resultMetaInfo: ResultMetaInfo

View File

@@ -0,0 +1,9 @@
package dev.inmo.saucenaoapi.additional.results
import dev.inmo.saucenaoapi.additional.header.IndexInfo
data class ResultHeader(
val similarity: Float,
val thumbnail: String,
val index: IndexInfo
)

View File

@@ -0,0 +1,34 @@
package dev.inmo.saucenaoapi.exceptions
import com.soywiz.klock.TimeSpan
import dev.inmo.saucenaoapi.additional.LONG_TIME_RECALCULATING_MILLIS
import dev.inmo.saucenaoapi.additional.SHORT_TIME_RECALCULATING_MILLIS
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode.Companion.TooManyRequests
import io.ktor.utils.io.errors.IOException
internal suspend fun ClientRequestException.sauceNaoAPIException(): Exception {
return when (response.status) {
TooManyRequests -> {
val answerContent = response.bodyAsText()
when {
answerContent.contains("daily limit") -> TooManyRequestsLongException(answerContent)
else -> TooManyRequestsShortException(answerContent)
}
}
else -> this
}
}
sealed class TooManyRequestsException(message: String, cause: Throwable? = null) : IOException(message, cause) {
abstract val answerContent: String
abstract val waitTime: TimeSpan
}
class TooManyRequestsShortException(override val answerContent: String) : TooManyRequestsException("Too many requests were sent in the short period") {
override val waitTime: TimeSpan = SHORT_TIME_RECALCULATING_MILLIS
}
class TooManyRequestsLongException(override val answerContent: String) : TooManyRequestsException("Too many requests were sent in the long period") {
override val waitTime: TimeSpan = LONG_TIME_RECALCULATING_MILLIS
}

View File

@@ -1,9 +1,13 @@
package com.github.insanusmokrassar.SauceNaoAPI.models package dev.inmo.saucenaoapi.models
import dev.inmo.saucenaoapi.defaultSauceNaoParser
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.internal.StringDescriptor import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.json.JsonObjectSerializer import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
@Serializable @Serializable
data class Header( data class Header(
@@ -39,21 +43,24 @@ data class Header(
) )
internal object IndexesSerializer : KSerializer<List<HeaderIndex?>> { internal object IndexesSerializer : KSerializer<List<HeaderIndex?>> {
override val descriptor: SerialDescriptor = StringDescriptor override val descriptor: SerialDescriptor = String.serializer().descriptor
override fun deserialize(decoder: Decoder): List<HeaderIndex?> { override fun deserialize(decoder: Decoder): List<HeaderIndex?> {
val json = JsonObjectSerializer.deserialize(decoder) val json = JsonObject.serializer().deserialize(decoder)
val parsed = json.keys.mapNotNull { it.toIntOrNull() }.sorted().mapNotNull { val parsed = json.keys.mapNotNull { it.toIntOrNull() }.sorted().mapNotNull {
val jsonObject = json.getObjectOrNull(it.toString()) ?: return@mapNotNull null val jsonObject = json[it.toString()] ?.jsonObject ?: return@mapNotNull null
val index = Json.nonstrict.parse(HeaderIndex.serializer(), Json.stringify(JsonObjectSerializer, jsonObject)) val index = defaultSauceNaoParser.decodeFromString(
HeaderIndex.serializer(),
defaultSauceNaoParser.encodeToString(JsonObject.serializer(), jsonObject)
)
it to index it to index
}.toMap() }.toMap()
return Array<HeaderIndex?>(parsed.keys.max() ?: 0) { return Array<HeaderIndex?>(parsed.keys.maxOrNull() ?: 0) {
parsed[it] parsed[it]
}.toList() }.toList()
} }
override fun serialize(encoder: Encoder, obj: List<HeaderIndex?>) { override fun serialize(encoder: Encoder, value: List<HeaderIndex?>) {
TODO() TODO()
} }
} }

View File

@@ -1,4 +1,4 @@
package com.github.insanusmokrassar.SauceNaoAPI.models package dev.inmo.saucenaoapi.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.github.insanusmokrassar.SauceNaoAPI.models package dev.inmo.saucenaoapi.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@@ -1,4 +1,4 @@
package com.github.insanusmokrassar.SauceNaoAPI.models package dev.inmo.saucenaoapi.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@@ -0,0 +1,176 @@
package dev.inmo.saucenaoapi.models
import dev.inmo.saucenaoapi.utils.CommonMultivariantStringSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ResultData(
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("danbooru_id")
val danbooruId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("gelbooru_id")
val gelbooruId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("drawr_id")
val drawrId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("pixiv_id")
val pixivId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("seiga_id")
val seigaId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("yandere_id")
val yandereId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("konachan_id")
val konachanId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("sankaku_id")
val sankakuId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("anime-pictures_id")
val animePicturesId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("e621_id")
val e621Id: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("idol_id")
val idolId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("imdb_id")
val imdbId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("anidb_aid")
val anidbAId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("bcy_id")
val bcyId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("ddb_id")
val ddbId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("nijie_id")
val nijieId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("getchu_id")
val getchuId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("shutterstock_id")
val shutterstockId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("contributor_id")
val contributorId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("est_time")
val estTime: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("bcy_type")
val bcyType: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("da_id")
val daId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("pg_id")
val pgId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("mal_id")
val malId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("md_id")
val mdId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("mu_id")
val muId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("pawoo_id")
val pawooId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("pawoo_user_acct")
val pawooUserAcct: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("pawoo_user_username")
val pawooUserUsername: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("pawoo_user_display_name")
val pawooUserDisplayname: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val title: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("jp_title")
val titleJp: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("eng_title")
val titleEng: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("alt_titles")
val titleAlt: List<String> = emptyList(),
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("jp_name")
val nameJp: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("eng_name")
val nameEng: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val creator: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val material: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("member_name")
val memberName: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("member_id")
val memberId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val part: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("part_name")
val partName: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val date: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val company: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val file: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val year: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("member_link_id")
val memberLinkId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("author_name")
val authorName: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("author_url")
val authorUrl: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val characters: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val source: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val url: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val type: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("created_at")
val createdAt: String? = null,
@SerialName("ext_urls")
val extUrls: List<String> = emptyList()
)
val ResultData.froms: List<String>
get() = material ?.split(", ") ?: emptyList()
val ResultData.authors: List<String>
get() = (creator ?.split(", ") ?: emptyList()) + (memberName ?.split(", ") ?: emptyList())
val ResultData.charactersList: List<String>
get() = characters ?.split(", ") ?: emptyList()
val ResultData.titles: List<String>
get() = title ?.split(", ") ?: emptyList()
val ResultData.urls: List<String>
get() = extUrls + (url ?.split(", ") ?: emptyList())

View File

@@ -1,4 +1,4 @@
package com.github.insanusmokrassar.SauceNaoAPI.models package dev.inmo.saucenaoapi.models
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@@ -0,0 +1,56 @@
package dev.inmo.saucenaoapi.models
import dev.inmo.saucenaoapi.defaultSauceNaoParser
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
@Serializable
private data class TemporalSauceNaoAnswerRepresentation(
val header: Header,
val results: List<Result> = emptyList(),
)
@Serializable(SauceNaoAnswerSerializer::class)
data class SauceNaoAnswer internal constructor(
val header: Header,
val results: List<Result> = emptyList(),
val raw: JsonObject = JsonObject(emptyMap())
)
@Serializer(SauceNaoAnswer::class)
object SauceNaoAnswerSerializer : KSerializer<SauceNaoAnswer> {
private val resultsSerializer = ListSerializer(Result.serializer())
private const val headerField = "header"
private const val resultsField = "results"
private val serializer = defaultSauceNaoParser
override fun deserialize(decoder: Decoder): SauceNaoAnswer {
val raw = JsonObject.serializer().deserialize(decoder)
return serializer.decodeFromJsonElement(
TemporalSauceNaoAnswerRepresentation.serializer(),
raw
).let {
SauceNaoAnswer(
it.header,
it.results,
raw
)
}
}
override fun serialize(encoder: Encoder, value: SauceNaoAnswer) {
val resultObject = buildJsonObject {
value.raw.forEach {
put(it.key, it.value)
}
put(headerField, serializer.encodeToJsonElement(Header.serializer(), value.header))
put(resultsField, serializer.encodeToJsonElement(resultsSerializer, value.results))
}
JsonObject.serializer().serialize(encoder, resultObject)
}
}

View File

@@ -0,0 +1,18 @@
package dev.inmo.saucenaoapi.utils
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.json.*
@Serializer(String::class)
object CommonMultivariantStringSerializer : KSerializer<String> by String.serializer() {
override fun deserialize(decoder: Decoder): String {
return when (val parsed = JsonElement.serializer().deserialize(decoder)) {
is JsonPrimitive -> parsed.content
is JsonArray -> parsed.joinToString { it.jsonPrimitive.content }
else -> error("Unexpected answer object has been received: $parsed")
}
}
}

View File

@@ -0,0 +1,9 @@
package dev.inmo.saucenaoapi.utils
import io.ktor.http.ContentType
import io.ktor.utils.io.core.Input
expect class MPPFile
expect val MPPFile.input: Input
expect val MPPFile.contentType: ContentType

View File

@@ -1,22 +1,20 @@
package com.github.insanusmokrassar.SauceNaoAPI.utils package dev.inmo.saucenaoapi.utils
import com.github.insanusmokrassar.SauceNaoAPI.additional.LONG_TIME_RECALCULATING_MILLIS
import com.github.insanusmokrassar.SauceNaoAPI.additional.SHORT_TIME_RECALCULATING_MILLIS
import com.github.insanusmokrassar.SauceNaoAPI.exceptions.TooManyRequestsException
import com.github.insanusmokrassar.SauceNaoAPI.exceptions.TooManyRequestsLongException
import com.github.insanusmokrassar.SauceNaoAPI.models.Header
import com.github.insanusmokrassar.SauceNaoAPI.models.LimitsState
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import dev.inmo.saucenaoapi.additional.LONG_TIME_RECALCULATING_MILLIS
import dev.inmo.saucenaoapi.additional.SHORT_TIME_RECALCULATING_MILLIS
import dev.inmo.saucenaoapi.exceptions.TooManyRequestsException
import dev.inmo.saucenaoapi.exceptions.TooManyRequestsLongException
import dev.inmo.saucenaoapi.models.Header
import dev.inmo.saucenaoapi.models.LimitsState
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import java.io.Closeable
import kotlin.coroutines.suspendCoroutine
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
internal class RequestQuotaManager ( internal class RequestQuotaManager (
scope: CoroutineScope scope: CoroutineScope
) : Closeable { ) {
private var longQuota = 1 private var longQuota = 1
private var shortQuota = 1 private var shortQuota = 1
private var longMaxQuota = 1 private var longMaxQuota = 1
@@ -36,6 +34,10 @@ internal class RequestQuotaManager (
for (callback in quotaActions) { for (callback in quotaActions) {
callback() callback()
} }
}.also {
it.invokeOnCompletion {
quotaActions.close(it)
}
} }
private suspend fun updateQuota( private suspend fun updateQuota(
@@ -84,21 +86,16 @@ internal class RequestQuotaManager (
) )
suspend fun getQuota() { suspend fun getQuota() {
return suspendCoroutine { val job = Job()
lateinit var callback: suspend () -> Unit lateinit var callback: suspend () -> Unit
callback = suspend { callback = suspend {
if (longQuota > 0 && shortQuota > 0) { if (longQuota > 0 && shortQuota > 0) {
it.resumeWith(Result.success(Unit)) job.complete()
} else { } else {
quotaActions.send(callback) quotaActions.send(callback)
}
} }
quotaActions.offer(callback)
} }
} quotaActions.trySend(callback)
return job.join()
override fun close() {
quotaJob.cancel()
quotaActions.close()
} }
} }

View File

@@ -0,0 +1,22 @@
package dev.inmo.saucenaoapi.utils
import kotlinx.coroutines.supervisorScope
interface SauceCloseable {
fun close()
}
inline fun <T> SauceCloseable.use(block: (SauceCloseable) -> T): T = try {
block(this)
} finally {
close()
}
@Deprecated("Useless")
suspend fun <T> SauceCloseable.useSafe(block: suspend (SauceCloseable) -> T): T = try {
supervisorScope {
block(this@useSafe)
}
} finally {
close()
}

View File

@@ -1,15 +1,10 @@
package com.github.insanusmokrassar.SauceNaoAPI.utils package dev.inmo.saucenaoapi.utils
import com.github.insanusmokrassar.SauceNaoAPI.additional.LONG_TIME_RECALCULATING_MILLIS
import com.github.insanusmokrassar.SauceNaoAPI.additional.SHORT_TIME_RECALCULATING_MILLIS
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import com.soywiz.klock.TimeSpan import dev.inmo.saucenaoapi.additional.LONG_TIME_RECALCULATING_MILLIS
import kotlinx.coroutines.CoroutineScope import dev.inmo.saucenaoapi.additional.SHORT_TIME_RECALCULATING_MILLIS
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import java.io.Closeable
import kotlin.coroutines.Continuation
import kotlin.coroutines.suspendCoroutine
private fun MutableList<DateTime>.clearTooOldTimes(relatedTo: DateTime = DateTime.now()) { private fun MutableList<DateTime>.clearTooOldTimes(relatedTo: DateTime = DateTime.now()) {
val limitValue = relatedTo - LONG_TIME_RECALCULATING_MILLIS val limitValue = relatedTo - LONG_TIME_RECALCULATING_MILLIS
@@ -40,16 +35,16 @@ private data class TimeManagerTimeAdder(
} }
private data class TimeManagerMostOldestInLongGetter( private data class TimeManagerMostOldestInLongGetter(
private val continuation: Continuation<DateTime?> private val deferred: CompletableDeferred<DateTime?>
) : TimeManagerAction { ) : TimeManagerAction {
override suspend fun makeChangeWith(times: MutableList<DateTime>) { override suspend fun makeChangeWith(times: MutableList<DateTime>) {
times.clearTooOldTimes() times.clearTooOldTimes()
continuation.resumeWith(Result.success(times.min())) deferred.complete(times.minOrNull())
} }
} }
private data class TimeManagerMostOldestInShortGetter( private data class TimeManagerMostOldestInShortGetter(
private val continuation: Continuation<DateTime?> private val deferred: CompletableDeferred<DateTime?>
) : TimeManagerAction { ) : TimeManagerAction {
override suspend fun makeChangeWith(times: MutableList<DateTime>) { override suspend fun makeChangeWith(times: MutableList<DateTime>) {
times.clearTooOldTimes() times.clearTooOldTimes()
@@ -58,19 +53,17 @@ private data class TimeManagerMostOldestInShortGetter(
val limitTime = now - SHORT_TIME_RECALCULATING_MILLIS val limitTime = now - SHORT_TIME_RECALCULATING_MILLIS
continuation.resumeWith( deferred.complete(
Result.success( times.asSequence().filter {
times.asSequence().filter { limitTime < it
limitTime < it }.minOrNull()
}.min()
)
) )
} }
} }
internal class TimeManager( internal class TimeManager(
scope: CoroutineScope scope: CoroutineScope
) : Closeable { ) {
private val actionsChannel = Channel<TimeManagerAction>(Channel.UNLIMITED) private val actionsChannel = Channel<TimeManagerAction>(Channel.UNLIMITED)
private val timeUpdateJob = scope.launch { private val timeUpdateJob = scope.launch {
@@ -78,6 +71,10 @@ internal class TimeManager(
for (action in actionsChannel) { for (action in actionsChannel) {
action(times) action(times)
} }
}.also {
it.invokeOnCompletion {
actionsChannel.close(it)
}
} }
suspend fun addTimeAndClear() { suspend fun addTimeAndClear() {
@@ -85,21 +82,20 @@ internal class TimeManager(
} }
suspend fun getMostOldestInLongPeriod(): DateTime? { suspend fun getMostOldestInLongPeriod(): DateTime? {
return suspendCoroutine { val deferred = CompletableDeferred<DateTime?>()
actionsChannel.offer( return if (actionsChannel.trySend(TimeManagerMostOldestInLongGetter(deferred)).isSuccess) {
TimeManagerMostOldestInLongGetter(it) deferred.await()
) } else {
null
} }
} }
suspend fun getMostOldestInShortPeriod(): DateTime? { suspend fun getMostOldestInShortPeriod(): DateTime? {
return suspendCoroutine { val deferred = CompletableDeferred<DateTime?>()
actionsChannel.offer(TimeManagerMostOldestInShortGetter(it)) return if (actionsChannel.trySend(TimeManagerMostOldestInShortGetter(deferred)).isSuccess) {
deferred.await()
} else {
null
} }
} }
override fun close() {
actionsChannel.close()
timeUpdateJob.cancel()
}
} }

View File

@@ -0,0 +1,18 @@
package dev.inmo.saucenaoapi.utils
import io.ktor.http.ContentType
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Input
import org.khronos.webgl.Int8Array
import org.w3c.files.File
import org.w3c.files.FileReaderSync
actual typealias MPPFile = File
actual val MPPFile.input: Input
get() {
val reader = FileReaderSync()
return ByteReadPacket(Int8Array(reader.readAsArrayBuffer(this)) as ByteArray)
}
actual val MPPFile.contentType: ContentType
get() = ContentType.parse(type)

View File

@@ -0,0 +1,14 @@
package dev.inmo.saucenaoapi.utils
import io.ktor.http.ContentType
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.streams.asInput
import java.io.File
import java.nio.file.Files
actual typealias MPPFile = File
actual val MPPFile.input: Input
get() = inputStream().asInput()
actual val MPPFile.contentType: ContentType
get() = ContentType.parse(Files.probeContentType(toPath()))

View File

@@ -0,0 +1,27 @@
import dev.inmo.saucenaoapi.SauceNaoAPI
import io.ktor.client.HttpClient
import kotlinx.coroutines.*
import java.io.File
suspend fun main(vararg args: String) {
val (key, requestSubject) = args
val client = HttpClient()
val scope = CoroutineScope(Dispatchers.IO).also {
it.coroutineContext.job.invokeOnCompletion {
client.close()
}
}
val api = SauceNaoAPI(key, client, scope = scope)
println(
when {
requestSubject.startsWith("/") -> File(requestSubject).let {
api.request(it)
}
else -> api.request(requestSubject)
}
)
scope.cancel()
}

View File

@@ -1,30 +0,0 @@
package com.github.insanusmokrassar.SauceNaoAPI
import io.ktor.http.ContentType
import io.ktor.utils.io.streams.asInput
import kotlinx.coroutines.*
import java.io.File
import java.nio.file.Files
suspend fun main(vararg args: String) {
val (key, requestSubject) = args
val scope = CoroutineScope(Dispatchers.Default)
val api = SauceNaoAPI(key, scope = scope)
api.use { _ ->
println(
when {
requestSubject.startsWith("/") -> File(requestSubject).let {
api.request(
it.inputStream().asInput(),
ContentType.parse(Files.probeContentType(it.toPath()))
)
}
else -> api.request(requestSubject)
}
)
}
scope.cancel()
}

View File

@@ -1,17 +0,0 @@
package com.github.insanusmokrassar.SauceNaoAPI
sealed class OutputType {
abstract val typeCode: Int
}
object HtmlOutputType : OutputType() {
override val typeCode: Int = 0
}
object XmlOutputType : OutputType() {
override val typeCode: Int = 1
}
object JsonOutputType : OutputType() {
override val typeCode: Int = 2
}

View File

@@ -1,23 +0,0 @@
package com.github.insanusmokrassar.SauceNaoAPI.additional
import com.github.insanusmokrassar.SauceNaoAPI.additional.header.ResultMetaInfo
import com.github.insanusmokrassar.SauceNaoAPI.additional.header.adapted
import com.github.insanusmokrassar.SauceNaoAPI.additional.results.AdaptedResult
import com.github.insanusmokrassar.SauceNaoAPI.additional.results.adapted
import com.github.insanusmokrassar.SauceNaoAPI.models.SauceNaoAnswer
val SauceNaoAnswer.adapted: AdaptedAnswer
get() = header.adapted.let { resultMetainfo ->
val adaptedResults = results.map {
it.adapted(resultMetainfo)
}
AdaptedAnswer(
resultMetainfo,
adaptedResults
)
}
data class AdaptedAnswer(
val resultMetaInfo: ResultMetaInfo,
val results: List<AdaptedResult>
)

View File

@@ -1,9 +0,0 @@
package com.github.insanusmokrassar.SauceNaoAPI.additional.results
import com.github.insanusmokrassar.SauceNaoAPI.additional.header.IndexInfo
data class ResultHeader(
val similarity: Float,
val thumbnail: String,
val index: IndexInfo
)

View File

@@ -1,34 +0,0 @@
package com.github.insanusmokrassar.SauceNaoAPI.exceptions
import com.github.insanusmokrassar.SauceNaoAPI.additional.LONG_TIME_RECALCULATING_MILLIS
import com.github.insanusmokrassar.SauceNaoAPI.additional.SHORT_TIME_RECALCULATING_MILLIS
import com.soywiz.klock.TimeSpan
import io.ktor.client.features.ClientRequestException
import io.ktor.client.statement.readText
import io.ktor.http.HttpStatusCode.Companion.TooManyRequests
import io.ktor.utils.io.errors.IOException
internal suspend fun ClientRequestException.sauceNaoAPIException(): Exception {
return when (response.status) {
TooManyRequests -> {
val answerContent = response.readText()
when {
answerContent.contains("daily limit") -> TooManyRequestsLongException(answerContent)
else -> TooManyRequestsShortException(answerContent)
}
}
else -> this
}
}
sealed class TooManyRequestsException : IOException() {
abstract val answerContent: String
abstract val waitTime: TimeSpan
}
class TooManyRequestsShortException(override val answerContent: String) : TooManyRequestsException() {
override val waitTime: TimeSpan = SHORT_TIME_RECALCULATING_MILLIS
}
class TooManyRequestsLongException(override val answerContent: String) : TooManyRequestsException() {
override val waitTime: TimeSpan = LONG_TIME_RECALCULATING_MILLIS
}

View File

@@ -1,37 +0,0 @@
package com.github.insanusmokrassar.SauceNaoAPI.models
import com.github.insanusmokrassar.SauceNaoAPI.utils.CommonMultivariantStringSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ResultData(
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("danbooru_id")
val danbooruId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("gelbooru_id")
val gelbooruId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("drawr_id")
val drawrId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("pixiv_id")
val pixivId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val title: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val creator: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val material: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("member_name")
val memberName: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
@SerialName("member_id")
val memberId: String? = null,
@Serializable(CommonMultivariantStringSerializer::class)
val characters: String? = null,
@SerialName("ext_urls")
val extUrls: List<String> = emptyList()
)

View File

@@ -1,44 +0,0 @@
package com.github.insanusmokrassar.SauceNaoAPI.models
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.*
@Serializable
data class SauceNaoAnswer internal constructor(
val header: Header,
val results: List<Result> = emptyList(),
val raw: JsonObject = JsonObject(emptyMap())
)
@Serializer(SauceNaoAnswer::class)
object SauceNaoAnswerSerializer : KSerializer<SauceNaoAnswer> {
private val resultsSerializer = ListSerializer(Result.serializer())
private const val headerField = "header"
private const val resultsField = "results"
private val serializer = Json.nonstrict
override fun deserialize(decoder: Decoder): SauceNaoAnswer {
val raw = JsonObjectSerializer.deserialize(decoder)
val stringRaw = serializer.stringify(JsonObjectSerializer, raw)
return serializer.parse(
SauceNaoAnswer.serializer(),
stringRaw
).copy(
raw = raw
)
}
override fun serialize(encoder: Encoder, obj: SauceNaoAnswer) {
val resultObject = JsonObject(
obj.raw.content.let {
it + mapOf(
headerField to serializer.toJson(Header.serializer(), obj.header),
resultsField to serializer.toJson(resultsSerializer, obj.results)
)
}
)
JsonObject.serializer().serialize(encoder, resultObject)
}
}

View File

@@ -1,19 +0,0 @@
package com.github.insanusmokrassar.SauceNaoAPI.utils
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.serializer
@Serializer(String::class)
object CommonMultivariantStringSerializer : KSerializer<String> by String.serializer() {
private val stringArraySerializer = ListSerializer(String.serializer())
override fun deserialize(decoder: Decoder): String {
return try {
decoder.decodeSerializableValue(String.serializer())
} catch (e: Exception) {
decoder.decodeSerializableValue(stringArraySerializer).joinToString()
}
}
}

View File

@@ -1,16 +0,0 @@
package com.github.insanusmokrassar.SauceNaoAPI.utils
import io.ktor.http.ContentType
import io.ktor.util.asStream
import io.ktor.utils.io.core.Input
import java.io.InputStream
import java.net.URLConnection
val InputStream.mimeType: ContentType
get() {
val contentType = URLConnection.guessContentTypeFromStream(this)
return ContentType.parse(contentType)
}
val Input.mimeType: ContentType
get() = asStream().mimeType

View File

@@ -1,19 +0,0 @@
package com.github.insanusmokrassar.SauceNaoAPI.utils
import com.github.insanusmokrassar.SauceNaoAPI.additional.LONG_TIME_RECALCULATING_MILLIS
import com.github.insanusmokrassar.SauceNaoAPI.additional.SHORT_TIME_RECALCULATING_MILLIS
import com.github.insanusmokrassar.SauceNaoAPI.models.Header
import com.soywiz.klock.DateTime
import com.soywiz.klock.TimeSpan
internal suspend fun calculateSleepTime(
header: Header,
mostOldestInShortPeriodGetter: suspend () -> DateTime?,
mostOldestInLongPeriodGetter: suspend () -> DateTime?
): DateTime? {
return when {
header.longRemaining < 1 -> mostOldestInLongPeriodGetter() ?.plus(LONG_TIME_RECALCULATING_MILLIS)
header.shortRemaining < 1 -> mostOldestInShortPeriodGetter() ?.plus(SHORT_TIME_RECALCULATING_MILLIS)
else -> null
}
}