reimplement this in python

This commit is contained in:
Michael Zhang 2020-12-06 10:27:32 -06:00
parent 4e527e5670
commit d1c23350c3
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
24 changed files with 731 additions and 431 deletions

View file

File diff suppressed because it is too large Load diff

View file

@ -17,20 +17,22 @@ name = "rocketchat"
crate-type = ["cdylib"]
[dependencies]
anyhow = "1.0.31"
futures = "0.3.5"
libc = "0.2.70"
serde = { version = "1.0.110", features = ["derive"] }
serde_json = "1.0.53"
tokio = { version = "0.2.21", features = ["full"] }
anyhow = "1.0.35"
futures = "0.3.8"
libc = "0.2.80"
serde = { version = "1.0.118", features = ["derive"] }
serde_json = "1.0.60"
tokio = { version = "0.3.5", features = ["full"] }
tokio-tls = "0.3.1"
rocketchat = { path = "rocketchat" }
tracing = "0.1.21"
tracing-subscriber = "0.2.14"
tracing = "0.1.22"
tracing-subscriber = "0.2.15"
clap = "2.33.3"
derivative = "2.1.1"
log = { version = "0.4.11", features = ["std"] }
[dependencies.weechat]
git = "https://github.com/poljar/rust-weechat"
rev = "e82d64057240997e7a5fe8dff00c29158b05efd9"
rev = "84da85b8aecf312f97b7a5cc627e29139a730918"
features = ["async", "config_macro"]

View file

@ -8,4 +8,8 @@ edition = "2018"
anyhow = "1.0.33"
parking_lot = "0.11.0"
serde = { version = "1.0.110", features = ["derive"] }
tokio-tungstenite = { version = "0.10.1", features = ["tls"] }
serde_json = "1.0.60"
derivative = "2.1.1"
tokio-tungstenite = { version = "0.12.0", features = ["tls"] }
tokio-native-tls = "0.2.0"
tokio = { version = "0.3.5", features = ["full"] }

View file

@ -0,0 +1,50 @@
use std::sync::Arc;
use anyhow::Result;
use parking_lot::Mutex;
use tokio::net::TcpStream;
use tokio_native_tls::TlsStream;
use tokio_tungstenite::{connect_async, stream::Stream, WebSocketStream};
use crate::config::Config;
type Websocket = WebSocketStream<Stream<TcpStream, TlsStream<TcpStream>>>;
#[derive(Clone, Derivative)]
#[derivative(Debug)]
pub struct Client {
config: Config,
#[derivative(Debug = "ignore")]
inner: Arc<Mutex<InnerClient>>,
}
impl Client {
pub fn new(config: Config) -> Result<Self> {
let inner = InnerClient {
login_state: LoginState::NotLoggedIn,
};
let inner = Arc::new(Mutex::new(inner));
Ok(Client { config, inner })
}
pub async fn connect(&self) -> Result<()> {
let url = format!("wss://{}/websocket", self.config.hostname);
let (wss_stream, _) = connect_async(url).await?;
let mut inner = self.inner.lock();
inner.login_state = LoginState::Connected(wss_stream);
Ok(())
}
}
pub struct InnerClient {
login_state: LoginState,
}
pub enum LoginState {
NotLoggedIn,
Connected(Websocket),
}

View file

@ -1,4 +1,4 @@
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct Config {
pub hostname: String,
}

View file

@ -1,5 +1,7 @@
#[macro_use]
extern crate serde;
#[macro_use]
extern crate derivative;
mod client;
mod config;

View file

