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 c71c9f275a68fabd21b57efbcdf859cafaee3adb
parent c266488c37d0ca037dc0afc04f24f761af3c7f8f
Author: MTRNord <mtrnord1@gmail.com>
Date:   Fri, 20 May 2022 15:49:59 +0200

Improve banlist overview

Diffstat:
Msrc/components/Banlist.tsx | 191+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/components/forms/BanForm.tsx | 15+++++++--------
Msrc/index.tsx | 13++++++++++---
Msrc/windowExt.ts | 17++++-------------
4 files changed, 119 insertions(+), 117 deletions(-)

diff --git a/src/components/Banlist.tsx b/src/components/Banlist.tsx @@ -5,23 +5,28 @@ import { DEV_NORDGEDANKEN_MJOLNIR_BANLISTS, MembershipEvent, MJjolnirBanlists, + M_POLICY_RULE_ROOM, + M_POLICY_RULE_ROOM_ALT, + M_POLICY_RULE_ROOM_OLD, 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 + RuleEvent } from '../windowExt'; type Props = Record<string, unknown>; type State = { - user_rules: UserRuleEvent[]; - server_rules: ServerRuleEvent[]; + user_rules: RuleEvent[]; + server_rules: RuleEvent[]; + room_rules: RuleEvent[]; roomId?: string; filter?: string; memberships: MembershipEvent[]; + bantype: "user" | "server" | "room"; + aliases: CanonicalAliasEvent[]; }; class BanList extends PureComponent<Props, State> { @@ -30,22 +35,29 @@ class BanList extends PureComponent<Props, State> { const urlParams = (new URL(window.location.href)).searchParams; const roomId = urlParams.get("room_id") ?? undefined; this.state = { + bantype: "user", roomId: roomId, user_rules: [], server_rules: [], - memberships: [] + room_rules: [], + memberships: [], + aliases:[] }; } 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 = await window.widget_api.readStateEvents(M_POLICY_RULE_USER, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const server_rules = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const room_rules = await window.widget_api.readStateEvents(M_POLICY_RULE_ROOM, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const user_rules_old = await window.widget_api.readStateEvents(M_POLICY_RULE_USER_OLD, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const server_rules_old = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER_OLD, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const room_rules_old = await window.widget_api.readStateEvents(M_POLICY_RULE_ROOM_OLD, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const user_rules_alt = await window.widget_api.readStateEvents(M_POLICY_RULE_USER_ALT, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const server_rules_alt = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER_ALT, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const room_rules_alt = await window.widget_api.readStateEvents(M_POLICY_RULE_ROOM_ALT, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; 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 room_rules_all = [...room_rules, ...room_rules_old, ...room_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[]; @@ -54,6 +66,7 @@ class BanList extends PureComponent<Props, State> { const aliases = await window.widget_api.readStateEvents(CANONICAL_ALIAS, list_rooms.length, undefined, list_rooms) as CanonicalAliasEvent[]; this.setState({ + aliases, user_rules: user_rules_all.filter(rules => { if (banlist_codes.length > 0) { return banlist_codes.some(banlist_codes_event => ((aliases.find(aliases_event => aliases_event.room_id === rules.room_id)?.content?.alias ?? "") in (banlist_codes_event.content?.banlists ?? {})) || (rules.room_id in (banlist_codes_event.content?.banlists ?? {}))); @@ -68,6 +81,13 @@ class BanList extends PureComponent<Props, State> { return true; } }), + room_rules: room_rules_all.filter(rules => { + if (banlist_codes.length > 0) { + return banlist_codes.some(banlist_codes_event => ((aliases.find(aliases_event => aliases_event.room_id === rules.room_id)?.content?.alias ?? "") in (banlist_codes_event.content?.banlists ?? {})) || (rules.room_id in (banlist_codes_event.content?.banlists ?? {}))); + } else { + return true; + } + }), memberships: memberships_needed }); } @@ -78,97 +98,82 @@ class BanList extends PureComponent<Props, State> { }); } + 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); + } + render() { - const { user_rules, server_rules, filter, memberships } = this.state; + const { user_rules, server_rules, filter, memberships, bantype, room_rules,aliases } = 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; + const filtered_room_rules = filter ? room_rules.filter(element => element.content && Object.keys(element.content).length > 0 && element.content?.entity.includes(filter)) : room_rules; + + const rules = bantype === "user" ? filtered_user_rules : (bantype === "server" ? filtered_server_rules : filtered_room_rules); return ( - <main className="m-8 flex items-center flex-col justify-center"> + <main className="m-8 flex items-start flex-col"> <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 className='flex flex-row'> + <select required defaultValue="" name="bantype" value={bantype} className="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> + <option value="room">Room</option> + </select> + <input onChange={this.onSearch.bind(this)} value={filter} placeholder="Search for user or server" className='ml-2 py-1.5 px-2 rounded border-none placeholder:text-gray-900 text-gray-9000'></input> + </div> + </div> + <div className='flex flex-row w-full mb-4'> + <h5 className="text-lg font-medium text-gray-900 dark:text-gray-200 mr-2">User Bans ({user_rules.length})</h5> + <h5 className="text-lg font-medium text-gray-900 dark:text-gray-200 mx-2">Server Bans ({server_rules.length})</h5> + <h5 className="text-lg font-medium text-gray-900 dark:text-gray-200 mx-2">Room Bans ({room_rules.length})</h5> + </div> + <div className='flex flex-row w-full mb-4'> + <h3 className="text-2xl font-bold text-gray-900 dark:text-gray-200">{bantype === "room" ? "Room Bans" : (bantype === "server" ? "Server Bans" : "User Bans")}</h3> </div> <div className='flex flex-row justify-between content-between w-full'> - <section className='mr-2 w-full'> - <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 w-full'> - <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> - <th className='border-2 border-slate-600 p-1'>Room</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> - <td className='p-1 min-w-[12rem] max-w-[14rem]'>{element.room_id}</td> - </tr> - ); - } - }) : ( - <tr className='border-2 border-slate-600'> - <td className='p-1'>No user ban found</td> - </tr> - ) - } - </tbody> - </table> - </section> - <section className="w-full"> - <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 w-full'> - <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> - <th className='border-2 border-slate-600 p-1'>Room</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; + <table className='p-16 table-auto border-collapse border-2 border-slate-500 text-gray-900 dark:text-gray-200 text-base font-normal break-words w-full'> + <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> + <th className='border-2 border-slate-600 p-1'>Room</th> + </tr> + </thead> + <tbody> + { + rules.length > 0 ? + 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> - <td className='p-1 min-w-[12rem] max-w-[14rem]'>{element.room_id}</td> - </tr> - ); - } - }) : ( - <tr className='border-2 border-slate-600'> - <td className='p-1'>No server ban found</td> - </tr> - ) - } - </tbody> - </table> - </section> + // 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> + <td className='p-1 min-w-[12rem] max-w-[14rem]'>{aliases.find(aliases_event => aliases_event.room_id === element.room_id)?.content?.alias ?? element.room_id}</td> + </tr> + ); + } + }) : ( + <tr className='border-2 border-slate-600'> + <td className='p-1'>No user ban found</td> + </tr> + ) + } + </tbody> + </table> </div> </main> ); diff --git a/src/components/forms/BanForm.tsx b/src/components/forms/BanForm.tsx @@ -11,9 +11,8 @@ import { M_POLICY_RULE_USER_ALT, M_POLICY_RULE_USER_OLD, ORG_MATRIX_MJOLNIR_SHORTCODE, - ServerRuleEvent, + RuleEvent, ShortcodeEvent, - UserRuleEvent } from "../../windowExt"; type Props = Record<string, unknown>; @@ -42,12 +41,12 @@ class BanForm extends PureComponent<Props, State> { } 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 = await window.widget_api.readStateEvents(M_POLICY_RULE_USER, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const server_rules = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const user_rules_old = await window.widget_api.readStateEvents(M_POLICY_RULE_USER_OLD, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const server_rules_old = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER_OLD, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const user_rules_alt = await window.widget_api.readStateEvents(M_POLICY_RULE_USER_ALT, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; + const server_rules_alt = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER_ALT, 250_000_000_000, undefined, ["*"]) as RuleEvent[]; const banlist_codes = await window.widget_api.readStateEvents(DEV_NORDGEDANKEN_MJOLNIR_BANLISTS, 250_000_000_000, undefined, [this.state.roomId ?? "*"]) as MJjolnirBanlists[]; const user_rules_all = [...user_rules, ...user_rules_old, ...user_rules_alt]; const server_rules_all = [...server_rules, ...server_rules_old, ...server_rules_alt]; diff --git a/src/index.tsx b/src/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import './index.css'; import App from './components/App'; import reportWebVitals from './reportWebVitals'; @@ -7,6 +7,9 @@ import { WidgetApi } from 'matrix-widget-api'; import { CANONICAL_ALIAS, DEV_NORDGEDANKEN_MJOLNIR_BANLISTS, + M_POLICY_RULE_ROOM, + M_POLICY_RULE_ROOM_ALT, + M_POLICY_RULE_ROOM_OLD, M_POLICY_RULE_SERVER, M_POLICY_RULE_SERVER_ALT, M_POLICY_RULE_SERVER_OLD, @@ -23,10 +26,13 @@ const widgetId = urlParams.get("widgetId") ?? undefined; window.widget_api = new WidgetApi(widgetId); window.widget_api.requestCapabilityToReceiveState(M_POLICY_RULE_USER); window.widget_api.requestCapabilityToReceiveState(M_POLICY_RULE_SERVER); +window.widget_api.requestCapabilityToReceiveState(M_POLICY_RULE_ROOM); window.widget_api.requestCapabilityToReceiveState(M_POLICY_RULE_USER_OLD); window.widget_api.requestCapabilityToReceiveState(M_POLICY_RULE_SERVER_OLD); +window.widget_api.requestCapabilityToReceiveState(M_POLICY_RULE_ROOM_OLD); window.widget_api.requestCapabilityToReceiveState(M_POLICY_RULE_USER_ALT); window.widget_api.requestCapabilityToReceiveState(M_POLICY_RULE_SERVER_ALT); +window.widget_api.requestCapabilityToReceiveState(M_POLICY_RULE_ROOM_ALT); window.widget_api.requestCapabilityToReceiveState(CANONICAL_ALIAS); window.widget_api.requestCapabilityToReceiveState(ORG_MATRIX_MJOLNIR_SHORTCODE); window.widget_api.requestCapabilityToSendMessage(M_TEXT); @@ -36,11 +42,12 @@ window.widget_api.requestCapabilityToReceiveState(DEV_NORDGEDANKEN_MJOLNIR_BANLI window.widget_api.requestCapabilityForRoomTimeline("*"); window.widget_api.on(`ready`, () => { - ReactDOM.render( + const container = document.querySelector('#root'); + const root = createRoot(container!); + root.render( <React.StrictMode> <App /> </React.StrictMode>, - document.querySelector('#root') ); }); diff --git a/src/windowExt.ts b/src/windowExt.ts @@ -6,10 +6,13 @@ declare global { export const M_POLICY_RULE_USER = "m.policy.rule.user"; export const M_POLICY_RULE_SERVER = "m.policy.rule.server"; +export const M_POLICY_RULE_ROOM = "m.policy.rule.room"; export const M_POLICY_RULE_USER_OLD = "org.matrix.mjolnir.rule.user"; export const M_POLICY_RULE_SERVER_OLD = "org.matrix.mjolnir.rule.server"; +export const M_POLICY_RULE_ROOM_OLD = "org.matrix.mjolnir.rule.room"; export const M_POLICY_RULE_USER_ALT = "m.room.rule.user"; export const M_POLICY_RULE_SERVER_ALT = "m.room.rule.server"; +export const M_POLICY_RULE_ROOM_ALT = "m.room.rule.room"; export const CANONICAL_ALIAS = "m.room.canonical_alias"; export const ORG_MATRIX_MJOLNIR_SHORTCODE = "org.matrix.mjolnir.shortcode"; export const M_TEXT = "m.text"; @@ -17,19 +20,7 @@ export const M_ROOM_MEMBER = "m.room.member"; export const M_ROOM_POWERLEVELS = "m.room.power_levels"; export const DEV_NORDGEDANKEN_MJOLNIR_BANLISTS = "dev.nordgedanken.mjolnir_banlists"; -export type UserRuleEvent = { - sender: string; - state_key: string; - event_id: string; - room_id: string; - content?: { - entity: string; - reason: string; - recommendation: string; - }; -}; - -export type ServerRuleEvent = { +export type RuleEvent = { sender: string; state_key: string; event_id: string;