Skip to content

Commit b35485a

Browse files
authored
Merge pull request #6 from jagerman/master-i-mean-main-improvements
Add many administration capabilities to `python3 -msogs`
2 parents 956bfee + 1910849 commit b35485a

File tree

6 files changed

+510
-146
lines changed

6 files changed

+510
-146
lines changed

contrib/uwsgi-vassal-sogs.ini

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[uwsgi]
2+
chdir = /home/USER/session-pysogs
3+
socket = sogs.wsgi
4+
chmod-socket = 660
5+
plugins = python3,logfile
6+
processes = 4
7+
enable-threads = true
8+
manage-script-name = true
9+
mount = /=sogs.web:app
10+
11+
logger = file:logfile=/home/USER/session-pysogs/sogs.log

sogs/__init__.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +0,0 @@
1-
#!/usr/bin/env python3
2-
3-
from . import config
4-
from . import logging
5-
from . import db
6-
from . import routes
7-
from . import onion_request
8-
from . import legacy_routes
9-
from .web import app
10-
from . import cleanup

sogs/__main__.py

Lines changed: 272 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,280 @@
1-
from argparse import ArgumentParser as AP
1+
from argparse import ArgumentParser as AP, RawDescriptionHelpFormatter
2+
import re
3+
import sys
4+
import sqlite3
25

36
from . import db
7+
from . import config
8+
from . import model
9+
from . import crypto
10+
from . import logging
411

5-
ap = AP()
6-
ap.add_argument('--add', action='store_const', default=False, const=True)
7-
ap.add_argument('--remove', action='store_const', default=False, const=True)
8-
ap.add_argument('--admin', type=str)
9-
ap.add_argument('--mod', type=str)
10-
ap.add_argument('--room', type=str)
12+
ap = AP(epilog="""
13+
14+
Examples:
15+
16+
# Add new room 'xyz':
17+
python3 -msogs --add-room xyz --name 'XYZ Room'
18+
19+
# Add 2 admins to each of rooms 'xyz' and 'abc':
20+
python3 -msogs --rooms abc xyz --add-moderators 050123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef 0500112233445566778899aabbccddeeff00112233445566778899aabbccddeeff --admin
21+
22+
# Add a global moderator visible as a moderator of all rooms:
23+
python3 -msogs --add-moderators 050123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef --rooms=+ --visible
24+
25+
# List room info:
26+
python3 -msogs -L
27+
28+
""", formatter_class=RawDescriptionHelpFormatter)
29+
30+
actions = ap.add_mutually_exclusive_group()
31+
32+
actions.add_argument('--add-room', help="Add a room with the given token", metavar='TOKEN')
33+
ap.add_argument(
34+
'--name', help="Set the room's initial name for --add-room; if omitted use the token name"
35+
)
36+
ap.add_argument('--description', help="Specifies the room's initial description for --add-room")
37+
actions.add_argument('--delete-room', help="Delete the room with the given token", metavar='TOKEN')
38+
actions.add_argument(
39+
'--add-moderators',
40+
nargs='+',
41+
metavar='SESSIONID',
42+
help="Add the given Session ID(s) as a moderator of the room given by --rooms",
43+
)
44+
actions.add_argument(
45+
'--delete-moderators',
46+
nargs='+',
47+
metavar='SESSIONID',
48+
help="Delete the the given Session ID(s) as moderator and admins of the room given by --rooms",
49+
)
50+
ap.add_argument(
51+
'--admin',
52+
action='store_true',
53+
help="Add the given moderators as admins rather than ordinary moderators",
54+
)
55+
ap.add_argument(
56+
'--rooms',
57+
nargs='+',
58+
metavar='TOKEN',
59+
help="Room(s) to use when adding/removing moderators/admins. If a single room name of '+' is "
60+
"given then the user will be added/removed as a global admin/moderator. If a single room name "
61+
"of '*' is given then the user is added/removed as an admin/moderator from each of the "
62+
"server's current rooms.",
63+
)
64+
vis_group = ap.add_mutually_exclusive_group()
65+
vis_group.add_argument(
66+
'--visible',
67+
action='store_true',
68+
help="Make an added moderator/admins' status publicly visible. This is the default for room "
69+
"mods, but not for global mods",
70+
)
71+
vis_group.add_argument(
72+
'--hidden',
73+
action='store_true',
74+
help="Hide the added moderator/admins' status from public users. This is the default for "
75+
"global mods, but not for room mods",
76+
)
77+
actions.add_argument(
78+
"--list-rooms", "-L", action='store_true', help="List current rooms and basic stats"
79+
)
80+
actions.add_argument(
81+
'--list-global-mods', '-M', action='store_true', help="List global moderators/admins"
82+
)
83+
ap.add_argument(
84+
"--yes", action='store_true', help="Don't prompt for confirmation for some commands, just do it"
85+
)
1186

