2025-09-30 18:45:35 +00:00
use std ::{ collections ::HashSet , env , fmt ::Write , fs , path ::PathBuf , str ::FromStr } ;
2025-01-24 01:57:13 +09:00
use command_parser ::{
2025-04-04 05:24:09 +09:00
parameter ::{ Parameter , ParameterKind } ,
token ::Token ,
2025-01-24 01:57:13 +09:00
} ;
fn main ( ) -> Result < ( ) , Box < dyn std ::error ::Error > > {
let write_location = env ::args ( )
. nth ( 1 )
. expect ( " file location should be provided " ) ;
let write_location = PathBuf ::from_str ( & write_location ) . unwrap ( ) ;
let commands = command_definitions ::all ( ) . collect ::< Vec < _ > > ( ) ;
let mut glue = String ::new ( ) ;
writeln! ( & mut glue , " #nullable enable \n " ) ? ;
writeln! ( & mut glue , " using PluralKit.Core; \n " ) ? ;
2025-04-04 05:24:09 +09:00
writeln! ( & mut glue , " using Myriad.Types; " ) ? ;
2025-01-24 01:57:13 +09:00
writeln! ( & mut glue , " namespace PluralKit.Bot; \n " ) ? ;
2025-09-30 18:45:35 +00:00
let mut commands_seen = HashSet ::new ( ) ;
2025-01-24 01:57:13 +09:00
let mut record_fields = String ::new ( ) ;
for command in & commands {
2025-09-30 18:45:35 +00:00
if commands_seen . contains ( & command . cb ) {
continue ;
}
2025-01-24 01:57:13 +09:00
writeln! (
& mut record_fields ,
r # "public record {command_name}({command_name}Params parameters, {command_name}Flags flags): Commands;"# ,
command_name = command_callback_to_name ( & command . cb ) ,
) ? ;
2025-09-30 18:45:35 +00:00
commands_seen . insert ( command . cb . clone ( ) ) ;
2025-01-24 01:57:13 +09:00
}
2025-09-30 18:45:35 +00:00
commands_seen . clear ( ) ;
2025-01-24 01:57:13 +09:00
let mut match_branches = String ::new ( ) ;
for command in & commands {
2025-09-30 18:45:35 +00:00
if commands_seen . contains ( & command . cb ) {
continue ;
}
2025-01-24 01:57:13 +09:00
let mut command_params_init = String ::new ( ) ;
let command_params = find_parameters ( & command . tokens ) ;
for param in & command_params {
writeln! (
& mut command_params_init ,
2025-10-11 05:49:01 +00:00
r # "@{fieldName} = await ctx.ParamResolve{extract_fn_name}("{name}"){throw_null},"# ,
fieldName = param . name ( ) . replace ( " - " , " _ " ) ,
name = param . name ( ) ,
2025-01-24 01:57:13 +09:00
extract_fn_name = get_param_param_ty ( param . kind ( ) ) ,
2025-10-03 15:50:54 +00:00
throw_null = param
. is_optional ( )
2025-10-13 10:25:55 +00:00
. then ( String ::new )
. unwrap_or ( format! ( " ?? throw new PKError( \" parameter {} not found but was required, this is a bug in the command parser, for command: {} ! \" ) " , param . name ( ) , command . cb ) ) ,
2025-01-24 01:57:13 +09:00
) ? ;
}
let mut command_flags_init = String ::new ( ) ;
for flag in & command . flags {
2025-10-03 15:50:54 +00:00
if let Some ( param ) = flag . get_value ( ) {
2025-01-24 01:57:13 +09:00
writeln! (
& mut command_flags_init ,
2025-10-11 05:49:01 +00:00
r # "@{fieldName} = await ctx.FlagResolve{extract_fn_name}("{name}"),"# ,
fieldName = flag . get_name ( ) . replace ( " - " , " _ " ) ,
name = flag . get_name ( ) ,
2025-10-03 15:50:54 +00:00
extract_fn_name = get_param_param_ty ( param . kind ( ) ) ,
2025-01-24 01:57:13 +09:00
) ? ;
} else {
writeln! (
& mut command_flags_init ,
2025-10-11 05:49:01 +00:00
r # "@{fieldName} = ctx.Parameters.HasFlag("{name}"),"# ,
fieldName = flag . get_name ( ) . replace ( " - " , " _ " ) ,
name = flag . get_name ( ) ,
2025-01-24 01:57:13 +09:00
) ? ;
}
}
write! (
& mut match_branches ,
r #"
" {command_callback} " = > new { command_name } (
new { command_name } Params { { { command_params_init } } } ,
new { command_name } Flags { { { command_flags_init } } }
) ,
" #,
command_name = command_callback_to_name ( & command . cb ) ,
command_callback = command . cb ,
) ? ;
2025-09-30 18:45:35 +00:00
commands_seen . insert ( command . cb . clone ( ) ) ;
2025-01-24 01:57:13 +09:00
}
write! (
& mut glue ,
r #"
public abstract record Commands ( )
{ {
{ record_fields }
public static async Task < Commands ? > FromContext ( Context ctx )
{ {
return ctx . Parameters . Callback ( ) switch
{ {
{ match_branches }
_ = > null ,
} } ;
} }
} }
" #,
) ? ;
2025-09-30 18:45:35 +00:00
commands_seen . clear ( ) ;
2025-01-24 01:57:13 +09:00
for command in & commands {
2025-09-30 18:45:35 +00:00
if commands_seen . contains ( & command . cb ) {
continue ;
}
2025-01-24 01:57:13 +09:00
let mut command_params_fields = String ::new ( ) ;
let command_params = find_parameters ( & command . tokens ) ;
for param in & command_params {
writeln! (
& mut command_params_fields ,
2025-10-03 15:50:54 +00:00
r # "public required {ty}{nullable} @{name};"# ,
2025-04-04 04:47:00 +09:00
name = param . name ( ) . replace ( " - " , " _ " ) ,
2025-01-24 01:57:13 +09:00
ty = get_param_ty ( param . kind ( ) ) ,
2025-10-03 15:50:54 +00:00
nullable = param . is_optional ( ) . then_some ( " ? " ) . unwrap_or ( " " ) ,
2025-01-24 01:57:13 +09:00
) ? ;
}
let mut command_flags_fields = String ::new ( ) ;
for flag in & command . flags {
2025-10-03 15:50:54 +00:00
if let Some ( param ) = flag . get_value ( ) {
2025-01-24 01:57:13 +09:00
writeln! (
& mut command_flags_fields ,
2025-03-31 22:22:38 +09:00
r # "public {ty}? @{name};"# ,
2025-04-04 04:47:00 +09:00
name = flag . get_name ( ) . replace ( " - " , " _ " ) ,
2025-10-03 15:50:54 +00:00
ty = get_param_ty ( param . kind ( ) ) ,
2025-01-24 01:57:13 +09:00
) ? ;
} else {
writeln! (
& mut command_flags_fields ,
2025-03-31 22:22:38 +09:00
r # "public required bool @{name};"# ,
2025-04-04 04:47:00 +09:00
name = flag . get_name ( ) . replace ( " - " , " _ " ) ,
2025-01-24 01:57:13 +09:00
) ? ;
}
}
2025-03-31 22:22:38 +09:00
let mut command_reply_format = String ::new ( ) ;
2025-04-04 05:24:09 +09:00
if command
. flags
. iter ( )
. any ( | flag | flag . get_name ( ) = = " plaintext " )
{
2025-03-31 22:22:38 +09:00
writeln! (
& mut command_reply_format ,
r # "if (plaintext) return ReplyFormat.Plaintext;"# ,
) ? ;
}
if command . flags . iter ( ) . any ( | flag | flag . get_name ( ) = = " raw " ) {
writeln! (
& mut command_reply_format ,
r # "if (raw) return ReplyFormat.Raw;"# ,
) ? ;
}
command_reply_format . push_str ( " return ReplyFormat.Standard; \n " ) ;
2025-09-30 18:45:35 +00:00
let mut command_list_options = String ::new ( ) ;
let mut command_list_options_class = String ::new ( ) ;
let list_flags = command_definitions ::utils ::get_list_flags ( ) ;
if list_flags . iter ( ) . all ( | flag | command . flags . contains ( & flag ) ) {
write! ( & mut command_list_options_class , " : IHasListOptions " ) ? ;
writeln! (
& mut command_list_options ,
r #"
public ListOptions GetListOptions ( Context ctx , SystemId target )
{ {
var directLookupCtx = ctx . DirectLookupContextFor ( target ) ;
var lookupCtx = ctx . LookupContextFor ( target ) ;
var p = new ListOptions ( ) ;
p . Type = full ? ListType . Long : ListType . Short ;
// Search description filter
p . SearchDescription = search_description ;
// Sort property
if ( by_name ) p . SortProperty = SortProperty . Name ;
if ( by_display_name ) p . SortProperty = SortProperty . DisplayName ;
if ( by_id ) p . SortProperty = SortProperty . Hid ;
if ( by_message_count ) p . SortProperty = SortProperty . MessageCount ;
if ( by_created ) p . SortProperty = SortProperty . CreationDate ;
if ( by_last_fronted ) p . SortProperty = SortProperty . LastSwitch ;
if ( by_last_message ) p . SortProperty = SortProperty . LastMessage ;
if ( by_birthday ) p . SortProperty = SortProperty . Birthdate ;
if ( random ) p . SortProperty = SortProperty . Random ;
// Sort reverse
p . Reverse = reverse ;
// Privacy filter
if ( all ) p . PrivacyFilter = null ;
if ( private_only ) p . PrivacyFilter = PrivacyLevel . Private ;
// PERM CHECK: If we're trying to access non-public members of another system, error
if ( p . PrivacyFilter ! = PrivacyLevel . Public & & directLookupCtx ! = LookupContext . ByOwner )
// TODO: should this just return null instead of throwing or something? >.>
throw Errors . NotOwnInfo ;
// this is for searching
p . Context = lookupCtx ;
// Additional fields to include
p . IncludeLastSwitch = with_last_switch ;
p . IncludeLastMessage = with_last_message ;
p . IncludeMessageCount = with_message_count ;
p . IncludeCreated = with_created ;
p . IncludeAvatar = with_avatar ;
p . IncludePronouns = with_pronouns ;
p . IncludeDisplayName = with_displayname ;
p . IncludeBirthday = with_birthday ;
// Always show the sort property (unless short list and already showing something else)
if ( p . Type ! = ListType . Short | | p . includedCount = = 0 )
{ {
if ( p . SortProperty = = SortProperty . DisplayName ) p . IncludeDisplayName = true ;
if ( p . SortProperty = = SortProperty . MessageCount ) p . IncludeMessageCount = true ;
if ( p . SortProperty = = SortProperty . CreationDate ) p . IncludeCreated = true ;
if ( p . SortProperty = = SortProperty . LastSwitch ) p . IncludeLastSwitch = true ;
if ( p . SortProperty = = SortProperty . LastMessage ) p . IncludeLastMessage = true ;
if ( p . SortProperty = = SortProperty . Birthdate ) p . IncludeBirthday = true ;
} }
p . AssertIsValid ( ) ;
return p ;
} }
" #,
) ? ;
}
2025-01-24 01:57:13 +09:00
write! (
& mut glue ,
r #"
public class { command_name } Params
{ {
{ command_params_fields }
} }
2025-09-30 18:45:35 +00:00
public class { command_name } Flags { command_list_options_class }
2025-01-24 01:57:13 +09:00
{ {
{ command_flags_fields }
2025-03-31 22:22:38 +09:00
public ReplyFormat GetReplyFormat ( )
{ {
{ command_reply_format }
} }
2025-09-30 18:45:35 +00:00
{ command_list_options }
2025-01-24 01:57:13 +09:00
} }
" #,
command_name = command_callback_to_name ( & command . cb ) ,
) ? ;
2025-09-30 18:45:35 +00:00
commands_seen . insert ( command . cb . clone ( ) ) ;
2025-01-24 01:57:13 +09:00
}
fs ::write ( write_location , glue ) ? ;
Ok ( ( ) )
}
fn command_callback_to_name ( cb : & str ) -> String {
cb . split ( " _ " )
. map ( | w | w . chars ( ) . nth ( 0 ) . unwrap ( ) . to_uppercase ( ) . collect ::< String > ( ) + & w [ 1 .. ] )
. collect ( )
}
fn get_param_ty ( kind : ParameterKind ) -> & 'static str {
match kind {
2025-11-23 10:28:46 +00:00
ParameterKind ::OpaqueString = > " string " ,
2025-10-04 01:57:48 +00:00
ParameterKind ::OpaqueInt = > " int " ,
2025-01-24 01:57:13 +09:00
ParameterKind ::MemberRef = > " PKMember " ,
2025-09-24 21:32:42 +03:00
ParameterKind ::MemberRefs = > " List<PKMember> " ,
2025-09-26 23:56:49 +00:00
ParameterKind ::GroupRef = > " PKGroup " ,
2025-10-01 00:51:45 +00:00
ParameterKind ::GroupRefs = > " List<PKGroup> " ,
2025-01-24 01:57:13 +09:00
ParameterKind ::SystemRef = > " PKSystem " ,
2025-10-04 01:57:48 +00:00
ParameterKind ::UserRef = > " User " ,
2025-01-24 01:57:13 +09:00
ParameterKind ::MemberPrivacyTarget = > " MemberPrivacySubject " ,
2025-10-01 00:51:45 +00:00
ParameterKind ::GroupPrivacyTarget = > " GroupPrivacySubject " ,
2025-04-04 06:14:17 +09:00
ParameterKind ::SystemPrivacyTarget = > " SystemPrivacySubject " ,
ParameterKind ::PrivacyLevel = > " PrivacyLevel " ,
2025-01-24 01:57:13 +09:00
ParameterKind ::Toggle = > " bool " ,
2025-04-04 03:50:07 +09:00
ParameterKind ::Avatar = > " ParsedImage " ,
2025-10-03 02:21:12 +00:00
ParameterKind ::MessageRef = > " Message.Reference " ,
ParameterKind ::ChannelRef = > " Channel " ,
2025-04-04 05:24:09 +09:00
ParameterKind ::GuildRef = > " Guild " ,
2025-10-04 19:32:58 +00:00
ParameterKind ::ProxySwitchAction = > " SystemConfig.ProxySwitchAction " ,
2025-01-24 01:57:13 +09:00
}
}
fn get_param_param_ty ( kind : ParameterKind ) -> & 'static str {
match kind {
2025-11-23 10:28:46 +00:00
ParameterKind ::OpaqueString = > " Opaque " ,
2025-10-04 01:57:48 +00:00
ParameterKind ::OpaqueInt = > " Number " ,
2025-01-24 01:57:13 +09:00
ParameterKind ::MemberRef = > " Member " ,
2025-09-24 21:32:42 +03:00
ParameterKind ::MemberRefs = > " Members " ,
2025-09-26 23:56:49 +00:00
ParameterKind ::GroupRef = > " Group " ,
2025-10-01 00:51:45 +00:00
ParameterKind ::GroupRefs = > " Groups " ,
2025-01-24 01:57:13 +09:00
ParameterKind ::SystemRef = > " System " ,
2025-10-04 01:57:48 +00:00
ParameterKind ::UserRef = > " User " ,
2025-01-24 01:57:13 +09:00
ParameterKind ::MemberPrivacyTarget = > " MemberPrivacyTarget " ,
2025-10-01 00:51:45 +00:00
ParameterKind ::GroupPrivacyTarget = > " GroupPrivacyTarget " ,
2025-04-04 06:14:17 +09:00
ParameterKind ::SystemPrivacyTarget = > " SystemPrivacyTarget " ,
2025-01-24 01:57:13 +09:00
ParameterKind ::PrivacyLevel = > " PrivacyLevel " ,
ParameterKind ::Toggle = > " Toggle " ,
2025-04-04 03:50:07 +09:00
ParameterKind ::Avatar = > " Avatar " ,
2025-10-03 02:21:12 +00:00
ParameterKind ::MessageRef = > " Message " ,
ParameterKind ::ChannelRef = > " Channel " ,
2025-04-04 05:24:09 +09:00
ParameterKind ::GuildRef = > " Guild " ,
2025-10-04 19:32:58 +00:00
ParameterKind ::ProxySwitchAction = > " ProxySwitchAction " ,
2025-01-24 01:57:13 +09:00
}
}
fn find_parameters ( tokens : & [ Token ] ) -> Vec < & Parameter > {
let mut result = Vec ::new ( ) ;
for token in tokens {
match token {
Token ::Parameter ( param ) = > result . push ( param ) ,
_ = > { }
}
}
result
}