Add basic messaging
This commit is contained in:
parent
18a5892371
commit
dd55f8c623
4 changed files with 144 additions and 35 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -472,6 +472,7 @@ dependencies = [
|
|||
"futures",
|
||||
"iced",
|
||||
"irc",
|
||||
"once_cell",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
|
|
@ -10,4 +10,5 @@ color-eyre = "0.6.2"
|
|||
futures = "0.3.29"
|
||||
iced = { version = "0.10.0", features = ["tokio"] }
|
||||
irc = "0.15.0"
|
||||
once_cell = "1.18.0"
|
||||
tokio = { version = "1.33.0", features = ["full"] }
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
pkgs.mkShell rec {
|
||||
buildInputs = with pkgs; [
|
||||
cargo
|
||||
clippy
|
||||
clang
|
||||
llvmPackages.bintools
|
||||
watchexec
|
||||
|
||||
pkg-config
|
||||
openssl
|
||||
|
||||
watchexec
|
||||
|
||||
libxkbcommon
|
||||
libGL
|
||||
|
||||
|
|
172
src/main.rs
172
src/main.rs
|
@ -1,59 +1,82 @@
|
|||
use std::cell::RefCell;
|
||||
|
||||
use futures::prelude::*;
|
||||
use irc::client::prelude::*;
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use futures::StreamExt;
|
||||
use iced::theme::Theme;
|
||||
use iced::widget::{container, text};
|
||||
use iced::{executor, Application, Settings};
|
||||
use iced::widget::{column, container, scrollable, text, text_input};
|
||||
use iced::{executor, Application, Color, Length, Settings};
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::select;
|
||||
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
||||
|
||||
async fn irc_loop(sender: UnboundedSender<irc::proto::Message>) -> Result<()> {
|
||||
let config = Config {
|
||||
nickname: Some("cri".to_string()),
|
||||
server: Some("vijf.life".to_string()),
|
||||
channels: vec!["#h".to_string()],
|
||||
..Config::default()
|
||||
};
|
||||
static INPUT_ID: Lazy<text_input::Id> = Lazy::new(text_input::Id::unique);
|
||||
|
||||
let mut client = Client::from_config(config).await?;
|
||||
async fn irc_loop(
|
||||
mut client: irc::client::Client,
|
||||
message_tx: UnboundedSender<irc::proto::Message>,
|
||||
mut input_rx: UnboundedReceiver<irc::proto::Message>,
|
||||
) -> Result<()> {
|
||||
client.identify()?;
|
||||
let mut irc_stream = client.stream()?;
|
||||
|
||||
let mut stream = client.stream()?;
|
||||
|
||||
while let Some(message) = stream.next().await.transpose()? {
|
||||
sender.send(message)?;
|
||||
loop {
|
||||
select! {
|
||||
val = irc_stream.next() => {
|
||||
if let Some(message) = val.transpose()? {
|
||||
message_tx.send(message)?;
|
||||
}
|
||||
}
|
||||
val = input_rx.recv() => {
|
||||
client.send(val.unwrap())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
|
||||
let (sender, receiver) = mpsc::unbounded_channel::<irc::proto::Message>();
|
||||
let (message_tx, message_rx) = mpsc::unbounded_channel::<irc::proto::Message>();
|
||||
let (input_tx, input_rx) = mpsc::unbounded_channel::<irc::proto::Message>();
|
||||
|
||||
tokio::task::spawn(irc_loop(sender));
|
||||
let config = irc::client::prelude::Config {
|
||||
nickname: Some("cri".to_string()),
|
||||
server: Some("vijf.life".to_string()),
|
||||
channels: vec!["#h".to_string()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Cri::run(Settings::with_flags(CriFlags { receiver }))?;
|
||||
let client = irc::client::Client::from_config(config).await?;
|
||||
let task = tokio::task::spawn(irc_loop(client, message_tx, input_rx));
|
||||
|
||||
Cri::run(Settings::with_flags(CriFlags {
|
||||
message_rx,
|
||||
input_tx,
|
||||
}))?;
|
||||
|
||||
task.abort();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Cri {
|
||||
pub receiver: RefCell<Option<UnboundedReceiver<irc::proto::Message>>>,
|
||||
pub irc_message: String,
|
||||
message_rx: RefCell<Option<UnboundedReceiver<irc::proto::Message>>>,
|
||||
message_log: Vec<irc::proto::Message>,
|
||||
input_tx: RefCell<UnboundedSender<irc::proto::Message>>,
|
||||
input_value: String,
|
||||
}
|
||||
|
||||
struct CriFlags {
|
||||
pub receiver: UnboundedReceiver<irc::proto::Message>,
|
||||
pub message_rx: UnboundedReceiver<irc::proto::Message>,
|
||||
pub input_tx: UnboundedSender<irc::proto::Message>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
IrcMessageReceived(irc::proto::Message),
|
||||
InputChanged(String),
|
||||
InputSubmitted,
|
||||
}
|
||||
|
||||
impl Application for Cri {
|
||||
|
@ -65,8 +88,10 @@ impl Application for Cri {
|
|||
fn new(flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
|
||||
(
|
||||
Self {
|
||||
receiver: RefCell::new(Some(flags.receiver)),
|
||||
irc_message: String::new(),
|
||||
message_rx: RefCell::new(Some(flags.message_rx)),
|
||||
message_log: Vec::new(),
|
||||
input_tx: RefCell::new(flags.input_tx),
|
||||
input_value: String::new(),
|
||||
},
|
||||
iced::Command::none(),
|
||||
)
|
||||
|
@ -78,9 +103,17 @@ impl Application for Cri {
|
|||
|
||||
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> {
|
||||
match message {
|
||||
Message::IrcMessageReceived(message) => {
|
||||
let message = message.to_string().replace("\r", "");
|
||||
self.irc_message += &message;
|
||||
Message::IrcMessageReceived(message) => self.message_log.push(message),
|
||||
Message::InputChanged(text) => self.input_value = text,
|
||||
Message::InputSubmitted => {
|
||||
let command =
|
||||
irc::proto::Command::PRIVMSG("#h".to_string(), self.input_value.clone());
|
||||
let message: irc::proto::Message = command.into();
|
||||
|
||||
self.message_log.push(message.clone());
|
||||
self.input_tx.borrow().send(message.clone()).unwrap();
|
||||
|
||||
self.input_value.clear();
|
||||
}
|
||||
}
|
||||
iced::Command::none()
|
||||
|
@ -89,7 +122,7 @@ impl Application for Cri {
|
|||
fn subscription(&self) -> iced::Subscription<Self::Message> {
|
||||
iced::subscription::unfold(
|
||||
"irc message",
|
||||
self.receiver.take(),
|
||||
self.message_rx.take(),
|
||||
move |mut receiver| async move {
|
||||
let message = receiver.as_mut().unwrap().recv().await.unwrap();
|
||||
(Message::IrcMessageReceived(message), receiver)
|
||||
|
@ -98,7 +131,78 @@ impl Application for Cri {
|
|||
}
|
||||
|
||||
fn view(&self) -> iced::Element<'_, Self::Message, iced::Renderer<Self::Theme>> {
|
||||
let log = text(self.irc_message.clone());
|
||||
container(log).into()
|
||||
let dark_green = Color::new(0.153, 0.682, 0.377, 1.0);
|
||||
let dark_grey = Color::new(0.584, 0.647, 0.651, 1.0);
|
||||
let darker_grey = Color::new(0.498, 0.549, 0.553, 1.0);
|
||||
|
||||
let log = scrollable(column(
|
||||
self.message_log
|
||||
.iter()
|
||||
.flat_map(|message| {
|
||||
// TODO use actual nickname
|
||||
let source_nickname = message.source_nickname().unwrap_or("cri");
|
||||
|
||||
match &message.command {
|
||||
irc::proto::Command::NICK(nickname) => Some(
|
||||
text(format!(
|
||||
"* {} changed their nickname to {}",
|
||||
source_nickname, nickname,
|
||||
))
|
||||
.style(dark_green),
|
||||
),
|
||||
irc::proto::Command::JOIN(chanlist, _, _) => Some(
|
||||
text(format!(
|
||||
"[{}] * {} joined the channel",
|
||||
chanlist, source_nickname,
|
||||
))
|
||||
.style(dark_green),
|
||||
),
|
||||
irc::proto::Command::PART(chanlist, comment) => Some(
|
||||
text(format!(
|
||||
"[{}] * {} left the channel{}",
|
||||
chanlist,
|
||||
source_nickname,
|
||||
comment
|
||||
.as_ref()
|
||||
.map(|c| format!(" ({})", c))
|
||||
.unwrap_or_default(),
|
||||
))
|
||||
.style(dark_green),
|
||||
),
|
||||
irc::proto::Command::QUIT(comment) => Some(
|
||||
text(format!(
|
||||
"* {} quit the server{}",
|
||||
source_nickname,
|
||||
comment
|
||||
.as_ref()
|
||||
.map(|c| format!(" ({})", c))
|
||||
.unwrap_or_default(),
|
||||
))
|
||||
.style(dark_green),
|
||||
),
|
||||
irc::proto::Command::PRIVMSG(msgtarget, content) => Some(text(format!(
|
||||
"[{}] <{}> {}",
|
||||
message.response_target().unwrap_or(msgtarget),
|
||||
source_nickname,
|
||||
content,
|
||||
))),
|
||||
_ => None,
|
||||
//_ => text(format!("{:?}", m.command)).style(dark_grey),
|
||||
}
|
||||
})
|
||||
.map(|element| element.into())
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
.height(Length::Fill)
|
||||
.width(Length::Fill);
|
||||
|
||||
let message_box = text_input("your magnum opus", &self.input_value)
|
||||
.id(INPUT_ID.clone())
|
||||
.on_input(Message::InputChanged)
|
||||
.on_submit(Message::InputSubmitted);
|
||||
|
||||
let content = column![log, message_box].height(Length::Fill);
|
||||
|
||||
container(content).into()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue