commit 9ab751c0649f887a1c94bc4d3e4b2207a9a8e084
parent 409b2bf5ddf13248bb71556872c9fd15ad913df7
Author: MTRNord <mtrnord1@gmail.com>
Date: Sun, 20 Feb 2022 22:01:50 +0100
Add redacting action
Diffstat:
6 files changed, 272 insertions(+), 165 deletions(-)
diff --git a/FeatureIdeas.md b/FeatureIdeas.md
@@ -0,0 +1,5 @@
+# Ideas
+
+- Wordlist modifications
+- Statistics
+- Wordlist suggestions
+\ No newline at end of file
diff --git a/src/components/App.tsx b/src/components/App.tsx
@@ -1,5 +1,5 @@
import { PureComponent } from 'react';
-import BanList from './Banlist';
+import BanList from './forms/Banlist';
import Home from './Home';
type Pages = "banlist" | "home";
@@ -41,7 +41,7 @@ class App extends PureComponent<Props, State> {
</nav>
</div>
</header>
- <div className='lg:pt-20 pt-52 z-0'>
+ <div className='lg:pt-20 pt-52 z-0 max-h-full'>
{
current_page === "home" ? <Home /> : (current_page === "banlist" ? <BanList /> : <Home />)
}
diff --git a/src/components/Banlist.tsx b/src/components/Banlist.tsx
@@ -1,156 +0,0 @@
-import { PureComponent } from 'react';
-import {
- MembershipEvent,
- M_POLICY_RULE_SERVER,
- M_POLICY_RULE_SERVER_ALT,
- M_POLICY_RULE_SERVER_OLD,
- M_POLICY_RULE_USER,
- M_POLICY_RULE_USER_ALT,
- M_POLICY_RULE_USER_OLD,
- ServerRuleEvent,
- UserRuleEvent
-} from '../windowExt';
-
-type Props = Record<string, unknown>;
-type State = {
- user_rules: UserRuleEvent[];
- server_rules: ServerRuleEvent[];
- roomId?: string;
- filter?: string;
- memberships: MembershipEvent[];
-};
-
-class BanList extends PureComponent<Props, State> {
- constructor(props: Props) {
- super(props);
- const urlParams = (new URL(window.location.href)).searchParams;
- const roomId = urlParams.get("room_id") ?? undefined;
- this.state = {
- roomId: roomId,
- user_rules: [],
- server_rules: [],
- memberships: []
- };
- }
-
- async componentDidMount() {
- const user_rules = await window.widget_api.readStateEvents(M_POLICY_RULE_USER, 250_000_000_000, undefined, ["*"]) as UserRuleEvent[];
- const server_rules = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER, 250_000_000_000, undefined, ["*"]) as ServerRuleEvent[];
- const user_rules_old = await window.widget_api.readStateEvents(M_POLICY_RULE_USER_OLD, 250_000_000_000, undefined, ["*"]) as UserRuleEvent[];
- const server_rules_old = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER_OLD, 250_000_000_000, undefined, ["*"]) as ServerRuleEvent[];
- const user_rules_alt = await window.widget_api.readStateEvents(M_POLICY_RULE_USER_ALT, 250_000_000_000, undefined, ["*"]) as UserRuleEvent[];
- const server_rules_alt = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER_ALT, 250_000_000_000, undefined, ["*"]) as ServerRuleEvent[];
- const user_rules_all = [...user_rules, ...user_rules_old, ...user_rules_alt];
- const server_rules_all = [...server_rules, ...server_rules_old, ...server_rules_alt];
- const list_rooms = [...new Set([...user_rules_all.map(element => element.room_id), ...server_rules_all.map(element => element.room_id)])];
- const sender = new Set([...user_rules_all.map(element => element.sender), ...server_rules_all.map(element => element.sender)]);
- const memberships = await window.widget_api.readStateEvents("m.room.member", 250_000_000_000, undefined, list_rooms) as MembershipEvent[];
- const memberships_needed = memberships.filter(member => sender.has(member.state_key));
-
- this.setState({
- user_rules: user_rules_all,
- server_rules: server_rules_all,
- memberships: memberships_needed
- });
- }
-
- onSearch(ev: { target: { value: string; }; }) {
- this.setState({
- filter: ev.target.value
- });
- }
-
- render() {
- const { user_rules, server_rules, filter, memberships } = this.state;
- const filtered_user_rules = filter ? user_rules.filter(element => element.content && Object.keys(element.content).length > 0 && element.content?.entity.includes(filter)) : user_rules;
- const filtered_server_rules = filter ? server_rules.filter(element => element.content && Object.keys(element.content).length > 0 && element.content?.entity.includes(filter)) : server_rules;
- return (
- <main className="m-8 flex items-center flex-col justify-center">
- <div className='flex flex-row justify-between content-between w-full'>
- <h1 className="text-3xl font-bold underline text-gray-900 dark:text-gray-200 mb-4">Banlist data</h1>
- <input onChange={this.onSearch.bind(this)} value={filter} placeholder="Search for user or server" className='py-1.5 px-2 rounded border-none placeholder:text-gray-900 text-gray-9000'></input>
- </div>
- <div className='flex flex-row justify-between content-between w-full'>
- <section className='mr-2'>
- <h3 className="text-2xl font-bold text-gray-900 dark:text-gray-200 mb-2">User Bans ({user_rules.length})</h3>
- <table className='table-auto border-collapse border-2 border-slate-500 text-gray-900 dark:text-gray-200 text-base font-normal break-words'>
- <thead>
- <tr>
- <th className='border-2 border-slate-600 p-1'>Entity</th>
- <th className='border-2 border-slate-600 p-1'>Recommendation</th>
- <th className='border-2 border-slate-600 p-1'>Reason</th>
- <th className='border-2 border-slate-600 p-1'>Sender</th>
- </tr>
- </thead>
- <tbody>
- {
- filtered_user_rules.length > 0 ?
- filtered_user_rules.map(element => {
- if (element.content && Object.keys(element.content).length > 0) {
- const recommendation = element.content.recommendation === "m.ban" || element.content.recommendation === "org.matrix.mjolnir.ban" ? "Ban" : element.content.recommendation;
-
- // Entity is intentionally left as a mxid
- const sender_dp = memberships.find(membership => membership.state_key === element.sender)?.content?.displayname ?? element.sender;
- return (
- <tr key={`${element.event_id}`} className='border-2 border-slate-600'>
- <td className='p-1 min-w-[8rem] max-w-[14rem]'>{element.content.entity}</td>
- <td className='p-1 min-w-[1rem] max-w-[14rem]'>{recommendation}</td>
- <td className='p-1 min-w-[8rem] max-w-[14rem]'>{element.content.reason}</td>
- <td className='p-1 min-w-[12rem] max-w-[14rem]'>{sender_dp}</td>
- </tr>
- );
- }
- }) : (
- <tr className='border-2 border-slate-600'>
- <td className='p-1'>No user ban found</td>
- </tr>
- )
- }
- </tbody>
- </table>
- </section>
- <section>
- <h3 className="text-2xl font-bold text-gray-900 dark:text-gray-200 mb-2">Server Bans ({server_rules.length})</h3>
- <table className='table-auto border-collapse border-2 border-slate-500 text-gray-900 dark:text-gray-200 text-base font-normal break-words'>
- <thead>
- <tr>
- <th className='border-2 border-slate-600 p-2'>Entity</th>
- <th className='border-2 border-slate-600 p-2'>Recommendation</th>
- <th className='border-2 border-slate-600 p-2'>Reason</th>
- <th className='border-2 border-slate-600 p-1'>Sender</th>
- </tr>
- </thead>
- <tbody>
- {
- filtered_server_rules.length > 0 ?
- filtered_server_rules.map(element => {
- if (element.content && Object.keys(element.content).length > 0) {
- const recommendation = element.content.recommendation === "m.ban" || element.content.recommendation === "org.matrix.mjolnir.ban" ? "Ban" : element.content.recommendation;
-
- // Entity is intentionally left as a mxid
- const sender_dp = memberships.find(membership => membership.state_key === element.sender)?.content?.displayname ?? element.sender;
- return (
- <tr key={`${element.event_id}`} className='border-2 border-slate-600'>
- <td className='p-1 min-w-[8rem] max-w-[14rem]'>{element.content.entity}</td>
- <td className='p-1 min-w-[1rem] max-w-[14rem]'>{recommendation}</td>
- <td className='p-1 min-w-[8rem] max-w-[14rem]'>{element.content.reason}</td>
- <td className='p-1 min-w-[12rem] max-w-[14rem]'>{sender_dp}</td>
- </tr>
- );
- }
- }) : (
- <tr className='border-2 border-slate-600'>
- <td className='p-1'>No server ban found</td>
- </tr>
- )
- }
- </tbody>
- </table>
- </section>
- </div>
- </main>
- );
- }
-}
-
-export default BanList;
-\ No newline at end of file
diff --git a/src/components/Home.tsx b/src/components/Home.tsx
@@ -1,5 +1,6 @@
import { PureComponent } from 'react';
import BanForm from './BanForm';
+import RedactForm from './forms/RedactForm';
type Props = Record<string, unknown>;
type State = Record<string, unknown>;
@@ -10,12 +11,15 @@ class Home extends PureComponent<Props, State> {
return (
<main className="m-8">
<h1 className="text-3xl font-bold underline text-gray-900 dark:text-gray-200 mb-4">Home</h1>
- <div className='flex flex-row justify-between'>
- <section>
- <BanForm />
- </section>
- <section>
- </section>
+ <div className='flex flex-row justify-center items-center flex-wrap'>
+ <div className='flex flex-row flex-wrap'>
+ <section className='mr-16 mb-16'>
+ <BanForm />
+ </section>
+ <section>
+ <RedactForm />
+ </section>
+ </div>
</div>
</main>
);
diff --git a/src/components/forms/Banlist.tsx b/src/components/forms/Banlist.tsx
@@ -0,0 +1,156 @@
+import { PureComponent } from 'react';
+import {
+ MembershipEvent,
+ M_POLICY_RULE_SERVER,
+ M_POLICY_RULE_SERVER_ALT,
+ M_POLICY_RULE_SERVER_OLD,
+ M_POLICY_RULE_USER,
+ M_POLICY_RULE_USER_ALT,
+ M_POLICY_RULE_USER_OLD,
+ ServerRuleEvent,
+ UserRuleEvent
+} from '../../windowExt';
+
+type Props = Record<string, unknown>;
+type State = {
+ user_rules: UserRuleEvent[];
+ server_rules: ServerRuleEvent[];
+ roomId?: string;
+ filter?: string;
+ memberships: MembershipEvent[];
+};
+
+class BanList extends PureComponent<Props, State> {
+ constructor(props: Props) {
+ super(props);
+ const urlParams = (new URL(window.location.href)).searchParams;
+ const roomId = urlParams.get("room_id") ?? undefined;
+ this.state = {
+ roomId: roomId,
+ user_rules: [],
+ server_rules: [],
+ memberships: []
+ };
+ }
+
+ async componentDidMount() {
+ const user_rules = await window.widget_api.readStateEvents(M_POLICY_RULE_USER, 250_000_000_000, undefined, ["*"]) as UserRuleEvent[];
+ const server_rules = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER, 250_000_000_000, undefined, ["*"]) as ServerRuleEvent[];
+ const user_rules_old = await window.widget_api.readStateEvents(M_POLICY_RULE_USER_OLD, 250_000_000_000, undefined, ["*"]) as UserRuleEvent[];
+ const server_rules_old = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER_OLD, 250_000_000_000, undefined, ["*"]) as ServerRuleEvent[];
+ const user_rules_alt = await window.widget_api.readStateEvents(M_POLICY_RULE_USER_ALT, 250_000_000_000, undefined, ["*"]) as UserRuleEvent[];
+ const server_rules_alt = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER_ALT, 250_000_000_000, undefined, ["*"]) as ServerRuleEvent[];
+ const user_rules_all = [...user_rules, ...user_rules_old, ...user_rules_alt];
+ const server_rules_all = [...server_rules, ...server_rules_old, ...server_rules_alt];
+ const list_rooms = [...new Set([...user_rules_all.map(element => element.room_id), ...server_rules_all.map(element => element.room_id)])];
+ const sender = new Set([...user_rules_all.map(element => element.sender), ...server_rules_all.map(element => element.sender)]);
+ const memberships = await window.widget_api.readStateEvents("m.room.member", 250_000_000_000, undefined, list_rooms) as MembershipEvent[];
+ const memberships_needed = memberships.filter(member => sender.has(member.state_key));
+
+ this.setState({
+ user_rules: user_rules_all,
+ server_rules: server_rules_all,
+ memberships: memberships_needed
+ });
+ }
+
+ onSearch(ev: { target: { value: string; }; }) {
+ this.setState({
+ filter: ev.target.value
+ });
+ }
+
+ render() {
+ const { user_rules, server_rules, filter, memberships } = this.state;
+ const filtered_user_rules = filter ? user_rules.filter(element => element.content && Object.keys(element.content).length > 0 && element.content?.entity.includes(filter)) : user_rules;
+ const filtered_server_rules = filter ? server_rules.filter(element => element.content && Object.keys(element.content).length > 0 && element.content?.entity.includes(filter)) : server_rules;
+ return (
+ <main className="m-8 flex items-center flex-col justify-center">
+ <div className='flex flex-row justify-between content-between w-full'>
+ <h1 className="text-3xl font-bold underline text-gray-900 dark:text-gray-200 mb-4">Banlist data</h1>
+ <input onChange={this.onSearch.bind(this)} value={filter} placeholder="Search for user or server" className='py-1.5 px-2 rounded border-none placeholder:text-gray-900 text-gray-9000'></input>
+ </div>
+ <div className='flex flex-row justify-between content-between w-full'>
+ <section className='mr-2'>
+ <h3 className="text-2xl font-bold text-gray-900 dark:text-gray-200 mb-2">User Bans ({user_rules.length})</h3>
+ <table className='table-auto border-collapse border-2 border-slate-500 text-gray-900 dark:text-gray-200 text-base font-normal break-words'>
+ <thead>
+ <tr>
+ <th className='border-2 border-slate-600 p-1'>Entity</th>
+ <th className='border-2 border-slate-600 p-1'>Recommendation</th>
+ <th className='border-2 border-slate-600 p-1'>Reason</th>
+ <th className='border-2 border-slate-600 p-1'>Sender</th>
+ </tr>
+ </thead>
+ <tbody>
+ {
+ filtered_user_rules.length > 0 ?
+ filtered_user_rules.map(element => {
+ if (element.content && Object.keys(element.content).length > 0) {
+ const recommendation = element.content.recommendation === "m.ban" || element.content.recommendation === "org.matrix.mjolnir.ban" ? "Ban" : element.content.recommendation;
+
+ // Entity is intentionally left as a mxid
+ const sender_dp = memberships.find(membership => membership.state_key === element.sender)?.content?.displayname ?? element.sender;
+ return (
+ <tr key={`${element.event_id}`} className='border-2 border-slate-600'>
+ <td className='p-1 min-w-[8rem] max-w-[14rem]'>{element.content.entity}</td>
+ <td className='p-1 min-w-[1rem] max-w-[14rem]'>{recommendation}</td>
+ <td className='p-1 min-w-[8rem] max-w-[14rem]'>{element.content.reason}</td>
+ <td className='p-1 min-w-[12rem] max-w-[14rem]'>{sender_dp}</td>
+ </tr>
+ );
+ }
+ }) : (
+ <tr className='border-2 border-slate-600'>
+ <td className='p-1'>No user ban found</td>
+ </tr>
+ )
+ }
+ </tbody>
+ </table>
+ </section>
+ <section>
+ <h3 className="text-2xl font-bold text-gray-900 dark:text-gray-200 mb-2">Server Bans ({server_rules.length})</h3>
+ <table className='table-auto border-collapse border-2 border-slate-500 text-gray-900 dark:text-gray-200 text-base font-normal break-words'>
+ <thead>
+ <tr>
+ <th className='border-2 border-slate-600 p-2'>Entity</th>
+ <th className='border-2 border-slate-600 p-2'>Recommendation</th>
+ <th className='border-2 border-slate-600 p-2'>Reason</th>
+ <th className='border-2 border-slate-600 p-1'>Sender</th>
+ </tr>
+ </thead>
+ <tbody>
+ {
+ filtered_server_rules.length > 0 ?
+ filtered_server_rules.map(element => {
+ if (element.content && Object.keys(element.content).length > 0) {
+ const recommendation = element.content.recommendation === "m.ban" || element.content.recommendation === "org.matrix.mjolnir.ban" ? "Ban" : element.content.recommendation;
+
+ // Entity is intentionally left as a mxid
+ const sender_dp = memberships.find(membership => membership.state_key === element.sender)?.content?.displayname ?? element.sender;
+ return (
+ <tr key={`${element.event_id}`} className='border-2 border-slate-600'>
+ <td className='p-1 min-w-[8rem] max-w-[14rem]'>{element.content.entity}</td>
+ <td className='p-1 min-w-[1rem] max-w-[14rem]'>{recommendation}</td>
+ <td className='p-1 min-w-[8rem] max-w-[14rem]'>{element.content.reason}</td>
+ <td className='p-1 min-w-[12rem] max-w-[14rem]'>{sender_dp}</td>
+ </tr>
+ );
+ }
+ }) : (
+ <tr className='border-2 border-slate-600'>
+ <td className='p-1'>No server ban found</td>
+ </tr>
+ )
+ }
+ </tbody>
+ </table>
+ </section>
+ </div>
+ </main>
+ );
+ }
+}
+
+export default BanList;
+\ No newline at end of file
diff --git a/src/components/forms/RedactForm.tsx b/src/components/forms/RedactForm.tsx
@@ -0,0 +1,96 @@
+import { PureComponent } from "react";
+type Props = Record<string, unknown>;
+type State = {
+ user: string;
+ event: string;
+ limit: string;
+ room: string;
+ roomId?: string;
+};
+class RedactForm extends PureComponent<Props, State> {
+ constructor(props: Props | Readonly<Props>) {
+ super(props);
+ const urlParams = (new URL(window.location.href)).searchParams;
+ const roomId = urlParams.get("room_id") ?? undefined;
+ this.state = {
+ user: "",
+ event: "",
+ room: "",
+ limit: "",
+ roomId: roomId
+ };
+ }
+
+ handleInputChange(event: { target: { type: string, checked?: boolean, value: string | boolean, name: string; }; }) {
+ const target = event.target;
+ const value = target.type === 'checkbox' ? target.checked : target.value;
+ const name = target.name;
+ this.setState({
+ [name]: value
+ } as unknown as State);
+ }
+
+ async handleSubmit(ev: { preventDefault: () => void; }) {
+ ev.preventDefault();
+ let command = "";
+ if (this.state.event !== "" && this.state.user === "" && this.state.room === "" && this.state.limit === "") {
+ command = `!mjolnir redact ${this.state.event}`;
+ } else if (this.state.event === "" && this.state.user !== "" && this.state.room === "" && this.state.limit === "") {
+ command = `!mjolnir redact ${this.state.user}`;
+ } else if (this.state.event === "" && this.state.user !== "" && this.state.room !== "" && this.state.limit === "") {
+ command = `!mjolnir redact ${this.state.user} ${this.state.room}`;
+ } else if (this.state.event === "" && this.state.user !== "" && this.state.room !== "" && this.state.limit !== "") {
+ command = `!mjolnir redact ${this.state.user} ${this.state.room} ${this.state.limit}`;
+ } else if (this.state.event === "" && this.state.user !== "" && this.state.room === "" && this.state.limit !== "") {
+ command = `!mjolnir redact ${this.state.user} ${this.state.limit}`;
+ } else {
+ return;
+ }
+ await window.widget_api.sendRoomEvent("m.room.message", {
+ msgtype: "m.text",
+ body: command,
+ }, this.state.roomId);
+ }
+
+ render() {
+ const { user, room, limit, event } = this.state;
+ return (
+ <>
+ <h3 className="text-2xl font-bold text-gray-900 dark:text-gray-200 mb-2">Redact</h3>
+ <h4 className="text-gray-900 dark:text-gray-200 text-base font-thin mb-2 w-96 italic">(Note that this goes with the same combinations as the commands. Make sure empty fields are empty when submitting!)</h4>
+ <form onSubmit={this.handleSubmit.bind(this)} className="grid grid-cols-1 gap-4">
+ <h4 className="text-gray-900 dark:text-gray-200 text-lg font-normal mb-1 w-96">Redacting a user</h4>
+ <label className="block w-96">
+ <span className="text-gray-900 dark:text-gray-200 visually-hidden mb-2">User ID (required if Event Permalink empty)</span>
+ <div className="mt-1 w-full flex flex-row box-border items-center cursor-text duration-300 max-w-full">
+ <input placeholder="User ID (required if Permalink empty)" className="rounded py-1.5 px-2 min-w-[1.25rem] flex-[1] border-none placeholder:text-gray-900 text-gray-900" type="text" name="user" value={user} onChange={this.handleInputChange.bind(this)} />
+ </div>
+ </label>
+ <label className="block w-96">
+ <span className="text-gray-900 dark:text-gray-200 visually-hidden mb-2">Room (optional)</span>
+ <div className="mt-1 w-full flex flex-row box-border items-center cursor-text duration-300 max-w-full">
+ <input placeholder="Room (optional)" className="rounded py-1.5 px-2 min-w-[1.25rem] flex-[1] border-none placeholder:text-gray-900 text-gray-900" type="text" name="room" value={room} onChange={this.handleInputChange.bind(this)} />
+ </div>
+ </label>
+ <label className="block w-96">
+ <span className="text-gray-900 dark:text-gray-200 visually-hidden mb-2">Limit (optional)</span>
+ <div className="mt-1 w-full flex flex-row box-border items-center cursor-text duration-300 max-w-full">
+ <input placeholder="Limit (optional)" className="rounded py-1.5 px-2 min-w-[1.25rem] flex-[1] border-none placeholder:text-gray-900 text-gray-900" type="text" name="limit" value={limit} onChange={this.handleInputChange.bind(this)} />
+ </div>
+ </label>
+ <h4 className="text-gray-900 dark:text-gray-200 text-lg font-normal mb-1 w-96">Redacting an event</h4>
+ <label className="block w-96">
+ <span className="text-gray-900 dark:text-gray-200 visually-hidden mb-2">Event Permalink (required if User ID empty)</span>
+ <div className="mt-1 w-full flex flex-row box-border items-center cursor-text duration-300 max-w-full">
+ <input placeholder="Event Permalink (required if User ID empty)" className="rounded py-1.5 px-2 min-w-[1.25rem] flex-[1] border-none placeholder:text-gray-900 text-gray-900" type="text" name="event" value={event} onChange={this.handleInputChange.bind(this)} />
+ </div>
+ </label>
+
+ <input className="bg-blue-400 hover:bg-blue-500 cursor-pointer h-10 rounded dark:text-gray-900 text-gray-200 w-96" type="submit" value="Redact" />
+ </form>
+ </>
+ );
+ }
+}
+
+export default RedactForm;
+\ No newline at end of file