diff --git a/src/filter-component.ts b/src/filter-component.ts index 16665acb2..9ef535558 100644 --- a/src/filter-component.ts +++ b/src/filter-component.ts @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { RelationType } from "./@types/event"; import { UNSTABLE_FILTER_RELATION_SENDERS, UNSTABLE_FILTER_RELATION_TYPES } from "./filter"; import { MatrixEvent } from "./models/event"; @@ -47,6 +48,7 @@ export interface IFilterComponent { not_senders?: string[]; contains_url?: boolean; limit?: number; + [UNSTABLE_FILTER_RELATION_TYPES.name]?: Array; } /* eslint-enable camelcase */ @@ -62,7 +64,7 @@ export interface IFilterComponent { * @param {Object} filterJson the definition of this filter JSON, e.g. { 'contains_url': true } */ export class FilterComponent { - constructor(private filterJson: IFilterComponent) {} + constructor(private filterJson: IFilterComponent, public readonly userId?: string) {} /** * Checks with the filter component matches the given event @@ -70,11 +72,24 @@ export class FilterComponent { * @return {boolean} true if the event matches the filter */ public check(event: MatrixEvent): boolean { + const bundledRelationships = event.getUnsigned()?.["m.relations"] || {}; + const relations: Array = Object.keys(bundledRelationships); + // Relation senders allows in theory a look-up of any senders + // however clients can only know about the current user participation status + // as sending a whole list of participants could be proven problematic in terms + // of performance + // This should be improved when bundled relationships solve that problem + const relationSenders = []; + if (this.userId && relations?.[RelationType.Thread]?.current_user_participated) { + relationSenders.push(this.userId); + } return this.checkFields( event.getRoomId(), event.getSender(), event.getType(), event.getContent() ? event.getContent().url !== undefined : false, + relations, + relationSenders, ); } @@ -101,9 +116,18 @@ export class FilterComponent { * @param {String} sender the sender of the event being checked * @param {String} eventType the type of the event being checked * @param {boolean} containsUrl whether the event contains a content.url field + * @param {boolean} relationTypes whether has aggregated relation of the given type + * @param {boolean} relationSenders whether one of the relation is sent by the user listed * @return {boolean} true if the event fields match the filter */ - private checkFields(roomId: string, sender: string, eventType: string, containsUrl: boolean): boolean { + private checkFields( + roomId: string, + sender: string, + eventType: string, + containsUrl: boolean, + relationTypes: Array, + relationSenders: string[], + ): boolean { const literalKeys = { "rooms": function(v: string): boolean { return roomId === v; @@ -136,15 +160,35 @@ export class FilterComponent { return false; } + const relationTypesFilter = this.filterJson[UNSTABLE_FILTER_RELATION_TYPES.name]; + if (relationTypesFilter !== undefined) { + if (!this.arrayMatchesFilter(relationTypesFilter, relationTypes)) { + return false; + } + } + + const relationSendersFilter = this.filterJson[UNSTABLE_FILTER_RELATION_SENDERS.name]; + if (relationSendersFilter !== undefined) { + if (!this.arrayMatchesFilter(relationSendersFilter, relationSenders)) { + return false; + } + } + return true; } + private arrayMatchesFilter(filter: any[], values: any[]): boolean { + return values.length > 0 && values.every(value => { + return filter.includes(value); + }); + } + /** * Filters a list of events down to those which match this filter component * @param {MatrixEvent[]} events Events to be checked against the filter component * @return {MatrixEvent[]} events which matched the filter component */ - filter(events: MatrixEvent[]): MatrixEvent[] { + public filter(events: MatrixEvent[]): MatrixEvent[] { return events.filter(this.check, this); } @@ -153,7 +197,7 @@ export class FilterComponent { * 10 if none is otherwise specified. Cargo-culted from Synapse. * @return {Number} the limit for this filter component. */ - limit(): number { + public limit(): number { return this.filterJson.limit !== undefined ? this.filterJson.limit : 10; } } diff --git a/src/filter.ts b/src/filter.ts index eb619e78d..888d82a61 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -185,8 +185,8 @@ export class Filter { } } - this.roomFilter = new FilterComponent(roomFilterFields); - this.roomTimelineFilter = new FilterComponent(roomFilterJson?.timeline || {}); + this.roomFilter = new FilterComponent(roomFilterFields, this.userId); + this.roomTimelineFilter = new FilterComponent(roomFilterJson?.timeline || {}, this.userId); // don't bother porting this from synapse yet: // this._room_state_filter =