@ -0,0 +1,80 @@
use serde_json::Value;
#[derive(Debug, Serialize, Deserialize)]
pub struct WrappedMessage {
id: String,
#[serde(flatten)]
message: Message,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "msg")]
pub enum Message {
#[serde(rename = "connect")]
Connect(Connect),
#[serde(rename = "method")]
Method(Method),
#[serde(rename = "result")]
Result(Value),
#[serde(rename = "ping")]
Ping,
#[serde(rename = "pong")]
Pong,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Connect {
pub version: String,
pub support: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "method")]
pub enum Method {
#[serde(rename = "login")]
Login(Login),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Login {
pub params: Vec<LoginParam>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LoginParam {
UserPassword {
user: LoginUser,
password: LoginPassword,
},
Ldap {
ldap: bool,
username: String,
ldapPass: String,
ldapOptions: LdapOptions,
},
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LdapOptions {}
#[derive(Debug, Serialize, Deserialize)]
pub struct LoginUser {
pub username: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LoginPassword {
pub digest: String,
pub algorithm: PasswordAlgorithm,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum PasswordAlgorithm {
#[serde(rename = "sha-256")]
Sha256,
}

View file

@ -24,8 +24,10 @@ impl RocketchatCommand {
) -> Result<Command, ()> {
let settings = CommandSettings::new("rocketchat")
.description("Rocketchat protocol command")
.add_argument("server add <server-name> <hostname>")
.add_completion("server |add");
.add_argument("server add <friendly-name> <host-name>")
.add_argument("connect <friendly-name>")
.add_completion("server |add")
.add_completion("connect");
Command::new(
settings,
@ -36,6 +38,26 @@ impl RocketchatCommand {
)
}
fn connect_command(&self, args: &ArgMatches) {
let server_names = args
.values_of("name")
.expect("server names not set but were required");
let mut servers = self.servers.borrow_mut();
for server_name in server_names {
let server = servers.get_mut(server_name);
if let Some(s) = server {
match s.connect() {
Ok(_) => (),
Err(e) => Weechat::print(&format!("{:?}", e)),
}
} else {
Weechat::print(&format!("server not found: {}", server_name));
}
}
}
fn server_command(&self, args: &ArgMatches) {
match args.subcommand() {
("add", Some(subargs)) => self.add_server(subargs),
@ -91,7 +113,17 @@ impl CommandCallback for RocketchatCommand {
.global_setting(ArgParseSettings::DisableVersion)
.global_setting(ArgParseSettings::VersionlessSubcommands)
.setting(ArgParseSettings::SubcommandRequiredElseHelp)
.subcommand(server_command);
.subcommand(server_command)
.subcommand(
SubCommand::with_name("connect")
.about("connect to rocketchat servers")
.arg(
Arg::with_name("name")
.value_name("server-name")
.required(true)
.multiple(true),
),
);
let matches = match argparse.get_matches_from_safe(args) {
Ok(m) => m,
@ -110,6 +142,7 @@ impl CommandCallback for RocketchatCommand {
match matches.subcommand() {
("server", Some(subargs)) => self.server_command(subargs),
("connect", Some(subargs)) => self.connect_command(subargs),
_ => unreachable!(),
}
}

View file

@ -12,11 +12,8 @@ use weechat::{
use crate::servers::Servers;
config! {
"matrix-rust",
Section look {
},
Section network {
"rocketchat",
Section server {
},
}
@ -30,6 +27,7 @@ pub struct ConfigHandle {
impl ConfigHandle {
pub fn new(servers: &Servers) -> Self {
let config = Config::new().expect("can't create new config");
let config = ConfigHandle {
inner: Rc::new(RefCell::new(config)),
servers: servers.clone(),
@ -37,9 +35,14 @@ impl ConfigHandle {
let server_section_options = ConfigSectionSettings::new("server")
.set_write_callback(
|_: &Weechat, config: &Conf, section: &mut ConfigSection| {},
);
// .set_read_callback(config.clone());
|_: &Weechat, config: &Conf, section: &mut ConfigSection| {
config.write_section(section.name());
for option in section.options() {
config.write_option(option);
}
},
)
.set_read_callback(config.clone());
{
let mut config_borrow = config.borrow_mut();
@ -73,6 +76,8 @@ impl SectionReadCallback for ConfigHandle {
return OptionChanged::Error;
}
weechat::Weechat::print(&format!("looking for {}", option_name));
match section.search_option(option_name) {
Some(o) => o.set(option_value, true),
None => OptionChanged::NotFound,

View file

@ -1,13 +1,15 @@
use std::cell::RefCell;
use std::future::Future;
use std::rc::Rc;
use std::rc::{Rc, Weak};
use rocketchat::Client;
use tokio::{
runtime::Runtime,
sync::mpsc::{self, Receiver, Sender},
};
use weechat::Weechat;
use crate::server::RocketchatServer;
use crate::server::{InnerServer, RocketchatServer};
pub struct Connection {
client: Client,
@ -18,6 +20,11 @@ impl Connection {
pub fn new(server: &RocketchatServer, client: &Client) -> Self {
let (tx, rx) = mpsc::channel(1000);
Weechat::spawn(Connection::response_receiver(
rx,
server.clone_inner_weak(),
));
let runtime = Runtime::new().unwrap();
runtime.spawn(Connection::sync_loop(client.clone(), tx));
@ -43,6 +50,12 @@ impl Connection {
channel: Sender<Result<ClientMessage, String>>,
) {
}
pub async fn response_receiver(
receiver: Receiver<Result<ClientMessage, String>>,
server: Weak<RefCell<InnerServer>>,
) {
}
}
pub enum ClientMessage {}

View file

@ -28,7 +28,7 @@ impl Debug {
let mut debug_buffer = matrix.debug_buffer.borrow_mut();
if false {
if true {
// matrix.config.borrow().network().debug_buffer() {
let buffer = if let Some(buffer) = debug_buffer.as_ref() {
if let Ok(buffer) = buffer.upgrade() {

View file

@ -1,17 +1,24 @@
#[macro_use]
extern crate weechat;
#[macro_use]
extern crate log;
#[macro_use]
extern crate derivative;
mod commands;
mod config;
mod connection;
mod debug;
mod logger;
mod server;
mod servers;
use std::cell::RefCell;
use std::collections::HashMap;
use tokio::sync::mpsc;
use weechat::{buffer::BufferHandle, Args, Plugin, Weechat};
use tracing::{event, Level};
use crate::commands::Commands;
use crate::config::ConfigHandle;
@ -53,6 +60,10 @@ impl Plugin for Rocketchat {
let config = ConfigHandle::new(&servers);
let commands = Commands::hook_all(&servers, &config)?;
// let (tx, rx) = mpsc::unbounded_channel();
// logger::init(5, tx);
// Weechat::spawn(logger::log_loop(rx)).detach();
tracing_subscriber::fmt()
.with_writer(debug::Debug)
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())

53
old/src/logger.rs Normal file
View file

@ -0,0 +1,53 @@
use log::{LevelFilter, Log, Metadata, Record};
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use weechat::Weechat;
#[derive(Debug)]
pub enum Action {
Print(String),
}
struct Wrapper(LevelFilter, UnboundedSender<Action>);
pub fn init(verbosity: usize, tx: UnboundedSender<Action>) {
let verbosity = match verbosity {
0 => LevelFilter::Error,
1 => LevelFilter::Warn,
2 => LevelFilter::Info,
3 => LevelFilter::Debug,
_ => LevelFilter::Trace,
};
let wrapper = Wrapper(verbosity, tx);
log::set_max_level(LevelFilter::max());
log::set_boxed_logger(Box::new(wrapper)).unwrap();
}
pub async fn log_loop(mut rx: UnboundedReceiver<Action>) {
while let Some(next) = rx.recv().await {
match next {
Action::Print(s) => Weechat::print(&s),
}
}
}
impl Log for Wrapper {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= self.0
}
fn log(&self, record: &Record) {
if !self.enabled(record.metadata()) {
return;
}
if let Some(module) = record.module_path() {
let string =
format!("{}[{}] - {}", module, record.level(), record.args());
self.1.send(Action::Print(string)).unwrap();
}
}
fn flush(&self) {}
}

View file

@ -1,13 +1,17 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::rc::{Rc, Weak};
use anyhow::Result;
use rocketchat::Client;
use weechat::config::{ConfigSection, StringOptionSettings};
use weechat::{
config::{ConfigSection, StringOptionSettings},
Weechat,
};
use crate::config::ConfigHandle;
use crate::connection::Connection;
#[derive(Debug)]
pub struct RocketchatServer {
server_name: Rc<String>,
inner: Rc<RefCell<InnerServer>>,
@ -40,6 +44,10 @@ impl RocketchatServer {
self.inner.borrow().is_connected()
}
pub fn clone_inner_weak(&self) -> Weak<RefCell<InnerServer>> {
Rc::downgrade(&self.inner)
}
pub fn connect(&self) -> Result<()> {
if self.is_connected() {
return Ok(());
@ -82,11 +90,15 @@ impl RocketchatServer {
}
}
#[derive(Derivative)]
#[derivative(Debug)]
pub struct InnerServer {
client: Option<Client>,
login_state: Option<LoginInfo>,
connection: Rc<RefCell<Option<Connection>>>,
settings: ServerSettings,
#[derivative(Debug = "ignore")]
connection: Rc<RefCell<Option<Connection>>>,
}
impl InnerServer {
@ -99,7 +111,6 @@ impl InnerServer {
use rocketchat::Config;
let config = Config { hostname };
let client = Client::new(config)?;
Ok(client)
@ -115,9 +126,10 @@ impl InnerServer {
}
}
#[derive(Debug)]
pub struct LoginInfo {}
#[derive(Default)]
#[derive(Default, Debug)]
pub struct ServerSettings {
hostname: String,
}

178
rocketchat.py Normal file
View file

@ -0,0 +1,178 @@
#!/usr/bin/env python3
try:
import weechat
except:
print("This script must be run under WeeChat.")
print("Get WeeChat now at: http://www.weechat.org/")
import sys
sys.exit(1)
import asyncio
import websockets
import ssl
# globals
rocketchat_servers = []
rocketchat_config_file = None
rocketchat_config_section = {}
class RocketchatServer:
def __init__(self, name, hostname):
self.name = name
self.buffer = ""
self.client = None
self._counter = 0
options = dict(
hostname=hostname,
autoconnect="on",
autoreconnect="on",
)
self.options = dict()
for key, value in options.items():
self.options[key] = weechat.config_new_option(
rocketchat_config_file,
rocketchat_config_section["server"],
f"{self.name}.{key}",
"string", "", "", 0, 99999999,
"", value, 0, "", self.name, "", "", "", "",
)
async def connect(self):
if not self.buffer:
bufname = f"rc:{self.name}"
self.buffer = weechat.buffer_search("python", bufname)
if not self.buffer:
self.buffer = weechat.buffer_new(bufname, "", "", "", "")
self.disconnect()
# connect to the server
hostname = weechat.config_string(self.options["hostname"])
uri = f"wss://{hostname}/websocket"
weechat.prnt("", f"connecting to: {uri}")
self.client = await websockets.connect(uri, ssl=True)
weechat.prnt(self.buffer, f"rocketchat: connected to {self.name}")
# run the listener in a loop
asyncio.create_task(self.main_loop())
async def main_loop(self):
weechat.prnt(self.buffer, "starting to receive!")
while True:
g = await self.client.recv()
weechat.prnt(self.buffer, f"received '{g}'")
def disconnect(self):
# TODO:
pass
def is_connected(self):
if self.client is not None and self.client.open:
return True
return False
def init():
weechat.hook_command(
"rocketchat",
"Rocketchat",
"list || server || connect",
"bruh",
"list %(weechat_servers)"
" || server add|del"
" || connect %(weechat_servers)",
"rocketchat_main_command", ""
)
weechat.hook_completion("rocketchat_servers", "list of rocketchat servers",
"rocketchat_complete_servers", "")
global rocketchat_config_file, rocketchat_config_section
rocketchat_config_file = weechat.config_new("rocketchat", "rocketchat_reload_config", "")
if not rocketchat_config_file:
return
rocketchat_config_section["server"] = weechat.config_new_section(
rocketchat_config_file, "server", 0, 0,
"rocketchat_config_servers_read", "",
"rocketchat_config_servers_write", "",
"", "", "", "", "", "",
)
def rocketchat_main_command(data, buffer, args):
global rocketchat_servers
if args == "" or args == "list":
pass # TODO: list
return weechat.WEECHAT_RC_OK
argv = args.split(" ")
if argv[0] == "server":
if argv[1] == "add":
name = argv[2]
hostname = argv[3]
server = RocketchatServer(name, hostname)
rocketchat_servers.append(server)
weechat.prnt("", f"server '{name}' added!")
elif argv[0] == "connect":
for name in argv[1:]:
server = rocketchat_search_server_by_name(name)
if server:
asyncio.get_event_loop().run_until_complete(server.connect())
else:
weechat.prnt("", f"could not find server name '{name}'")
return weechat.WEECHAT_RC_OK
def rocketchat_reload_config(data, config_file):
return weechat.config_reload(config_file)
def rocketchat_config_servers_read(data, config_file, section, option_name, value):
global rocketchat_servers
rc = weechat.WEECHAT_CONFIG_OPTION_SET_ERROR
items = option_name.split(".", 1)
if len(items) == 2:
server = rocketchat_search_server_by_name(items[0])
if not server:
server = RocketchatServer(items[0])
rocketchat_servers.append(server)
if server:
rc = weechat.config_option_set(server.options[items[1]], value, 1)
return rc
def rocketchat_config_servers_write(data, config_file, section_name):
global rocketchat_servers
weechat.config_write_line(config_file, section_name, "")
for server in rocketchat_servers:
for name, option in sorted(server.options.items()):
weechat.config_write_option(config_file, option)
return weechat.WEECHAT_RC_OK
def rocketchat_save_config():
global rocketchat_config_file
return weechat.config_write(rocketchat_config_file)
def rocketchat_search_server_by_name(name):
global rocketchat_servers
for server in rocketchat_servers:
if server.name == name:
return server
return None
def rocketchat_complete_servers(data, completion_item, buffer, completion):
global rocketchat_servers
for server in rocketchat_servers:
weechat.hook_completion_list_add(completion, server.name,
0, weechat.WEECHAT_LIST_POS_SORT)
return weechat.WEECHAT_RC_OK
def rocketchat_unload():
global rocketchat_servers
rocketchat_save_config()
return weechat.WEECHAT_RC_OK
if weechat.register(
"rocketchat",
"Michael Zhang <mail@mzhang.io>",
"0.1.0",
"MIT",
"Rocketchat",
"rocketchat_unload",
"utf-8",
):
init()

View file

@ -1,34 +0,0 @@
use std::sync::Arc;
use anyhow::Result;
use parking_lot::Mutex;
use crate::config::Config;
#[derive(Clone)]
pub struct Client {
config: Config,
inner: Arc<Mutex<InnerClient>>,
}
impl Client {
pub fn new(config: Config) -> Result<Self> {
let inner = InnerClient {
login_state: LoginState::NotLoggedIn,
};
let inner = Arc::new(Mutex::new(inner));
Ok(Client { config, inner })
}
pub async fn connect() {}
}
pub struct InnerClient {
login_state: LoginState,
}
#[derive(Clone)]
pub enum LoginState {
NotLoggedIn,
}

View file

@ -1,49 +0,0 @@
#[derive(Debug, Deserialize)]
#[serde(tag = "msg")]
pub enum RocketchatResponse {
#[serde(rename = "connected")]
Connected { session: String },
#[serde(rename = "ping")]
Ping,
}
#[derive(Debug, Serialize)]
#[serde(tag = "msg")]
pub enum RocketchatRequest {
#[serde(rename = "method")]
Method(RequestMethod),
#[serde(rename = "pong")]
Pong,
}
#[derive(Debug, Serialize)]
#[serde(tag = "method")]
pub enum RequestMethod {
#[serde(rename = "login")]
Login { params: Vec<LoginMethod> },
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum LoginMethod {
UserPass { user: User, password: Password },
}
#[derive(Debug, Serialize)]
pub struct User {
pub username: String,
}
#[derive(Debug, Serialize)]
pub struct Password {
pub digest: String,
pub algorithm: Algorithm,
}
#[derive(Debug, Serialize)]
pub enum Algorithm {
#[serde(rename = "sha-256")]
Sha256,
}