@@ -51,3 +51,33 @@ pub fn def_gitea_release() -> (discord::ApplicationCommand, Option<String>) { | |||
}, Some("616329232389505055".to_string())) | |||
} | |||
// gitea-issue | |||
pub fn def_gitea_issue() -> (discord::ApplicationCommand, Option<String>) { | |||
(discord::ApplicationCommand { | |||
id: None, | |||
application_id: None, | |||
name: "gitea-issue".to_string(), | |||
description: "Display an issue".to_string(), | |||
options: Some(vec![ | |||
discord::ApplicationCommandOption::String { | |||
name: "username".to_string(), | |||
description: "Gitea username".to_string(), | |||
required: true, | |||
choices: None | |||
}, | |||
discord::ApplicationCommandOption::String { | |||
name: "repo".to_string(), | |||
description: "Gitea repository".to_string(), | |||
required: true, | |||
choices: None | |||
}, | |||
discord::ApplicationCommandOption::Integer { | |||
name: "issue".to_string(), | |||
description: "Gitea issue number".to_string(), | |||
required: true, | |||
choices: None | |||
}, | |||
]), | |||
}, Some("616329232389505055".to_string())) | |||
} | |||
@@ -139,10 +139,28 @@ pub struct ApplicationCommandInteractionData { | |||
#[derive(Serialize, Deserialize, Clone)] | |||
pub struct ApplicationCommandInteractionDataOption { | |||
pub name: String, | |||
pub value: Option<String>, // FIXME this could be bool, integer, or sub-command as well | |||
pub value: Option<CommandValue>, // FIXME this could be bool, integer, or sub-command as well | |||
pub options: Option<Vec<ApplicationCommandInteractionDataOption>>, | |||
} | |||
#[derive(Serialize, Deserialize, Clone)] | |||
#[serde(untagged)] | |||
pub enum CommandValue { | |||
BoolVal(bool), | |||
IntVal(usize), | |||
StrVal(String), | |||
} | |||
impl ToString for CommandValue { | |||
fn to_string(&self) -> String { | |||
match self { | |||
Self::StrVal(v) => v.to_string(), | |||
Self::BoolVal(v) => v.to_string(), | |||
Self::IntVal(v) => v.to_string(), | |||
} | |||
} | |||
} | |||
pub enum InteractionResponse { | |||
//#[serde(rename = "1")] | |||
Pong {}, | |||
@@ -19,6 +19,21 @@ pub fn get_releases(owner: &str, repo: &str) -> Result<Vec<Release>, String> { | |||
Err("Invalid Result".to_string()) | |||
} | |||
pub fn get_issue_by_index(owner: &str, repo: &str, index: usize) -> Result<Issue, String> { | |||
let client = blocking::Client::new(); | |||
let url = format!("{}/repos/{}/{}/issues/{}", GITEA_API_URL, owner, repo, index); | |||
let result = client.get(&url).send(); | |||
if let Ok(resp) = result { | |||
let result = resp.json::<Issue>(); | |||
if let Ok(data) = result { | |||
return Ok(data); | |||
} else { | |||
return Err(format!("Invalid JSON payload {}", result.err().unwrap())); | |||
} | |||
} | |||
Err("Invalid Result".to_string()) | |||
} | |||
// API structures | |||
#[derive(Serialize, Deserialize, Clone)] | |||
@@ -62,4 +77,45 @@ pub struct Asset { | |||
pub created_at: String, | |||
pub uuid: String, | |||
pub browser_download_url: String, | |||
} | |||
} | |||
#[derive(Serialize, Deserialize, Clone)] | |||
pub struct Issue { | |||
pub id: usize, | |||
pub url: String, | |||
pub html_url: String, | |||
pub number: usize, | |||
pub user: Author, | |||
pub original_author: String, | |||
pub original_author_id: usize, | |||
pub title: String, | |||
pub body: String, | |||
pub labels: Vec<IssueTag>, | |||
//pub milestone: Option<???>, | |||
//pub assignee: Option<???>, | |||
//pub assignees: Option<???>, | |||
pub state: String, | |||
pub comments: usize, | |||
pub created_at: String, | |||
pub updated_at: String, | |||
pub closed_at: Option<String>, | |||
pub due_date: Option<String>, | |||
//pub pull_request: Option<???>, | |||
pub repository: IssueRespository, | |||
} | |||
#[derive(Serialize, Deserialize, Clone)] | |||
pub struct IssueTag { | |||
pub id: usize, | |||
pub name: String, | |||
pub color: String, | |||
pub description: String, | |||
pub url: Option<String>, | |||
} | |||
#[derive(Serialize, Deserialize, Clone)] | |||
pub struct IssueRespository { | |||
pub id: usize, | |||
pub name: String, | |||
pub full_name: String, | |||
} |
@@ -1,5 +1,5 @@ | |||
use crate::gitea::get_releases; | |||
use crate::discord::{Interaction, InteractionResponse, InteractionApplicationCommandCallbackData, Embed, EmbedFooter, EmbedAuthor, EmbedField}; | |||
use crate::gitea::{get_releases, get_issue_by_index}; | |||
use crate::discord::{Interaction, InteractionResponse, InteractionApplicationCommandCallbackData, Embed, EmbedFooter, EmbedAuthor, EmbedField, CommandValue}; | |||
pub fn gitea_release(interaction: &Interaction) -> InteractionResponse { | |||
let cmd = interaction.cmd().unwrap(); | |||
@@ -7,8 +7,8 @@ pub fn gitea_release(interaction: &Interaction) -> InteractionResponse { | |||
let mut repo_name = String::new(); | |||
for opt in &cmd.data.options.unwrap() { | |||
match &opt.name as &str { | |||
"username" => username = opt.value.clone().unwrap(), | |||
"repo" => repo_name = opt.value.clone().unwrap(), | |||
"username" => username = opt.value.clone().unwrap().to_string(), | |||
"repo" => repo_name = opt.value.clone().unwrap().to_string(), | |||
_ => {} | |||
} | |||
} | |||
@@ -89,3 +89,87 @@ pub fn gitea_release(interaction: &Interaction) -> InteractionResponse { | |||
} | |||
} | |||
} | |||
pub fn gitea_issue(interaction: &Interaction) -> InteractionResponse { | |||
let cmd = interaction.cmd().unwrap(); | |||
// these should always be populated, but Rust doesn't know that | |||
let mut username = String::new(); | |||
let mut repo_name = String::new(); | |||
let mut index = 0; | |||
for opt in &cmd.data.options.unwrap() { | |||
match &opt.name as &str { | |||
"username" => { | |||
if let CommandValue::StrVal(v) = opt.value.clone().unwrap() { | |||
username = v; | |||
} | |||
}, | |||
"repo" => { | |||
if let CommandValue::StrVal(v) = opt.value.clone().unwrap() { | |||
repo_name = v; | |||
} | |||
}, | |||
"issue" => { | |||
if let CommandValue::IntVal(v) = opt.value.clone().unwrap() { | |||
index = v; | |||
} | |||
} | |||
_ => {} | |||
} | |||
} | |||
let res = get_issue_by_index(&username, &repo_name, index); | |||
if let Ok(resp) = res { | |||
// limit description to 2000 characters | |||
let mut desc = resp.body.clone(); | |||
if desc.len() > 2000 { | |||
desc = desc[..2000].to_string() + "..."; | |||
} | |||
let embed = Embed { | |||
title: Some(format!("{} #{}", &repo_name, index)), | |||
type_: None, | |||
description: Some(desc), | |||
url: None, | |||
timestamp: None, | |||
color: Some(0x00C800), // Colour::from_rgb(0, 200, 0) | |||
footer: Some(EmbedFooter { | |||
text: resp.user.login.clone(), | |||
icon_url: Some(resp.user.avatar_url.clone()), | |||
proxy_icon_url: None | |||
}), | |||
image: None, | |||
thumbnail: None, | |||
video: None, | |||
provider: None, | |||
author: Some(EmbedAuthor { | |||
name: Some(resp.title.clone()), | |||
url: Some(format!("https://git.exmods.org/{}/{}/issues/{}", &username, &repo_name, index)), | |||
icon_url: None, | |||
proxy_icon_url: None | |||
}), | |||
fields: None, | |||
/*fields: Some(vec![ | |||
EmbedField { | |||
name: "Download".to_string(), | |||
value: asset_str, | |||
inline: Some(true) | |||
} | |||
])*/ | |||
}; | |||
return InteractionResponse::ChannelMessageWithSource { | |||
data: Some(InteractionApplicationCommandCallbackData { | |||
tts: false, | |||
content: "".to_string(), | |||
embeds: Some(vec![embed]), | |||
allowed_mentions: None | |||
}) | |||
} | |||
} else { | |||
return InteractionResponse::ChannelMessageWithSource { | |||
data: Some(InteractionApplicationCommandCallbackData { | |||
tts: false, | |||
content: format!("Gitea API error: `{}`", res.err().unwrap()), | |||
embeds: None, | |||
allowed_mentions: None | |||
}) | |||
} | |||
} | |||
} |
@@ -13,8 +13,8 @@ use rocket_contrib::json::Json; | |||
use std::sync::RwLock; | |||
use crate::auth_tools::{AuthenticatedInteraction}; | |||
use crate::discord::{Interaction, InteractionResponse, InteractionResponseRaw, InteractionApplicationCommandCallbackData}; | |||
use crate::command_definitions::{hello_world, def_hello_world, def_gitea_release}; | |||
use crate::gitea_command::gitea_release; | |||
use crate::command_definitions::{hello_world, def_hello_world, def_gitea_release, def_gitea_issue}; | |||
use crate::gitea_command::{gitea_release, gitea_issue}; | |||
static GLOBAL_COMMAND_KEY: &str = "GLOBAL command KEY"; | |||
@@ -33,6 +33,7 @@ fn root_post(interaction: AuthenticatedInteraction) -> Json<InteractionResponseR | |||
match &data.name as &str { | |||
"hello-world" => hello_world(&interaction.interaction), | |||
"gitea-release" => gitea_release(&interaction.interaction), | |||
"gitea-issue" => gitea_issue(&interaction.interaction), | |||
_ => InteractionResponse::ChannelMessageWithSource { | |||
data: Some(InteractionApplicationCommandCallbackData { | |||
tts: false, | |||
@@ -77,6 +78,7 @@ fn main() { | |||
// TODO add more commands | |||
register_command(&def_hello_world, &req_client); | |||
register_command(&def_gitea_release, &req_client); | |||
register_command(&def_gitea_issue, &req_client); | |||
// start web server | |||
rocket::ignite().mount("/", routes![root_post, hello_get]).launch(); | |||
} | |||