matrix-moderation-widget

A widget for moderating with mjolnir. Highly WIP. Use at your own risk!
git clone git://archive.git.mtrnord.blog/MTRNord/matrix-moderation-widget.git
Log | Files | Refs | README | LICENSE

commit 96c43309f1969a5e5e8bf458f233dabf0aa6fd12
parent 5661c8cdd07cb4237050e743e6a7453f8500d09b
Author: MTRNord <mtrnord1@gmail.com>
Date:   Tue, 22 Feb 2022 21:06:13 +0100

Add some form validation and move correct files in the correct folders

Diffstat:
Msrc/components/App.tsx | 2+-
Dsrc/components/BanForm.tsx | 133-------------------------------------------------------------------------------
Asrc/components/Banlist.tsx | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/Home.tsx | 2+-
Asrc/components/forms/BanForm.tsx | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/components/forms/Banlist.tsx | 157-------------------------------------------------------------------------------
Msrc/components/forms/RedactForm.tsx | 9+++++----
7 files changed, 314 insertions(+), 296 deletions(-)

diff --git a/src/components/App.tsx b/src/components/App.tsx @@ -1,5 +1,5 @@ import { PureComponent } from 'react'; -import BanList from './forms/Banlist'; +import BanList from './Banlist'; import Home from './Home'; type Pages = "banlist" | "home"; diff --git a/src/components/BanForm.tsx b/src/components/BanForm.tsx @@ -1,132 +0,0 @@ -import { PureComponent } from "react"; -import { - CanonicalAliasEvent, - CANONICAL_ALIAS, - 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, - ORG_MATRIX_MJOLNIR_SHORTCODE, - ServerRuleEvent, - ShortcodeEvent, - UserRuleEvent -} from "../windowExt"; - -type Props = Record<string, unknown>; -type State = { - glob: string; - reason: string; - roomId?: string; - aliases_shortcodes: Map<string, string>; - banlist?: string; - bantype?: "user" | "server"; -}; - -class BanForm 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 = { - glob: "", - reason: "", - roomId: roomId, - aliases_shortcodes: new Map() - }; - } - - 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 listrooms = [...new Set([...user_rules_all.map(element => element.room_id), ...server_rules_all.map(element => element.room_id)])]; - const aliases = await window.widget_api.readStateEvents(CANONICAL_ALIAS, listrooms.length, undefined, listrooms) as CanonicalAliasEvent[]; - const shortcodes = await window.widget_api.readStateEvents(ORG_MATRIX_MJOLNIR_SHORTCODE, listrooms.length, undefined, listrooms) as ShortcodeEvent[]; - const aliases_shortcodes = new Map(); - - for (const event of aliases) { - if (event.content?.alias) { - aliases_shortcodes.set(event.content?.alias, shortcodes.find(shortcode_event => shortcode_event.room_id === event.room_id)?.content?.shortcode); - } - } - - this.setState({ - aliases_shortcodes - }); - } - - 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(); - await window.widget_api.sendRoomEvent("m.room.message", { - msgtype: "m.text", - body: `!mjolnir ban ${this.state.banlist} ${this.state.bantype} ${this.state.glob} ${this.state.reason}`, - }, this.state.roomId); - } - - render() { - const { glob, banlist, aliases_shortcodes, bantype, reason } = this.state; - return ( - <> - <h3 className="text-2xl font-bold text-gray-900 dark:text-gray-200 mb-2">Ban</h3> - <form onSubmit={this.handleSubmit.bind(this)} className="grid grid-cols-1 gap-4"> - <label className="inner-flex flex-col"> - <span className="text-gray-900 dark:text-gray-200 visually-hidden mb-2">Bantype</span> - <h4 className="text-gray-900 dark:text-gray-200 text-base font-thin mb-2 w-96 italic">(Warning: This doesn't check if the banlist can be written to or the bot if watches it)</h4> - <div className="w-96"> - <select required name="banlist" value={banlist ?? ""} className="max-w-full min-w-full placeholder:text-gray-900 text-gray-900 rounded py-1.5 px-2" onChange={this.handleInputChange.bind(this)}> - <option value="" disabled>Select an Banlist</option> - { - [...aliases_shortcodes].map(([alias, shortcode]) => { - return <option key={alias} value={shortcode}>{alias}</option>; - }) - } - </select> - </div> - </label> - <label className="inner-flex flex-col w-96"> - <span className="text-gray-900 dark:text-gray-200 visually-hidden mb-2">Bantype</span> - <div className="w-96"> - <select defaultValue="" required name="bantype" value={bantype ?? ""} className="max-w-full min-w-full placeholder:text-gray-900 text-gray-900 rounded py-1.5 px-2" onChange={this.handleInputChange.bind(this)}> - <option value="" disabled>Select an Bantype</option> - <option value="user">User</option> - <option value="server">Server</option> - </select> - </div> - </label> - <label className="block w-96"> - <span className="text-gray-900 dark:text-gray-200 visually-hidden mb-2">Glob</span> - <div className="mt-1 w-full flex flex-row box-border items-center cursor-text duration-300 max-w-full"> - <input placeholder="Glob" 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="glob" value={glob} 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">Reason</span> - <div className="mt-1 w-full flex flex-row box-border items-center cursor-text duration-300 max-w-full"> - <input placeholder="Reason" 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="reason" value={reason} onChange={this.handleInputChange.bind(this)} /> - </div> - </label> - - <input className="bg-red-400 hover:bg-red-500 cursor-pointer h-10 rounded dark:text-gray-900 text-gray-200 w-96" type="submit" value="Ban" /> - </form> - </> - ); - } -} - -export default BanForm; -\ No newline at end of file diff --git a/src/components/Banlist.tsx b/src/components/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/Home.tsx b/src/components/Home.tsx @@ -1,5 +1,5 @@ import { PureComponent } from 'react'; -import BanForm from './BanForm'; +import BanForm from './forms/BanForm'; import RedactForm from './forms/RedactForm'; type Props = Record<string, unknown>; diff --git a/src/components/forms/BanForm.tsx b/src/components/forms/BanForm.tsx @@ -0,0 +1,149 @@ +import { PureComponent } from "react"; +import { + CanonicalAliasEvent, + CANONICAL_ALIAS, + 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, + ORG_MATRIX_MJOLNIR_SHORTCODE, + ServerRuleEvent, + ShortcodeEvent, + UserRuleEvent +} from "../../windowExt"; + +type Props = Record<string, unknown>; +type State = { + glob: string; + reason: string; + roomId?: string; + aliases_shortcodes: Map<string, string>; + banlist: string; + bantype: "user" | "server" | ""; +}; + +class BanForm 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 = { + glob: "", + reason: "", + roomId: roomId, + banlist: "", + bantype: "", + aliases_shortcodes: new Map() + }; + } + + 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 listrooms = [...new Set([...user_rules_all.map(element => element.room_id), ...server_rules_all.map(element => element.room_id)])]; + const aliases = await window.widget_api.readStateEvents(CANONICAL_ALIAS, listrooms.length, undefined, listrooms) as CanonicalAliasEvent[]; + const shortcodes = await window.widget_api.readStateEvents(ORG_MATRIX_MJOLNIR_SHORTCODE, listrooms.length, undefined, listrooms) as ShortcodeEvent[]; + const aliases_shortcodes = new Map(); + + for (const event of aliases) { + if (event.content?.alias) { + aliases_shortcodes.set(event.content?.alias, shortcodes.find(shortcode_event => shortcode_event.room_id === event.room_id)?.content?.shortcode); + } + } + + this.setState({ + aliases_shortcodes + }); + } + + 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(); + + const { glob, banlist, bantype, reason, roomId } = this.state; + + if (glob === "" || banlist === "" || bantype === "") { + // TODO display warning + return; + } else { + if (reason === "") { + await window.widget_api.sendRoomEvent("m.room.message", { + msgtype: "m.text", + body: `!mjolnir ban ${banlist} ${bantype} ${glob}`, + }, roomId); + } else { + await window.widget_api.sendRoomEvent("m.room.message", { + msgtype: "m.text", + body: `!mjolnir ban ${banlist} ${bantype} ${glob} ${reason}`, + }, roomId); + } + } + } + + render() { + const { glob, banlist, aliases_shortcodes, bantype, reason } = this.state; + return ( + <> + <h3 className="text-2xl font-bold text-gray-900 dark:text-gray-200 mb-2">Ban</h3> + <form onSubmit={this.handleSubmit.bind(this)} className="grid grid-cols-1 gap-4"> + <label className="inner-flex flex-col"> + <span className="text-gray-900 dark:text-gray-200 visually-hidden mb-2">Banlist</span> + <h4 className="text-gray-900 dark:text-gray-200 text-base font-thin mb-2 w-96 italic">(Warning: This doesn't check if the banlist can be written to or the bot if watches it)</h4> + <div className="w-96"> + <select required name="banlist" value={banlist} className="max-w-full min-w-full placeholder:text-gray-900 text-gray-900 rounded py-1.5 px-2" onChange={this.handleInputChange.bind(this)}> + <option value="" disabled>Select an Banlist</option> + { + [...aliases_shortcodes].map(([alias, shortcode]) => { + return <option key={alias} value={shortcode}>{alias}</option>; + }) + } + </select> + </div> + </label> + <label className="inner-flex flex-col w-96"> + <span className="text-gray-900 dark:text-gray-200 visually-hidden mb-2">Bantype</span> + <div className="w-96"> + <select required defaultValue="" name="bantype" value={bantype} className="max-w-full min-w-full placeholder:text-gray-900 text-gray-900 rounded py-1.5 px-2" onChange={this.handleInputChange.bind(this)}> + <option value="" disabled>Select an Bantype</option> + <option value="user">User</option> + <option value="server">Server</option> + </select> + </div> + </label> + <label className="block w-96"> + <span className="text-gray-900 dark:text-gray-200 visually-hidden mb-2">Glob</span> + <div className="mt-1 w-full flex flex-row box-border items-center cursor-text duration-300 max-w-full"> + <input required placeholder="Glob" 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="glob" value={glob} 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">Reason</span> + <div className="mt-1 w-full flex flex-row box-border items-center cursor-text duration-300 max-w-full"> + <input placeholder="Reason" 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="reason" value={reason} onChange={this.handleInputChange.bind(this)} /> + </div> + </label> + + <input className="bg-red-400 hover:bg-red-500 cursor-pointer h-10 rounded dark:text-gray-900 text-gray-200 w-96" type="submit" value="Ban" /> + </form> + </> + ); + } +} + +export default BanForm; +\ No newline at end of file diff --git a/src/components/forms/Banlist.tsx b/src/components/forms/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/forms/RedactForm.tsx b/src/components/forms/RedactForm.tsx @@ -32,6 +32,7 @@ class RedactForm extends PureComponent<Props, 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}`; @@ -63,26 +64,26 @@ class RedactForm extends PureComponent<Props, State> { <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)} /> + <input disabled={event !== ""} required={event === ""} 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)} /> + <input disabled={event !== ""} 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)} /> + <input disabled={event !== ""} 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)} /> + <input disabled={user !== ""} required={user === ""} 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>