gatewayd: a turn's speaker, the username of the one person who wrote its burst
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
@@ -19,6 +19,7 @@ use tmp::TempDir;
|
||||
|
||||
const DM: &str = "d0000000000000000000000000";
|
||||
const ROOT: &str = "r0000000000000000000000000";
|
||||
const KYLE: &str = "k0000000000000000000000000";
|
||||
|
||||
#[derive(Default)]
|
||||
struct Record {
|
||||
@@ -59,6 +60,7 @@ fn batch(resume: bool, text: &str) -> Batch {
|
||||
},
|
||||
resume,
|
||||
text: text.to_string(),
|
||||
senders: vec![KYLE.to_string()],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +75,7 @@ fn run(dir: &TempDir, poster: &Record, batch: &Batch) -> Vec<String> {
|
||||
poster,
|
||||
&dir.path().join("loop.sock"),
|
||||
batch,
|
||||
None,
|
||||
&|line| log.lock().unwrap().push(line.to_string()),
|
||||
¬ice,
|
||||
);
|
||||
|
||||
@@ -68,6 +68,7 @@ fn posts(tag: &str, reply: fn(&str) -> Reply, content: &'static str) -> Vec<Stri
|
||||
},
|
||||
resume: false,
|
||||
text: "hi".to_string(),
|
||||
senders: vec!["k0000000000000000000000000".to_string()],
|
||||
};
|
||||
let never = |_: u64| false;
|
||||
let notice = Notice {
|
||||
@@ -80,6 +81,7 @@ fn posts(tag: &str, reply: fn(&str) -> Reply, content: &'static str) -> Vec<Stri
|
||||
&record,
|
||||
&socket,
|
||||
&batch,
|
||||
None,
|
||||
&|line| log.lock().unwrap().push(line.to_string()),
|
||||
¬ice,
|
||||
);
|
||||
|
||||
@@ -54,10 +54,18 @@ fn turn(tag: &str, posted: &dyn Fn(u64) -> bool, wait: Duration) -> (Vec<String>
|
||||
},
|
||||
resume: false,
|
||||
text: "go".to_string(),
|
||||
senders: vec!["k0000000000000000000000000".to_string()],
|
||||
};
|
||||
let poster = Record::default();
|
||||
let started = Instant::now();
|
||||
deliver(&poster, &socket, &batch, &|_| {}, &Notice { posted, wait });
|
||||
deliver(
|
||||
&poster,
|
||||
&socket,
|
||||
&batch,
|
||||
None,
|
||||
&|_| {},
|
||||
&Notice { posted, wait },
|
||||
);
|
||||
let took = started.elapsed();
|
||||
(poster.0.into_inner().unwrap(), took)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
//! The speaker `deliver` is given goes on the turn it sends to `loopd`, and nowhere else: not into
|
||||
//! the turn's text (M5c spec, section 11). Do not edit.
|
||||
|
||||
#[path = "support/fake_loop.rs"]
|
||||
mod fake_loop;
|
||||
#[path = "support/tmp.rs"]
|
||||
mod tmp;
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
use bxcore::{ErrorCode, SessionId, Turn};
|
||||
use fake_loop::{done, error, serve_loop};
|
||||
use gatewayd::deliver::{Notice, Poster, deliver};
|
||||
use gatewayd::mm::MmError;
|
||||
use gatewayd::sessions::{Batch, Thread};
|
||||
use tmp::TempDir;
|
||||
|
||||
const DM: &str = "d0000000000000000000000000";
|
||||
const ROOT: &str = "r0000000000000000000000000";
|
||||
const KYLE: &str = "k0000000000000000000000000";
|
||||
const WAIT: Duration = Duration::from_secs(5);
|
||||
|
||||
#[derive(Default)]
|
||||
struct Record(Mutex<Vec<String>>);
|
||||
|
||||
impl Poster for Record {
|
||||
fn post(&self, _: &str, _: &str, text: &str) -> Result<(), MmError> {
|
||||
self.0.lock().unwrap().push(text.to_string());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// One `deliver` of "one\n\ntwo" with `speaker`, against a fake `loopd` whose n-th turn gets
|
||||
/// `script(n)`; the turns it received and what was posted.
|
||||
fn deliver_with(
|
||||
tag: &str,
|
||||
speaker: Option<&str>,
|
||||
script: fn(usize) -> Vec<fake_loop::Reply>,
|
||||
) -> (Vec<Turn>, Vec<String>) {
|
||||
let dir = TempDir::new(tag);
|
||||
let socket = dir.path().join("loop.sock");
|
||||
let turns = serve_loop(&socket, move |n, _| script(n));
|
||||
let batch = Batch {
|
||||
session: SessionId::new(&format!("mm-{ROOT}")).unwrap(),
|
||||
thread: Thread {
|
||||
channel: DM.to_string(),
|
||||
root: ROOT.to_string(),
|
||||
},
|
||||
resume: true,
|
||||
text: "one\n\ntwo".to_string(),
|
||||
senders: vec![KYLE.to_string(), KYLE.to_string()],
|
||||
};
|
||||
let never = |_: u64| false;
|
||||
let notice = Notice {
|
||||
posted: &never,
|
||||
wait: Duration::ZERO,
|
||||
};
|
||||
let poster = Record::default();
|
||||
let log = Mutex::new(Vec::new());
|
||||
deliver(
|
||||
&poster,
|
||||
&socket,
|
||||
&batch,
|
||||
speaker,
|
||||
&|line| log.lock().unwrap().push(line.to_string()),
|
||||
¬ice,
|
||||
);
|
||||
assert!(log.lock().unwrap().is_empty(), "{:?}", log.lock().unwrap());
|
||||
// The fake reports each turn before it answers, so every turn is in by now.
|
||||
let mut got = vec![turns.recv_timeout(WAIT).unwrap()];
|
||||
while let Ok(turn) = turns.try_recv() {
|
||||
got.push(turn);
|
||||
}
|
||||
(got, poster.0.into_inner().unwrap())
|
||||
}
|
||||
|
||||
fn answer(_: usize) -> Vec<fake_loop::Reply> {
|
||||
vec![done("fine")]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_speaker_goes_on_the_turn_and_not_into_its_text() {
|
||||
let (turns, posts) = deliver_with("speaker-some", Some("first.last"), answer);
|
||||
assert_eq!(turns.len(), 1);
|
||||
let turn = &turns[0];
|
||||
assert_eq!(turn.speaker.as_deref(), Some("first.last"));
|
||||
assert_eq!(
|
||||
(turn.content.as_str(), turn.resume),
|
||||
("one\n\ntwo", true),
|
||||
"the text is the batch's, unchanged"
|
||||
);
|
||||
assert_eq!(posts, ["fine"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_a_speaker_the_turn_has_none() {
|
||||
let (turns, posts) = deliver_with("speaker-none", None, answer);
|
||||
assert_eq!(turns.len(), 1);
|
||||
assert_eq!(turns[0].speaker, None, "never the user id");
|
||||
assert_eq!(turns[0].content, "one\n\ntwo");
|
||||
assert_eq!(posts, ["fine"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_turn_sent_again_for_an_unknown_session_keeps_the_speaker() {
|
||||
let unknown_first = |n: usize| {
|
||||
if n == 0 {
|
||||
vec![error(ErrorCode::NoSuchSession, "no such session")]
|
||||
} else {
|
||||
vec![done("hello")]
|
||||
}
|
||||
};
|
||||
let (turns, posts) = deliver_with("speaker-again", Some("kyle"), unknown_first);
|
||||
let seen: Vec<(bool, Option<&str>)> = turns
|
||||
.iter()
|
||||
.map(|t| (t.resume, t.speaker.as_deref()))
|
||||
.collect();
|
||||
assert_eq!(seen, [(true, Some("kyle")), (false, Some("kyle"))]);
|
||||
assert_eq!(posts, ["hello"]);
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
//! `gatewayd` end to end: each turn's speaker is the Mattermost username of the one person who
|
||||
//! wrote every message of its burst, and there is none when two people did or the name cannot be
|
||||
//! found (M5c spec, sections 4 and 11). Do not edit.
|
||||
|
||||
#[path = "support/fake_loop.rs"]
|
||||
mod fake_loop;
|
||||
#[path = "support/fake_mm.rs"]
|
||||
mod fake_mm;
|
||||
#[path = "support/gateway.rs"]
|
||||
mod gateway;
|
||||
#[path = "support/tmp.rs"]
|
||||
mod tmp;
|
||||
|
||||
use std::sync::{Mutex, mpsc};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use fake_loop::{done, serve_loop};
|
||||
use fake_mm::{DM, EVE, FakeMm, KYLE, id, post};
|
||||
use gateway::{SHARED, WAIT, loop_dir, read_state, start, up};
|
||||
use gatewayd::config::Config;
|
||||
use tmp::TempDir;
|
||||
|
||||
/// Kyle and Eve both allowed, in the shared channel.
|
||||
fn two_people(home: &TempDir, url: &str) -> Config {
|
||||
let text = format!(
|
||||
r#"
|
||||
[mattermost]
|
||||
url = "{url}"
|
||||
[secrets.mattermost_token]
|
||||
env = "NOT_READ_BY_RUN"
|
||||
[allow]
|
||||
users = ["{KYLE}", "{EVE}"]
|
||||
channels = ["{SHARED}"]
|
||||
[paths]
|
||||
home = "{}"
|
||||
[limits]
|
||||
typing_every_ms = 100
|
||||
"#,
|
||||
home.path().display()
|
||||
);
|
||||
Config::parse(&text).unwrap()
|
||||
}
|
||||
|
||||
/// Wait until `gatewayd` has handled `post`: its id is in the state file's `recent`. The post is
|
||||
/// queued by then, since the event loop finishes a post before it looks at ended turns.
|
||||
fn wait_handled(home: &TempDir, post: &str) {
|
||||
let until = Instant::now() + WAIT;
|
||||
loop {
|
||||
let state = read_state(home);
|
||||
if state["recent"]
|
||||
.as_array()
|
||||
.is_some_and(|r| r.iter().any(|p| p == post))
|
||||
{
|
||||
return;
|
||||
}
|
||||
assert!(Instant::now() < until, "{post} not handled: {state}");
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
|
||||
fn lookups(mm: &FakeMm, user: &str) -> usize {
|
||||
let path = format!("/api/v4/users/{user}");
|
||||
mm.calls()
|
||||
.iter()
|
||||
.filter(|(m, p)| m == "GET" && *p == path)
|
||||
.count()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_persons_message_has_their_username() {
|
||||
let home = TempDir::new("speaker-one");
|
||||
let (mm, _running, turns, mut ws) = up(&home, Duration::ZERO);
|
||||
ws.posted(&post(&id('p', 1), KYLE, DM, "", "hello there", 5), "D");
|
||||
let turn = turns.recv_timeout(WAIT).unwrap();
|
||||
assert_eq!(
|
||||
(turn.content.as_str(), turn.speaker.as_deref()),
|
||||
("hello there", Some("kyle"))
|
||||
);
|
||||
assert_eq!(mm.wait_posts(1, WAIT).len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_username_is_looked_up_once() {
|
||||
let home = TempDir::new("speaker-cache");
|
||||
let (mm, _running, turns, mut ws) = up(&home, Duration::ZERO);
|
||||
ws.posted(&post(&id('p', 1), KYLE, DM, "", "first", 5), "D");
|
||||
ws.posted(&post(&id('p', 2), KYLE, DM, "", "second", 6), "D");
|
||||
let speakers = [
|
||||
turns.recv_timeout(WAIT).unwrap().speaker,
|
||||
turns.recv_timeout(WAIT).unwrap().speaker,
|
||||
];
|
||||
assert_eq!(
|
||||
speakers,
|
||||
[Some("kyle".to_string()), Some("kyle".to_string())]
|
||||
);
|
||||
assert_eq!(lookups(&mm, KYLE), 1, "{:?}", mm.calls());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bursts_have_a_speaker_only_when_one_person_wrote_them() {
|
||||
let home = TempDir::new("speaker-burst");
|
||||
loop_dir(&home);
|
||||
let (release_tx, release_rx) = mpsc::channel::<()>();
|
||||
let release = Mutex::new(release_rx);
|
||||
let turns = serve_loop(&home.path().join("run/loop/loop.sock"), move |n, turn| {
|
||||
if n <= 1 {
|
||||
let _ = release.lock().unwrap().recv_timeout(WAIT);
|
||||
}
|
||||
vec![done(&format!("answer to {}", turn.content))]
|
||||
});
|
||||
let mm = FakeMm::start();
|
||||
let _running = start(two_people(&home, &mm.url()));
|
||||
let mut ws = mm.next_ws(WAIT);
|
||||
let p1 = id('p', 1);
|
||||
ws.posted(
|
||||
&post(&p1, KYLE, SHARED, "", "@boxmaker-straylight start", 5),
|
||||
"O",
|
||||
);
|
||||
let first = turns.recv_timeout(WAIT).unwrap();
|
||||
assert_eq!(
|
||||
(first.content.as_str(), first.speaker.as_deref()),
|
||||
("start", Some("kyle"))
|
||||
);
|
||||
|
||||
// Eve and Kyle while the first turn runs: one turn, from two people.
|
||||
ws.posted(&post(&id('p', 2), EVE, SHARED, &p1, "two", 6), "O");
|
||||
ws.posted(&post(&id('p', 3), KYLE, SHARED, &p1, "three", 7), "O");
|
||||
wait_handled(&home, &id('p', 3));
|
||||
release_tx.send(()).unwrap();
|
||||
let second = turns.recv_timeout(WAIT).unwrap();
|
||||
assert_eq!(
|
||||
(second.content.as_str(), second.resume),
|
||||
("two\n\nthree", true),
|
||||
"the text is joined as before"
|
||||
);
|
||||
assert_eq!(second.speaker, None, "two people: no speaker");
|
||||
|
||||
// Eve alone while the second runs: her name, and nothing left over from the burst before.
|
||||
ws.posted(&post(&id('p', 4), EVE, SHARED, &p1, "four", 8), "O");
|
||||
wait_handled(&home, &id('p', 4));
|
||||
release_tx.send(()).unwrap();
|
||||
let third = turns.recv_timeout(WAIT).unwrap();
|
||||
assert_eq!(
|
||||
(third.content.as_str(), third.speaker.as_deref()),
|
||||
("four", Some("user-e000"))
|
||||
);
|
||||
assert_eq!(mm.wait_posts(3, WAIT).len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_name_that_cannot_be_found_leaves_the_turn_without_a_speaker() {
|
||||
let home = TempDir::new("speaker-missing");
|
||||
let (mm, running, turns, mut ws) = up(&home, Duration::ZERO);
|
||||
mm.refuse_users(true);
|
||||
ws.posted(&post(&id('p', 1), KYLE, DM, "", "hello", 5), "D");
|
||||
let turn = turns.recv_timeout(WAIT).unwrap();
|
||||
assert_eq!(
|
||||
(turn.content.as_str(), turn.speaker),
|
||||
("hello", None),
|
||||
"never the user id, never a guess"
|
||||
);
|
||||
assert_eq!(
|
||||
mm.wait_posts(1, WAIT).len(),
|
||||
1,
|
||||
"the turn is answered all the same"
|
||||
);
|
||||
let line = format!(
|
||||
"gatewayd: no username for {KYLE}: status 404: \"{{\\\"message\\\":\\\"not found\\\"}}\"; \
|
||||
the turn has no speaker"
|
||||
);
|
||||
let log = running.wait_log("no username for");
|
||||
assert_eq!(
|
||||
log.iter()
|
||||
.filter(|l| l.contains("no username"))
|
||||
.collect::<Vec<_>>(),
|
||||
[&line]
|
||||
);
|
||||
|
||||
// A failure is not remembered: the next turn looks again.
|
||||
mm.refuse_users(false);
|
||||
ws.posted(&post(&id('p', 2), KYLE, DM, "", "again", 6), "D");
|
||||
let again = turns.recv_timeout(WAIT).unwrap();
|
||||
assert_eq!(
|
||||
(again.content.as_str(), again.speaker.as_deref()),
|
||||
("again", Some("kyle"))
|
||||
);
|
||||
assert_eq!(lookups(&mm, KYLE), 2, "{:?}", mm.calls());
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Which posts become turns, in which session, and how messages wait for a running turn (M4a spec,
|
||||
//! section 7, including its table of examples). Do not edit.
|
||||
//! section 7, including its table of examples), with the author of each (M5c spec, section 11).
|
||||
//! Do not edit.
|
||||
|
||||
use bxcore::SessionId;
|
||||
use gatewayd::mm::Post;
|
||||
@@ -179,6 +180,7 @@ fn sessions_and_threads() {
|
||||
);
|
||||
assert!(!top.resume && !top.joins_thread);
|
||||
assert_eq!(top.text, "hello");
|
||||
assert_eq!(top.user, KYLE);
|
||||
let reply = queued(r.route(&post(DM, ROOT, "more"), "D", &unknown));
|
||||
assert_eq!(reply.session.as_str(), format!("mm-{ROOT}"));
|
||||
assert_eq!(reply.thread.root, ROOT);
|
||||
@@ -244,6 +246,10 @@ fn commands() {
|
||||
}
|
||||
|
||||
fn message(root: &str, resume: bool, text: &str) -> Message {
|
||||
message_by(KYLE, root, resume, text)
|
||||
}
|
||||
|
||||
fn message_by(user: &str, root: &str, resume: bool, text: &str) -> Message {
|
||||
Message {
|
||||
session: SessionId::new(&format!("mm-{root}")).unwrap(),
|
||||
thread: Thread {
|
||||
@@ -253,9 +259,14 @@ fn message(root: &str, resume: bool, text: &str) -> Message {
|
||||
resume,
|
||||
text: text.to_string(),
|
||||
joins_thread: false,
|
||||
user: user.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn senders(batch: &Batch) -> Vec<&str> {
|
||||
batch.senders.iter().map(String::as_str).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn messages_wait_for_the_running_turn_and_go_together() {
|
||||
let mut q = Queues::new(3);
|
||||
@@ -269,7 +280,8 @@ fn messages_wait_for_the_running_turn_and_go_together() {
|
||||
session: first.session.clone(),
|
||||
thread: first.thread.clone(),
|
||||
resume: false,
|
||||
text: "one".to_string()
|
||||
text: "one".to_string(),
|
||||
senders: vec![KYLE.to_string()],
|
||||
}
|
||||
);
|
||||
assert_eq!(q.push(message(ROOT, true, "two")), Pushed::Waiting);
|
||||
@@ -305,10 +317,85 @@ fn a_full_queue_drops_the_message() {
|
||||
assert_eq!(q.push(message(ROOT, true, "a")), Pushed::Waiting);
|
||||
assert_eq!(q.push(message(ROOT, true, "b")), Pushed::Waiting);
|
||||
assert_eq!(
|
||||
q.push(message(ROOT, true, "c")),
|
||||
q.push(message_by(EVE, ROOT, true, "c")),
|
||||
Pushed::Full(m.thread.clone())
|
||||
);
|
||||
assert_eq!(q.finish(&m.session).unwrap().text, "a\n\nb");
|
||||
let next = q.finish(&m.session).unwrap();
|
||||
assert_eq!(next.text, "a\n\nb");
|
||||
assert_eq!(
|
||||
senders(&next),
|
||||
[KYLE, KYLE],
|
||||
"the dropped message's author is not kept"
|
||||
);
|
||||
assert_eq!(next.sender(), Some(KYLE));
|
||||
assert!(!BUSY.is_empty());
|
||||
assert_eq!(q.finish(&SessionId::new("mm-never").unwrap()), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_queued_message_names_its_author() {
|
||||
let r = Router::new(
|
||||
BOT,
|
||||
"boxmaker-straylight",
|
||||
&[KYLE.to_string(), EVE.to_string()],
|
||||
&[SHARED.to_string()],
|
||||
);
|
||||
for (text, expected) in [
|
||||
("hello", "hello"),
|
||||
("!!approve is a word", "!approve is a word"),
|
||||
] {
|
||||
let mut from_eve = post(DM, ROOT, text);
|
||||
from_eve.user_id = EVE.to_string();
|
||||
let m = queued(r.route(&from_eve, "D", &unknown));
|
||||
assert_eq!(
|
||||
(m.user.as_str(), m.text.as_str()),
|
||||
(EVE, expected),
|
||||
"{text}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_batch_carries_the_author_of_each_message() {
|
||||
let mut q = Queues::new(5);
|
||||
let Pushed::Start(first) = q.push(message(ROOT, false, "one")) else {
|
||||
panic!("not started")
|
||||
};
|
||||
assert_eq!((senders(&first), first.sender()), (vec![KYLE], Some(KYLE)));
|
||||
assert_eq!(q.push(message_by(EVE, ROOT, true, "two")), Pushed::Waiting);
|
||||
assert_eq!(q.push(message(ROOT, true, "three")), Pushed::Waiting);
|
||||
let session = SessionId::new(&format!("mm-{ROOT}")).unwrap();
|
||||
let mixed = q.finish(&session).unwrap();
|
||||
assert_eq!(mixed.text, "two\n\nthree", "the text is joined as before");
|
||||
assert_eq!(senders(&mixed), [EVE, KYLE]);
|
||||
assert_eq!(mixed.sender(), None, "two people: no one sender");
|
||||
assert_eq!(q.push(message_by(EVE, ROOT, true, "four")), Pushed::Waiting);
|
||||
assert_eq!(q.push(message_by(EVE, ROOT, true, "five")), Pushed::Waiting);
|
||||
let one = q.finish(&session).unwrap();
|
||||
assert_eq!(
|
||||
(senders(&one), one.sender()),
|
||||
(vec![EVE, EVE], Some(EVE)),
|
||||
"each batch has only its own messages' authors"
|
||||
);
|
||||
assert_eq!(q.finish(&session), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_batch_with_no_authors_has_no_sender() {
|
||||
let batch = Batch {
|
||||
session: SessionId::new(&format!("mm-{ROOT}")).unwrap(),
|
||||
thread: Thread {
|
||||
channel: DM.to_string(),
|
||||
root: ROOT.to_string(),
|
||||
},
|
||||
resume: false,
|
||||
text: "orphan".to_string(),
|
||||
senders: Vec::new(),
|
||||
};
|
||||
assert_eq!(batch.sender(), None);
|
||||
let three = Batch {
|
||||
senders: vec![KYLE.to_string(), KYLE.to_string(), EVE.to_string()],
|
||||
..batch
|
||||
};
|
||||
assert_eq!(three.sender(), None, "the last one differs");
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ struct Inner {
|
||||
edits: Vec<(String, String)>,
|
||||
/// When set, `POST /posts` is refused with 400.
|
||||
refuse_posts: bool,
|
||||
/// When set, `GET /users/<id>` (not `users/me`) answers 404.
|
||||
refuse_users: bool,
|
||||
}
|
||||
|
||||
pub struct FakeMm {
|
||||
@@ -249,6 +251,10 @@ impl FakeMm {
|
||||
pub fn refuse_posts(&self, refuse: bool) {
|
||||
self.inner.lock().unwrap().refuse_posts = refuse;
|
||||
}
|
||||
|
||||
pub fn refuse_users(&self, refuse: bool) {
|
||||
self.inner.lock().unwrap().refuse_users = refuse;
|
||||
}
|
||||
}
|
||||
|
||||
fn connection(mut stream: TcpStream, inner: &Mutex<Inner>, ws_tx: &mpsc::Sender<WsPeer>) {
|
||||
@@ -367,6 +373,9 @@ fn rest(inner: &Mutex<Inner>, method: &str, path: &str, body: &[u8]) -> (u16, Va
|
||||
inner.edits.push((post_id.clone(), message.clone()));
|
||||
(200, post(&post_id, BOT, DM, "", &message, 1))
|
||||
}
|
||||
("GET", p) if p.starts_with("/api/v4/users/") && inner.refuse_users => {
|
||||
(404, json!({"message": "not found"}))
|
||||
}
|
||||
("GET", p) if p.starts_with("/api/v4/users/") => {
|
||||
let user = p.trim_start_matches("/api/v4/users/");
|
||||
let name = if user == KYLE {
|
||||
|
||||
Reference in New Issue
Block a user