Skip to content

Commit 9c889dc

Browse files
authored
cmdhandler.d: implement 'exportdata' command (#68)
Allows users to export their account data in JSON format.
1 parent dade0d0 commit 9c889dc

File tree

3 files changed

+170
-13
lines changed

3 files changed

+170
-13
lines changed

src/server/cmdhandler.d

Lines changed: 150 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ module soulfind.server.cmdhandler;
77
@safe:
88

99
import soulfind.db : Sdb;
10-
import soulfind.defines : blue, kick_duration, log_user, norm, RoomType,
11-
server_username;
10+
import soulfind.defines : blue, kick_duration, log_user, norm, RoomMemberType,
11+
RoomType, server_username;
1212
import soulfind.server.messages;
1313
import soulfind.server.room : Room;
1414
import soulfind.server.server : Server;
@@ -49,8 +49,18 @@ final class CommandHandler
4949
case "help":
5050
respond(
5151
sender_username,
52-
"Available commands:"
53-
~ "\n\ndeleteaccount\n Delete your account"
52+
text(
53+
"Available commands:",
54+
"\n\nexportdata\n Export your account data",
55+
"\n\ndeleteaccount\n Delete your account"
56+
)
57+
);
58+
break;
59+
60+
case "exportdata":
61+
respond(
62+
sender_username,
63+
"Account data in JSON format:\n" ~ user_export(sender_username)
5464
);
5565
break;
5666

@@ -96,6 +106,7 @@ final class CommandHandler
96106
"\n\nadmins\n List admins",
97107
"\n\nusers [connected|banned|privileged]\n List users",
98108
"\n\nrooms\n List public rooms",
109+
"\n\nexportdata\n Export your account data",
99110
"\n\nuserinfo <user>\n Show info about user",
100111
"\n\nroominfo <room>\n Show info about public room",
101112
"\n\nremovetickers <user>\n Remove user's public room",
@@ -159,6 +170,13 @@ final class CommandHandler
159170
respond(admin_username, output[]);
160171
break;
161172

173+
case "exportdata":
174+
respond(
175+
admin_username,
176+
"Account data in JSON format:\n" ~ user_export(admin_username)
177+
);
178+
break;
179+
162180
case "userinfo":
163181
if (args.length < 2) {
164182
respond(admin_username, "Syntax is: userinfo <user>");
@@ -517,10 +535,10 @@ final class CommandHandler
517535
auto joined_global_room = "no";
518536
auto admin = "no";
519537
auto banned = "no";
520-
auto searchable = "yes";
521538
auto privileged = "no";
522539
SysTime privileged_until;
523540
auto supporter = "no";
541+
auto searchable = "yes";
524542
uint upload_speed;
525543
uint shared_files, shared_folders;
526544
const tickers = server.db.user_tickers!(RoomType._public)(username);
@@ -573,12 +591,12 @@ final class CommandHandler
573591
else if (banned_until > now)
574592
banned = text("until ", banned_until.toSimpleString);
575593

576-
if (server.db.is_user_unsearchable(username))
577-
searchable = "no";
578-
579594
if (privileged_until > now)
580595
privileged = text("until ", privileged_until.toSimpleString);
581596

597+
if (server.db.is_user_unsearchable(username))
598+
searchable = "no";
599+
582600
Appender!string output;
583601
output ~= text(
584602
username,
@@ -600,23 +618,143 @@ final class CommandHandler
600618
"\nPresistent info:",
601619
"\n admin: ", admin,
602620
"\n banned: ", banned,
603-
"\n searchable: ", searchable,
604621
"\n privileged: ", privileged,
605622
"\n supporter: ", supporter,
623+
"\n searchable: ", searchable,
606624
"\n upload speed: ", upload_speed,
607625
"\n files: ", shared_files,
608-
"\n dirs: ", shared_folders,
626+
"\n folders: ", shared_folders,
609627
"\n public tickers: ", tickers.length
610628
);
611629

612630
if (tickers.length > 0) {
613-
output ~= text("\n\nPublic room tickers (", tickers.length, "):");
614631
foreach (ticker ; tickers) {
615632
const room_name = ticker[0], content = ticker[1];
616-
output ~= text("\n [", room_name, "] ", content);
633+
output ~= text("\n [", room_name, "] ", content);
634+
}
635+
}
636+
637+
return output[];
638+
}
639+
640+
private string user_export(string username)
641+
{
642+
auto user = server.get_user(username);
643+
const status = (user.status == UserStatus.away) ? "away" : "online";
644+
auto obfuscation_type = "null";
645+
const joined_rooms = user.joined_room_names!(RoomType.any);
646+
const accept_invitations = (
647+
user.accept_room_invitations ? "true" : "false"
648+
);
649+
const joined_global_room = (
650+
server.is_global_room_joined(username) ? "true" : "false"
651+
);
652+
const admin_until = server.db.admin_until(username);
653+
auto admin = (admin_until > SysTime())
654+
? text("\"", admin_until.toISOExtString, "\"")
655+
: "null";
656+
const privileged_until = user.privileged_until;
657+
auto privileged = (privileged_until > SysTime())
658+
? text("\"", privileged_until.toISOExtString, "\"")
659+
: "null";
660+
const supporter = user.supporter ? "true" : "false";
661+
const searchable = (
662+
server.is_user_unsearchable(username) ? "false" : "true"
663+
);
664+
const private_rooms_owner = server.db.rooms(username);
665+
const private_rooms_member = server.db.rooms(null, username);
666+
const private_rooms_op = server.db.rooms(
667+
null, username, RoomMemberType.operator
668+
);
669+
670+
if (user.obfuscation_type == ObfuscationType.rotated)
671+
obfuscation_type = "\"rotated\"";
672+
else if (user.obfuscation_type != ObfuscationType.none)
673+
obfuscation_type = text(
674+
"\"", (cast(uint) user.obfuscation_type).text, "\""
675+
);
676+
677+
Appender!string output;
678+
output ~= text(
679+
"{",
680+
"\n \"username\": \"", username, "\",",
681+
"\n \"session_data\": {",
682+
"\n \"status\": \"", status, "\",",
683+
"\n \"client_version\": \"", user.client_version, "\",",
684+
"\n \"ip_address\": \"", user.address.toAddrString ~ "\",",
685+
"\n \"port\": ", user.address.port, ",",
686+
"\n \"obfuscated_port\": ", user.obfuscated_port, ",",
687+
"\n \"obfuscation_type\": ", obfuscation_type, ",",
688+
"\n \"accept_room_invitations\": ", accept_invitations, ",",
689+
"\n \"joined_global_room\": ", joined_global_room, ",",
690+
"\n \"liked_items\": ", user.liked_item_names, ",",
691+
"\n \"hated_items\": ", user.hated_item_names, ",",
692+
"\n \"joined_rooms\": ", joined_rooms, ",",
693+
"\n \"watched_users\": ", user.watched_usernames,
694+
"\n },",
695+
"\n \"persistent_data\": {",
696+
"\n \"admin_until\": ", admin, ",",
697+
"\n \"privileged_until\": ", privileged, ",",
698+
"\n \"supporter\": ", supporter, ",",
699+
"\n \"searchable\": ", searchable, ",",
700+
"\n \"num_files\": ", user.shared_files, ",",
701+
"\n \"num_folders\": ", user.shared_folders, ",",
702+
"\n \"upload_speed\": ", user.upload_speed, ",",
703+
"\n \"private_rooms_owner\": ", private_rooms_owner, ",",
704+
"\n \"private_rooms_member\": ", private_rooms_member, ",",
705+
"\n \"private_rooms_operator\": ", private_rooms_op, ",",
706+
"\n \"room_tickers\": {",
707+
);
708+
709+
const tickers = server.db.user_tickers!(RoomType.any)(username);
710+
if (tickers.length > 0) {
711+
auto first = true;
712+
foreach (ticker ; tickers) {
713+
const room_name = ticker[0], content = ticker[1];
714+
if (!first) output ~= ",";
715+
output ~= text(
716+
"\n \"", room_name, "\": \"", content, "\""
717+
);
718+
first = false;
719+
}
720+
output ~= "\n ";
721+
}
722+
723+
output ~= text(
724+
"},",
725+
"\n },",
726+
"\n \"volatile_data\": {",
727+
"\n \"private_messages_queued\": [",
728+
);
729+
730+
const pms = server.get_queued_pms(username);
731+
if (pms.length > 0) {
732+
auto first = true;
733+
foreach (pm ; pms) {
734+
const id = pm.id;
735+
const to_username = pm.to_username;
736+
const timestamp = pm.time.toISOExtString(0);
737+
const message = pm.message;
738+
739+
if (!first) output ~= ",";
740+
output ~= text(
741+
"\n {",
742+
"\n \"id\": \"", id, "\"",
743+
"\n \"recipient\": \"", to_username, "\"",
744+
"\n \"timestamp\": \"", timestamp, "\",",
745+
"\n \"message\": \"", message, "\"",
746+
"\n }"
747+
);
748+
first = false;
617749
}
750+
output ~= "\n ";
618751
}
619752

753+
output ~= text(
754+
"]",
755+
"\n }",
756+
"\n}"
757+
);
620758
return output[];
621759
}
622760
}

