Skip to content

Commit

Permalink
v0.0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
hinto-janai committed Jun 24, 2024
1 parent f3ebeb0 commit 32dd897
Show file tree
Hide file tree
Showing 13 changed files with 710 additions and 23 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "moo"
version = "0.0.2"
version = "0.0.3"
edition = "2021"
authors = ["hinto-janai"]
repository = "https://github.com/Cuprate/moo"
Expand All @@ -11,6 +11,7 @@ description = "Matrix bot for Cuprate"

[dependencies]
anyhow = { version = "1.0.86" }
chrono = { version = "0.4.38" }
const_format = { version = "0.2.32" }
dirs = { version = "5.0.1" }
matrix-sdk = { version = "0.7.1", features = ["markdown", "anyhow"] }
Expand Down
29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,15 @@ If you don't want to save a password unencrypted to disk, set this environment v
MOO_PASSWORD="$correct_password" ./moo
```

### 4. Start
### 4. (optional) Add correct `@moo900` GitHub API token to `moo.toml`
If you don't want to save this to disk, set this environment variable (leading with a space):
```bash
# There's a space leading this command so
# it isn't saved in your shell's history file.
MOO_GITHUB_TOKEN="$moo900_github_token" ./moo
```

### 5. Start
```bash
./moo
```
Expand All @@ -61,7 +69,9 @@ sudo systemctl start moo
- Reply to commands (if you're in the allowed list of users)

## Commands
`moo` is currently only used as priority merge queue bot.
`moo` is currently used as:
- Priority merge queue bot
- [Cuprate meeting bot](https://github.com/monero-project/meta/issues)

The below commands read/write PR numbers to the queue.

Expand All @@ -80,6 +90,8 @@ The below commands read/write PR numbers to the queue.
| `!sweep` | Remove any PRs in the queue that can be removed (since they were merged).
| `!sweeper` | Report how long before an automatic `!sweep` occurs.
| `!clear` | Clear the entire queue.
| `!meeting` | Begin/end Cuprate meetings. Issues/logs will be handled automatically after ending.
| `!agenda <ARRAY_OF_STRINGS>` | Re-write the current Cuprate meeting's extra agenda items.
| `!status` | Report `moo` status.
| `!help` | Print all `moo` commands.
| `!shutdown` | Shutdown `moo`.
Expand All @@ -88,6 +100,10 @@ Parameters are delimited by spaces, e.g.:
```
!add 3 123 52 low
```
`!agenda` expects a JSON array containing JSON strings:
```
!agenda ["Greetings", "Updates", "New Agenda Item"];
```

## Config
For configuration, see [`moo.toml`](moo.toml).
Expand All @@ -100,11 +116,4 @@ For configuration, see [`moo.toml`](moo.toml).
| Config | `~/.config/moo/moo.toml`

## Forking
`moo` is hardcoded for Cuprate but it _probably_ works with any account in any room, just edit these [constants](https://github.com/Cuprate/moo/blob/2e2be1abecfac8c75a5a1942dae1f40d880f4756/src/constants.rs):
- [`MOO_MATRIX_ID`](https://github.com/Cuprate/moo/blob/2e2be1abecfac8c75a5a1942dae1f40d880f4756/src/constants.rs#L62-L64) (your bot's username)
- [`CUPRATE_GITHUB_PULL`](https://github.com/Cuprate/moo/blob/2e2be1abecfac8c75a5a1942dae1f40d880f4756/src/constants.rs#L18)
- [`CUPRATE_GITHUB_PULL_API`](https://github.com/Cuprate/moo/blob/2e2be1abecfac8c75a5a1942dae1f40d880f4756/src/constants.rs#L21)
- [`CUPRATE_MATRIX_ROOM_ID`](https://github.com/Cuprate/moo/blob/2e2be1abecfac8c75a5a1942dae1f40d880f4756/src/constants.rs#L53-L55)
- [`ALLOWED_MATRIX_IDS_DEFAULT`](https://github.com/Cuprate/moo/blob/2e2be1abecfac8c75a5a1942dae1f40d880f4756/src/constants.rs#L78-L85)

and remove the `allowed_users` in `moo.toml`.
`moo` is hardcoded for Cuprate but it _probably_ works with any account in any room, just edit some of these [constants](https://github.com/Cuprate/moo/blob/2e2be1abecfac8c75a5a1942dae1f40d880f4756/src/constants.rs), and remove the `allowed_users` in `moo.toml`.
11 changes: 10 additions & 1 deletion moo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


#----------------------------------------------------------#
# API #
# AUTHENTICATION #
#----------------------------------------------------------#
# The correct pass for the `@moo:monero.social` account.
#
Expand All @@ -18,6 +18,15 @@
# TYPE | string
password = ""

# A valid GitHub API token (with all permissions)
# for <https://github.com/moo900>.
#
# Any value within the `MOO_GITHUB_TOKEN` environment
# variable will override this.
#
# TYPE | string
token = ""


#----------------------------------------------------------#
# AUTHORIZATION #
Expand Down
1 change: 1 addition & 0 deletions src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The structure of folders & files.
| `handler.rs` | Matrix room event handler, i.e. when a message is received, what to do?
| `logger.rs` | Logging init.
| `main.rs` | Init and `main()`.
| `meeting.rs` | Cuprate meeting handler.
| `panic.rs` | Custom panic handler.
| `priority.rs` | `Priority` enum.
| `pull_request.rs` | Pull request types.
Expand Down
37 changes: 37 additions & 0 deletions src/command/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//---------------------------------------------------------------------------------------------------- Use
use std::fmt::{Display, Write};

use serde_json::Value;
use strum::{AsRefStr, EnumCount, EnumIs, EnumIter, FromRepr, IntoStaticStr, VariantNames};

use crate::{priority::Priority, pull_request::PullRequest};
Expand Down Expand Up @@ -44,6 +45,10 @@ pub enum Command {
Sweeper,
/// `!clear`
Clear,
/// `!meeting`
Meeting,
/// `!agenda`
Agenda(Vec<String>),
/// `!status`
Status,
/// `!help`
Expand All @@ -69,6 +74,7 @@ impl Display for Command {
Self::Sweep => f.write_str("sweep"),
Self::Sweeper => f.write_str("sweeper"),
Self::Clear => f.write_str("clear"),
Self::Meeting => f.write_str("meeting"),
Self::Status => f.write_str("status"),
Self::Help => f.write_str("help"),
Self::Shutdown => f.write_str("shutdown"),
Expand All @@ -93,6 +99,32 @@ impl Display for Command {
}
Ok(())
}

// Agenda
Self::Agenda(items) => {
f.write_str("agenda")?;

if items.is_empty() {
return Ok(());
}

f.write_str(" ")?;

let items = items
.iter()
.map(|item| Value::String(item.to_string()))
.collect::<Vec<Value>>();

let array = Value::Array(items);

let Ok(json) = serde_json::to_string(&array) else {
return Err(std::fmt::Error);
};

f.write_str(&json)?;

Ok(())
}
}
}
}
Expand Down Expand Up @@ -120,6 +152,11 @@ mod test {
(Command::Remove(vec![45, 2]), "!remove 45 2"),
(Command::Sweep, "!sweep"),
(Command::Sweeper, "!sweeper"),
(Command::Meeting, "!meeting"),
(
Command::Agenda(vec!["hello".into(), "world".into()]),
r#"!agenda ["hello","world"]"#,
),
(Command::Clear, "!clear"),
(Command::Status, "!status"),
(Command::Help, "!help"),
Expand Down
51 changes: 47 additions & 4 deletions src/command/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//---------------------------------------------------------------------------------------------------- Use
use std::{
sync::Arc,
sync::{atomic::Ordering, Arc},
time::{SystemTime, UNIX_EPOCH},
};

Expand All @@ -12,9 +12,12 @@ use tracing::{info, instrument, trace};

use crate::{
command::Command,
constants::{CONFIG, CUPRATE_GITHUB_PULL, HELP, INIT_INSTANT, MOO, TXT_EMPTY},
constants::{
CONFIG, CUPRATE_GITHUB_PULL, HELP, INIT_INSTANT, MOO, TXT_EMPTY, TXT_MEETING_START_IDENT,
},
database::Database,
github::pr_is_open,
meeting::{MEETING_DATABASE, MEETING_ONGOING},
priority::Priority,
pull_request::{PullRequest, PullRequestMetadata},
};
Expand Down Expand Up @@ -58,6 +61,8 @@ impl Command {
Self::Sweep => Self::handle_sweep(db).await,
Self::Sweeper => Self::handle_sweeper(db).await,
Self::Clear => Self::handle_clear(db).await,
Self::Meeting => Self::handle_meeting().await,
Self::Agenda(items) => Self::handle_agenda(items).await,
Self::Status => Self::handle_status(),
Self::Help => Self::handle_help(),
Self::Shutdown => Self::handle_shutdown(db).await,
Expand Down Expand Up @@ -306,13 +311,51 @@ impl Command {
RoomMessageEventContent::text_plain(msg)
}

/// TODO
#[instrument]
async fn handle_meeting() -> RoomMessageEventContent {
let msg = if MEETING_ONGOING.load(Ordering::Acquire) {
let mut logs = String::new();
let mut db = MEETING_DATABASE.lock().await;
std::mem::swap(&mut logs, &mut db);

MEETING_ONGOING.store(false, Ordering::Release);

match crate::github::finish_cuprate_meeting(logs).await {
Ok((logs, next_meeting)) => {
format!("- Logs: {logs}\n - Next meeting: {next_meeting}")
}
Err(e) => e.to_string(),
}
} else {
let mut db = MEETING_DATABASE.lock().await;
*db = String::from("## Meeting logs");
MEETING_ONGOING.store(true, Ordering::Release);
TXT_MEETING_START_IDENT.to_string()
};

trace!(msg);
RoomMessageEventContent::text_markdown(msg)
}

/// TODO
async fn handle_agenda(items: Vec<String>) -> RoomMessageEventContent {
let msg = match crate::github::edit_cuprate_meeting_agenda(items).await {
Ok(url) => format!("Updated: {url}"),
Err(e) => e.to_string(),
};
trace!(msg);
RoomMessageEventContent::text_plain(msg)
}

/// TODO
#[instrument]
fn handle_status() -> RoomMessageEventContent {
let elapsed = INIT_INSTANT.elapsed().as_secs_f32();

let uptime = UptimeFull::from(elapsed).to_string();
let msg = format!("{MOO}, uptime: {uptime}");
let meeting = MEETING_ONGOING.load(Ordering::Acquire);

let msg = format!("{MOO}, meeting: {meeting}, uptime: {uptime}");

trace!(msg);
RoomMessageEventContent::text_markdown(msg)
Expand Down
75 changes: 75 additions & 0 deletions src/command/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//---------------------------------------------------------------------------------------------------- Use
use std::str::FromStr;

use serde_json::Value;
use strum::VariantNames;
use tracing::debug;

Expand Down Expand Up @@ -92,6 +93,47 @@ impl Command {

Ok(Self::Remove(vec))
}

/// TODO
#[inline]
fn from_str_agenda<'a, I: Iterator<Item = &'a str>>(
iter: I,
) -> Result<Self, CommandParseError> {
let mut json = String::new();
for item in iter {
json += " ";
json += item;
}

if json.is_empty() {
return Err(CommandParseError::MissingParameter);
}

let Ok(items) = serde_json::from_str::<Vec<Value>>(&json) else {
return Err(CommandParseError::IncorrectParameter);
};

let mut vec = vec![];

for item in items {
let Value::String(item) = item else {
return Err(CommandParseError::IncorrectParameter);
};

vec.push(item);
}

if vec.is_empty() {
return Err(CommandParseError::MissingParameter);
}

// Error on duplicate parameters.
if slice_contains_duplicates(&vec) {
return Err(CommandParseError::DuplicateParameter);
}

Ok(Self::Agenda(vec))
}
}

impl FromStr for Command {
Expand Down Expand Up @@ -120,6 +162,8 @@ impl FromStr for Command {
"!sweep" => Self::Sweep,
"!sweeper" => Self::Sweeper,
"!clear" => Self::Clear,
"!meeting" => Self::Meeting,
"!agenda" => Self::from_str_agenda(iter)?,
"!status" => Self::Status,
"!help" => Self::Help,
"!shutdown" => Self::Shutdown,
Expand Down Expand Up @@ -254,4 +298,35 @@ mod test {
fn parse_remove_dup_param() {
Command::from_str("!remove 2 2").unwrap();
}

/// Test `FromStr` for `Command::Agenda`.
#[test]
fn parse_agenda() {
let command = Command::from_str(r#"!agenda ["hello", "world"]"#).unwrap();
let expected = Command::Agenda(vec!["hello".into(), "world".into()]);
assert_eq!(command, expected);

let command = Command::from_str(r#"!agenda ["item 1", "item 2 3"]"#).unwrap();
let expected = Command::Agenda(vec!["item 1".into(), "item 2 3".into()]);
assert_eq!(command, expected);

let command = Command::from_str(r#"!agenda ["item 1"]"#).unwrap();
let expected = Command::Agenda(vec!["item 1".into()]);
assert_eq!(command, expected);

let command = Command::from_str("!agenda");
let expected = Err(CommandParseError::MissingParameter);
assert_eq!(command, expected);

let command = Command::from_str("!agenda []");
let expected = Err(CommandParseError::MissingParameter);
assert_eq!(command, expected);
}

/// Test `FromStr` for `Command::Agenda` fails with duplicate parameters.
#[test]
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: DuplicateParameter")]
fn parse_agenda_dup_param() {
Command::from_str(r#"!agenda ["a", "a"]"#).unwrap();
}
}
Loading

0 comments on commit 32dd897

Please sign in to comment.