Skip to content

Commit adc980d

Browse files
committed
feat: initial commit
0 parents  commit adc980d

19 files changed

+701
-0
lines changed

.github/workflows/ci.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: CI
2+
on: [push, pull_request]
3+
4+
jobs:
5+
ci:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: actions/checkout@v2
9+
- uses: dart-lang/setup-dart@v1.3
10+
with:
11+
sdk: '2.18.3'
12+
13+
- name: Install Dependencies
14+
run: dart pub get
15+
16+
- name: Check Code Format
17+
run: dart format --output=none --set-exit-if-changed .
18+
19+
- name: Analyze Code
20+
run: dart analyze
21+
22+
- name: Run Test
23+
run: dart test

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Files and directories created by pub.
2+
.dart_tool/
3+
.packages
4+
5+
# Conventional directory for build outputs.
6+
build/
7+
8+
# Omit committing pubspec.lock for library packages; see
9+
# https://dart.dev/guides/libraries/private-files#pubspeclock.
10+
pubspec.lock
11+
12+
# Env files
13+
.env
14+
15+
# Dev testing file
16+
dev.dart

.hooks/pre-commit

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env sh
2+
3+
dart format --output=none --set-exit-if-changed .
4+
dart test

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 0.1.0
2+
3+
- Initial version.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Yakiyo
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# SRA - Some Random Api
2+
[![CI](https://github.com/Yakiyo/sra.dart/actions/workflows/ci.yml/badge.svg)]() [![Pub Package](https://img.shields.io/pub/v/sra.svg)](https://pub.dev/packages/sra)
3+
4+
Minimal Dart wrapper to interact with [Some Random Api](https://some-random-api.ml). Easy to use, simplified and lightweight.
5+
6+
## Getting started
7+
8+
Add the package to your project.
9+
```bash
10+
$ dart pub add sra
11+
```
12+
13+
## Usage
14+
15+
Import the package to your file/project.
16+
17+
```dart
18+
import 'package:sra/sra.dart' as sra;
19+
```
20+
The package exports a base `Client` class which is the central spot for interacting with the api. The client class itself has only one public method `fetch`.
21+
```dart
22+
import 'package:sra/sra.dart' show Client;
23+
24+
void main() async {
25+
var client = Client();
26+
// Optionally pass the api key on class initiation
27+
// Example: Client(apiKey);
28+
var res = await client.fetch('/animal/dog');
29+
print(res);
30+
}
31+
```
32+
The fetch method takes 1 mandatory argument of type String, the endpoint path. You can optionally pass query parameters to the function (if the endpoint requires so). For endpoints that have some required parameters, if you don't specify one of them, the function throws an error.
33+
```dart
34+
// Fetching with parameters
35+
client.fetch('/canvas/misc/oogway', {
36+
'quote': 'Nothing is a mistake'
37+
});
38+
// The endpoint doesn't need any parameters so we don't
39+
// need to provide anything else
40+
client.fetch('/animal/dog');
41+
// This throws an error, because the lied endpoint
42+
// requires a mandatory parameter called 'avatar' which
43+
// is not specified here, so it'll throw an error.
44+
client.fetch('/canvas/misc/lied', {
45+
'username': 'Yakiyo'
46+
});
47+
```
48+
The fetch method returns `Object` which can be one of the two types, a `Map<String, String>` or `List<int>`. Endpoints that return a json result return Map, while endpoints that return PNG images return a `List<U8int>` or a buffer that can be used to write to a file (see [fetch_binary.dart](./example/fetch_binary.dart)). Detailed usages can be found in the [example/](./example/) directory. Feel free to ping me (Yakiyo#1206) in the [SRA Discord Server](https://discord.gg/tTUMWFd) for help, questions or anything.
49+
50+
All valid endpoints and their required/optional arguments are availabale in [SRA docs](https://some-random-api.ml/docs). Currently only the [Rank Card](https://some-random-api.ml/docs/premium#rank-card), [Welcome Image (premium)](https://some-random-api.ml/docs/premium#welcome-images) and [Welcome Image (free)](https://some-random-api.ml/docs/welcome#welcome-images%20(free)) are unavailable in the package. To be added soon:tm:.
51+
## Contribution and Issues
52+
53+
For any issues or bugs, please file a new issue [here](https://github.com/Yakiyo/sra.dart/issues/).
54+
55+
All contributions are welcome. Before creating a Pull Request or working on a new feature, please open up an issue so that it can be discussed.
56+
57+
## Author
58+
**sra.dart** © [Yakiyo](https://github.com/Yakiyo). Authored and maintained by Yakiyo.
59+
60+
Released under [MIT](https://opensource.org/licenses/MIT) License

analysis_options.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This file configures the static analysis results for your project (errors,
2+
# warnings, and lints).
3+
#
4+
# This enables the 'recommended' set of lints from `package:lints`.
5+
# This set helps identify many issues that may lead to problems when running
6+
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
7+
# style and format.
8+
#
9+
# If you want a smaller set of lints you can change this to specify
10+
# 'package:lints/core.yaml'. These are just the most critical lints
11+
# (the recommended set includes the core lints).
12+
# The core lints are also what is used by pub.dev for scoring packages.
13+
14+
include: package:lints/recommended.yaml
15+
16+
# Uncomment the following section to specify additional rules.
17+
18+
# linter:
19+
# rules:
20+
# - camel_case_types
21+
22+
# analyzer:
23+
# exclude:
24+
# - path/to/excluded/files/**
25+
26+
# For more information about the core and recommended set of lints, see
27+
# https://dart.dev/go/core-lints
28+
29+
# For additional information about configuring this file, see
30+
# https://dart.dev/guides/language/analysis-options

example/apikey.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import 'dart:io' show Platform;
2+
import 'package:sra/sra.dart' as sra;
3+
4+
void main() async {
5+
var env = Platform.environment; // Import the env variables
6+
// Initialize the client with an api on contructing it
7+
var client = sra.Client(env['SRA_TOKEN']);
8+
// You can access premium commands with it now. Otherwise it would have thrown an error.
9+
client.fetch('/premium/amongus', {
10+
'avatar':
11+
'https://cdn.discordapp.com/avatars/235148962103951360/ed3dac3b6e7a851df781632a4295fcb9.png?size=1024',
12+
'username': 'Carl',
13+
'custom': 'Yeet'
14+
});
15+
}

example/fetch.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import 'package:sra/sra.dart' as sra;
2+
3+
void main() async {
4+
var client = sra.Client();
5+
6+
var res = await client.fetch('/animal/dog');
7+
8+
// Check if res is Map or not. If it isn't, then something went wrong.
9+
if (res is Map) {
10+
print(res);
11+
} else {
12+
throw 'res should be a map since /animal/dog endpoint returns json result';
13+
}
14+
}

example/fetch_binary.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import 'package:sra/sra.dart' as sra;
2+
import 'dart:io' as io;
3+
4+
void main() async {
5+
var client = sra.Client();
6+
7+
var res = await client.fetch('/canvas/misc/youtube-comment', {
8+
'avatar':
9+
'https://cdn.discordapp.com/avatars/235148962103951360/ed3dac3b6e7a851df781632a4295fcb9.png?size=1024',
10+
// No, I wasn't paid to advertise carl, I just like it myself.
11+
'comment': 'Visit https://carl.gg',
12+
'username': 'Carl'
13+
});
14+
15+
// Endpoints that return binary data (image files) will always return
16+
// a List<U8int> object. If it isn't a list, then something went wrong
17+
if (res is List<int>) {
18+
// Write the result to a file.
19+
io.File("./img.png").writeAsBytesSync(res);
20+
} else {
21+
throw 'Unexpected error. Received $res';
22+
}
23+
}

example/fetch_query.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import 'package:sra/sra.dart' as sra;
2+
import 'dart:io' as io;
3+
4+
void main() async {
5+
var client = sra.Client();
6+
7+
// Pass the query parameters as Map<String, String> to the function
8+
var res = await client.fetch('/canvas/overlay/glass', {
9+
'avatar':
10+
'https://cdn.discordapp.com/avatars/235148962103951360/ed3dac3b6e7a851df781632a4295fcb9.png?size=1024',
11+
});
12+
if (res is List<int>) {
13+
// Write the result to a file.
14+
io.File("./img.png").writeAsBytesSync(res);
15+
} else {
16+
throw 'Unexpected error. Received $res';
17+
}
18+
}

lib/sra.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/// Dart wrapper for interacting with Some Random Api.
2+
///
3+
/// Easy to use, simplified and fast methods
4+
library sra;
5+
6+
export 'src/client.dart' show Client;
7+
export 'src/endpoint.dart' show Endpoint, EndpointQuery;

lib/src/client.dart

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import 'dart:convert' show jsonDecode;
2+
import 'package:http/http.dart' show get;
3+
import './endpoint.dart';
4+
import './util.dart';
5+
6+
/// Base class to interact with the api
7+
class Client {
8+
final baseUrl = 'some-random-api.ml';
9+
late final Map<String, Endpoint> endpoints;
10+
late final String? apiKey;
11+
12+
/// Pass the api key if available on constructing. If its provided, the key
13+
/// is passed along all the requests. If no key is provided, premium endpoints
14+
/// will throw an error.
15+
Client([this.apiKey]) {
16+
_loadEndpoints();
17+
}
18+
19+
void _loadEndpoints() {
20+
endpoints = <String, Endpoint>{};
21+
loadEndpoints(endpoints);
22+
}
23+
24+
/// Fetch data from an endpoint
25+
///
26+
/// path - the endpoint to fetch from, endpoint must include its category
27+
/// example: '/img/dog'
28+
/// query - a map of query parameters to be passed along with the request.
29+
Future<Object> fetch(String path, [Map<String, String>? query]) async {
30+
if (!path.startsWith('/')) {
31+
path = '/$path';
32+
}
33+
if (!endpoints.containsKey(path)) {
34+
throw "Invalid endpoint provided.";
35+
}
36+
var endpoint = endpoints[path] as Endpoint;
37+
if (query != null && apiKey != null) {
38+
query['key'] = apiKey as String;
39+
}
40+
var url = Uri.https(baseUrl, path, query);
41+
_validateRequest(endpoint, query);
42+
try {
43+
var res = await get(url);
44+
var type = res.headers['content-type'] ?? 'application/json';
45+
if (type.contains('application/json')) {
46+
var out = jsonDecode(res.body);
47+
if (out['error'] != null) {
48+
throw out['error'];
49+
} else if (res.statusCode != 200) {
50+
throw 'Internal error when fetching from api. Got status code ${res.statusCode}';
51+
}
52+
return out;
53+
} else if (type.contains('image')) {
54+
return res.bodyBytes;
55+
}
56+
// Dart throws error if a default return is not provided. As long as the api works, the
57+
// return type should always be either a json or image data and this would not be required.
58+
throw 'Unexpected error, api did not return expected type. Got $res';
59+
} catch (e) {
60+
rethrow;
61+
}
62+
}
63+
64+
// This was messy af to write
65+
void _validateRequest(Endpoint endpoint, Map<String, String>? query) {
66+
if (endpoint.isPremium && apiKey == null) {
67+
throw 'Endpoint \'${endpoint.path}\' is premium and requires an api key to work';
68+
}
69+
if (endpoint.query.isNotEmpty) {
70+
if (query == null || query.isEmpty) {
71+
throw 'Endpoint "${endpoint.path}" requires query parameters but was provided none';
72+
}
73+
var keys = endpoint.query.keys;
74+
for (final key in keys) {
75+
final k = endpoint.query[key] as EndpointQuery;
76+
if (k.required && !query.containsKey(k.key)) {
77+
throw 'Query parameter ${k.key} is required but was not provided';
78+
}
79+
}
80+
}
81+
}
82+
}

lib/src/endpoint.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/// Represents a Endpoint
2+
///
3+
/// path - the endpoint path
4+
/// isPremium - wether the endpoint is premium or not
5+
/// query - a map of all the arguments of the endpoint
6+
class Endpoint {
7+
late final Map<String, EndpointQuery> query = {};
8+
late final bool isPremium;
9+
late final String path;
10+
Endpoint(this.path, [List<Map<String, dynamic>>? queries, bool? premium]) {
11+
if (premium != null) {
12+
isPremium = premium;
13+
} else {
14+
isPremium = false;
15+
}
16+
if (queries != null && queries.isNotEmpty) {
17+
for (var element in queries) {
18+
var val = EndpointQuery(element);
19+
query[val.key] = val;
20+
}
21+
}
22+
}
23+
}
24+
25+
/// Represents a class of an Endpoint's query
26+
///
27+
/// Using a class makes it easier then using
28+
/// a map.
29+
/// Key - the query key
30+
/// required - wether the query is must or not
31+
class EndpointQuery {
32+
late final String key;
33+
late bool required = false;
34+
EndpointQuery(Map<String, dynamic> query) {
35+
key = query['key'] as String;
36+
var cond = query['required'];
37+
if (cond is bool) {
38+
required = cond;
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)