Update to 0.2.0
parent
ca4580790b
commit
6aa2f12e2c
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "issue-bot"
|
name = "issue-bot"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -11,12 +11,15 @@ rusqlite = { version="0.20.0", features=["uuid",]}
|
||||||
uuid = "*"
|
uuid = "*"
|
||||||
time = "*"
|
time = "*"
|
||||||
serde_json = "1.0.40"
|
serde_json = "1.0.40"
|
||||||
serde= { version = "1.0.101", features = ["derive"]}
|
serde = { version = "1.0.101", features = ["derive"]}
|
||||||
reqwest = "0.9.20"
|
reqwest = "0.9.20"
|
||||||
toml = "0.5.3"
|
toml = "0.5.3"
|
||||||
|
log = "0.4.11"
|
||||||
|
simplelog = "^0.8.0"
|
||||||
|
error-chain = "0.12.4"
|
||||||
|
|
||||||
[dependencies.melib]
|
[dependencies.melib]
|
||||||
git = "https://git.meli.delivery/meli/meli"
|
git = "https://git.meli.delivery/meli/meli"
|
||||||
version = "0.3.2"
|
version = "0.6.1"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = []
|
features = []
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
tag = "repo-issues"
|
||||||
|
auth_token= "Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
local_part= "issues"
|
||||||
|
domain= "example.tld"
|
||||||
|
base_url = "https://git.example.tld"
|
||||||
|
repo = "username/repo"
|
||||||
|
bot_name = "IssueBot"
|
||||||
|
bot_username = "username"
|
||||||
|
mailer = "cat"
|
||||||
|
log_file = "issue-bot.log"
|
42
src/api.rs
42
src/api.rs
|
@ -1,3 +1,19 @@
|
||||||
|
/* This file is part of issue-bot.
|
||||||
|
*
|
||||||
|
* issue-bot is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* issue-bot is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with issue-bot. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
static ISSUES_BASE_URL: &'static str = "{base_url}/api/v1/repos/{repo}/issues";
|
static ISSUES_BASE_URL: &'static str = "{base_url}/api/v1/repos/{repo}/issues";
|
||||||
|
@ -20,7 +36,7 @@ pub fn new_issue(
|
||||||
body: String,
|
body: String,
|
||||||
anonymous: bool,
|
anonymous: bool,
|
||||||
submitter: Address,
|
submitter: Address,
|
||||||
conf: &Config,
|
conf: &Configuration,
|
||||||
) -> Result<(Password, i64)> {
|
) -> Result<(Password, i64)> {
|
||||||
let issue = CreateIssueOption {
|
let issue = CreateIssueOption {
|
||||||
title,
|
title,
|
||||||
|
@ -88,7 +104,7 @@ pub fn new_reply(
|
||||||
body: String,
|
body: String,
|
||||||
password: Password,
|
password: Password,
|
||||||
submitter: Address,
|
submitter: Address,
|
||||||
conf: &Config,
|
conf: &Configuration,
|
||||||
) -> Result<(String, i64, bool)> {
|
) -> Result<(String, i64, bool)> {
|
||||||
let mut stmt =
|
let mut stmt =
|
||||||
conn.prepare("SELECT id, title, subscribed, anonymous FROM issue WHERE password = ?")?;
|
conn.prepare("SELECT id, title, subscribed, anonymous FROM issue WHERE password = ?")?;
|
||||||
|
@ -99,7 +115,7 @@ pub fn new_reply(
|
||||||
.map(|r| r.unwrap())
|
.map(|r| r.unwrap())
|
||||||
.collect::<Vec<(i64, String, bool, bool)>>();
|
.collect::<Vec<(i64, String, bool, bool)>>();
|
||||||
if results.is_empty() {
|
if results.is_empty() {
|
||||||
return Err(IssueError::new("Not found".to_string()));
|
return Err(Error::new("Not found".to_string()));
|
||||||
}
|
}
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
|
@ -133,7 +149,7 @@ pub fn new_reply(
|
||||||
submitter.to_string(),
|
submitter.to_string(),
|
||||||
body
|
body
|
||||||
);
|
);
|
||||||
Err(IssueError::new(
|
Err(Error::new(
|
||||||
"You can not reply to this issue due to an internal error.",
|
"You can not reply to this issue due to an internal error.",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -144,7 +160,11 @@ struct EditIssueOption {
|
||||||
state: String,
|
state: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(conn: &Connection, password: Password, conf: &Config) -> Result<(String, i64, bool)> {
|
pub fn close(
|
||||||
|
conn: &Connection,
|
||||||
|
password: Password,
|
||||||
|
conf: &Configuration,
|
||||||
|
) -> Result<(String, i64, bool)> {
|
||||||
let mut stmt = conn.prepare("SELECT id, title, subscribed FROM issue WHERE password = ?")?;
|
let mut stmt = conn.prepare("SELECT id, title, subscribed FROM issue WHERE password = ?")?;
|
||||||
let mut results = stmt
|
let mut results = stmt
|
||||||
.query_map(&[password.as_bytes().to_vec()], |row| {
|
.query_map(&[password.as_bytes().to_vec()], |row| {
|
||||||
|
@ -153,7 +173,7 @@ pub fn close(conn: &Connection, password: Password, conf: &Config) -> Result<(St
|
||||||
.map(|r| r.unwrap())
|
.map(|r| r.unwrap())
|
||||||
.collect::<Vec<(i64, String, bool)>>();
|
.collect::<Vec<(i64, String, bool)>>();
|
||||||
if results.is_empty() {
|
if results.is_empty() {
|
||||||
return Err(IssueError::new("Not found".to_string()));
|
return Err(Error::new("Not found".to_string()));
|
||||||
}
|
}
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let res = client
|
let res = client
|
||||||
|
@ -177,7 +197,7 @@ pub fn close(conn: &Connection, password: Password, conf: &Config) -> Result<(St
|
||||||
Ok((title, issue_id, is_subscribed))
|
Ok((title, issue_id, is_subscribed))
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Issue could not be closed: {:#?}", map);
|
eprintln!("Issue could not be closed: {:#?}", map);
|
||||||
Err(IssueError::new(
|
Err(Error::new(
|
||||||
"Issue cannot be closed due to an internal error.",
|
"Issue cannot be closed due to an internal error.",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -196,16 +216,16 @@ pub fn change_subscription(
|
||||||
.map(|r| r.unwrap())
|
.map(|r| r.unwrap())
|
||||||
.collect::<Vec<(i64, String, bool)>>();
|
.collect::<Vec<(i64, String, bool)>>();
|
||||||
if results.is_empty() {
|
if results.is_empty() {
|
||||||
return Err(IssueError::new("Issue not found".to_string()));
|
return Err(Error::new("Issue not found".to_string()));
|
||||||
}
|
}
|
||||||
let (issue_id, title, is_subscribed) = results.remove(0);
|
let (issue_id, title, is_subscribed) = results.remove(0);
|
||||||
if !is_subscribed && !new_val {
|
if !is_subscribed && !new_val {
|
||||||
return Err(IssueError::new(format!(
|
return Err(Error::new(format!(
|
||||||
"You are not subscribed to issue `{}`",
|
"You are not subscribed to issue `{}`",
|
||||||
&title
|
&title
|
||||||
)));
|
)));
|
||||||
} else if is_subscribed && new_val {
|
} else if is_subscribed && new_val {
|
||||||
return Err(IssueError::new(format!(
|
return Err(Error::new(format!(
|
||||||
"You are already subscribed to issue `{}`",
|
"You are already subscribed to issue `{}`",
|
||||||
&title
|
&title
|
||||||
)));
|
)));
|
||||||
|
@ -226,7 +246,7 @@ pub fn change_subscription(
|
||||||
pub fn comments(
|
pub fn comments(
|
||||||
id: i64,
|
id: i64,
|
||||||
since: &str,
|
since: &str,
|
||||||
conf: &Config,
|
conf: &Configuration,
|
||||||
) -> Vec<serde_json::map::Map<String, serde_json::Value>> {
|
) -> Vec<serde_json::map::Map<String, serde_json::Value>> {
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let result = client
|
let result = client
|
||||||
|
|
20
src/conf.rs
20
src/conf.rs
|
@ -1,7 +1,23 @@
|
||||||
|
/* This file is part of issue-bot.
|
||||||
|
*
|
||||||
|
* issue-bot is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* issue-bot is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with issue-bot. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct Config {
|
pub struct Configuration {
|
||||||
/** eg. meli-issues becomes [meli-issues] **/
|
/** eg. meli-issues becomes [meli-issues] **/
|
||||||
pub tag: String,
|
pub tag: String,
|
||||||
/** your bot's authentication token from Gitea's Swagger **/
|
/** your bot's authentication token from Gitea's Swagger **/
|
||||||
|
@ -20,4 +36,6 @@ pub struct Config {
|
||||||
pub bot_username: String,
|
pub bot_username: String,
|
||||||
/** the command to pipe an email to **/
|
/** the command to pipe an email to **/
|
||||||
pub mailer: String,
|
pub mailer: String,
|
||||||
|
/** file to write logs **/
|
||||||
|
pub log_file: String,
|
||||||
}
|
}
|
||||||
|
|
59
src/cron.rs
59
src/cron.rs
|
@ -1,7 +1,24 @@
|
||||||
use super::*;
|
/* This file is part of issue-bot.
|
||||||
|
*
|
||||||
|
* issue-bot is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* issue-bot is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with issue-bot. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
pub fn check(conn: Connection, conf: Config) {
|
use super::*;
|
||||||
let mut stmt = conn.prepare("SELECT * FROM issue").unwrap();
|
use melib::email::address::Address;
|
||||||
|
|
||||||
|
pub fn check(conn: Connection, conf: Configuration) -> Result<()> {
|
||||||
|
let mut stmt = conn.prepare("SELECT * FROM issue")?;
|
||||||
let mut results = stmt
|
let mut results = stmt
|
||||||
.query_map(NO_PARAMS, |row| {
|
.query_map(NO_PARAMS, |row| {
|
||||||
let submitter: String = row.get(1)?;
|
let submitter: String = row.get(1)?;
|
||||||
|
@ -9,7 +26,7 @@ pub fn check(conn: Connection, conf: Config) {
|
||||||
let last_update: Option<String> = row.get(7)?;
|
let last_update: Option<String> = row.get(7)?;
|
||||||
Ok(Issue {
|
Ok(Issue {
|
||||||
id: row.get(0)?,
|
id: row.get(0)?,
|
||||||
submitter: new_address(submitter.as_str()),
|
submitter: Address::new(None, submitter.as_str().to_string()),
|
||||||
password: Password::from_slice(password.as_slice()).unwrap(),
|
password: Password::from_slice(password.as_slice()).unwrap(),
|
||||||
time_created: row.get(3)?,
|
time_created: row.get(3)?,
|
||||||
anonymous: row.get(4)?,
|
anonymous: row.get(4)?,
|
||||||
|
@ -17,10 +34,8 @@ pub fn check(conn: Connection, conf: Config) {
|
||||||
title: row.get(6)?,
|
title: row.get(6)?,
|
||||||
last_update: last_update.unwrap_or(String::new()),
|
last_update: last_update.unwrap_or(String::new()),
|
||||||
})
|
})
|
||||||
})
|
})?
|
||||||
.unwrap()
|
.collect::<std::result::Result<Vec<Issue>, _>>()?;
|
||||||
.map(|r| r.unwrap())
|
|
||||||
.collect::<Vec<Issue>>();
|
|
||||||
for issue in &mut results {
|
for issue in &mut results {
|
||||||
let mut update = false;
|
let mut update = false;
|
||||||
let mut comments = api::comments(issue.id, &issue.last_update, &conf);
|
let mut comments = api::comments(issue.id, &issue.last_update, &conf);
|
||||||
|
@ -38,12 +53,10 @@ pub fn check(conn: Connection, conf: Config) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if update {
|
if update {
|
||||||
let mut stmt = conn
|
let mut stmt =
|
||||||
.prepare("UPDATE issue SET last_update = (:last_update) WHERE id = (:id)")
|
conn.prepare("UPDATE issue SET last_update = (:last_update) WHERE id = (:id)")?;
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
stmt.execute_named(&[(":last_update", &new_value), (":id", &issue.id),])
|
stmt.execute_named(&[(":last_update", &new_value), (":id", &issue.id),])?,
|
||||||
.unwrap(),
|
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
if issue.subscribed {
|
if issue.subscribed {
|
||||||
|
@ -63,16 +76,19 @@ pub fn check(conn: Connection, conf: Config) {
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
let mut notice = melib::Draft::default();
|
let mut notice = melib::Draft::default();
|
||||||
notice.headers_mut().insert(
|
notice.headers_mut().insert(
|
||||||
"From".to_string(),
|
HeaderName::new_unchecked("From"),
|
||||||
new_address(&format!(
|
Address::new(
|
||||||
"{local_part}@{domain}",
|
None,
|
||||||
local_part = &conf.local_part,
|
format!(
|
||||||
domain = &conf.domain
|
"{local_part}@{domain}",
|
||||||
))
|
local_part = &conf.local_part,
|
||||||
|
domain = &conf.domain
|
||||||
|
),
|
||||||
|
)
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
notice.headers_mut().insert(
|
notice.headers_mut().insert(
|
||||||
"Subject".to_string(),
|
HeaderName::new_unchecked("Subject"),
|
||||||
format!(
|
format!(
|
||||||
"[{tag}] new replies in issue `{title}`",
|
"[{tag}] new replies in issue `{title}`",
|
||||||
tag = &conf.tag,
|
tag = &conf.tag,
|
||||||
|
@ -82,11 +98,12 @@ pub fn check(conn: Connection, conf: Config) {
|
||||||
);
|
);
|
||||||
notice
|
notice
|
||||||
.headers_mut()
|
.headers_mut()
|
||||||
.insert("To".to_string(), issue.submitter.to_string());
|
.insert(HeaderName::new_unchecked("To"), issue.submitter.to_string());
|
||||||
|
|
||||||
notice.set_body(templates::reply_update(&issue, &conf, comments));
|
notice.set_body(templates::reply_update(&issue, &conf, comments));
|
||||||
send_mail(notice, &conf);
|
send_mail(notice, &conf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
114
src/error.rs
114
src/error.rs
|
@ -1,92 +1,32 @@
|
||||||
use std::borrow::Cow;
|
/* This file is part of issue-bot.
|
||||||
use std::error::Error;
|
*
|
||||||
use std::fmt;
|
* issue-bot is free software: you can redistribute it and/or modify
|
||||||
use std::io;
|
* it under the terms of the GNU General Public License as published by
|
||||||
use std::result;
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
use std::str;
|
* (at your option) any later version.
|
||||||
use std::string;
|
*
|
||||||
|
* issue-bot is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with issue-bot. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, IssueError>;
|
error_chain! {
|
||||||
|
foreign_links {
|
||||||
#[derive(Debug, Clone)]
|
Io(std::io::Error);
|
||||||
pub struct IssueError {
|
Reqwest(reqwest::Error);
|
||||||
details: String,
|
Database(rusqlite::Error);
|
||||||
|
Unicode(std::str::Utf8Error);
|
||||||
|
UnicodeS(std::string::FromUtf8Error);
|
||||||
|
Email(melib::error::MeliError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IssueError {
|
impl Error {
|
||||||
pub fn new<M>(msg: M) -> IssueError
|
pub fn new<S: Into<String>>(msg: S) -> Self {
|
||||||
where
|
msg.into().into()
|
||||||
M: Into<String>,
|
|
||||||
{
|
|
||||||
IssueError {
|
|
||||||
details: msg.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for IssueError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.details)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<String> for IssueError {
|
|
||||||
fn into(self) -> String {
|
|
||||||
self.details
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for IssueError {
|
|
||||||
fn description(&self) -> &str {
|
|
||||||
&self.details
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<io::Error> for IssueError {
|
|
||||||
#[inline]
|
|
||||||
fn from(kind: io::Error) -> IssueError {
|
|
||||||
IssueError::new(kind.description())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<io::Error> for IssueError {
|
|
||||||
#[inline]
|
|
||||||
fn into(self) -> io::Error {
|
|
||||||
io::Error::new(io::ErrorKind::Other, self.description())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<Cow<'a, str>> for IssueError {
|
|
||||||
#[inline]
|
|
||||||
fn from(kind: Cow<'_, str>) -> IssueError {
|
|
||||||
IssueError::new(format!("{:?}", kind))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<string::FromUtf8Error> for IssueError {
|
|
||||||
#[inline]
|
|
||||||
fn from(kind: string::FromUtf8Error) -> IssueError {
|
|
||||||
IssueError::new(format!("{:?}", kind))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<str::Utf8Error> for IssueError {
|
|
||||||
#[inline]
|
|
||||||
fn from(kind: str::Utf8Error) -> IssueError {
|
|
||||||
IssueError::new(format!("{:?}", kind))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<rusqlite::Error> for IssueError {
|
|
||||||
#[inline]
|
|
||||||
fn from(kind: rusqlite::Error) -> IssueError {
|
|
||||||
IssueError::new(format!("{}", kind.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<reqwest::Error> for IssueError {
|
|
||||||
#[inline]
|
|
||||||
fn from(kind: reqwest::Error) -> IssueError {
|
|
||||||
IssueError::new(format!("{}", kind.to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
473
src/main.rs
473
src/main.rs
|
@ -1,12 +1,38 @@
|
||||||
use melib::Envelope;
|
/* This file is part of issue-bot.
|
||||||
|
*
|
||||||
|
* issue-bot is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* issue-bot is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with issue-bot. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#![recursion_limit = "1024"]
|
||||||
|
|
||||||
|
extern crate log;
|
||||||
|
extern crate simplelog;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate error_chain;
|
||||||
|
|
||||||
|
use log::{error, info, trace};
|
||||||
|
use melib::email::headers::HeaderName;
|
||||||
|
use melib::{Address, Envelope};
|
||||||
use rusqlite::types::ToSql;
|
use rusqlite::types::ToSql;
|
||||||
use rusqlite::{Connection, NO_PARAMS};
|
use rusqlite::{Connection, NO_PARAMS};
|
||||||
|
use simplelog::*;
|
||||||
|
use std::fs::File;
|
||||||
use std::io::{stdin, Read};
|
use std::io::{stdin, Read};
|
||||||
use time::Timespec;
|
use time::Timespec;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
use error::*;
|
pub use error::*;
|
||||||
mod api;
|
mod api;
|
||||||
mod conf;
|
mod conf;
|
||||||
use conf::*;
|
use conf::*;
|
||||||
|
@ -16,8 +42,6 @@ mod templates;
|
||||||
type Password = Uuid;
|
type Password = Uuid;
|
||||||
static PASSWORD_COMMANDS: &'static [&'static str] = &["reply", "unsubscribe", "subscribe", "close"];
|
static PASSWORD_COMMANDS: &'static [&'static str] = &["reply", "unsubscribe", "subscribe", "close"];
|
||||||
|
|
||||||
use melib::{email::parser, Address};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Issue {
|
pub struct Issue {
|
||||||
id: i64,
|
id: i64,
|
||||||
|
@ -30,11 +54,7 @@ pub struct Issue {
|
||||||
last_update: String,
|
last_update: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_address(s: &str) -> Address {
|
pub fn send_mail(d: melib::email::Draft, conf: &Configuration) {
|
||||||
parser::address(s.as_bytes()).to_full_result().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_mail(d: melib::email::Draft, conf: &Config) {
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
let parts = conf.mailer.split_whitespace().collect::<Vec<&str>>();
|
let parts = conf.mailer.split_whitespace().collect::<Vec<&str>>();
|
||||||
|
@ -55,12 +75,229 @@ pub fn send_mail(d: melib::email::Draft, conf: &Config) {
|
||||||
let output = mailer.wait().expect("Failed to wait on mailer");
|
let output = mailer.wait().expect("Failed to wait on mailer");
|
||||||
if !output.success() {
|
if !output.success() {
|
||||||
// TODO: commit to database queue
|
// TODO: commit to database queue
|
||||||
|
error!("mailer fail");
|
||||||
eprintln!("mailer fail");
|
eprintln!("mailer fail");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> std::result::Result<(), std::io::Error> {
|
fn run_app(conn: Connection, conf: Configuration) -> Result<()> {
|
||||||
|
let mut new_message_raw = vec![];
|
||||||
|
stdin().lock().read_to_end(&mut new_message_raw)?;
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Received this raw message:\n{}",
|
||||||
|
&String::from_utf8_lossy(&new_message_raw)
|
||||||
|
);
|
||||||
|
|
||||||
|
let envelope = Envelope::from_bytes(new_message_raw.as_slice(), None)?;
|
||||||
|
let mut reply = melib::Draft::new_reply(&envelope, new_message_raw.as_slice(), true);
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("From"),
|
||||||
|
format!(
|
||||||
|
"{local_part}@{domain}",
|
||||||
|
local_part = &conf.local_part,
|
||||||
|
domain = &conf.domain
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let tags: Vec<String> = envelope.to()[0].get_tags('+');
|
||||||
|
match tags.as_slice() {
|
||||||
|
s if s.is_empty() || s == &["anonymous"] => {
|
||||||
|
/* Assign new issue */
|
||||||
|
let subject = envelope.subject().to_string();
|
||||||
|
let body = envelope.body_bytes(new_message_raw.as_slice()).text();
|
||||||
|
let from = envelope.from()[0].clone();
|
||||||
|
info!("Assign new issue with subject {} from {}", &subject, &from);
|
||||||
|
let mut reply = melib::Draft::new_reply(&envelope, new_message_raw.as_slice(), true);
|
||||||
|
let anonymous = !tags.is_empty();
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("From"),
|
||||||
|
format!(
|
||||||
|
"{local_part}@{domain}",
|
||||||
|
local_part = &conf.local_part,
|
||||||
|
domain = &conf.domain
|
||||||
|
),
|
||||||
|
);
|
||||||
|
match api::new_issue(&conn, subject.clone(), body, anonymous, from, &conf) {
|
||||||
|
Ok((password, issue_id)) => {
|
||||||
|
info!("Issue {} successfully created.", &subject);
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("Subject"),
|
||||||
|
format!(
|
||||||
|
"[{tag}] Issue `{}` successfully created",
|
||||||
|
&subject,
|
||||||
|
tag = &conf.tag
|
||||||
|
),
|
||||||
|
);
|
||||||
|
reply.set_body(templates::new_issue_success(
|
||||||
|
subject, password, issue_id, &conf,
|
||||||
|
));
|
||||||
|
send_mail(reply, &conf);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("Issue {} could not be created {}.", &subject, &err);
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("Subject"),
|
||||||
|
format!(
|
||||||
|
"[{tag}] Issue `{}` could not be created",
|
||||||
|
&subject,
|
||||||
|
tag = &conf.tag
|
||||||
|
),
|
||||||
|
);
|
||||||
|
reply.set_body(templates::new_issue_failure(err, &conf));
|
||||||
|
send_mail(reply, &conf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&[ref p, ref cmd]
|
||||||
|
if Password::parse_str(p).is_ok() && PASSWORD_COMMANDS.contains(&cmd.as_str()) =>
|
||||||
|
{
|
||||||
|
trace!("Got command {} from {}", cmd.as_str(), &envelope.from()[0]);
|
||||||
|
let p = Password::parse_str(p).unwrap();
|
||||||
|
match cmd.as_str() {
|
||||||
|
"reply" => {
|
||||||
|
info!(
|
||||||
|
"Got reply with subject {} from {}.",
|
||||||
|
&envelope.subject(),
|
||||||
|
&envelope.from()[0]
|
||||||
|
);
|
||||||
|
let body = envelope.body_bytes(new_message_raw.as_slice()).text();
|
||||||
|
let from = envelope.from()[0].clone();
|
||||||
|
match api::new_reply(&conn, body, p, from, &conf) {
|
||||||
|
Ok((title, issue_id, is_subscribed)) => {
|
||||||
|
info!("Reply successfully created.");
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("Subject"),
|
||||||
|
format!(
|
||||||
|
"[{tag}] Your reply on issue `{}` has been posted",
|
||||||
|
&title,
|
||||||
|
tag = &conf.tag,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
reply.set_body(templates::new_reply_success(
|
||||||
|
title,
|
||||||
|
p,
|
||||||
|
issue_id,
|
||||||
|
is_subscribed,
|
||||||
|
&conf,
|
||||||
|
));
|
||||||
|
send_mail(reply, &conf);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!(
|
||||||
|
"Reply {} could not be created {}.",
|
||||||
|
&envelope.subject(),
|
||||||
|
&err
|
||||||
|
);
|
||||||
|
reply.headers_mut().insert(HeaderName::new_unchecked("Subject"),
|
||||||
|
format!(
|
||||||
|
"[{tag}] Your reply could not be created",
|
||||||
|
tag = &conf.tag,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
reply.set_body(templates::new_reply_failure(err, &conf));
|
||||||
|
send_mail(reply, &conf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"close" => match api::close(&conn, p, &conf) {
|
||||||
|
Ok((title, issue_id, _)) => {
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("Subject"),
|
||||||
|
format!(
|
||||||
|
"[{tag}] issue `{}` has been closed",
|
||||||
|
&title,
|
||||||
|
tag = &conf.tag
|
||||||
|
),
|
||||||
|
);
|
||||||
|
reply.set_body(templates::close_success(title, issue_id, &conf));
|
||||||
|
send_mail(reply, &conf);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("Subject"),
|
||||||
|
format!("[{tag}] issue could not be closed", tag = &conf.tag,),
|
||||||
|
);
|
||||||
|
reply.set_body(templates::close_failure(e, &conf));
|
||||||
|
send_mail(reply, &conf);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unsubscribe" => match api::change_subscription(&conn, p, false) {
|
||||||
|
Ok((title, issue_id, _)) => {
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("Subject"),
|
||||||
|
format!(
|
||||||
|
"[{tag}] subscription removal to `{}` successful",
|
||||||
|
&title,
|
||||||
|
tag = &conf.tag
|
||||||
|
),
|
||||||
|
);
|
||||||
|
reply.set_body(templates::change_subscription_success(
|
||||||
|
title, p, issue_id, false, &conf,
|
||||||
|
));
|
||||||
|
send_mail(reply, &conf);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("unsubscribe error: {}", e.to_string());
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("Subject"),
|
||||||
|
format!("[{tag}] could not unsubscribe", tag = &conf.tag,),
|
||||||
|
);
|
||||||
|
reply.set_body(templates::change_subscription_failure(false, &conf));
|
||||||
|
send_mail(reply, &conf);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"subscribe" => match api::change_subscription(&conn, p, true) {
|
||||||
|
Ok((title, issue_id, _)) => {
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("Subject"),
|
||||||
|
format!(
|
||||||
|
"[{tag}] subscription to `{}` successful",
|
||||||
|
&title,
|
||||||
|
tag = &conf.tag
|
||||||
|
),
|
||||||
|
);
|
||||||
|
reply.set_body(templates::change_subscription_success(
|
||||||
|
title, p, issue_id, true, &conf,
|
||||||
|
));
|
||||||
|
send_mail(reply, &conf);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("subscribe error: {}", e.to_string());
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("Subject"),
|
||||||
|
format!("[{tag}] could not subscribe", tag = &conf.tag,),
|
||||||
|
);
|
||||||
|
reply.set_body(templates::change_subscription_failure(true, &conf));
|
||||||
|
send_mail(reply, &conf);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
other => {
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("Subject"),
|
||||||
|
format!("[{tag}] invalid action: `{}`", &other, tag = &conf.tag),
|
||||||
|
);
|
||||||
|
reply.set_body(templates::invalid_request(&conf));
|
||||||
|
send_mail(reply, &conf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
reply.headers_mut().insert(
|
||||||
|
HeaderName::new_unchecked("Subject"),
|
||||||
|
format!("[{tag}] invalid request", tag = &conf.tag),
|
||||||
|
);
|
||||||
|
reply.set_body(templates::invalid_request(&conf));
|
||||||
|
send_mail(reply, &conf);
|
||||||
|
error!("invalid request: {:?}", other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
let mut file = std::fs::File::open("./config.toml")?;
|
let mut file = std::fs::File::open("./config.toml")?;
|
||||||
let args = std::env::args().skip(1).collect::<Vec<String>>();
|
let args = std::env::args().skip(1).collect::<Vec<String>>();
|
||||||
let perform_cron: bool;
|
let perform_cron: bool;
|
||||||
|
@ -78,7 +315,17 @@ fn main() -> std::result::Result<(), std::io::Error> {
|
||||||
|
|
||||||
let mut contents = String::new();
|
let mut contents = String::new();
|
||||||
file.read_to_string(&mut contents)?;
|
file.read_to_string(&mut contents)?;
|
||||||
let conf: Config = toml::from_str(&contents).unwrap();
|
let conf: Configuration = toml::from_str(&contents).unwrap();
|
||||||
|
CombinedLogger::init(vec![
|
||||||
|
TermLogger::new(LevelFilter::Error, Config::default(), TerminalMode::Mixed),
|
||||||
|
WriteLogger::new(
|
||||||
|
LevelFilter::Trace,
|
||||||
|
Config::default(),
|
||||||
|
File::create(&conf.log_file).unwrap(),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
/* - read mail from stdin
|
/* - read mail from stdin
|
||||||
* - decide which case this mail falls to
|
* - decide which case this mail falls to
|
||||||
* a) error/junk
|
* a) error/junk
|
||||||
|
@ -112,205 +359,17 @@ fn main() -> std::result::Result<(), std::io::Error> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if perform_cron {
|
if perform_cron {
|
||||||
cron::check(conn, conf);
|
info!("Performing cron duties.");
|
||||||
|
if let Err(err) = cron::check(conn, conf) {
|
||||||
|
error!("Encountered an error: {}", &err);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut new_message_raw = String::new();
|
if let Err(err) = run_app(conn, conf) {
|
||||||
stdin().lock().read_to_string(&mut new_message_raw).unwrap();
|
error!("Encountered an error: {}", &err);
|
||||||
|
return Err(err);
|
||||||
let envelope = Envelope::from_bytes(new_message_raw.as_bytes(), None);
|
|
||||||
if let Ok(envelope) = envelope {
|
|
||||||
let mut reply = melib::Draft::new_reply(&envelope, &[]);
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"From".to_string(),
|
|
||||||
format!(
|
|
||||||
"{local_part}@{domain}",
|
|
||||||
local_part = &conf.local_part,
|
|
||||||
domain = &conf.domain
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let tags: Vec<String> = envelope.to()[0].get_tags('+');
|
|
||||||
match tags.as_slice() {
|
|
||||||
s if s.is_empty() || s == &["anonymous"] => {
|
|
||||||
/* Assign new issue */
|
|
||||||
let subject = envelope.subject().to_string();
|
|
||||||
let body = envelope.body_bytes(new_message_raw.as_bytes()).text();
|
|
||||||
let from = envelope.from()[0].clone();
|
|
||||||
let mut reply = melib::Draft::new_reply(&envelope, &[]);
|
|
||||||
let anonymous = !tags.is_empty();
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"From".to_string(),
|
|
||||||
format!(
|
|
||||||
"{local_part}@{domain}",
|
|
||||||
local_part = &conf.local_part,
|
|
||||||
domain = &conf.domain
|
|
||||||
),
|
|
||||||
);
|
|
||||||
match api::new_issue(&conn, subject.clone(), body, anonymous, from, &conf) {
|
|
||||||
Ok((password, issue_id)) => {
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"Subject".to_string(),
|
|
||||||
format!(
|
|
||||||
"[{tag}] Issue `{}` successfully created",
|
|
||||||
&subject,
|
|
||||||
tag = &conf.tag
|
|
||||||
),
|
|
||||||
);
|
|
||||||
reply.set_body(templates::new_issue_success(
|
|
||||||
subject, password, issue_id, &conf,
|
|
||||||
));
|
|
||||||
send_mail(reply, &conf);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"Subject".to_string(),
|
|
||||||
format!(
|
|
||||||
"[{tag}] Issue `{}` could not be created",
|
|
||||||
&subject,
|
|
||||||
tag = &conf.tag
|
|
||||||
),
|
|
||||||
);
|
|
||||||
reply.set_body(templates::new_issue_failure(e, &conf));
|
|
||||||
send_mail(reply, &conf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&[ref p, ref cmd]
|
|
||||||
if Password::parse_str(p).is_ok() && PASSWORD_COMMANDS.contains(&cmd.as_str()) =>
|
|
||||||
{
|
|
||||||
let p = Password::parse_str(p).unwrap();
|
|
||||||
match cmd.as_str() {
|
|
||||||
"reply" => {
|
|
||||||
let body = envelope.body_bytes(new_message_raw.as_bytes()).text();
|
|
||||||
let from = envelope.from()[0].clone();
|
|
||||||
match api::new_reply(&conn, body, p, from, &conf) {
|
|
||||||
Ok((title, issue_id, is_subscribed)) => {
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"Subject".to_string(),
|
|
||||||
format!(
|
|
||||||
"[{tag}] Your reply on issue `{}` has been posted",
|
|
||||||
&title,
|
|
||||||
tag = &conf.tag,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
reply.set_body(templates::new_reply_success(
|
|
||||||
title,
|
|
||||||
p,
|
|
||||||
issue_id,
|
|
||||||
is_subscribed,
|
|
||||||
&conf,
|
|
||||||
));
|
|
||||||
send_mail(reply, &conf);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"Subject".to_string(),
|
|
||||||
format!(
|
|
||||||
"[{tag}] Your reply could not be created",
|
|
||||||
tag = &conf.tag,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
reply.set_body(templates::new_reply_failure(e, &conf));
|
|
||||||
send_mail(reply, &conf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"close" => match api::close(&conn, p, &conf) {
|
|
||||||
Ok((title, issue_id, _)) => {
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"Subject".to_string(),
|
|
||||||
format!(
|
|
||||||
"[{tag}] issue `{}` has been closed",
|
|
||||||
&title,
|
|
||||||
tag = &conf.tag
|
|
||||||
),
|
|
||||||
);
|
|
||||||
reply.set_body(templates::close_success(title, issue_id, &conf));
|
|
||||||
send_mail(reply, &conf);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"Subject".to_string(),
|
|
||||||
format!("[{tag}] issue could not be closed", tag = &conf.tag,),
|
|
||||||
);
|
|
||||||
reply.set_body(templates::close_failure(e, &conf));
|
|
||||||
send_mail(reply, &conf);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"unsubscribe" => match api::change_subscription(&conn, p, false) {
|
|
||||||
Ok((title, issue_id, _)) => {
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"Subject".to_string(),
|
|
||||||
format!(
|
|
||||||
"[{tag}] subscription removal to `{}` successful",
|
|
||||||
&title,
|
|
||||||
tag = &conf.tag
|
|
||||||
),
|
|
||||||
);
|
|
||||||
reply.set_body(templates::change_subscription_success(
|
|
||||||
title, p, issue_id, false, &conf,
|
|
||||||
));
|
|
||||||
send_mail(reply, &conf);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("error: {}", e.to_string());
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"Subject".to_string(),
|
|
||||||
format!("[{tag}] could not unsubscribe", tag = &conf.tag,),
|
|
||||||
);
|
|
||||||
reply.set_body(templates::change_subscription_failure(false, &conf));
|
|
||||||
send_mail(reply, &conf);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"subscribe" => match api::change_subscription(&conn, p, true) {
|
|
||||||
Ok((title, issue_id, _)) => {
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"Subject".to_string(),
|
|
||||||
format!(
|
|
||||||
"[{tag}] subscription to `{}` successful",
|
|
||||||
&title,
|
|
||||||
tag = &conf.tag
|
|
||||||
),
|
|
||||||
);
|
|
||||||
reply.set_body(templates::change_subscription_success(
|
|
||||||
title, p, issue_id, true, &conf,
|
|
||||||
));
|
|
||||||
send_mail(reply, &conf);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("error: {}", e.to_string());
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"Subject".to_string(),
|
|
||||||
format!("[{tag}] could not subscribe", tag = &conf.tag,),
|
|
||||||
);
|
|
||||||
reply.set_body(templates::change_subscription_failure(true, &conf));
|
|
||||||
send_mail(reply, &conf);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
other => {
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"Subject".to_string(),
|
|
||||||
format!("[{tag}] invalid action: `{}`", &other, tag = &conf.tag),
|
|
||||||
);
|
|
||||||
reply.set_body(templates::invalid_request(&conf));
|
|
||||||
send_mail(reply, &conf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
reply.headers_mut().insert(
|
|
||||||
"Subject".to_string(),
|
|
||||||
format!("[{tag}] invalid request", tag = &conf.tag),
|
|
||||||
);
|
|
||||||
reply.set_body(templates::invalid_request(&conf));
|
|
||||||
send_mail(reply, &conf);
|
|
||||||
println!("error: {:?}", other);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,24 @@
|
||||||
|
/* This file is part of issue-bot.
|
||||||
|
*
|
||||||
|
* issue-bot is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* issue-bot is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with issue-bot. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
static BASE_ISSUE_URL: &'static str = "{base_url}/{repo}/issues";
|
static BASE_ISSUE_URL: &'static str = "{base_url}/{repo}/issues";
|
||||||
|
|
||||||
pub fn new_issue_failure(e: IssueError, conf: &Config) -> String {
|
pub fn new_issue_failure(e: Error, conf: &Configuration) -> String {
|
||||||
format!("Hello,
|
format!("Hello,
|
||||||
|
|
||||||
Unfortunately we were not able to create your issue. The reason was: `{}`. Please contact the repository's owners for assistance.
|
Unfortunately we were not able to create your issue. The reason was: `{}`. Please contact the repository's owners for assistance.
|
||||||
|
@ -14,7 +30,7 @@ pub fn new_issue_success(
|
||||||
title: String,
|
title: String,
|
||||||
password: Password,
|
password: Password,
|
||||||
issue_id: i64,
|
issue_id: i64,
|
||||||
conf: &Config,
|
conf: &Configuration,
|
||||||
) -> String {
|
) -> String {
|
||||||
format!("Hello,
|
format!("Hello,
|
||||||
|
|
||||||
|
@ -33,7 +49,7 @@ Please keep this email in order to be able to keep in touch with your issue.
|
||||||
This is an automated email from {bot_name} <{local_part}+help@{domain}>", title = title, password = password.to_string(), issue_id = issue_id, url = BASE_ISSUE_URL.replace("{base_url}", &conf.base_url).replace("{repo}", &conf.repo), local_part = &conf.local_part, domain = &conf.domain, bot_name = &conf.bot_name)
|
This is an automated email from {bot_name} <{local_part}+help@{domain}>", title = title, password = password.to_string(), issue_id = issue_id, url = BASE_ISSUE_URL.replace("{base_url}", &conf.base_url).replace("{repo}", &conf.repo), local_part = &conf.local_part, domain = &conf.domain, bot_name = &conf.bot_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_reply_failure(e: IssueError, conf: &Config) -> String {
|
pub fn new_reply_failure(e: Error, conf: &Configuration) -> String {
|
||||||
format!("Hello,
|
format!("Hello,
|
||||||
|
|
||||||
Unfortunately we were not able to post your reply. The reason was: `{}`. Please contact the repository's owners for assistance.
|
Unfortunately we were not able to post your reply. The reason was: `{}`. Please contact the repository's owners for assistance.
|
||||||
|
@ -46,7 +62,7 @@ pub fn new_reply_success(
|
||||||
password: Password,
|
password: Password,
|
||||||
issue_id: i64,
|
issue_id: i64,
|
||||||
is_subscribed: bool,
|
is_subscribed: bool,
|
||||||
conf: &Config,
|
conf: &Configuration,
|
||||||
) -> String {
|
) -> String {
|
||||||
if is_subscribed {
|
if is_subscribed {
|
||||||
format!("Hello,
|
format!("Hello,
|
||||||
|
@ -83,7 +99,7 @@ This is an automated email from {bot_name} <{local_part}+help@{domain}>", title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_success(title: String, issue_id: i64, conf: &Config) -> String {
|
pub fn close_success(title: String, issue_id: i64, conf: &Configuration) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Hello,
|
"Hello,
|
||||||
|
|
||||||
|
@ -103,7 +119,7 @@ This is an automated email from {bot_name} <{local_part}+help@{domain}>",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_failure(e: IssueError, conf: &Config) -> String {
|
pub fn close_failure(e: Error, conf: &Configuration) -> String {
|
||||||
format!("Hello,
|
format!("Hello,
|
||||||
|
|
||||||
Unfortunately we were not able to close this issue. The reason was: `{}`. Please contact the repository's owners for assistance.
|
Unfortunately we were not able to close this issue. The reason was: `{}`. Please contact the repository's owners for assistance.
|
||||||
|
@ -111,7 +127,7 @@ Unfortunately we were not able to close this issue. The reason was: `{}`. Please
|
||||||
This is an automated email from {bot_name} <{local_part}+help@{domain}>", e.to_string(), local_part = &conf.local_part, domain = &conf.domain, bot_name = &conf.bot_name)
|
This is an automated email from {bot_name} <{local_part}+help@{domain}>", e.to_string(), local_part = &conf.local_part, domain = &conf.domain, bot_name = &conf.bot_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn invalid_request(conf: &Config) -> String {
|
pub fn invalid_request(conf: &Configuration) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Hello,
|
"Hello,
|
||||||
|
|
||||||
|
@ -138,7 +154,7 @@ pub fn change_subscription_success(
|
||||||
password: Password,
|
password: Password,
|
||||||
issue_id: i64,
|
issue_id: i64,
|
||||||
is_subscribed: bool,
|
is_subscribed: bool,
|
||||||
conf: &Config,
|
conf: &Configuration,
|
||||||
) -> String {
|
) -> String {
|
||||||
format!("Hello,
|
format!("Hello,
|
||||||
|
|
||||||
|
@ -157,7 +173,7 @@ Please keep this email in order to be able to keep in touch with your issue.
|
||||||
This is an automated email from {bot_name} <{local_part}+help@{domain}>", title = title, password = password.to_string(), issue_id = issue_id, url = BASE_ISSUE_URL.replace("{base_url}", &conf.base_url).replace("{repo}", &conf.repo), local_part = &conf.local_part, domain = &conf.domain, bot_name = &conf.bot_name, not = if is_subscribed { "" }else {"not "}, un = if is_subscribed { "un" } else { "" } )
|
This is an automated email from {bot_name} <{local_part}+help@{domain}>", title = title, password = password.to_string(), issue_id = issue_id, url = BASE_ISSUE_URL.replace("{base_url}", &conf.base_url).replace("{repo}", &conf.repo), local_part = &conf.local_part, domain = &conf.domain, bot_name = &conf.bot_name, not = if is_subscribed { "" }else {"not "}, un = if is_subscribed { "un" } else { "" } )
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_subscription_failure(is_subscribed: bool, conf: &Config) -> String {
|
pub fn change_subscription_failure(is_subscribed: bool, conf: &Configuration) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Hello,
|
"Hello,
|
||||||
|
|
||||||
|
@ -171,7 +187,7 @@ This is an automated email from {bot_name} <{local_part}+help@{domain}>",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reply_update(issue: &Issue, conf: &Config, comments: Vec<String>) -> String {
|
pub fn reply_update(issue: &Issue, conf: &Configuration, comments: Vec<String>) -> String {
|
||||||
assert!(comments.len() > 0);
|
assert!(comments.len() > 0);
|
||||||
format!(
|
format!(
|
||||||
"Hello,
|
"Hello,
|
||||||
|
|
Loading…
Reference in New Issue