1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
use std::sync::Arc;
use actix::{Actor, Addr, Handler, Message, SyncArbiter, SyncContext};
use actix_web::{
get, middleware::Logger, web, App, HttpResponse, HttpServer, Responder, ResponseError,
};
use askama::actix_web::TemplateIntoResponse;
use askama::Template;
use derive_more::Display;
use serde::Serialize;
use crate::bot::MasterBot;
use crate::youtube_dl::AudioMetadata;
pub struct WebServerArgs {
pub domain: String,
pub bind_address: String,
pub bot: Arc<MasterBot>,
}
#[actix_rt::main]
pub async fn start(args: WebServerArgs) -> std::io::Result<()> {
let cbot = args.bot.clone();
let bot_addr: Addr<BotExecutor> = SyncArbiter::start(4, move || BotExecutor(cbot.clone()));
HttpServer::new(move || {
App::new()
.data(bot_addr.clone())
.wrap(Logger::default())
.service(index)
.service(web::scope("/api").service(get_bot_list).service(get_bot))
.service(actix_files::Files::new("/static", "static/"))
})
.bind(args.bind_address)?
.run()
.await?;
args.bot.quit(String::from("Stopping"));
Ok(())
}
pub struct BotExecutor(pub Arc<MasterBot>);
impl Actor for BotExecutor {
type Context = SyncContext<Self>;
}
struct BotDataListRequest;
impl Message for BotDataListRequest {
// A plain Vec does not work for some reason
type Result = Result<Vec<BotData>, ()>;
}
impl Handler<BotDataListRequest> for BotExecutor {
type Result = Result<Vec<BotData>, ()>;
fn handle(&mut self, _: BotDataListRequest, _: &mut Self::Context) -> Self::Result {
let bot = &self.0;
Ok(bot.bot_datas())
}
}
struct BotDataRequest(String);
impl Message for BotDataRequest {
type Result = Option<BotData>;
}
impl Handler<BotDataRequest> for BotExecutor {
type Result = Option<BotData>;
fn handle(&mut self, r: BotDataRequest, _: &mut Self::Context) -> Self::Result {
let name = r.0;
let bot = &self.0;
bot.bot_data(name)
}
}
#[derive(Template)]
#[template(path = "index.htm")]
struct OverviewTemplate<'a> {
bots: &'a [BotData],
}
#[derive(Debug, Serialize)]
pub struct BotData {
pub name: String,
pub state: crate::bot::State,
pub volume: f64,
pub currently_playing: Option<AudioMetadata>,
pub playlist: Vec<AudioMetadata>,
}
#[get("/")]
async fn index(bot: web::Data<Addr<BotExecutor>>) -> impl Responder {
let bot_datas = match bot.send(BotDataListRequest).await.unwrap() {
Ok(data) => data,
Err(_) => Vec::with_capacity(0),
};
OverviewTemplate {
bots: &bot_datas[..],
}
.into_response()
}
#[get("/bots")]
async fn get_bot_list(bot: web::Data<Addr<BotExecutor>>) -> impl Responder {
let bot_datas = match bot.send(BotDataListRequest).await.unwrap() {
Ok(data) => data,
Err(_) => Vec::with_capacity(0),
};
web::Json(bot_datas)
}
#[derive(Serialize)]
struct ApiError {
error: String,
description: String,
}
#[derive(Debug, Display)]
enum ApiErrorKind {
#[display(fmt = "Not Found")]
NotFound,
}
impl ResponseError for ApiErrorKind {
fn error_response(&self) -> HttpResponse {
match *self {
ApiErrorKind::NotFound => HttpResponse::NotFound().json(ApiError {
error: self.to_string(),
description: String::from("The requested resource was not found"),
}),
}
}
}
#[get("/bots/{name}")]
async fn get_bot(bot: web::Data<Addr<BotExecutor>>, name: web::Path<String>) -> impl Responder {
if let Some(bot_data) = bot.send(BotDataRequest(name.into_inner())).await.unwrap() {
Ok(web::Json(bot_data))
} else {
Err(ApiErrorKind::NotFound)
}
}
|