begin tenquestionmarks 0.0.2. Separate Module trait into Module struct (which holds metadata and config about the module) and EventLoop trait, which implements the event loop. The constructors still return Modules, but they are structs and not boxes.
This commit is contained in:
parent
23e32f28fe
commit
9e9da11f79
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name="tenquestionmarks"
|
name="tenquestionmarks"
|
||||||
version="0.0.1"
|
version="0.0.2"
|
||||||
authors=["Adrian Malacoda <adrian.malacoda@monarch-pass.net>"]
|
authors=["Adrian Malacoda <adrian.malacoda@monarch-pass.net>"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -15,15 +15,45 @@ pub enum Event {
|
|||||||
pub struct Envelope {
|
pub struct Envelope {
|
||||||
pub from: Option<String>,
|
pub from: Option<String>,
|
||||||
pub event: Event,
|
pub event: Event,
|
||||||
pub to: Vec<String>
|
pub to: Vec<String>,
|
||||||
|
pub tags: Vec<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Envelope {
|
impl Envelope {
|
||||||
pub fn new (event: Event) -> Envelope {
|
pub fn new (event: Event) -> Envelope {
|
||||||
|
let mut tags: Vec<String> = vec![];
|
||||||
|
|
||||||
|
match &event {
|
||||||
|
&Event::Message { ref message } => {
|
||||||
|
tags.push(format!("user:{:}", message.author.name));
|
||||||
|
match message.channel {
|
||||||
|
Some(ref channel) => {
|
||||||
|
tags.push(format!("channel:{:}", channel.name));
|
||||||
|
},
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
&Event::SelfJoin { ref channel } => {
|
||||||
|
tags.push(format!("channel:{:}", channel.name));
|
||||||
|
},
|
||||||
|
&Event::SelfQuit { ref channel } => {
|
||||||
|
tags.push(format!("channel:{:}", channel.name));
|
||||||
|
},
|
||||||
|
&Event::UserJoin { ref channel, ref user } => {
|
||||||
|
tags.push(format!("channel:{:}", channel.name));
|
||||||
|
tags.push(format!("user:{:}", user.name));
|
||||||
|
},
|
||||||
|
&Event::UserQuit { ref channel, ref user } => {
|
||||||
|
tags.push(format!("channel:{:}", channel.name));
|
||||||
|
tags.push(format!("user:{:}", user.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Envelope {
|
Envelope {
|
||||||
from: None,
|
from: None,
|
||||||
event: event,
|
event: event,
|
||||||
to: vec![]
|
to: vec![],
|
||||||
|
tags: tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,8 @@ impl Tenquestionmarks {
|
|||||||
Envelope {
|
Envelope {
|
||||||
from: Some(from.clone()),
|
from: Some(from.clone()),
|
||||||
event: envelope.event,
|
event: envelope.event,
|
||||||
to: envelope.to
|
to: envelope.to,
|
||||||
|
tags: envelope.tags
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use modules::Module;
|
use modules::{Module, EventLoop};
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -19,8 +19,12 @@ use regex::Regex;
|
|||||||
pub struct AutolinkModule {}
|
pub struct AutolinkModule {}
|
||||||
|
|
||||||
impl AutolinkModule {
|
impl AutolinkModule {
|
||||||
pub fn new (_: &Table, _: &Table) -> Box<Module> {
|
pub fn new (_: &Table, config: &Table) -> Module {
|
||||||
Box::new(AutolinkModule {})
|
Module {
|
||||||
|
event_loop: Box::new(AutolinkModule {}),
|
||||||
|
module_type: String::from("autolink"),
|
||||||
|
config: config.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +98,7 @@ fn print_any_link (link: &Link, message: &Message) {
|
|||||||
message.reply(&format!("**Autolink:** {} -> {}", link.label(), link.url()));
|
message.reply(&format!("**Autolink:** {} -> {}", link.label(), link.url()));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module for AutolinkModule {
|
impl EventLoop for AutolinkModule {
|
||||||
fn run (&self, _: Box<ExtSender<Envelope>>, receiver: Receiver<Arc<Envelope>>) {
|
fn run (&self, _: Box<ExtSender<Envelope>>, receiver: Receiver<Arc<Envelope>>) {
|
||||||
let link_regex = Regex::new(r"\[\[([^\[\]]*)\]\]").expect("Invalid regex...");
|
let link_regex = Regex::new(r"\[\[([^\[\]]*)\]\]").expect("Invalid regex...");
|
||||||
let mut searchers = AggregateSearcher::new();
|
let mut searchers = AggregateSearcher::new();
|
||||||
|
@ -3,6 +3,7 @@ use discord::Discord;
|
|||||||
use discord::model::{Event, PossibleServer};
|
use discord::model::{Event, PossibleServer};
|
||||||
|
|
||||||
use modules::Module;
|
use modules::Module;
|
||||||
|
use modules::EventLoop;
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
|
||||||
use event;
|
use event;
|
||||||
@ -19,10 +20,10 @@ pub struct DiscordModule {
|
|||||||
playing: String
|
playing: String
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_PLAYING: &'static str = "tenquestionmarks 0.0.1";
|
const DEFAULT_PLAYING: &'static str = "tenquestionmarks 0.0.2";
|
||||||
|
|
||||||
impl DiscordModule {
|
impl DiscordModule {
|
||||||
pub fn new (_: &Table, configuration: &Table) -> Box<Module> {
|
pub fn new (_: &Table, configuration: &Table) -> Module {
|
||||||
let token = configuration.get("token")
|
let token = configuration.get("token")
|
||||||
.and_then(|value| value.as_str())
|
.and_then(|value| value.as_str())
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
@ -31,10 +32,14 @@ impl DiscordModule {
|
|||||||
.and_then(|value| value.as_str())
|
.and_then(|value| value.as_str())
|
||||||
.unwrap_or(DEFAULT_PLAYING);
|
.unwrap_or(DEFAULT_PLAYING);
|
||||||
|
|
||||||
Box::new(DiscordModule {
|
Module {
|
||||||
token: String::from(token),
|
module_type: String::from("Discord"),
|
||||||
playing: String::from(playing)
|
event_loop: Box::new(DiscordModule {
|
||||||
})
|
token: String::from(token),
|
||||||
|
playing: String::from(playing)
|
||||||
|
}),
|
||||||
|
config: configuration.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +58,7 @@ impl MessageSender for DiscordMessageSender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module for DiscordModule {
|
impl EventLoop for DiscordModule {
|
||||||
fn run (&self, sender: Box<ExtSender<event::Envelope>>, _: Receiver<Arc<event::Envelope>>) {
|
fn run (&self, sender: Box<ExtSender<event::Envelope>>, _: Receiver<Arc<event::Envelope>>) {
|
||||||
let discord = Arc::new(Discord::from_bot_token(&self.token[..]).expect("Discord module: Login failed"));
|
let discord = Arc::new(Discord::from_bot_token(&self.token[..]).expect("Discord module: Login failed"));
|
||||||
let (mut connection, _) = discord.connect().expect("Discord module: Connection failed");
|
let (mut connection, _) = discord.connect().expect("Discord module: Connection failed");
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use modules::Module;
|
use modules::{Module, EventLoop};
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -13,18 +13,22 @@ pub struct EchoModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EchoModule {
|
impl EchoModule {
|
||||||
pub fn new (_: &Table, configuration: &Table) -> Box<Module> {
|
pub fn new (_: &Table, configuration: &Table) -> Module {
|
||||||
let prefix = configuration.get("prefix")
|
let prefix = configuration.get("prefix")
|
||||||
.and_then(|value| value.as_str())
|
.and_then(|value| value.as_str())
|
||||||
.unwrap_or("!echo");
|
.unwrap_or("!echo");
|
||||||
|
|
||||||
Box::new(EchoModule {
|
Module {
|
||||||
prefix: String::from(prefix)
|
module_type: String::from("echo"),
|
||||||
})
|
event_loop: Box::new(EchoModule {
|
||||||
|
prefix: String::from(prefix)
|
||||||
|
}),
|
||||||
|
config: configuration.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module for EchoModule {
|
impl EventLoop for EchoModule {
|
||||||
fn run(&self, _: Box<ExtSender<Envelope>>, receiver: Receiver<Arc<Envelope>>) {
|
fn run(&self, _: Box<ExtSender<Envelope>>, receiver: Receiver<Arc<Envelope>>) {
|
||||||
loop {
|
loop {
|
||||||
match receiver.recv() {
|
match receiver.recv() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use modules::Module;
|
use modules::{Module, EventLoop};
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -15,7 +15,7 @@ pub struct EchoboxModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EchoboxModule {
|
impl EchoboxModule {
|
||||||
pub fn new (_: &Table, configuration: &Table) -> Box<Module> {
|
pub fn new (_: &Table, configuration: &Table) -> Module {
|
||||||
let prefix = configuration.get("prefix")
|
let prefix = configuration.get("prefix")
|
||||||
.and_then(|value| value.as_str())
|
.and_then(|value| value.as_str())
|
||||||
.unwrap_or("?echobox");
|
.unwrap_or("?echobox");
|
||||||
@ -24,14 +24,18 @@ impl EchoboxModule {
|
|||||||
.and_then(|value| value.as_str())
|
.and_then(|value| value.as_str())
|
||||||
.unwrap_or("echobox.db");
|
.unwrap_or("echobox.db");
|
||||||
|
|
||||||
Box::new(EchoboxModule {
|
Module {
|
||||||
prefix: String::from(prefix),
|
module_type: String::from("echo"),
|
||||||
file: String::from(file)
|
event_loop: Box::new(EchoboxModule {
|
||||||
})
|
prefix: String::from(prefix),
|
||||||
|
file: String::from(file)
|
||||||
|
}),
|
||||||
|
config: configuration.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module for EchoboxModule {
|
impl EventLoop for EchoboxModule {
|
||||||
fn run(&self, _: Box<ExtSender<Envelope>>, receiver: Receiver<Arc<Envelope>>) {
|
fn run(&self, _: Box<ExtSender<Envelope>>, receiver: Receiver<Arc<Envelope>>) {
|
||||||
let echobox = Echobox::with_file(&self.file).unwrap();
|
let echobox = Echobox::with_file(&self.file).unwrap();
|
||||||
|
|
||||||
|
@ -15,26 +15,26 @@ use modules::echobox::EchoboxModule;
|
|||||||
use modules::autolink::AutolinkModule;
|
use modules::autolink::AutolinkModule;
|
||||||
|
|
||||||
pub struct ModuleLoader {
|
pub struct ModuleLoader {
|
||||||
types: BTreeMap<&'static str, fn(&Table, &Table) -> Box<Module>>
|
types: BTreeMap<&'static str, fn(&Table, &Table) -> Module>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModuleLoader {
|
impl ModuleLoader {
|
||||||
pub fn new () -> ModuleLoader {
|
pub fn new () -> ModuleLoader {
|
||||||
let mut types = BTreeMap::new();
|
let mut types = BTreeMap::new();
|
||||||
types.insert("discord", DiscordModule::new as fn(&Table, &Table) -> Box<Module>);
|
types.insert("discord", DiscordModule::new as fn(&Table, &Table) -> Module);
|
||||||
types.insert("lua", LuaModule::new as fn(&Table, &Table) -> Box<Module>);
|
types.insert("lua", LuaModule::new as fn(&Table, &Table) -> Module);
|
||||||
types.insert("stdin", StdinModule::new as fn(&Table, &Table) -> Box<Module>);
|
types.insert("stdin", StdinModule::new as fn(&Table, &Table) -> Module);
|
||||||
types.insert("echo", EchoModule::new as fn(&Table, &Table) -> Box<Module>);
|
types.insert("echo", EchoModule::new as fn(&Table, &Table) -> Module);
|
||||||
types.insert("random", RandomModule::new as fn(&Table, &Table) -> Box<Module>);
|
types.insert("random", RandomModule::new as fn(&Table, &Table) -> Module);
|
||||||
types.insert("pvn", PvnModule::new as fn(&Table, &Table) -> Box<Module>);
|
types.insert("pvn", PvnModule::new as fn(&Table, &Table) -> Module);
|
||||||
types.insert("echobox", EchoboxModule::new as fn(&Table, &Table) -> Box<Module>);
|
types.insert("echobox", EchoboxModule::new as fn(&Table, &Table) -> Module);
|
||||||
types.insert("autolink", AutolinkModule::new as fn(&Table, &Table) -> Box<Module>);
|
types.insert("autolink", AutolinkModule::new as fn(&Table, &Table) -> Module);
|
||||||
ModuleLoader {
|
ModuleLoader {
|
||||||
types: types
|
types: types
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_from_configuration (&self, configuration: Table) -> Result<BTreeMap<String, Box<Module>>, ModuleLoaderError> {
|
pub fn load_from_configuration (&self, configuration: Table) -> Result<BTreeMap<String, Module>, ModuleLoaderError> {
|
||||||
let general_config = configuration.get("general")
|
let general_config = configuration.get("general")
|
||||||
.and_then(|value| value.as_table())
|
.and_then(|value| value.as_table())
|
||||||
.map(|value| value.clone())
|
.map(|value| value.clone())
|
||||||
@ -53,7 +53,7 @@ impl ModuleLoader {
|
|||||||
}).collect()
|
}).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_single_module (&self, name: &str, general_configuration: &Table, module_configuration: &Table) -> Result<Box<Module>, ModuleLoaderError> {
|
pub fn load_single_module (&self, name: &str, general_configuration: &Table, module_configuration: &Table) -> Result<Module, ModuleLoaderError> {
|
||||||
/*
|
/*
|
||||||
* The Module type defaults to the instance name (in the tenquestionmarks configuration)
|
* The Module type defaults to the instance name (in the tenquestionmarks configuration)
|
||||||
* but can explicitly be set by using the special "type" parameter.
|
* but can explicitly be set by using the special "type" parameter.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use modules::Module;
|
use modules::{Module, EventLoop};
|
||||||
|
|
||||||
use toml::{Table, Value};
|
use toml::{Table, Value};
|
||||||
|
|
||||||
@ -22,12 +22,16 @@ pub struct LuaModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LuaModule {
|
impl LuaModule {
|
||||||
pub fn new (_: &Table, config: &Table) -> Box<Module> {
|
pub fn new (_: &Table, config: &Table) -> Module {
|
||||||
Box::new(LuaModule {
|
Module {
|
||||||
code: config.get("code").and_then(|value| value.as_str()).map(String::from),
|
module_type: String::from("lua"),
|
||||||
file: config.get("file").and_then(|value| value.as_str()).map(String::from),
|
event_loop: Box::new(LuaModule {
|
||||||
variables: config.clone()
|
code: config.get("code").and_then(|value| value.as_str()).map(String::from),
|
||||||
})
|
file: config.get("file").and_then(|value| value.as_str()).map(String::from),
|
||||||
|
variables: config.clone()
|
||||||
|
}),
|
||||||
|
config: config.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +52,7 @@ implement_lua_push!(Message, |mut metatable| {
|
|||||||
index.set("reply", hlua::function2(|message: &mut Message, reply: String| message.reply(&reply)));
|
index.set("reply", hlua::function2(|message: &mut Message, reply: String| message.reply(&reply)));
|
||||||
});
|
});
|
||||||
|
|
||||||
impl Module for LuaModule {
|
impl EventLoop for LuaModule {
|
||||||
fn run (&self, _: Box<ExtSender<Envelope>>, receiver: Receiver<Arc<Envelope>>) {
|
fn run (&self, _: Box<ExtSender<Envelope>>, receiver: Receiver<Arc<Envelope>>) {
|
||||||
let mut lua = Lua::new();
|
let mut lua = Lua::new();
|
||||||
lua.openlibs();
|
lua.openlibs();
|
||||||
|
@ -16,7 +16,15 @@ use std::sync::Arc;
|
|||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
use transformable_channels::mpsc::ExtSender;
|
use transformable_channels::mpsc::ExtSender;
|
||||||
|
|
||||||
pub trait Module : Sync {
|
use toml::Table;
|
||||||
fn register (&self, _: &Tenquestionmarks) {}
|
|
||||||
|
pub struct Module {
|
||||||
|
event_loop: Box<EventLoop>,
|
||||||
|
module_type: String,
|
||||||
|
config: Table
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Module {}
|
||||||
|
pub trait EventLoop : Sync {
|
||||||
fn run (&self, _: Box<ExtSender<Envelope>>, _: Receiver<Arc<Envelope>>) {}
|
fn run (&self, _: Box<ExtSender<Envelope>>, _: Receiver<Arc<Envelope>>) {}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use modules::Module;
|
use modules::{Module, EventLoop};
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -17,8 +17,12 @@ use pvn::ninjas::{Ninja, Ninjas};
|
|||||||
pub struct PvnModule {}
|
pub struct PvnModule {}
|
||||||
|
|
||||||
impl PvnModule {
|
impl PvnModule {
|
||||||
pub fn new (_: &Table, _: &Table) -> Box<Module> {
|
pub fn new (_: &Table, configuration: &Table) -> Module {
|
||||||
Box::new(PvnModule {})
|
Module {
|
||||||
|
module_type: String::from("pvn"),
|
||||||
|
event_loop: Box::new(PvnModule {}),
|
||||||
|
config: configuration.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +140,7 @@ impl PirateVsNinja {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module for PvnModule {
|
impl EventLoop for PvnModule {
|
||||||
fn run(&self, _: Box<ExtSender<Envelope>>, receiver: Receiver<Arc<Envelope>>) {
|
fn run(&self, _: Box<ExtSender<Envelope>>, receiver: Receiver<Arc<Envelope>>) {
|
||||||
let mut pvn = PirateVsNinja {
|
let mut pvn = PirateVsNinja {
|
||||||
pirates: Pirates::new(),
|
pirates: Pirates::new(),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use modules::Module;
|
use modules::{Module, EventLoop};
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -15,7 +15,7 @@ pub struct RandomModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RandomModule {
|
impl RandomModule {
|
||||||
pub fn new (_: &Table, configuration: &Table) -> Box<Module> {
|
pub fn new (_: &Table, configuration: &Table) -> Module {
|
||||||
let prefix = configuration.get("prefix")
|
let prefix = configuration.get("prefix")
|
||||||
.and_then(|value| value.as_str())
|
.and_then(|value| value.as_str())
|
||||||
.unwrap_or("?random");
|
.unwrap_or("?random");
|
||||||
@ -28,14 +28,18 @@ impl RandomModule {
|
|||||||
.map(|value| { String::from(value.as_str().unwrap()) })
|
.map(|value| { String::from(value.as_str().unwrap()) })
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Box::new(RandomModule {
|
Module {
|
||||||
prefix: String::from(prefix),
|
module_type: String::from("random"),
|
||||||
responses: responses
|
event_loop: Box::new(RandomModule {
|
||||||
})
|
prefix: String::from(prefix),
|
||||||
|
responses: responses
|
||||||
|
}),
|
||||||
|
config: configuration.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module for RandomModule {
|
impl EventLoop for RandomModule {
|
||||||
fn run(&self, _: Box<ExtSender<Envelope>>, receiver: Receiver<Arc<Envelope>>) {
|
fn run(&self, _: Box<ExtSender<Envelope>>, receiver: Receiver<Arc<Envelope>>) {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use modules::Module;
|
use modules::{Module, EventLoop};
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
|
||||||
use {MessageSender, Message, User};
|
use {MessageSender, Message, User};
|
||||||
@ -25,12 +25,16 @@ impl MessageSender for StdinMessageSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StdinModule {
|
impl StdinModule {
|
||||||
pub fn new (_: &Table, _: &Table) -> Box<Module> {
|
pub fn new (_: &Table, config: &Table) -> Module {
|
||||||
Box::new(StdinModule {})
|
Module {
|
||||||
|
module_type: String::from("stdin"),
|
||||||
|
event_loop: Box::new(StdinModule {}),
|
||||||
|
config: config.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module for StdinModule {
|
impl EventLoop for StdinModule {
|
||||||
fn run(&self, sender: Box<ExtSender<Envelope>>, _: Receiver<Arc<Envelope>>) {
|
fn run(&self, sender: Box<ExtSender<Envelope>>, _: Receiver<Arc<Envelope>>) {
|
||||||
loop {
|
loop {
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user