import gleam/http import gleam/http/request import gleam/httpc import gleam/json import gleam/int import gleam/list import gleam/string import gleam/option.{ type Option, None, Some } // Main webhook object pub opaque type Webhook { Webhook(url: String) } // Embed type pub opaque type Embed { Embed( title: Option(String), description: Option(String), color: Option(Int), fields: List(#(String, String, Bool)), ) } // Message type pub opaque type Message { Message(content: List(String), embeds: List(Embed)) } // Initialize an empty embed pub fn embed() -> Embed { Embed(title: None, description: None, color: None, fields: []) } // Add embed title pub fn embed_title(embed: Embed, title: String) -> Embed { Embed(..embed, title: Some(title)) } // Add embed description pub fn embed_description(embed: Embed, description: String) -> Embed { Embed(..embed, description: Some(description)) } // Add embed color pub fn embed_color(embed: Embed, color: Int) -> Embed { Embed(..embed, color: Some(color)) } // Appends a new field to the end of the embed's fields list pub fn embed_field( embed: Embed, name: String, value: String, inline: Bool, ) -> Embed { let new_field = #(name, value, inline) Embed(..embed, fields: list.append(embed.fields, [new_field])) } // Create a message with text content pub fn message(content: String) -> Message { Message(content: [content], embeds: []) } // Create an empty message for building from scratch pub fn empty() -> Message { Message(content: [], embeds: []) } // Add more text to the message pub fn add_message(msg: Message, content: String) -> Message { Message(..msg, content: list.append(msg.content, [content])) } // Add embed to message pub fn add_embed(msg: Message, embed: Embed) -> Message { Message(..msg, embeds: list.append(msg.embeds, [embed])) } // .new() function to declare a webhook pub fn new(url: String) -> Webhook { Webhook(url: url) } // Private helper to convert Embed type into raw JSON format fn embed_to_json(embed: Embed) -> json.Json { let title_field = case embed.title { Some(t) -> [#("title", json.string(t))] None -> [] } let description_field = case embed.description { Some(d) -> [#("description", json.string(d))] None -> [] } let color_field = case embed.color { Some(c) -> [#("color", json.int(c))] None -> [] } let fields_json = case embed.fields { [] -> [] fields -> [ #( "fields", json.array( fields, fn(field) { json.object([ #("name", json.string(field.0)), #("value", json.string(field.1)), #("inline", json.bool(field.2)), ]) }, ), ) ] } let all_fields = list.flatten([title_field, description_field, color_field, fields_json]) json.object(all_fields) } // Private helper to convert Message type into raw JSON text format fn convert_json(message: Message) -> String { let content_field = case message.content { [] -> [] [single] -> [#("content", json.string(single))] multiple -> [#("content", json.string(string.join(multiple, "\n")))] } let embeds_field = case message.embeds { [] -> [] embeds -> [#("embeds", json.array(embeds, embed_to_json))] } json.object(list.flatten([content_field, embeds_field])) |> json.to_string } // .send() function to send the webhook pub fn send(webhook: Webhook, msg: Message) -> Result(Nil, String) { let json_payload = convert_json(msg) case request.to(webhook.url) { Error(_) -> Error("Failed to parse webhook URL.\nHint: Check the webhook url.") Ok(base_req) -> { let req = base_req |> request.set_method(http.Post) |> request.set_header("content-type", "application/json") |> request.set_body(json_payload) case httpc.send(req) { Ok(response) -> case response.status { 204 -> Ok(Nil) _ -> Error( "Expected status 204, got " <> int.to_string(response.status), ) } Error(_) -> Error("Failed to fire HTTP request entirely.") } } } }