10
10
from starlette .responses import JSONResponse , RedirectResponse
11
11
from starlette .datastructures import URL
12
12
from starlette .types import ASGIApp , Receive , Scope , Send
13
+
13
14
repeated_quotes = re .compile (r'//+' ) # handling multiple // in url
14
15
from urllib .parse import urlparse , unquote
15
16
import sys
18
19
19
20
# disable requests certificate warning
20
21
from requests .packages .urllib3 .exceptions import InsecureRequestWarning
22
+
21
23
requests .packages .urllib3 .disable_warnings (InsecureRequestWarning )
22
24
23
25
templates = Jinja2Templates (directory = 'templates' )
24
26
25
27
# config
26
28
import configparser
29
+
27
30
config = configparser .ConfigParser ()
28
31
config .read ('config.cfg' )
29
32
ail_url = config ['DEFAULT' ]['ail_url' ]
33
36
cache = valkey .Valkey (host = valkey_host , port = valkey_port , db = 0 )
34
37
# API definition
35
38
from apiman .starlette import Apiman
39
+
36
40
apiman = Apiman (template = "openapi.yml" )
37
41
38
42
if not cache .ping ():
@@ -58,6 +62,7 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
58
62
else :
59
63
await self .app (scope , receive , send )
60
64
65
+
61
66
def extract_onion_from_url (url ):
62
67
if not url .startswith ('http' ):
63
68
url = f'http://{ url } '
@@ -70,6 +75,18 @@ def extract_onion_from_url(url):
70
75
else :
71
76
return None
72
77
78
+
79
+ def stats_onion (onion = None , typeo = "global" ):
80
+ if onion is None :
81
+ return False
82
+ if typeo == "global" :
83
+ r = cache .zincrby ("onion-lookup:stats" , 1 , onion )
84
+ else :
85
+ r = cache .zincrby ("onion-lookup:ail-stats" , 1 , onion )
86
+
87
+ return r
88
+
89
+
73
90
def check_onion (onion = None ):
74
91
# We only support onion_v3 and automatically append dot onion if missing
75
92
if onion is None or onion == '' :
@@ -80,19 +97,25 @@ def check_onion(onion=None):
80
97
return f"{ onion } .onion"
81
98
return False
82
99
100
+
83
101
def query_onion (onion = None ):
84
102
if onion is None :
85
103
return False
86
104
keycache = f'onion-lookup:{ onion } '
105
+ stats_onion (onion = onion )
87
106
if cache .exists (keycache ):
88
107
return json .loads (cache .get (keycache ))
89
- headers = {'Authorization' : ail_apikey }
90
- r = requests .get (f'{ ail_url } /api/v1/lookup/onion/{ onion } ' , headers = headers , verify = False )
108
+ headers = {'Authorization' : ail_apikey }
109
+ r = requests .get (
110
+ f'{ ail_url } /api/v1/lookup/onion/{ onion } ' , headers = headers , verify = False
111
+ )
91
112
if r .status_code != 200 :
92
113
return False
93
114
cache .set (keycache , r .text , ex = 3600 )
115
+ stats_onion (onion = onion , typeo = "local" )
94
116
return json .loads (cache .get (keycache ))
95
117
118
+
96
119
async def homepage (request ):
97
120
template = "index.html"
98
121
context = {"request" : request }
@@ -103,12 +126,13 @@ async def homepage(request):
103
126
context ['onion' ] = onion
104
127
onion_meta = query_onion (onion = onion )
105
128
if onion_meta is not False :
106
- context ['onion_meta' ] = onion_meta
129
+ context ['onion_meta' ] = onion_meta
107
130
else :
108
131
context ['error' ] = 'Incorrect format'
109
132
110
133
return templates .TemplateResponse (template , context )
111
134
135
+
112
136
@apiman .from_yaml (
113
137
"""
114
138
summary: lookup api
@@ -123,7 +147,8 @@ async def homepage(request):
123
147
responses:
124
148
"200":
125
149
description: OK
126
- """ )
150
+ """
151
+ )
127
152
async def lookup (request ):
128
153
onion = extract_onion_from_url (request .path_params ['onion' ].lower ())
129
154
onion = check_onion (onion = onion )
@@ -139,9 +164,10 @@ async def lookup(request):
139
164
140
165
return JSONResponse ({})
141
166
167
+
142
168
async def error (request ):
143
169
"""
144
- Generic catch-call error
170
+ Generic catch-call error
145
171
"""
146
172
raise RuntimeError ("Oh no" )
147
173
@@ -163,23 +189,19 @@ async def server_error(request: Request, exc: HTTPException):
163
189
context = {"request" : request }
164
190
return templates .TemplateResponse (template , context , status_code = 500 )
165
191
192
+
166
193
routes = [
167
194
Route ('/' , homepage ),
168
195
Route ('/api/lookup/{onion}' , lookup , methods = ['GET' ]),
169
196
Route ('/error' , error ),
170
- Mount ('/static' , app = StaticFiles (directory = 'statics' ), name = 'static' )
171
-
197
+ Mount ('/static' , app = StaticFiles (directory = 'statics' ), name = 'static' ),
172
198
]
173
199
174
- exception_handlers = {
175
- 404 : not_found ,
176
- 500 : server_error
177
- }
200
+ exception_handlers = {404 : not_found , 500 : server_error }
178
201
179
202
app = Starlette (debug = False , routes = routes , exception_handlers = exception_handlers )
180
203
app .add_middleware (HttpUrlRedirectMiddleware )
181
204
apiman .init_app (app )
182
205
183
206
if __name__ == "__main__" :
184
207
uvicorn .run (app , host = '0.0.0.0' , port = 8000 )
185
-
0 commit comments