-
Notifications
You must be signed in to change notification settings - Fork 1
/
api.py
139 lines (103 loc) · 3.91 KB
/
api.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
"""The Inconnu API endpoints."""
import functools
import glob
import os
import re
from datetime import datetime
from json import dumps
from urllib.parse import urlparse
import aiohttp
import async_timeout
from logger import Logger
# An argument can be made that these should simply live with their appropriate
# command counterparts, but I see a value in keeping them together.
AUTH_HEADER = {"Authorization": os.environ["INCONNU_API_TOKEN"]}
BASE_API = "https://api.inconnu.app/"
BUCKET = "pcs.inconnu.app" # The name of the bucket where the images live
class ApiError(Exception):
"""An exception raised when there's an error with the API."""
def measure(func):
"""A decorator that measures API response time."""
@functools.wraps(func)
async def wrapper(*args, **kwargs):
start = datetime.now()
val = await func(*args, **kwargs)
end = datetime.now()
Logger.info("API: %s finished in %s", kwargs["path"], end - start)
return val
return wrapper
async def upload_faceclaim(character: "VChar", image_url: str) -> str:
"""Uploads a faceclaim to cloud storage."""
payload = {
"guild": character.guild,
"user": character.user,
"charid": character.id,
"image_url": image_url,
"bucket": BUCKET,
}
new_url = await _post(path="/faceclaim/upload", data=dumps(payload))
return new_url
async def delete_single_faceclaim(image: str) -> bool:
"""Delete a single faceclaim image."""
url = urlparse(image)
if url.netloc != BUCKET:
return False
if (match := re.match(r"/([A-F0-9a-f]+/[A-F0-9a-f]+\.webp)$", url.path)) is None:
return False
key = match.group(1)
res = await _delete(path=f"/faceclaim/delete/{BUCKET}/{key}")
Logger.debug("API: %s", res)
return True
async def delete_character_faceclaims(character: "VChar"):
"""Delete all of a character's faceclaims."""
res = await _delete(path=f"/faceclaim/delete/{BUCKET}/{character.id}/all")
del character.profile.images[:]
await character.commit()
Logger.info("API: %s", res)
async def upload_logs():
"""Upload log files."""
Logger.info("API: Uploading logs")
try:
logs = sorted(glob.glob("./logs/*.txt"))
for log in logs:
with open(log, "rb") as handle:
payload = {"log_file": handle}
res = await _post(path="/log/upload", data=payload)
Logger.info("API: %s", res)
if len(logs) > 1:
# Remove all but the most recent log file
for log in logs[:-1]:
Logger.info("API: Deleting old log: %s", log)
os.unlink(log)
elif not logs:
Logger.error("API: No log files found")
return False
# Logs all uploaded successfully
return True
except ApiError as err:
Logger.error("API: %s", str(err))
return False
@measure
async def _post(*, path: str, data: dict) -> str:
"""Send an API POST request."""
Logger.debug("API: POST to %s with %s", path, str(data))
url = BASE_API + path.lstrip("/")
async with async_timeout.timeout(60):
async with aiohttp.ClientSession(headers=AUTH_HEADER) as session:
async with session.post(url, data=data) as response:
json = await response.json()
if not response.ok:
raise ApiError(str(json))
return json
@measure
async def _delete(*, path: str) -> str:
"""Send an API DELETE request."""
Logger.debug("API: DELETE to %s", path)
url = BASE_API + path.lstrip("/")
async with async_timeout.timeout(60):
async with aiohttp.ClientSession(headers=AUTH_HEADER) as session:
async with session.delete(url) as response:
json = await response.json()
if not response.ok:
raise ApiError(str(json))
return json