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",
|
"futures",
|
||||||
"iced",
|
"iced",
|
||||||
"irc",
|
"irc",
|
||||||
|
"once_cell",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -10,4 +10,5 @@ color-eyre = "0.6.2"
|
||||||
futures = "0.3.29"
|
futures = "0.3.29"
|
||||||
iced = { version = "0.10.0", features = ["tokio"] }
|
iced = { version = "0.10.0", features = ["tokio"] }
|
||||||
irc = "0.15.0"
|
irc = "0.15.0"
|
||||||
|
once_cell = "1.18.0"
|
||||||
tokio = { version = "1.33.0", features = ["full"] }
|
tokio = { version = "1.33.0", features = ["full"] }
|
||||||
|
|
|
@ -2,12 +2,15 @@
|
||||||
pkgs.mkShell rec {
|
pkgs.mkShell rec {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
cargo
|
cargo
|
||||||
|
clippy
|
||||||
clang
|
clang
|
||||||
llvmPackages.bintools
|
llvmPackages.bintools
|
||||||
watchexec
|
|
||||||
pkg-config
|
pkg-config
|
||||||
openssl
|
openssl
|
||||||
|
|
||||||
|
watchexec
|
||||||
|
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
libGL
|
libGL
|
||||||
|
|
||||||
|
|
172
src/main.rs
172
src/main.rs
|
@ -1,59 +1,82 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use futures::prelude::*;
|
|
||||||
use irc::client::prelude::*;
|
|
||||||
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
|
use futures::StreamExt;
|
||||||
use iced::theme::Theme;
|
use iced::theme::Theme;
|
||||||
use iced::widget::{container, text};
|
use iced::widget::{column, container, scrollable, text, text_input};
|
||||||
use iced::{executor, Application, Settings};
|
use iced::{executor, Application, Color, Length, Settings};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use tokio::select;
|
||||||
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
||||||
|
|
||||||
async fn irc_loop(sender: UnboundedSender<irc::proto::Message>) -> Result<()> {
|
static INPUT_ID: Lazy<text_input::Id> = Lazy::new(text_input::Id::unique);
|
||||||
let config = Config {
|
|
||||||
nickname: Some("cri".to_string()),
|
|
||||||
server: Some("vijf.life".to_string()),
|
|
||||||
channels: vec!["#h".to_string()],
|
|
||||||
..Config::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
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()?;
|
client.identify()?;
|
||||||
|
let mut irc_stream = client.stream()?;
|
||||||
|
|
||||||
let mut stream = client.stream()?;
|
loop {
|
||||||
|
select! {
|
||||||
while let Some(message) = stream.next().await.transpose()? {
|
val = irc_stream.next() => {
|
||||||
sender.send(message)?;
|
if let Some(message) = val.transpose()? {
|
||||||
|
message_tx.send(message)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val = input_rx.recv() => {
|
||||||
|
client.send(val.unwrap())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
color_eyre::install()?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Cri {
|
struct Cri {
|
||||||
pub receiver: RefCell<Option<UnboundedReceiver<irc::proto::Message>>>,
|
message_rx: RefCell<Option<UnboundedReceiver<irc::proto::Message>>>,
|
||||||
pub irc_message: String,
|
message_log: Vec<irc::proto::Message>,
|
||||||
|
input_tx: RefCell<UnboundedSender<irc::proto::Message>>,
|
||||||
|
input_value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CriFlags {
|
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 {
|
enum Message {
|
||||||
IrcMessageReceived(irc::proto::Message),
|
IrcMessageReceived(irc::proto::Message),
|
||||||
|
InputChanged(String),
|
||||||
|
InputSubmitted,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Cri {
|
impl Application for Cri {
|
||||||
|
@ -65,8 +88,10 @@ impl Application for Cri {
|
||||||
fn new(flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
|
fn new(flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
receiver: RefCell::new(Some(flags.receiver)),
|
message_rx: RefCell::new(Some(flags.message_rx)),
|
||||||
irc_message: String::new(),
|
message_log: Vec::new(),
|
||||||
|
input_tx: RefCell::new(flags.input_tx),
|
||||||
|
input_value: String::new(),
|
||||||
},
|
},
|
||||||
iced::Command::none(),
|
iced::Command::none(),
|
||||||
)
|
)
|
||||||
|
@ -78,9 +103,17 @@ impl Application for Cri {
|
||||||
|
|
||||||
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> {
|
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::IrcMessageReceived(message) => {
|
Message::IrcMessageReceived(message) => self.message_log.push(message),
|
||||||
let message = message.to_string().replace("\r", "");
|
Message::InputChanged(text) => self.input_value = text,
|
||||||
self.irc_message += &message;
|
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()
|
iced::Command::none()
|
||||||
|
@ -89,7 +122,7 @@ impl Application for Cri {
|
||||||
fn subscription(&self) -> iced::Subscription<Self::Message> {
|
fn subscription(&self) -> iced::Subscription<Self::Message> {
|
||||||
iced::subscription::unfold(
|
iced::subscription::unfold(
|
||||||
"irc message",
|
"irc message",
|
||||||
self.receiver.take(),
|
self.message_rx.take(),
|
||||||
move |mut receiver| async move {
|
move |mut receiver| async move {
|
||||||
let message = receiver.as_mut().unwrap().recv().await.unwrap();
|
let message = receiver.as_mut().unwrap().recv().await.unwrap();
|
||||||
(Message::IrcMessageReceived(message), receiver)
|
(Message::IrcMessageReceived(message), receiver)
|
||||||
|
@ -98,7 +131,78 @@ impl Application for Cri {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> iced::Element<'_, Self::Message, iced::Renderer<Self::Theme>> {
|
fn view(&self) -> iced::Element<'_, Self::Message, iced::Renderer<Self::Theme>> {
|
||||||
let log = text(self.irc_message.clone());
|
let dark_green = Color::new(0.153, 0.682, 0.377, 1.0);
|
||||||
container(log).into()
|
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