A Discord API Library for Gleam! 💫

auto moderation revamp

+549 -701
+549
src/grom/guild/auto_moderation.gleam
··· 1 + import gleam/dynamic/decode 2 + import gleam/http 3 + import gleam/http/request 4 + import gleam/json.{type Json} 5 + import gleam/list 6 + import gleam/option.{type Option, None, Some} 7 + import gleam/result 8 + import gleam/time/duration.{type Duration} 9 + import grom/client.{type Client} 10 + import grom/error.{type Error} 11 + import grom/internal/rest 12 + import grom/internal/time_duration 13 + 14 + // TYPES ----------------------------------------------------------------------- 15 + 16 + pub type Rule { 17 + Rule( 18 + id: String, 19 + guild_id: String, 20 + name: String, 21 + creator_id: String, 22 + trigger: Trigger, 23 + actions: List(Action), 24 + is_enabled: Bool, 25 + exempt_role_ids: List(String), 26 + exempt_channel_ids: List(String), 27 + ) 28 + } 29 + 30 + pub type Trigger { 31 + KeywordTrigger( 32 + keyword_filter: List(String), 33 + regex_patterns: List(String), 34 + allow_list: List(String), 35 + ) 36 + MemberProfileTrigger( 37 + keyword_filter: List(String), 38 + regex_patterns: List(String), 39 + allow_list: List(String), 40 + ) 41 + KeywordPresetTrigger(presets: List(KeywordPreset), allow_list: List(String)) 42 + MentionSpamTrigger( 43 + total_mention_limit: Int, 44 + is_mention_raid_protection_enabled: Bool, 45 + ) 46 + SpamTrigger 47 + } 48 + 49 + pub type KeywordPreset { 50 + ProfanityPreset 51 + SexualContentPreset 52 + SlursPreset 53 + } 54 + 55 + pub type Action { 56 + BlockMessage(custom_message: Option(String)) 57 + SendAlertMessage(channel_id: String) 58 + TimeoutMember(duration: Duration) 59 + BlockMemberInteraction 60 + } 61 + 62 + pub opaque type CreateRule { 63 + CreateRule( 64 + name: String, 65 + trigger: Trigger, 66 + actions: List(Action), 67 + is_enabled: Bool, 68 + exempt_role_ids: Option(List(String)), 69 + exempt_channel_ids: Option(List(String)), 70 + ) 71 + } 72 + 73 + pub opaque type ModifyRule { 74 + ModifyRule( 75 + name: Option(String), 76 + /// You can only modify the inner data, you _can't_ change the trigger. 77 + trigger: Option(Trigger), 78 + actions: Option(List(Action)), 79 + is_enabled: Option(Bool), 80 + exempt_role_ids: Option(List(String)), 81 + exempt_channel_ids: Option(List(String)), 82 + ) 83 + } 84 + 85 + // DECODERS -------------------------------------------------------------------- 86 + 87 + @internal 88 + pub fn rule_decoder() -> decode.Decoder(Rule) { 89 + use id <- decode.field("id", decode.string) 90 + use guild_id <- decode.field("guild_id", decode.string) 91 + use name <- decode.field("name", decode.string) 92 + use creator_id <- decode.field("creator_id", decode.string) 93 + use trigger_type <- decode.field("trigger_type", decode.int) 94 + use trigger <- decode.field("trigger_metadata", trigger_decoder(trigger_type)) 95 + use actions <- decode.field("actions", decode.list(action_decoder())) 96 + use is_enabled <- decode.field("enabled", decode.bool) 97 + use exempt_role_ids <- decode.field( 98 + "exempt_roles", 99 + decode.list(decode.string), 100 + ) 101 + use exempt_channel_ids <- decode.field( 102 + "exempt_channels", 103 + decode.list(decode.string), 104 + ) 105 + 106 + decode.success(Rule( 107 + id:, 108 + guild_id:, 109 + name:, 110 + creator_id:, 111 + trigger:, 112 + actions:, 113 + is_enabled:, 114 + exempt_role_ids:, 115 + exempt_channel_ids:, 116 + )) 117 + } 118 + 119 + pub fn trigger_decoder(trigger_type: Int) -> decode.Decoder(Trigger) { 120 + case trigger_type { 121 + 1 | 6 -> { 122 + use keyword_filter <- decode.field( 123 + "keyword_filter", 124 + decode.list(decode.string), 125 + ) 126 + use regex_patterns <- decode.field( 127 + "regex_patterns", 128 + decode.list(decode.string), 129 + ) 130 + use allow_list <- decode.field("allow_list", decode.list(decode.string)) 131 + 132 + case trigger_type { 133 + 1 -> 134 + decode.success(KeywordTrigger( 135 + keyword_filter:, 136 + regex_patterns:, 137 + allow_list:, 138 + )) 139 + 6 -> 140 + decode.success(MemberProfileTrigger( 141 + keyword_filter:, 142 + regex_patterns:, 143 + allow_list:, 144 + )) 145 + _ -> decode.failure(KeywordTrigger([], [], []), "Trigger") 146 + } 147 + } 148 + 3 -> decode.success(SpamTrigger) 149 + 4 -> { 150 + use presets <- decode.field( 151 + "presets", 152 + decode.list(keyword_preset_decoder()), 153 + ) 154 + use allow_list <- decode.field("allow_list", decode.list(decode.string)) 155 + 156 + decode.success(KeywordPresetTrigger(presets:, allow_list:)) 157 + } 158 + 5 -> { 159 + use total_mention_limit <- decode.field("mention_total_limit", decode.int) 160 + use is_mention_raid_protection_enabled <- decode.field( 161 + "mention_raid_protection_enabled", 162 + decode.bool, 163 + ) 164 + 165 + decode.success(MentionSpamTrigger( 166 + total_mention_limit:, 167 + is_mention_raid_protection_enabled:, 168 + )) 169 + } 170 + _ -> decode.failure(SpamTrigger, "Trigger") 171 + } 172 + } 173 + 174 + @internal 175 + pub fn keyword_preset_decoder() -> decode.Decoder(KeywordPreset) { 176 + use variant <- decode.then(decode.int) 177 + case variant { 178 + 1 -> decode.success(ProfanityPreset) 179 + 2 -> decode.success(SexualContentPreset) 180 + 3 -> decode.success(SlursPreset) 181 + _ -> decode.failure(ProfanityPreset, "KeywordPreset") 182 + } 183 + } 184 + 185 + @internal 186 + pub fn action_decoder() -> decode.Decoder(Action) { 187 + use type_ <- decode.field("type", decode.int) 188 + use action <- decode.field("metadata", case type_ { 189 + 1 -> { 190 + use custom_message <- decode.optional_field( 191 + "custom_message", 192 + None, 193 + decode.optional(decode.string), 194 + ) 195 + decode.success(BlockMessage(custom_message:)) 196 + } 197 + 2 -> { 198 + use channel_id <- decode.field("channel_id", decode.string) 199 + decode.success(SendAlertMessage(channel_id:)) 200 + } 201 + 3 -> { 202 + use duration <- decode.field( 203 + "duration_seconds", 204 + time_duration.from_int_seconds_decoder(), 205 + ) 206 + decode.success(TimeoutMember(duration:)) 207 + } 208 + 4 -> decode.success(BlockMemberInteraction) 209 + _ -> decode.failure(BlockMessage(None), "Action") 210 + }) 211 + 212 + decode.success(action) 213 + } 214 + 215 + // ENCODERS -------------------------------------------------------------------- 216 + 217 + @internal 218 + pub fn create_rule_to_json(create_rule: CreateRule) -> Json { 219 + let name = [#("name", json.string(create_rule.name))] 220 + 221 + let event_type = [ 222 + #("event_type", case create_rule.trigger { 223 + MemberProfileTrigger(..) -> json.int(2) 224 + _ -> json.int(1) 225 + }), 226 + ] 227 + 228 + let trigger_type = [ 229 + #("trigger_type", case create_rule.trigger { 230 + KeywordTrigger(..) -> json.int(1) 231 + SpamTrigger -> json.int(3) 232 + KeywordPresetTrigger(..) -> json.int(4) 233 + MentionSpamTrigger(..) -> json.int(5) 234 + MemberProfileTrigger(..) -> json.int(6) 235 + }), 236 + ] 237 + 238 + let trigger = case create_rule.trigger { 239 + SpamTrigger -> [] 240 + _ -> [#("trigger_metadata", trigger_to_json(create_rule.trigger))] 241 + } 242 + 243 + let is_enabled = [#("enabled", json.bool(create_rule.is_enabled))] 244 + 245 + let exempt_role_ids = case create_rule.exempt_role_ids { 246 + Some(ids) -> [#("exempt_roles", json.array(ids, json.string))] 247 + None -> [] 248 + } 249 + 250 + let exempt_channel_ids = case create_rule.exempt_channel_ids { 251 + Some(ids) -> [#("exempt_channels", json.array(ids, json.string))] 252 + None -> [] 253 + } 254 + 255 + [ 256 + name, 257 + event_type, 258 + trigger_type, 259 + trigger, 260 + is_enabled, 261 + exempt_role_ids, 262 + exempt_channel_ids, 263 + ] 264 + |> list.flatten 265 + |> json.object 266 + } 267 + 268 + @internal 269 + pub fn trigger_to_json(trigger: Trigger) -> Json { 270 + case trigger { 271 + KeywordTrigger(..) -> [ 272 + #("keyword_filter", json.array(trigger.keyword_filter, json.string)), 273 + #("regex_patterns", json.array(trigger.regex_patterns, json.string)), 274 + #("allow_list", json.array(trigger.allow_list, json.string)), 275 + ] 276 + SpamTrigger -> [] 277 + KeywordPresetTrigger(..) -> [ 278 + #("presets", json.array(trigger.presets, keyword_preset_to_json)), 279 + #("allow_list", json.array(trigger.allow_list, json.string)), 280 + ] 281 + MentionSpamTrigger(..) -> [ 282 + #("mention_total_limit", json.int(trigger.total_mention_limit)), 283 + #( 284 + "mention_raid_protection_enabled", 285 + json.bool(trigger.is_mention_raid_protection_enabled), 286 + ), 287 + ] 288 + MemberProfileTrigger(..) -> [ 289 + #("keyword_filter", json.array(trigger.keyword_filter, json.string)), 290 + #("regex_patterns", json.array(trigger.regex_patterns, json.string)), 291 + #("allow_list", json.array(trigger.allow_list, json.string)), 292 + ] 293 + } 294 + |> json.object 295 + } 296 + 297 + @internal 298 + pub fn keyword_preset_to_json(keyword_preset: KeywordPreset) -> Json { 299 + case keyword_preset { 300 + ProfanityPreset -> 1 301 + SexualContentPreset -> 2 302 + SlursPreset -> 3 303 + } 304 + |> json.int 305 + } 306 + 307 + @internal 308 + pub fn action_to_json(action: Action) -> Json { 309 + [ 310 + [ 311 + #("type", case action { 312 + BlockMessage(..) -> json.int(1) 313 + SendAlertMessage(..) -> json.int(2) 314 + TimeoutMember(..) -> json.int(3) 315 + BlockMemberInteraction -> json.int(4) 316 + }), 317 + ], 318 + case action { 319 + BlockMemberInteraction -> [] 320 + BlockMessage(..) -> [ 321 + #("metadata", { 322 + let custom_message = case action.custom_message { 323 + Some(message) -> [#("custom_message", json.string(message))] 324 + None -> [] 325 + } 326 + 327 + [custom_message] 328 + |> list.flatten 329 + |> json.object 330 + }), 331 + ] 332 + TimeoutMember(..) -> [ 333 + #( 334 + "metadata", 335 + json.object([ 336 + #( 337 + "duration_seconds", 338 + time_duration.to_int_seconds_encode(action.duration), 339 + ), 340 + ]), 341 + ), 342 + ] 343 + SendAlertMessage(..) -> [ 344 + #( 345 + "metadata", 346 + json.object([#("channel_id", json.string(action.channel_id))]), 347 + ), 348 + ] 349 + }, 350 + ] 351 + |> list.flatten 352 + |> json.object 353 + } 354 + 355 + @internal 356 + pub fn modify_rule_to_json(modify_rule: ModifyRule) -> Json { 357 + let name = case modify_rule.name { 358 + Some(name) -> [#("name", json.string(name))] 359 + None -> [] 360 + } 361 + 362 + let trigger = case modify_rule.trigger { 363 + Some(SpamTrigger) | None -> [] 364 + Some(trigger) -> [#("trigger_metadata", trigger_to_json(trigger))] 365 + } 366 + 367 + let actions = case modify_rule.actions { 368 + Some(actions) -> [#("actions", json.array(actions, action_to_json))] 369 + None -> [] 370 + } 371 + 372 + let is_enabled = case modify_rule.is_enabled { 373 + Some(enabled) -> [#("enabled", json.bool(enabled))] 374 + None -> [] 375 + } 376 + 377 + let exempt_role_ids = case modify_rule.exempt_role_ids { 378 + Some(ids) -> [#("exempt_roles", json.array(ids, json.string))] 379 + None -> [] 380 + } 381 + 382 + let exempt_channel_ids = case modify_rule.exempt_channel_ids { 383 + Some(ids) -> [#("exempt_channels", json.array(ids, json.string))] 384 + None -> [] 385 + } 386 + 387 + [name, trigger, actions, is_enabled, exempt_role_ids, exempt_channel_ids] 388 + |> list.flatten 389 + |> json.object 390 + } 391 + 392 + // PUBLIC API FUNCTIONS -------------------------------------------------------- 393 + 394 + pub fn get_rule( 395 + client: Client, 396 + from guild_id: String, 397 + id rule_id: String, 398 + ) -> Result(Rule, Error) { 399 + use response <- result.try( 400 + client 401 + |> rest.new_request( 402 + http.Get, 403 + "/guilds/" <> guild_id <> "/auto-moderation/rules/" <> rule_id, 404 + ) 405 + |> rest.execute, 406 + ) 407 + 408 + response.body 409 + |> json.parse(using: rule_decoder()) 410 + |> result.map_error(error.CouldNotDecode) 411 + } 412 + 413 + pub fn create_rule( 414 + client: Client, 415 + in guild_id: String, 416 + with create_rule: CreateRule, 417 + because reason: Option(String), 418 + ) -> Result(Rule, Error) { 419 + let json = create_rule |> create_rule_to_json 420 + 421 + use response <- result.try( 422 + client 423 + |> rest.new_request( 424 + http.Post, 425 + "/guilds/" <> guild_id <> "/auto-moderation/rules", 426 + ) 427 + |> request.set_body(json |> json.to_string) 428 + |> rest.with_reason(reason) 429 + |> rest.execute, 430 + ) 431 + 432 + response.body 433 + |> json.parse(using: rule_decoder()) 434 + |> result.map_error(error.CouldNotDecode) 435 + } 436 + 437 + pub fn new_create_rule( 438 + named name: String, 439 + on trigger: Trigger, 440 + do actions: List(Action), 441 + enable is_enabled: Bool, 442 + ) -> CreateRule { 443 + CreateRule( 444 + name:, 445 + trigger:, 446 + actions:, 447 + is_enabled:, 448 + exempt_role_ids: None, 449 + exempt_channel_ids: None, 450 + ) 451 + } 452 + 453 + pub fn create_rule_with_exempt_roles( 454 + create_rule: CreateRule, 455 + ids exempt_role_ids: List(String), 456 + ) -> CreateRule { 457 + CreateRule(..create_rule, exempt_role_ids: Some(exempt_role_ids)) 458 + } 459 + 460 + pub fn create_rule_with_exempt_channels( 461 + create_rule: CreateRule, 462 + ids exempt_channel_ids: List(String), 463 + ) -> CreateRule { 464 + CreateRule(..create_rule, exempt_channel_ids: Some(exempt_channel_ids)) 465 + } 466 + 467 + pub fn modify_rule( 468 + client: Client, 469 + in guild_id: String, 470 + id rule_id: String, 471 + with modify_rule: ModifyRule, 472 + because reason: Option(String), 473 + ) -> Result(Rule, Error) { 474 + let json = modify_rule |> modify_rule_to_json 475 + 476 + use response <- result.try( 477 + client 478 + |> rest.new_request( 479 + http.Patch, 480 + "/guilds/" <> guild_id <> "/auto-moderation/rules/" <> rule_id, 481 + ) 482 + |> request.set_body(json |> json.to_string) 483 + |> rest.with_reason(reason) 484 + |> rest.execute, 485 + ) 486 + 487 + response.body 488 + |> json.parse(using: rule_decoder()) 489 + |> result.map_error(error.CouldNotDecode) 490 + } 491 + 492 + pub fn modify_rule_name(modify_rule: ModifyRule, new name: String) -> ModifyRule { 493 + ModifyRule(..modify_rule, name: Some(name)) 494 + } 495 + 496 + pub fn modify_rule_trigger( 497 + modify_rule: ModifyRule, 498 + new trigger: Trigger, 499 + ) -> ModifyRule { 500 + ModifyRule(..modify_rule, trigger: Some(trigger)) 501 + } 502 + 503 + pub fn modify_rule_actions( 504 + modify_rule: ModifyRule, 505 + new actions: List(Action), 506 + ) -> ModifyRule { 507 + ModifyRule(..modify_rule, actions: Some(actions)) 508 + } 509 + 510 + pub fn enable_rule(modify_rule: ModifyRule) -> ModifyRule { 511 + ModifyRule(..modify_rule, is_enabled: Some(True)) 512 + } 513 + 514 + pub fn disable_rule(modify_rule: ModifyRule) -> ModifyRule { 515 + ModifyRule(..modify_rule, is_enabled: Some(False)) 516 + } 517 + 518 + pub fn exempt_roles_from_rule( 519 + modify_rule: ModifyRule, 520 + ids exempt_role_ids: List(String), 521 + ) -> ModifyRule { 522 + ModifyRule(..modify_rule, exempt_role_ids: Some(exempt_role_ids)) 523 + } 524 + 525 + pub fn exempt_channels_from_rule( 526 + modify_rule: ModifyRule, 527 + ids exempt_channel_ids: List(String), 528 + ) -> ModifyRule { 529 + ModifyRule(..modify_rule, exempt_channel_ids: Some(exempt_channel_ids)) 530 + } 531 + 532 + pub fn delete_rule( 533 + client: Client, 534 + from guild_id: String, 535 + id rule_id: String, 536 + because reason: Option(String), 537 + ) -> Result(Nil, Error) { 538 + use _response <- result.try( 539 + client 540 + |> rest.new_request( 541 + http.Delete, 542 + "/guilds/" <> guild_id <> "/auto-moderation/rules/" <> rule_id, 543 + ) 544 + |> rest.with_reason(reason) 545 + |> rest.execute, 546 + ) 547 + 548 + Ok(Nil) 549 + }
-124
src/grom/guild/auto_moderation/action.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/json.{type Json} 3 - import gleam/list 4 - import gleam/option.{type Option, None, Some} 5 - import gleam/time/duration.{type Duration} 6 - import grom/internal/time_duration 7 - 8 - // TYPES ----------------------------------------------------------------------- 9 - 10 - pub type Action { 11 - Action(type_: Type, metadata: Option(Metadata)) 12 - } 13 - 14 - pub type Type { 15 - BlockMessage 16 - SendAlertMessage 17 - Timeout 18 - BlockMemberInteraction 19 - } 20 - 21 - pub type Metadata { 22 - SendAlertMessageMetadata(channel_id: String) 23 - TimeoutMetadata(duration: Duration) 24 - BlockMessageMetadata(custom_message: Option(String)) 25 - } 26 - 27 - // TYPES ----------------------------------------------------------------------- 28 - 29 - @internal 30 - pub fn decoder() -> decode.Decoder(Action) { 31 - use type_ <- decode.field("type", type_decoder()) 32 - use metadata <- decode.field( 33 - "metadata", 34 - decode.optional(metadata_decoder(type_)), 35 - ) 36 - decode.success(Action(type_:, metadata:)) 37 - } 38 - 39 - @internal 40 - pub fn type_decoder() -> decode.Decoder(Type) { 41 - use variant <- decode.then(decode.int) 42 - case variant { 43 - 1 -> decode.success(BlockMessage) 44 - 2 -> decode.success(SendAlertMessage) 45 - 3 -> decode.success(Timeout) 46 - 4 -> decode.success(BlockMemberInteraction) 47 - _ -> decode.failure(BlockMessage, "Type") 48 - } 49 - } 50 - 51 - @internal 52 - pub fn metadata_decoder(type_: Type) -> decode.Decoder(Metadata) { 53 - case type_ { 54 - SendAlertMessage -> send_alert_message_metadata_decoder() 55 - Timeout -> timeout_metadata_decoder() 56 - BlockMessage -> block_message_metadata_decoder() 57 - _ -> decode.failure(SendAlertMessageMetadata(""), "Metadata") 58 - } 59 - } 60 - 61 - fn send_alert_message_metadata_decoder() -> decode.Decoder(Metadata) { 62 - use channel_id <- decode.field("channel_id", decode.string) 63 - decode.success(SendAlertMessageMetadata(channel_id:)) 64 - } 65 - 66 - fn timeout_metadata_decoder() -> decode.Decoder(Metadata) { 67 - use duration <- decode.field( 68 - "duration_seconds", 69 - time_duration.from_int_seconds_decoder(), 70 - ) 71 - decode.success(TimeoutMetadata(duration:)) 72 - } 73 - 74 - fn block_message_metadata_decoder() -> decode.Decoder(Metadata) { 75 - use custom_message <- decode.optional_field( 76 - "custom_message", 77 - None, 78 - decode.optional(decode.string), 79 - ) 80 - decode.success(BlockMessageMetadata(custom_message:)) 81 - } 82 - 83 - // ENCODERS -------------------------------------------------------------------- 84 - 85 - @internal 86 - pub fn encode(action: Action) -> Json { 87 - let metadata = case action.metadata { 88 - Some(metadata) -> [#("metadata", metadata_encode(metadata))] 89 - None -> [] 90 - } 91 - 92 - [[#("type", type_encode(action.type_))], metadata] 93 - |> list.flatten 94 - |> json.object 95 - } 96 - 97 - @internal 98 - pub fn type_encode(type_: Type) -> Json { 99 - case type_ { 100 - BlockMessage -> 1 101 - SendAlertMessage -> 2 102 - Timeout -> 3 103 - BlockMemberInteraction -> 4 104 - } 105 - |> json.int 106 - } 107 - 108 - @internal 109 - pub fn metadata_encode(metadata: Metadata) -> Json { 110 - case metadata { 111 - SendAlertMessageMetadata(channel_id) -> [ 112 - #("channel_id", json.string(channel_id)), 113 - ] 114 - TimeoutMetadata(duration) -> [ 115 - #("duration_seconds", json.int(time_duration.to_int_seconds(duration))), 116 - ] 117 - BlockMessageMetadata(custom_message) -> 118 - case custom_message { 119 - Some(msg) -> [#("custom_message", json.string(msg))] 120 - None -> [] 121 - } 122 - } 123 - |> json.object 124 - }
-29
src/grom/guild/auto_moderation/event.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/json.{type Json} 3 - 4 - // TYPES ----------------------------------------------------------------------- 5 - 6 - pub type Type { 7 - MessageSend 8 - MemberUpdate 9 - } 10 - 11 - // DECODERS -------------------------------------------------------------------- 12 - 13 - @internal 14 - pub fn type_decoder() -> decode.Decoder(Type) { 15 - use variant <- decode.then(decode.int) 16 - case variant { 17 - 1 -> decode.success(MessageSend) 18 - 2 -> decode.success(MemberUpdate) 19 - _ -> decode.failure(MessageSend, "Type") 20 - } 21 - } 22 - 23 - @internal 24 - pub fn type_encode(type_: Type) -> Json { 25 - case type_ { 26 - MessageSend -> json.int(1) 27 - MemberUpdate -> json.int(2) 28 - } 29 - }
-35
src/grom/guild/auto_moderation/keyword_preset.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/json.{type Json} 3 - 4 - // TYPES ----------------------------------------------------------------------- 5 - 6 - pub type Type { 7 - Profanity 8 - SexualContent 9 - Slurs 10 - } 11 - 12 - // DECODERS -------------------------------------------------------------------- 13 - 14 - @internal 15 - pub fn type_decoder() -> decode.Decoder(Type) { 16 - use variant <- decode.then(decode.int) 17 - case variant { 18 - 1 -> decode.success(Profanity) 19 - 2 -> decode.success(SexualContent) 20 - 3 -> decode.success(Slurs) 21 - _ -> decode.failure(Profanity, "Type") 22 - } 23 - } 24 - 25 - // ENCODERS -------------------------------------------------------------------- 26 - 27 - @internal 28 - pub fn type_encode(type_: Type) -> Json { 29 - case type_ { 30 - Profanity -> 1 31 - SexualContent -> 2 32 - Slurs -> 3 33 - } 34 - |> json.int 35 - }
-350
src/grom/guild/auto_moderation/rule.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/http 3 - import gleam/http/request 4 - import gleam/json.{type Json} 5 - import gleam/list 6 - import gleam/option.{type Option, None, Some} 7 - import gleam/result 8 - import grom/client.{type Client} 9 - import grom/error 10 - import grom/guild/auto_moderation/action.{type Action} 11 - import grom/guild/auto_moderation/event 12 - import grom/guild/auto_moderation/trigger 13 - import grom/internal/rest 14 - 15 - // TYPES ----------------------------------------------------------------------- 16 - 17 - pub type Rule { 18 - Rule( 19 - id: String, 20 - guild_id: String, 21 - name: String, 22 - creator_id: String, 23 - event_type: event.Type, 24 - trigger_type: trigger.Type, 25 - trigger_metadata: trigger.Metadata, 26 - actions: List(Action), 27 - is_enabled: Bool, 28 - exempt_role_ids: List(String), 29 - exempt_channel_ids: List(String), 30 - ) 31 - } 32 - 33 - /// Disabled by default. Enable with `create_with_is_enabled()`. 34 - pub opaque type Create { 35 - Create( 36 - name: String, 37 - event_type: event.Type, 38 - trigger_type: trigger.Type, 39 - trigger_metadata: Option(trigger.Metadata), 40 - actions: List(Action), 41 - is_enabled: Option(Bool), 42 - exempt_role_ids: Option(List(String)), 43 - exempt_channel_ids: Option(List(String)), 44 - ) 45 - } 46 - 47 - pub opaque type Modify { 48 - Modify( 49 - name: Option(String), 50 - event_type: Option(event.Type), 51 - trigger_metadata: Option(trigger.Metadata), 52 - actions: Option(List(Action)), 53 - is_enabled: Option(Bool), 54 - exempt_role_ids: Option(List(String)), 55 - exempt_channel_ids: Option(List(String)), 56 - ) 57 - } 58 - 59 - // DECODERS -------------------------------------------------------------------- 60 - 61 - @internal 62 - pub fn decoder() -> decode.Decoder(Rule) { 63 - use id <- decode.field("id", decode.string) 64 - use guild_id <- decode.field("guild_id", decode.string) 65 - use name <- decode.field("name", decode.string) 66 - use creator_id <- decode.field("creator_id", decode.string) 67 - use event_type <- decode.field("event_type", event.type_decoder()) 68 - use trigger_type <- decode.field("trigger_type", trigger.type_decoder()) 69 - use trigger_metadata <- decode.field( 70 - "trigger_metadata", 71 - trigger.metadata_decoder(trigger_type), 72 - ) 73 - use actions <- decode.field("actions", decode.list(action.decoder())) 74 - use is_enabled <- decode.field("enabled", decode.bool) 75 - use exempt_role_ids <- decode.field( 76 - "exempt_roles", 77 - decode.list(decode.string), 78 - ) 79 - use exempt_channel_ids <- decode.field( 80 - "exempt_channels", 81 - decode.list(decode.string), 82 - ) 83 - decode.success(Rule( 84 - id:, 85 - guild_id:, 86 - name:, 87 - creator_id:, 88 - event_type:, 89 - trigger_type:, 90 - trigger_metadata:, 91 - actions:, 92 - is_enabled:, 93 - exempt_role_ids:, 94 - exempt_channel_ids:, 95 - )) 96 - } 97 - 98 - // ENCODERS -------------------------------------------------------------------- 99 - 100 - @internal 101 - pub fn create_encode(create: Create) -> Json { 102 - let trigger_metadata = case create.trigger_metadata { 103 - Some(metadata) -> [#("trigger_metadata", trigger.metadata_encode(metadata))] 104 - None -> [] 105 - } 106 - let is_enabled = case create.is_enabled { 107 - Some(enabled) -> [#("enabled", json.bool(enabled))] 108 - None -> [] 109 - } 110 - let exempt_role_ids = case create.exempt_role_ids { 111 - Some(ids) -> [#("exempt_roles", json.array(ids, json.string))] 112 - None -> [] 113 - } 114 - let exempt_channel_ids = case create.exempt_channel_ids { 115 - Some(ids) -> [#("exempt_channels", json.array(ids, json.string))] 116 - None -> [] 117 - } 118 - 119 - [ 120 - [ 121 - #("name", json.string(create.name)), 122 - #("event_type", event.type_encode(create.event_type)), 123 - #("trigger_type", trigger.type_encode(create.trigger_type)), 124 - #("actions", json.array(create.actions, action.encode)), 125 - ], 126 - trigger_metadata, 127 - is_enabled, 128 - exempt_role_ids, 129 - exempt_channel_ids, 130 - ] 131 - |> list.flatten 132 - |> json.object 133 - } 134 - 135 - @internal 136 - pub fn modify_encode(modify: Modify) -> Json { 137 - let name = case modify.name { 138 - Some(name) -> [#("name", json.string(name))] 139 - None -> [] 140 - } 141 - let event_type = case modify.event_type { 142 - Some(type_) -> [#("event_type", event.type_encode(type_))] 143 - None -> [] 144 - } 145 - let trigger_metadata = case modify.trigger_metadata { 146 - Some(metadata) -> [#("trigger_metadata", trigger.metadata_encode(metadata))] 147 - None -> [] 148 - } 149 - let actions = case modify.actions { 150 - Some(actions) -> [#("actions", json.array(actions, action.encode))] 151 - None -> [] 152 - } 153 - let is_enabled = case modify.is_enabled { 154 - Some(enabled) -> [#("enabled", json.bool(enabled))] 155 - None -> [] 156 - } 157 - let exempt_role_ids = case modify.exempt_role_ids { 158 - Some(ids) -> [#("exempt_roles", json.array(ids, json.string))] 159 - None -> [] 160 - } 161 - let exempt_channel_ids = case modify.exempt_channel_ids { 162 - Some(ids) -> [#("exempt_channels", json.array(ids, json.string))] 163 - None -> [] 164 - } 165 - 166 - [ 167 - name, 168 - event_type, 169 - trigger_metadata, 170 - actions, 171 - is_enabled, 172 - exempt_role_ids, 173 - exempt_channel_ids, 174 - ] 175 - |> list.flatten 176 - |> json.object 177 - } 178 - 179 - // PUBLIC API FUNCTIONS ------------------------------------------------------- 180 - 181 - pub fn get( 182 - client: Client, 183 - in guild_id: String, 184 - id rule_id: String, 185 - ) -> Result(Rule, error.Error) { 186 - use response <- result.try( 187 - client 188 - |> rest.new_request( 189 - http.Get, 190 - "/guilds/" <> guild_id <> "/auto-moderation/rules/" <> rule_id, 191 - ) 192 - |> rest.execute, 193 - ) 194 - 195 - response.body 196 - |> json.parse(using: decoder()) 197 - |> result.map_error(error.CouldNotDecode) 198 - } 199 - 200 - pub fn create( 201 - client: Client, 202 - in guild_id: String, 203 - with create: Create, 204 - reason reason: Option(String), 205 - ) -> Result(Rule, error.Error) { 206 - let json = create |> create_encode 207 - 208 - use response <- result.try( 209 - client 210 - |> rest.new_request( 211 - http.Post, 212 - "/guilds/" <> guild_id <> "/auto-moderation/rules", 213 - ) 214 - |> request.set_body(json |> json.to_string) 215 - |> rest.with_reason(reason) 216 - |> rest.execute, 217 - ) 218 - 219 - response.body 220 - |> json.parse(using: decoder()) 221 - |> result.map_error(error.CouldNotDecode) 222 - } 223 - 224 - pub fn new_create( 225 - name: String, 226 - event_type: event.Type, 227 - trigger_type: trigger.Type, 228 - actions: List(Action), 229 - ) -> Create { 230 - Create( 231 - name:, 232 - event_type:, 233 - trigger_type:, 234 - actions:, 235 - trigger_metadata: None, 236 - is_enabled: None, 237 - exempt_role_ids: None, 238 - exempt_channel_ids: None, 239 - ) 240 - } 241 - 242 - pub fn create_with_trigger_metadata( 243 - create: Create, 244 - metadata: trigger.Metadata, 245 - ) -> Create { 246 - Create(..create, trigger_metadata: Some(metadata)) 247 - } 248 - 249 - pub fn create_with_is_enabled(create: Create, is_enabled: Bool) -> Create { 250 - Create(..create, is_enabled: Some(is_enabled)) 251 - } 252 - 253 - pub fn create_with_exempt_role_ids( 254 - create: Create, 255 - exempt_role_ids: List(String), 256 - ) -> Create { 257 - Create(..create, exempt_role_ids: Some(exempt_role_ids)) 258 - } 259 - 260 - pub fn create_with_exempt_channel_ids( 261 - create: Create, 262 - exempt_channel_ids: List(String), 263 - ) -> Create { 264 - Create(..create, exempt_channel_ids: Some(exempt_channel_ids)) 265 - } 266 - 267 - pub fn modify( 268 - client: Client, 269 - in guild_id: String, 270 - id rule_id: String, 271 - with modify: Modify, 272 - reason reason: Option(String), 273 - ) { 274 - let json = modify |> modify_encode 275 - 276 - use response <- result.try( 277 - client 278 - |> rest.new_request( 279 - http.Patch, 280 - "/guilds/" <> guild_id <> "/auto-moderation/rules/" <> rule_id, 281 - ) 282 - |> rest.with_reason(reason) 283 - |> request.set_body(json |> json.to_string) 284 - |> rest.execute, 285 - ) 286 - 287 - response.body 288 - |> json.parse(using: decoder()) 289 - |> result.map_error(error.CouldNotDecode) 290 - } 291 - 292 - pub fn new_modify() -> Modify { 293 - Modify(None, None, None, None, None, None, None) 294 - } 295 - 296 - pub fn modify_name(modify: Modify, name: String) -> Modify { 297 - Modify(..modify, name: Some(name)) 298 - } 299 - 300 - pub fn modify_event_type(modify: Modify, event_type: event.Type) -> Modify { 301 - Modify(..modify, event_type: Some(event_type)) 302 - } 303 - 304 - pub fn modify_trigger_metadata( 305 - modify: Modify, 306 - trigger_metadata: trigger.Metadata, 307 - ) -> Modify { 308 - Modify(..modify, trigger_metadata: Some(trigger_metadata)) 309 - } 310 - 311 - pub fn modify_actions(modify: Modify, actions: List(Action)) -> Modify { 312 - Modify(..modify, actions: Some(actions)) 313 - } 314 - 315 - pub fn modify_is_enabled(modify: Modify, is_enabled: Bool) -> Modify { 316 - Modify(..modify, is_enabled: Some(is_enabled)) 317 - } 318 - 319 - pub fn modify_exempt_role_ids( 320 - modify: Modify, 321 - exempt_role_ids: List(String), 322 - ) -> Modify { 323 - Modify(..modify, exempt_role_ids: Some(exempt_role_ids)) 324 - } 325 - 326 - pub fn modify_exempt_channel_ids( 327 - modify: Modify, 328 - exempt_channel_ids: List(String), 329 - ) -> Modify { 330 - Modify(..modify, exempt_channel_ids: Some(exempt_channel_ids)) 331 - } 332 - 333 - pub fn delete( 334 - client: Client, 335 - in guild_id: String, 336 - id rule_id: String, 337 - reason reason: Option(String), 338 - ) -> Result(Nil, error.Error) { 339 - use _response <- result.try( 340 - client 341 - |> rest.new_request( 342 - http.Delete, 343 - "/guilds/" <> guild_id <> "/auto-moderation/rules/" <> rule_id, 344 - ) 345 - |> rest.with_reason(reason) 346 - |> rest.execute, 347 - ) 348 - 349 - Ok(Nil) 350 - }
-163
src/grom/guild/auto_moderation/trigger.gleam
··· 1 - import gleam/dynamic/decode 2 - import gleam/int 3 - import gleam/json.{type Json} 4 - import grom/guild/auto_moderation/keyword_preset 5 - 6 - // TYPES ----------------------------------------------------------------------- 7 - 8 - pub type Type { 9 - Keyword 10 - Spam 11 - KeywordPreset 12 - MentionSpam 13 - MemberProfile 14 - } 15 - 16 - pub type Metadata { 17 - KeywordMetadata( 18 - keyword_filter: List(String), 19 - regex_patterns: List(String), 20 - allow_list: List(String), 21 - ) 22 - MemberProfileMetadata( 23 - keyword_filter: List(String), 24 - regex_patterns: List(String), 25 - allow_list: List(String), 26 - ) 27 - KeywordPresetMetadata( 28 - presets: List(keyword_preset.Type), 29 - allow_list: List(String), 30 - ) 31 - MentionSpamMetadata( 32 - mention_total_limit: Int, 33 - is_mention_raid_protection_enabled: Bool, 34 - ) 35 - } 36 - 37 - // DECODERS -------------------------------------------------------------------- 38 - 39 - @internal 40 - pub fn type_decoder() -> decode.Decoder(Type) { 41 - use variant <- decode.then(decode.int) 42 - case variant { 43 - 1 -> decode.success(Keyword) 44 - 3 -> decode.success(Spam) 45 - 4 -> decode.success(KeywordPreset) 46 - 5 -> decode.success(MentionSpam) 47 - 6 -> decode.success(MemberProfile) 48 - _ -> decode.failure(Keyword, "Type") 49 - } 50 - } 51 - 52 - @internal 53 - pub fn type_string_decoder() -> decode.Decoder(Type) { 54 - use variant <- decode.then(decode.string) 55 - case int.parse(variant) { 56 - Ok(_) -> type_decoder() 57 - Error(_) -> decode.failure(Keyword, "Type") 58 - } 59 - } 60 - 61 - @internal 62 - pub fn metadata_decoder(type_: Type) -> decode.Decoder(Metadata) { 63 - case type_ { 64 - Keyword -> keyword_metadata_decoder() 65 - MemberProfile -> member_profile_metadata_decoder() 66 - KeywordPreset -> keyword_preset_metadata_decoder() 67 - MentionSpam -> mention_spam_metadata_decoder() 68 - Spam -> decode.failure(KeywordPresetMetadata([], []), "Metadata") 69 - } 70 - } 71 - 72 - fn keyword_metadata_decoder() -> decode.Decoder(Metadata) { 73 - use keyword_filter <- decode.field( 74 - "keyword_filter", 75 - decode.list(decode.string), 76 - ) 77 - use regex_patterns <- decode.field( 78 - "regex_patterns", 79 - decode.list(decode.string), 80 - ) 81 - use allow_list <- decode.field("allow_list", decode.list(decode.string)) 82 - decode.success(KeywordMetadata(keyword_filter:, regex_patterns:, allow_list:)) 83 - } 84 - 85 - fn member_profile_metadata_decoder() -> decode.Decoder(Metadata) { 86 - use keyword_filter <- decode.field( 87 - "keyword_filter", 88 - decode.list(decode.string), 89 - ) 90 - use regex_patterns <- decode.field( 91 - "regex_patterns", 92 - decode.list(decode.string), 93 - ) 94 - use allow_list <- decode.field("allow_list", decode.list(decode.string)) 95 - decode.success(MemberProfileMetadata( 96 - keyword_filter:, 97 - regex_patterns:, 98 - allow_list:, 99 - )) 100 - } 101 - 102 - fn keyword_preset_metadata_decoder() -> decode.Decoder(Metadata) { 103 - use presets <- decode.field( 104 - "presets", 105 - decode.list(keyword_preset.type_decoder()), 106 - ) 107 - use allow_list <- decode.field("allow_list", decode.list(decode.string)) 108 - decode.success(KeywordPresetMetadata(presets:, allow_list:)) 109 - } 110 - 111 - fn mention_spam_metadata_decoder() -> decode.Decoder(Metadata) { 112 - use mention_total_limit <- decode.field("mention_total_limit", decode.int) 113 - use is_mention_raid_protection_enabled <- decode.field( 114 - "mention_raid_protection_enabled", 115 - decode.bool, 116 - ) 117 - decode.success(MentionSpamMetadata( 118 - mention_total_limit:, 119 - is_mention_raid_protection_enabled:, 120 - )) 121 - } 122 - 123 - // ENCODERS -------------------------------------------------------------------- 124 - 125 - @internal 126 - pub fn type_encode(type_: Type) -> Json { 127 - case type_ { 128 - Keyword -> 1 129 - Spam -> 3 130 - KeywordPreset -> 4 131 - MentionSpam -> 5 132 - MemberProfile -> 6 133 - } 134 - |> json.int 135 - } 136 - 137 - @internal 138 - pub fn metadata_encode(metadata: Metadata) -> Json { 139 - case metadata { 140 - KeywordMetadata(keyword_filter, regex_patterns, allow_list) -> [ 141 - #("keyword_filter", json.array(keyword_filter, json.string)), 142 - #("regex_patterns", json.array(regex_patterns, json.string)), 143 - #("allow_list", json.array(allow_list, json.string)), 144 - ] 145 - MemberProfileMetadata(keyword_filter, regex_patterns, allow_list) -> [ 146 - #("keyword_filter", json.array(keyword_filter, json.string)), 147 - #("regex_patterns", json.array(regex_patterns, json.string)), 148 - #("allow_list", json.array(allow_list, json.string)), 149 - ] 150 - KeywordPresetMetadata(presets, allow_list) -> [ 151 - #("presets", json.array(presets, keyword_preset.type_encode)), 152 - #("allow_list", json.array(allow_list, json.string)), 153 - ] 154 - MentionSpamMetadata(mention_total_limit, is_mention_raid_protection_enabled) -> [ 155 - #("mention_total_limit", json.int(mention_total_limit)), 156 - #( 157 - "mention_raid_protection_enabled", 158 - json.bool(is_mention_raid_protection_enabled), 159 - ), 160 - ] 161 - } 162 - |> json.object 163 - }