src/server/server.d

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ final class Server
9090
if (query.length > max_search_query_length)
9191
return;
9292

93-
if (to_username in unsearchable_users)
93+
if (is_user_unsearchable(to_username))
9494
return;
9595

9696
auto user = get_user(to_username);
@@ -146,6 +146,11 @@ final class Server
146146
user.send_message(msg);
147147
}
148148

149+
bool is_user_unsearchable(string username)
150+
{
151+
return username in unsearchable_users ? true : false;
152+
}
153+
149154
void refresh_search_filters()
150155
{
151156
string[] new_filters; // Satisfy the linter
@@ -207,6 +212,15 @@ final class Server
207212
pms.remove(id);
208213
}
209214

215+
PM[] get_queued_pms(string from_username)
216+
{
217+
Appender!(PM[]) user_pms;
218+
foreach (ref pm ; pms)
219+
if (pm.from_username == from_username) user_pms ~= pm;
220+
user_pms[].sort();
221+
return user_pms[];
222+
}
223+
210224
void deliver_queued_pms(string to_username)
211225
{
212226
Appender!(PM[]) user_pms;

src/server/user.d

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,11 @@ final class User
513513
return watched_users.length;
514514
}
515515

516+
const watched_usernames()
517+
{
518+
return watched_users.byKey;
519+
}
520+
516521

517522
// Interests
518523

0 commit comments

Comments
 (0)