use crate::{c, from_cstring_opt, to_cstring, Context};
use bitflags::bitflags;
use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
use std::marker::PhantomData;
use std::mem;
use std::net::Ipv4Addr;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::ptr;
struct XList<T>
where
T: FromXList,
{
ph: *mut c::hexchat_plugin,
handle: *mut c::hexchat_list,
_ref: PhantomData<c::hexchat_list>,
_item: PhantomData<T>,
}
impl<T> Drop for XList<T>
where
T: FromXList,
{
fn drop(&mut self) {
unsafe {
c!(hexchat_list_free, self.ph, self.handle);
}
}
}
trait FromXList
where
Self: Sized,
{
const LIST_NAME: &'static str;
fn map_list(list: &XList<Self>) -> Self;
}
impl<T> XList<T>
where
T: FromXList,
{
fn new(ph: *mut c::hexchat_plugin) -> Self {
let name = to_cstring(T::LIST_NAME);
Self {
ph,
handle: unsafe { c!(hexchat_list_get, ph, name.as_ptr()) },
_ref: PhantomData,
_item: PhantomData,
}
}
fn move_next(&mut self) -> bool {
unsafe { c!(hexchat_list_next, self.ph, self.handle) != 0 }
}
fn get_item_string(&self, field: &str) -> Option<String> {
unsafe {
let cstr = to_cstring(field);
let ptr = c!(hexchat_list_str, self.ph, self.handle, cstr.as_ptr());
from_cstring_opt(ptr)
}
}
fn get_item_int(&self, field: &str) -> i32 {
unsafe {
let cstr = to_cstring(field);
c!(hexchat_list_int, self.ph, self.handle, cstr.as_ptr()) as _
}
}
fn get_item_time(&self, field: &str) -> DateTime<Utc> {
unsafe {
let cstr = to_cstring(field);
let time = c!(hexchat_list_time, self.ph, self.handle, cstr.as_ptr());
let naive = NaiveDateTime::from_timestamp(time as _, 0);
Utc.from_utc_datetime(&naive)
}
}
fn get_item_context(&self, field: &str) -> *mut c::hexchat_context {
unsafe {
let cstr = to_cstring(field);
let ptr = c!(hexchat_list_str, self.ph, self.handle, cstr.as_ptr());
ptr as *mut _
}
}
fn get_item_char(&self, field: &str) -> char {
unsafe {
let cstr = to_cstring(field);
let ptr = c!(hexchat_list_str, self.ph, self.handle, cstr.as_ptr());
if ptr.is_null() {
'\0'
} else {
mem::transmute::<_, u8>(*ptr) as _
}
}
}
fn get_current(&self) -> T {
T::map_list(self)
}
}
impl<T> Iterator for XList<T>
where
T: FromXList,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let res = unsafe { c!(hexchat_list_next, self.ph, self.handle) };
if res == 0 {
Some(self.get_current())
} else {
None
}
}
}
pub struct ChannelInfo {
channel_name: String,
channel_key: Option<String>,
channel_modes: String,
channel_types: String,
cref: ChannelRef,
flags: ChannelFlags,
id: i32,
lag: u32,
max_modes: u32,
network_name: String,
nick_prefixes: String,
nick_modes: String,
send_queue_size: u32,
server_name: String,
channel_type: ChannelType,
user_count: u32,
}
impl ChannelInfo {
pub fn get_name(&self) -> &str {
&self.channel_name
}
pub fn get_key(&self) -> Option<&str> {
self.channel_key.as_ref().map(|x| &**x)
}
pub fn get_mode_string(&self) -> &str {
&self.channel_modes
}
pub fn get_type_string(&self) -> &str {
&self.channel_types
}
pub fn get_flags(&self) -> ChannelFlags {
self.flags
}
pub fn get_id(&self) -> i32 {
self.id
}
pub fn get_lag_ms(&self) -> u32 {
self.lag
}
pub fn get_max_modes_per_line(&self) -> u32 {
self.max_modes
}
pub fn get_network_name(&self) -> &str {
&self.network_name
}
pub fn get_nick_prefix_string(&self) -> &str {
&self.nick_prefixes
}
pub fn get_nick_mode_string(&self) -> &str {
&self.nick_modes
}
pub fn get_send_queue_size(&self) -> u32 {
self.send_queue_size
}
pub fn get_server_name(&self) -> &str {
&self.server_name
}
pub fn get_type(&self) -> ChannelType {
self.channel_type
}
pub fn get_user_count(&self) -> u32 {
self.user_count
}
}
impl FromXList for ChannelInfo {
const LIST_NAME: &'static str = "channels";
fn map_list(list: &XList<Self>) -> Self {
Self {
channel_name: list.get_item_string("channel").unwrap_or_default(),
channel_key: list.get_item_string("channelkey"),
channel_modes: list.get_item_string("chanmodes").unwrap_or_default(),
channel_types: list.get_item_string("chantypes").unwrap_or_default(),
cref: ChannelRef {
handle: list.get_item_context("context"),
ph: list.ph,
},
flags: ChannelFlags::from_bits_truncate(list.get_item_int("flags") as _),
id: list.get_item_int("id"),
lag: list.get_item_int("lag") as _,
max_modes: list.get_item_int("maxmodes") as _,
network_name: list.get_item_string("network").unwrap_or_default(),
nick_prefixes: list.get_item_string("nickprefixes").unwrap_or_default(),
nick_modes: list.get_item_string("nickmodes").unwrap_or_default(),
send_queue_size: list.get_item_int("queue") as _,
server_name: list.get_item_string("server").unwrap_or_default(),
channel_type: ChannelType::VALUES[list.get_item_int("type") as usize + 1],
user_count: list.get_item_int("users") as _,
}
}
}
bitflags! {
pub struct ChannelFlags: u32 {
const CONNECTED = 1;
const CONNECTING = 1 << 1;
const AWAY = 1 << 2;
const MOTD_END = 1 << 3;
const WHOX = 1 << 4;
const IDMSG = 1 << 5;
const HIDE_JOINS_AND_PARTS = 1 << 6;
const HIDE_JOINS_AND_PARTS_UNSET = 1 << 7;
const BEEP_ON_MESSAGE = 1 << 8;
const BLINK_TRAY = 1 << 9;
const BLINK_TASKBAR = 1 << 10;
const LOGGING = 1 << 11;
const LOGGING_UNSET = 1 << 12;
const SCROLLBACK = 1 << 13;
const SCROLLBACK_UNSET = 1 << 14;
const STRIP_COLORS = 1 << 15;
const STRIP_COLORS_UNSET = 1 << 16;
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ChannelType {
Server,
Channel,
Dialog,
Notice,
SNotice,
}
impl ChannelType {
const VALUES: [Self; 5] = [
Self::Server,
Self::Channel,
Self::Dialog,
Self::Notice,
Self::SNotice,
];
}
pub struct DccTransferInfo {
address: Ipv4Addr,
bytes_per_second: u32,
destination: PathBuf,
filename: String,
sender_nick: String,
port: u16,
bytes_processed: u64,
resume_point: Option<u64>,
file_size: u64,
status: DccTransferStatus,
transfer_type: DccTransferType,
}
impl DccTransferInfo {
pub fn get_address(&self) -> Ipv4Addr {
self.address
}
pub fn get_bytes_per_second(&self) -> u32 {
self.bytes_per_second
}
pub fn get_destination(&self) -> &Path {
&self.destination
}
pub fn get_filename(&self) -> &str {
&self.filename
}
pub fn get_sender_nick(&self) -> &str {
&self.sender_nick
}
pub fn get_port(&self) -> u16 {
self.port
}
pub fn get_bytes_processed(&self) -> u64 {
self.bytes_processed
}
pub fn get_resume_point(&self) -> Option<u64> {
self.resume_point
}
pub fn get_file_size(&self) -> u64 {
self.file_size
}
pub fn get_status(&self) -> DccTransferStatus {
self.status
}
pub fn get_transfer_type(&self) -> DccTransferType {
self.transfer_type
}
}
impl FromXList for DccTransferInfo {
const LIST_NAME: &'static str = "dcc";
fn map_list(list: &XList<Self>) -> Self {
let bytes_processed_low = list.get_item_int("pos");
let bytes_processed_high = list.get_item_int("poshigh");
let bytes_processed = merge_unsigned(bytes_processed_low, bytes_processed_high);
let resume_point_low = list.get_item_int("resume");
let resume_point = if resume_point_low == 0 {
None
} else {
let resume_point_high = list.get_item_int("resumehigh");
Some(merge_unsigned(resume_point_low, resume_point_high))
};
let file_size_low = list.get_item_int("size");
let file_size_high = list.get_item_int("sizehigh");
let file_size = merge_unsigned(file_size_low, file_size_high);
Self {
address: Ipv4Addr::from(unsafe {
mem::transmute::<_, u32>(list.get_item_int("address32"))
}),
bytes_per_second: list.get_item_int("cps") as _,
destination: list
.get_item_string("destfile")
.map(PathBuf::from)
.unwrap_or_default(),
filename: list.get_item_string("file").unwrap_or_default(),
sender_nick: list.get_item_string("nick").unwrap_or_default(),
port: list.get_item_int("port") as _,
bytes_processed,
resume_point,
file_size,
status: DccTransferStatus::VALUES[list.get_item_int("status") as usize],
transfer_type: DccTransferType::VALUES[list.get_item_int("type") as usize],
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum DccTransferStatus {
Queued,
Active,
Failed,
Done,
Connecting,
Aborted,
}
impl DccTransferStatus {
const VALUES: [Self; 6] = [
Self::Queued,
Self::Active,
Self::Failed,
Self::Done,
Self::Connecting,
Self::Aborted,
];
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum DccTransferType {
Send,
Receive,
ChatReceive,
ChatSend,
}
impl DccTransferType {
const VALUES: [Self; 4] = [Self::Send, Self::Receive, Self::ChatReceive, Self::ChatSend];
}
pub struct IgnoreEntry {
mask: String,
ignore_type: IgnoreType,
}
impl IgnoreEntry {
pub fn get_mask(&self) -> &str {
&self.mask
}
pub fn get_ignore_type(&self) -> IgnoreType {
self.ignore_type
}
}
impl FromXList for IgnoreEntry {
const LIST_NAME: &'static str = "ignore";
fn map_list(list: &XList<Self>) -> Self {
Self {
mask: list.get_item_string("mask").unwrap_or_default(),
ignore_type: IgnoreType::from_bits_truncate(list.get_item_int("flags")),
}
}
}
bitflags! {
pub struct IgnoreType: i32 {
const PRIVATE = 1;
const NOTICE = 1 << 1;
const CHANNEL = 1 << 2;
const CTCP = 1 << 3;
const INVITE = 1 << 4;
const UNIGNORE = 1 << 5;
const NO_SAVE = 1 << 6;
const DCC = 1 << 7;
}
}
pub struct NotifyEntry {
networks: Vec<String>,
nick: String,
is_online: bool,
time_online: DateTime<Utc>,
time_offline: DateTime<Utc>,
last_seen: DateTime<Utc>,
}
impl NotifyEntry {
pub fn get_networks(&self) -> &[String] {
&self.networks
}
pub fn get_nick(&self) -> &str {
&self.nick
}
pub fn is_online(&self) -> bool {
self.is_online
}
pub fn get_time_online(&self) -> DateTime<Utc> {
self.time_online
}
pub fn get_time_offline(&self) -> DateTime<Utc> {
self.time_offline
}
pub fn get_time_last_seen(&self) -> DateTime<Utc> {
self.last_seen
}
}
impl FromXList for NotifyEntry {
const LIST_NAME: &'static str = "notify";
fn map_list(list: &XList<Self>) -> Self {
Self {
networks: list
.get_item_string("networks")
.map(|t| t.split(',').map(String::from).collect())
.unwrap_or_default(),
nick: list.get_item_string("nick").unwrap_or_default(),
is_online: list.get_item_int("flags") == 0,
time_online: list.get_item_time("on"),
time_offline: list.get_item_time("off"),
last_seen: list.get_item_time("seen"),
}
}
}
pub struct UserInfo {
account_name: Option<String>,
away: bool,
last_posted: DateTime<Utc>,
nick: String,
host: String,
prefix: char,
real_name: Option<String>,
is_selected: bool,
}
impl UserInfo {
pub fn get_account_name(&self) -> Option<&str> {
self.account_name.as_ref().map(|x| &**x)
}
pub fn is_away(&self) -> bool {
self.away
}
pub fn get_time_last_posted(&self) -> DateTime<Utc> {
self.last_posted
}
pub fn get_nick(&self) -> &str {
&self.nick
}
pub fn get_host_string(&self) -> &str {
&self.host
}
pub fn get_prefix(&self) -> char {
self.prefix
}
pub fn get_real_name(&self) -> Option<&str> {
self.real_name.as_ref().map(|x| &**x)
}
pub fn is_selected(&self) -> bool {
self.is_selected
}
}
impl FromXList for UserInfo {
const LIST_NAME: &'static str = "users";
fn map_list(list: &XList<Self>) -> Self {
Self {
account_name: list.get_item_string("account"),
away: list.get_item_int("away") != 0,
last_posted: list.get_item_time("lasttalk"),
nick: list.get_item_string("nick").unwrap_or_default(),
host: list.get_item_string("host").unwrap_or_default(),
prefix: list.get_item_char("prefix"),
real_name: list.get_item_string("realname"),
is_selected: list.get_item_int("selected") != 0,
}
}
}
impl Context {
pub fn get_all_channels(&self) -> impl Iterator<Item = ChannelInfo> {
XList::new(self.handle)
}
pub fn get_current_dcc_transfers(&self) -> impl Iterator<Item = DccTransferInfo> {
XList::new(self.handle)
}
pub fn get_ignore_entries(&self) -> impl Iterator<Item = IgnoreEntry> {
XList::new(self.handle)
}
pub fn get_notify_users(&self) -> impl Iterator<Item = NotifyEntry> {
XList::new(self.handle)
}
pub fn get_users_in_current_channel(&self) -> impl Iterator<Item = UserInfo> {
XList::new(self.handle)
}
pub fn get_users_in_channel(
&self,
channel: &ChannelRef,
) -> Option<impl Iterator<Item = UserInfo>> {
unsafe {
let context = Self { handle: channel.ph };
let ctx = c!(hexchat_get_context, channel.ph);
if c!(hexchat_set_context, channel.ph, channel.handle) == 0 {
None
} else {
let list = context.get_users_in_current_channel();
if c!(hexchat_set_context, channel.ph, ctx) == 0 {
c!(
hexchat_set_context,
channel.ph,
c!(hexchat_find_context, channel.ph, ptr::null(), ptr::null()),
);
}
Some(list)
}
}
}
}
fn merge_unsigned(low: i32, high: i32) -> u64 {
let [b0, b1, b2, b3] = high.to_be_bytes();
let [b4, b5, b6, b7] = low.to_be_bytes();
u64::from_be_bytes([b0, b1, b2, b3, b4, b5, b6, b7])
}
pub struct ChannelRef {
pub(crate) ph: *mut c::hexchat_plugin,
pub(crate) handle: *mut c::hexchat_context,
}
impl ChannelRef {
pub fn into_info(self) -> Option<ChannelInfo> {
let mut list = XList::new(self.ph);
while list.move_next() {
if list.get_item_context("context") == self.handle {
return Some(list.get_current());
}
}
None
}
}
impl Deref for ChannelInfo {
type Target = ChannelRef;
fn deref(&self) -> &Self::Target {
&self.cref
}
}