-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathredir_server.py
More file actions
executable file
·181 lines (143 loc) · 4.79 KB
/
redir_server.py
File metadata and controls
executable file
·181 lines (143 loc) · 4.79 KB
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/python3
import logging
import re
from aiohttp import web
import asyncpg
from yarl import URL
import config
logger = logging.getLogger(__name__)
KEY_DB = web.AppKey('db', asyncpg.Pool)
def leading_int(s):
m = re.search(r'^\d+', s)
if m:
return int(m.group(0))
else:
# reraise
return int(s)
def handle_value_error(func):
async def wrapper(request):
try:
return await func(request)
except ValueError as e:
logger.warning('%r for url %s', e, request.url)
raise web.HTTPBadRequest()
return wrapper
async def viewtopic(request):
db = request.app[KEY_DB]
if tid := request.query.get('id'):
topic = await get_topic_by_tid(db, leading_int(tid))
if (p := request.query.get('p')) and p != '1':
num = (leading_int(p)-1) * 15 + 1
url = f'{config.forum_url}t/topic/{topic}/{num}'
else:
url = f'{config.forum_url}t/topic/{topic}'
elif pid := request.query.get('pid'):
topic, num = await get_topic_by_pid(db, leading_int(pid))
url = f'{config.forum_url}t/topic/{topic}/{num}'
else:
url = config.forum_url
url = redirect_from(request.url, url)
raise web.HTTPFound(url)
async def rss(request):
db = request.app[KEY_DB]
action = request.query.get('action')
ty = request.query.get('type')
if not (action == 'feed' and ty == 'rss'):
raise web.HTTPNotFound()
if tid := request.query.get('tid'):
topic = await get_topic_by_tid(db, leading_int(tid))
url = f'{config.forum_url}t/topic/{topic}.rss'
else:
url = f'{config.forum_url}posts.rss'
raise web.HTTPFound(url)
async def get_topic_by_tid(db, tid):
async with db.acquire() as conn, conn.transaction():
sql = '''SELECT discourse_topic_id FROM posts
WHERE fluxbb_topic_id = $1 LIMIT 1'''
rs = await conn.fetch(sql, tid)
if not rs:
raise web.HTTPNotFound()
return rs[0][0]
async def get_topic_by_pid(db, pid):
async with db.acquire() as conn, conn.transaction():
sql = '''SELECT discourse_topic_id, discourse_topic_post_number
FROM posts
WHERE fluxbb_post_id = $1 LIMIT 1'''
rs = await conn.fetch(sql, pid)
if not rs:
raise web.HTTPNotFound()
return rs[0]
async def profile(request):
db = request.app[KEY_DB]
if uid := request.query.get('id'):
username = await get_username_by_uid(db, leading_int(uid))
url = f'{config.forum_url}u/{username}/summary'
else:
url = config.forum_url
url = redirect_from(request.url, url)
raise web.HTTPFound(url)
async def get_username_by_uid(db, uid):
async with db.acquire() as conn, conn.transaction():
sql = '''SELECT discourse_username FROM users
WHERE fluxbb_user_id = $1 LIMIT 1'''
rs = await conn.fetch(sql, uid)
if not rs:
raise web.HTTPNotFound()
return rs[0][0]
async def noredir(request):
r = web.Response(status=302)
r.headers['Location'] = '/'
r.set_cookie('noredir', '1', httponly=True, samesite='Strict', max_age=86400 * 7)
return r
async def yesredir(request):
r = web.Response(status=302)
r.headers['Location'] = '/'
r.del_cookie('noredir')
return r
async def default(request):
if request.path == '/':
url = redirect_from(request.url, config.forum_url)
else:
url = config.forum_url
raise web.HTTPFound(url)
def redirect_from(url, to):
u = URL(to) % {
# aiohttp doesn't use X-Forwarded-Proto and we only use https, so change
# to https always
'redirected_from': str(url.with_scheme('https')),
}
return str(u)
async def init_db(app):
app[KEY_DB] = await asyncpg.create_pool(config.db_url, setup=conn_init, min_size=0)
yield
await app[KEY_DB].close()
async def conn_init(conn):
await conn.execute("set search_path to 'fluxbbredir'")
def setup_app(app):
app.cleanup_ctx.append(init_db)
app.router.add_get('/viewtopic.php', handle_value_error(viewtopic))
app.router.add_get('/extern.php', handle_value_error(rss))
app.router.add_get('/profile.php', handle_value_error(profile))
app.router.add_get('/noredir', noredir)
app.router.add_get('/yesredir', yesredir)
app.router.add_get('/{p:.*}', default)
def main():
import argparse
from nicelogger import enable_pretty_logging
parser = argparse.ArgumentParser(
description = 'fluxbb-to-discourse redirector',
)
parser.add_argument('--port', default=9009, type=int,
help='port to listen on')
parser.add_argument('--ip', default='127.0.0.1',
help='address to listen on')
parser.add_argument('--loglevel', default='info',
choices=['debug', 'info', 'warn', 'error'],
help='log level')
args = parser.parse_args()
enable_pretty_logging(args.loglevel.upper())
app = web.Application()
setup_app(app)
web.run_app(app, host=args.ip, port=args.port)
if __name__ == '__main__':
main()