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

Banlist.tsx (11560B)


      1 import { PureComponent } from 'react';
      2 import {
      3     CanonicalAliasEvent,
      4     CANONICAL_ALIAS,
      5     DEV_NORDGEDANKEN_MJOLNIR_BANLISTS,
      6     MembershipEvent,
      7     MJjolnirBanlists,
      8     M_POLICY_RULE_ROOM,
      9     M_POLICY_RULE_ROOM_ALT,
     10     M_POLICY_RULE_ROOM_OLD,
     11     M_POLICY_RULE_SERVER,
     12     M_POLICY_RULE_SERVER_ALT,
     13     M_POLICY_RULE_SERVER_OLD,
     14     M_POLICY_RULE_USER,
     15     M_POLICY_RULE_USER_ALT,
     16     M_POLICY_RULE_USER_OLD,
     17     RuleEvent
     18 } from '../windowExt';
     19 
     20 type Props = Record<string, unknown>;
     21 type State = {
     22     user_rules: RuleEvent[];
     23     server_rules: RuleEvent[];
     24     room_rules: RuleEvent[];
     25     roomId?: string;
     26     filter?: string;
     27     memberships: MembershipEvent[];
     28     bantype: "user" | "server" | "room";
     29     aliases: CanonicalAliasEvent[];
     30 };
     31 
     32 class BanList extends PureComponent<Props, State> {
     33     constructor(props: Props) {
     34         super(props);
     35         const urlParams = (new URL(window.location.href)).searchParams;
     36         const roomId = urlParams.get("room_id") ?? undefined;
     37         this.state = {
     38             bantype: "user",
     39             roomId: roomId,
     40             user_rules: [],
     41             server_rules: [],
     42             room_rules: [],
     43             memberships: [],
     44             aliases:[]
     45         };
     46     }
     47 
     48     async componentDidMount() {
     49         const user_rules = await window.widget_api.readStateEvents(M_POLICY_RULE_USER, 250_000_000_000, undefined, ["*"]) as RuleEvent[];
     50         const server_rules = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER, 250_000_000_000, undefined, ["*"]) as RuleEvent[];
     51         const room_rules = await window.widget_api.readStateEvents(M_POLICY_RULE_ROOM, 250_000_000_000, undefined, ["*"]) as RuleEvent[];
     52         const user_rules_old = await window.widget_api.readStateEvents(M_POLICY_RULE_USER_OLD, 250_000_000_000, undefined, ["*"]) as RuleEvent[];
     53         const server_rules_old = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER_OLD, 250_000_000_000, undefined, ["*"]) as RuleEvent[];
     54         const room_rules_old = await window.widget_api.readStateEvents(M_POLICY_RULE_ROOM_OLD, 250_000_000_000, undefined, ["*"]) as RuleEvent[];
     55         const user_rules_alt = await window.widget_api.readStateEvents(M_POLICY_RULE_USER_ALT, 250_000_000_000, undefined, ["*"]) as RuleEvent[];
     56         const server_rules_alt = await window.widget_api.readStateEvents(M_POLICY_RULE_SERVER_ALT, 250_000_000_000, undefined, ["*"]) as RuleEvent[];
     57         const room_rules_alt = await window.widget_api.readStateEvents(M_POLICY_RULE_ROOM_ALT, 250_000_000_000, undefined, ["*"]) as RuleEvent[];
     58         const user_rules_all = [...user_rules, ...user_rules_old, ...user_rules_alt];
     59         const server_rules_all = [...server_rules, ...server_rules_old, ...server_rules_alt];
     60         const room_rules_all = [...room_rules, ...room_rules_old, ...room_rules_alt];
     61         const list_rooms = [...new Set([...user_rules_all.map(element => element.room_id), ...server_rules_all.map(element => element.room_id)])];
     62         const sender = new Set([...user_rules_all.map(element => element.sender), ...server_rules_all.map(element => element.sender)]);
     63         const memberships = await window.widget_api.readStateEvents("m.room.member", 250_000_000_000, undefined, list_rooms) as MembershipEvent[];
     64         const memberships_needed = memberships.filter(member => sender.has(member.state_key));
     65         const banlist_codes = await window.widget_api.readStateEvents(DEV_NORDGEDANKEN_MJOLNIR_BANLISTS, 250_000_000_000, undefined, [this.state.roomId ?? "*"]) as MJjolnirBanlists[];
     66         const aliases = await window.widget_api.readStateEvents(CANONICAL_ALIAS, list_rooms.length, undefined, list_rooms) as CanonicalAliasEvent[];
     67 
     68         this.setState({
     69             aliases,
     70             user_rules: user_rules_all.filter(rules => {
     71                 if (banlist_codes.length > 0) {
     72                     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 ?? {})));
     73                 } else {
     74                     return true;
     75                 }
     76             }),
     77             server_rules: server_rules_all.filter(rules => {
     78                 if (banlist_codes.length > 0) {
     79                     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 ?? {})));
     80                 } else {
     81                     return true;
     82                 }
     83             }),
     84             room_rules: room_rules_all.filter(rules => {
     85                 if (banlist_codes.length > 0) {
     86                     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 ?? {})));
     87                 } else {
     88                     return true;
     89                 }
     90             }),
     91             memberships: memberships_needed
     92         });
     93     }
     94 
     95     onSearch(ev: { target: { value: string; }; }) {
     96         this.setState({
     97             filter: ev.target.value
     98         });
     99     }
    100 
    101     handleInputChange(event: { target: { type: string, checked?: boolean, value: string | boolean, name: string; }; }) {
    102         const target = event.target;
    103         const value = target.type === 'checkbox' ? target.checked : target.value;
    104         const name = target.name;
    105         this.setState({
    106             [name]: value
    107         } as unknown as State);
    108     }
    109 
    110     render() {
    111         const { user_rules, server_rules, filter, memberships, bantype, room_rules,aliases } = this.state;
    112         const filtered_user_rules = filter ? user_rules.filter(element => element.content && Object.keys(element.content).length > 0 && element.content?.entity.includes(filter)) : user_rules;
    113         const filtered_server_rules = filter ? server_rules.filter(element => element.content && Object.keys(element.content).length > 0 && element.content?.entity.includes(filter)) : server_rules;
    114         const filtered_room_rules = filter ? room_rules.filter(element => element.content && Object.keys(element.content).length > 0 && element.content?.entity.includes(filter)) : room_rules;
    115 
    116         const rules = bantype === "user" ? filtered_user_rules : (bantype === "server" ? filtered_server_rules : filtered_room_rules);
    117         return (
    118             <main className="m-8 flex items-start flex-col">
    119                 <div className='flex flex-row justify-between content-between w-full'>
    120                     <h1 className="text-3xl font-bold underline text-gray-900 dark:text-gray-200 mb-4">Banlist data</h1>
    121                     <div className='flex flex-row'>
    122                         <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)}>
    123                             <option value="" disabled>Select an Bantype</option>
    124                             <option value="user">User</option>
    125                             <option value="server">Server</option>
    126                             <option value="room">Room</option>
    127                         </select>
    128                         <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>
    129                     </div>
    130                 </div>
    131                 <div className='flex flex-row w-full mb-4'>
    132                     <h5 className="text-lg font-medium text-gray-900 dark:text-gray-200 mr-2">User Bans ({user_rules.length})</h5>
    133                     <h5 className="text-lg font-medium text-gray-900 dark:text-gray-200 mx-2">Server Bans ({server_rules.length})</h5>
    134                     <h5 className="text-lg font-medium text-gray-900 dark:text-gray-200 mx-2">Room Bans ({room_rules.length})</h5>
    135                 </div>
    136                 <div className='flex flex-row w-full mb-4'>
    137                     <h3 className="text-2xl font-bold text-gray-900 dark:text-gray-200">{bantype === "room" ? "Room Bans" : (bantype === "server" ? "Server Bans" : "User Bans")}</h3>
    138                 </div>
    139                 <div className='flex flex-row justify-between content-between w-full'>
    140                     <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'>
    141                         <thead>
    142                             <tr>
    143                                 <th className='border-2 border-slate-600 p-1'>Entity</th>
    144                                 <th className='border-2 border-slate-600 p-1'>Recommendation</th>
    145                                 <th className='border-2 border-slate-600 p-1'>Reason</th>
    146                                 <th className='border-2 border-slate-600 p-1'>Sender</th>
    147                                 <th className='border-2 border-slate-600 p-1'>Room</th>
    148                             </tr>
    149                         </thead>
    150                         <tbody>
    151                             {
    152                                 rules.length > 0 ?
    153                                     rules.map(element => {
    154                                         if (element.content && Object.keys(element.content).length > 0) {
    155                                             const recommendation = element.content.recommendation === "m.ban" || element.content.recommendation === "org.matrix.mjolnir.ban" ? "Ban" : element.content.recommendation;
    156 
    157                                             // Entity is intentionally left as a mxid
    158                                             const sender_dp = memberships.find(membership => membership.state_key === element.sender)?.content?.displayname ?? element.sender;
    159                                             return (
    160                                                 <tr key={`${element.event_id}`} className='border-2 border-slate-600'>
    161                                                     <td className='p-1 min-w-[8rem] max-w-[14rem]'>{element.content.entity}</td>
    162                                                     <td className='p-1 min-w-[1rem] max-w-[14rem]'>{recommendation}</td>
    163                                                     <td className='p-1 min-w-[8rem] max-w-[14rem]'>{element.content.reason}</td>
    164                                                     <td className='p-1 min-w-[12rem] max-w-[14rem]'>{sender_dp}</td>
    165                                                     <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>
    166                                                 </tr>
    167                                             );
    168                                         }
    169                                     }) : (
    170                                         <tr className='border-2 border-slate-600'>
    171                                             <td className='p-1'>No user ban found</td>
    172                                         </tr>
    173                                     )
    174                             }
    175                         </tbody>
    176                     </table>
    177                 </div>
    178             </main>
    179         );
    180     }
    181 }
    182 
    183 export default BanList;