1287
args = ap.parse_args()
13-
with db.conn as conn:
14-
if args.add:
15-
if args.room:
16-
conn.execute(
17-
"INSERT INTO rooms(token, name, description) VALUES(?1, ?1, ?1)", [args.room]
18-
)
19-
if args.mod:
20-
conn.execute(
21-
"INSERT INTO users(session_id, moderator, visible_mod) VALUES(?, TRUE, TRUE)",
22-
[args.mod],
23-
)
24-
if args.admin:
88+
89+
90+
def print_room(room: model.Room):
91+
msgs, msgs_size = room.messages_size()
92+
files, files_size = room.attachments_size()
93+
94+
msgs_size /= 1_000_000
95+
files_size /= 1_000_000
96+
97+
active = [room.active_users(x * 86400) for x in (7, 14, 30)]
98+
m, a, hm, ha = room.get_all_moderators()
99+
admins = len(a) + len(ha)
100+
mods = len(m) + len(hm)
101+
102+
print(
103+
f"""
104+
{room.token}
105+
{"=" * len(room.token)}
106+
Name: {room.name}
107+
Description: {room.description}
108+
URL: {config.URL_BASE}/{room.token}?public_key={crypto.server_pubkey_hex}
109+
Messages: {msgs} ({msgs_size:.1f} MB)
110+
Attachments: {files} ({files_size:.1f} MB)
111+
Active users: {active[0]} (7d), {active[1]} (14d) {active[2]} (30d)
112+
Moderators: {admins} admins ({len(ha)} hidden), {mods} moderators ({len(hm)} hidden):"""
113+
)
114+
for id in a:
115+
print(f" - {id} (admin)")
116+
for id in ha:
117+
print(f" - {id} (hidden admin)")
118+
for id in m:
119+
print(f" - {id} (moderator)")
120+
for id in hm:
121+
print(f" - {id} (hidden moderator)")
122+
123+
124+
db.conn = db.sqlite_connect()
125+
if args.add_room:
126+
if not re.fullmatch(r'[\w-]{1,64}', args.add_room):
127+
print(
128+
"Error: room tokens may only contain a-z, A-Z, 0-9, _, and - characters",
129+
file=sys.stderr,
130+
)
131+
sys.exit(1)
132+
133+
with db.conn as conn:
134+
try:
25135
conn.execute(
26-
"INSERT INTO users(session_id, moderator, admin) VALUES(?, TRUE, TRUE)",
27-
[args.admin],
136+
"INSERT INTO rooms(token, name, description) VALUES(?, ?, ?)",
137+
[args.add_room, args.name or args.add_room, args.description],
28138
)
29-
if args.remove:
30-
if args.room:
31-
conn.execute("DELETE FROM rooms WHERE token = ?", [args.room])
32-
if args.mod:
33-
conn.execute(
34-
"UPDATE users WHERE session_id = ? SET moderator=FALSE visible_mod=FALSE",
35-
[args.mod],
139+
except sqlite3.IntegrityError:
140+
print(f"Error: room '{args.add_room}' already exists!", file=sys.stderr)
141+
sys.exit(1)
142+
print(f"Created room {args.add_room}:")
143+
print_room(model.Room(token=args.add_room))
144+
145+
elif args.delete_room:
146+
try:
147+
room = model.Room(token=args.delete_room)
148+
except model.NoSuchRoom:
149+
print(f"Error: no such room '{args.delete_room}'", file=sys.stderr)
150+
sys.exit(1)
151+
152+
print_room(room)
153+
if args.yes:
154+
res = "y"
155+
else:
156+
res = input("Are you sure you want to delete this room? [yN] ")
157+
if res.startswith("y") or res.startswith("Y"):
158+
with db.conn:
159+
cur = db.conn.cursor()
160+
cur.execute("DELETE FROM rooms WHERE token = ?", [args.delete_room])
161+
count = cur.rowcount
162+
print("Room deleted.")
163+
else:
164+
print("Aborted.")
165+
sys.exit(2)
166+
167+
elif args.add_moderators:
168+
if not args.rooms:
169+
print("Error: --rooms is required when using --add-moderators", file=sys.stderr)
170+
sys.exit(1)
171+
for a in args.add_moderators:
172+
if not re.fullmatch(r'05[A-Fa-f0-9]{64}', a):
173+
print("Error: '{}' is not a valid session id".format(a), file=sys.stderr)
174+
sys.exit(1)
175+
if len(args.rooms) > 1 and ('*' in args.rooms or '+' in args.rooms):
176+
print(
177+
"Error: '+'/'*' arguments to --rooms cannot be used with other rooms", file=sys.stderr
178+
)
179+
sys.exit(1)
180+
if args.rooms == ['+']:
181+
for sid in args.add_moderators:
182+
u = model.User(session_id=sid)
183+
u.set_moderator(admin=args.admin, visible=args.visible)
184+
print(
185+
"Added {} as {} global {}".format(
186+
sid,
187+
"visible" if args.visible else "hidden",
188+
"admin" if args.admin else "moderator",
189+
)
36190
)
37-
if args.admin:
38-
conn.execute("UPDATE users WHERE session_id = ? SET admin=FALSE", [args.mod])
191+
else:
192+
if args.rooms == ['*']:
193+
rooms = model.get_rooms()
194+
else:
195+
try:
196+
rooms = [model.Room(token=r) for r in args.rooms]
197+
except model.NoSuchRoom as nsr:
198+
print("No such room: '{}'".format(nsr.token), file=sys.stderr)
199+
200+
for sid in args.add_moderators:
201+
u = model.User(session_id=sid)
202+
for room in rooms:
203+
room.set_moderator(u, admin=args.admin, visible=not args.hidden)
204+
print(
205+
"Added {} as {} {} of {} ({})".format(
206+
u.session_id,
207+
"hidden" if args.hidden else "visible",
208+
"admin" if args.admin else "moderator",
209+
room.name,
210+
room.token,
211+
)
212+
)
213+
214+
elif args.delete_moderators:
215+
if not args.rooms:
216+
print("Error: --rooms is required when using --delete-moderators", file=sys.stderr)
217+
sys.exit(1)
218+
for a in args.delete_moderators:
219+
if not re.fullmatch(r'05[A-Fa-f0-9]{64}', a):
220+
print("Error: '{}' is not a valid session id".format(a), file=sys.stderr)
221+
sys.exit(1)
222+
if len(args.rooms) > 1 and ('*' in args.rooms or '+' in args.rooms):
223+
print(
224+
"Error: '+'/'*' arguments to --rooms cannot be used with other rooms", file=sys.stderr
225+
)
226+
sys.exit(1)
227+
if args.rooms == ['+']:
228+
for sid in args.delete_moderators:
229+
u = model.User(session_id=sid)
230+
was_admin = u.global_admin
231+
if not u.global_admin and not u.global_moderator:
232+
print("{} was not a global moderator".format(u.session_id))
233+
else:
234+
u.remove_moderator()
235+
print("Removed {} as global {}".format(sid, "admin" if was_admin else "moderator"))
236+
else:
237+
if args.rooms == ['*']:
238+
rooms = model.get_rooms()
239+
else:
240+
try:
241+
rooms = [model.Room(token=r) for r in args.rooms]
242+
except model.NoSuchRoom as nsr:
243+
print("No such room: '{}'".format(nsr.token), file=sys.stderr)
244+
245+
for sid in args.delete_moderators:
246+
u = model.User(session_id=sid)
247+
for room in rooms:
248+
room.remove_moderator(u)
249+
print(
250+
"Removed {} as moderator/admin of {} ({})".format(
251+
u.session_id, room.name, room.token
252+
)
253+
)
254+
elif args.list_rooms:
255+
rooms = model.get_rooms()
256+
if rooms:
257+
for room in rooms:
258+
print_room(room)
259+
else:
260+
print("No rooms.")
261+
262+
elif args.list_global_mods:
263+
m, a, hm, ha = model.get_all_global_moderators()
264+
admins = len(a) + len(ha)
265+
mods = len(m) + len(hm)
266+
267+
print(f"{admins} global admins ({len(ha)} hidden), {mods} moderators ({len(hm)} hidden):")
268+
for u in a:
269+
print(f"- {u.session_id} (admin)")
270+
for u in ha:
271+
print(f"- {u.session_id} (hidden admin)")
272+
for u in m:
273+
print(f"- {u.session_id} (moderator)")
274+
for u in hm:
275+
print(f"- {u.session_id} (hidden moderator)")
276+
277+
else:
278+
print("Error: no action given", file=sys.stderr)
279+
ap.print_usage()
280+
sys.exit(1)

sogs/migrate01x.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,6 @@ def migrate01x(conn):
277277
# file_id is an integer value but stored in a TEXT field, of course.
278278
file_id = int(file_id)
279279

280-
# Timestamp is milliseconds since unix epoch, of course.
281-
timestamp /= 1000.0
282-
283280
path = "files/{}_files/{}".format(room_token, file_id)
284281
try:
285282
size = os.path.getsize(path)
@@ -487,3 +484,8 @@ def migrate01x(conn):
487484
total_msgs, total_files, total_rooms
488485
)
489486
)
487+
488+
try:
489+
os.rename("database.db", "old-database.db")
490+
except Exception as e:
491+
logger.warn("Failed to rename database.db -> old-database.db: {}".format(e))

0 commit comments

Comments
 (0)