Telegram Web K with changes to work inside I2P https://web.telegram.i2p/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

311 lines
9.3 KiB

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type { MediaSearchContext } from "../components/appMediaPlaybackController";
import type { SearchSuperContext } from "../components/appSearchSuper.";
import type { Message } from "../layer";
import type { MessagesStorageKey, MyMessage } from "../lib/appManagers/appMessagesManager";
import { AppManagers } from "../lib/appManagers/managers";
import incrementMessageId from "../lib/appManagers/utils/messageId/incrementMessageId";
import rootScope from "../lib/rootScope";
import forEachReverse from "./array/forEachReverse";
import filterChatPhotosMessages from "./filterChatPhotosMessages";
import ListLoader, { ListLoaderOptions } from "./listLoader";
export default class SearchListLoader<Item extends {mid: number, peerId: PeerId}> extends ListLoader<Item, Message.message> {
public searchContext: MediaSearchContext;
public onEmptied: () => void;
private otherSideLoader: SearchListLoader<Item>;
private managers: AppManagers;
constructor(options: Omit<ListLoaderOptions<Item, Message.message>, 'loadMore'> & {
onEmptied?: () => void,
isInner?: boolean,
managers?: AppManagers
} = {}) {
super({
...options,
loadMore: (anchor, older, loadCount) => {
const backLimit = older ? 0 : loadCount;
let maxId = anchor?.mid;
if(maxId === undefined) maxId = this.searchContext.maxId;
if(!older) maxId = incrementMessageId(maxId, 1);
return this.managers.appMessagesManager.getSearch({
...this.searchContext,
peerId: this.searchContext.peerId || anchor?.peerId,
maxId,
limit: backLimit ? 0 : loadCount,
backLimit
}).then((value) => {
/* if(DEBUG) {
this.log('loaded more media by maxId:', maxId, value, older, this.reverse);
} */
if(this.searchContext.inputFilter._ === 'inputMessagesFilterChatPhotos') {
filterChatPhotosMessages(value);
}
if(value.next_rate) {
this.searchContext.nextRate = value.next_rate;
}
return {count: value.count, items: value.history};
});
},
processItem: async(message) => {
const filtered = await this.filterMids([message.mid]);
if(!filtered.length) {
return;
}
return options.processItem(message);
}
});
this.managers ??= rootScope.managers;
rootScope.addEventListener('history_delete', this.onHistoryDelete);
rootScope.addEventListener('history_multiappend', this.onHistoryMultiappend);
rootScope.addEventListener('message_sent', this.onMessageSent);
if(!options.isInner) {
this.otherSideLoader = new SearchListLoader({
...options,
isInner: true
});
// this.otherSideLoader.onLoadedMore = () => {
// };
}
}
protected async filterMids(mids: number[]) {
const storageKey: MessagesStorageKey = `${this.searchContext.peerId}_${this.searchContext.isScheduled ? 'scheduled' : 'history'}`;
const filtered = (await this.managers.appMessagesManager.filterMessagesByInputFilterFromStorage(this.searchContext.inputFilter._, mids, storageKey, mids.length)) as Message.message[];
return filtered;
}
protected onHistoryDelete = ({peerId, msgs}: {peerId: PeerId, msgs: Set<number>}) => {
const shouldBeDeleted = (item: Item) => item.peerId === peerId && msgs.has(item.mid);
const filter = (item: Item, idx: number, arr: Item[]) => {
if(shouldBeDeleted(item)) {
arr.splice(idx, 1);
}
};
forEachReverse(this.previous, filter);
forEachReverse(this.next, filter);
if(this.current && shouldBeDeleted(this.current)) {
this.current = undefined;
/* if(this.go(1)) {
this.previous.splice(this.previous.length - 1, 1);
} else if(this.go(-1)) {
this.next.splice(0, 1);
} else */if(this.onEmptied) {
this.onEmptied();
}
}
};
protected onHistoryMultiappend = async(obj: {
[peerId: string]: Set<number>;
}) => {
if(this.searchContext.folderId !== undefined) {
return;
}
// because it's reversed
if(!this.loadedAllUp || this.loadPromiseUp) {
return;
}
const mids = obj[this.searchContext.peerId];
if(!mids) {
return;
}
const sorted = Array.from(mids).sort((a, b) => a - b);
const filtered = await this.filterMids(sorted);
const targets = (await Promise.all(filtered.map((message) => this.processItem(message)))).filter(Boolean);
if(targets.length) {
/* const {previous, current, next} = this;
const targets = previous.concat(current, next);
const currentIdx = targets.length;
const mid = targets[0].mid;
let i = 0, length = targets.length;
for(; i < length; ++i) {
const target = targets[i];
if(!target || mid < target.mid) {
break;
}
}
if(i < currentIdx) previous.push(...targets);
else next. */
if(!this.current) {
this.previous.push(...targets);
} else {
this.next.push(...targets);
}
}
};
protected onMessageSent = ({message}: {message: MyMessage}) => {
this.onHistoryMultiappend({
[message.peerId]: new Set([message.mid])
});
};
public setSearchContext(context: SearchSuperContext) {
this.searchContext = context;
if(this.searchContext.folderId !== undefined) {
this.loadedAllUp = true;
if(this.searchContext.nextRate === undefined) {
this.loadedAllDown = true;
}
}
if(this.searchContext.inputFilter._ === 'inputMessagesFilterChatPhotos') {
this.loadedAllUp = true;
}
if(this.searchContext.useSearch === false) {
this.loadedAllDown = this.loadedAllUp = true;
}
if(this.otherSideLoader) {
this.otherSideLoader.setSearchContext(context);
}
}
public reset() {
super.reset();
this.searchContext = undefined;
if(this.otherSideLoader) {
this.otherSideLoader.reset();
}
}
public getPrevious() {
let previous = this.previous;
if(this.otherSideLoader) {
previous = previous.concat(this.otherSideLoader.previous);
}
return previous;
}
public getNext() {
let next = this.next;
if(this.otherSideLoader) {
next = next.concat(this.otherSideLoader.next);
}
return next;
}
public getCurrent() {
return this.current || this.otherSideLoader?.current;
}
private goToOtherEnd(length: number) {
if(length > 0) return this.go(-this.previous.length);
else return this.go(this.next.length);
}
public goRound(length: number, dispatchJump?: boolean) {
let ret: ReturnType<SearchListLoader<Item>['goUnsafe']>;
if(this.otherSideLoader?.current) {
ret = this.otherSideLoader.goUnsafe(length, dispatchJump);
if(ret.item) {
return ret.item;
}
length = ret.leftLength;
if(!(length > 0 ? this.otherSideLoader.next : this.otherSideLoader.previous).length) {
const loaded = length > 0 ? this.otherSideLoader.loadedAllUp : this.otherSideLoader.loadedAllDown;
if(!loaded) { // do not reset anything until it's loaded
return;
}
// if other side is loaded too will start from its begin
if((length > 0 && (this.otherSideLoader.searchContext.maxId === 1 || this.otherSideLoader.loadedAllDown)) ||
(length < 0 && (this.otherSideLoader.searchContext.maxId === 0 || this.otherSideLoader.loadedAllUp))) {
return this.otherSideLoader.goToOtherEnd(length);
}
this.otherSideLoader.unsetCurrent(length > 0);
}
}
ret = this.goUnsafe(length, dispatchJump);
if(!ret.item) {
if(this.loadedAllUp && this.loadedAllDown) { // just use the same loader if the list is too short
return this.goToOtherEnd(length);
} else if(this.otherSideLoader) {
length = ret.leftLength;
ret = this.otherSideLoader.goUnsafe(length, dispatchJump);
if(ret.item) {
this.unsetCurrent(length > 0);
}
}
}
return ret?.item;
}
// public setTargets(previous: Item[], next: Item[], reverse: boolean) {
// super.setTargets(previous, next, reverse);
// }
protected setLoaded(down: boolean, value: boolean) {
const changed = super.setLoaded(down, value);
if(changed &&
this.otherSideLoader &&
value &&
this.searchContext?.useSearch !== false/* &&
(this.reverse ? this.loadedAllUp : this.loadedAllDown) */) {
const reverse = this.loadedAllUp;
this.otherSideLoader.setSearchContext({
...this.searchContext,
maxId: reverse ? 1 : 0
});
// these 'reverse' are different, not a mistake here.
this.otherSideLoader.reverse = this.reverse;
this.otherSideLoader.setLoaded(reverse, true);
this.otherSideLoader.load(!reverse);
}
return changed;
}
public cleanup() {
this.reset();
rootScope.removeEventListener('history_delete', this.onHistoryDelete);
rootScope.removeEventListener('history_multiappend', this.onHistoryMultiappend);
rootScope.removeEventListener('message_sent', this.onMessageSent);
this.onEmptied = undefined;
if(this.otherSideLoader) {
this.otherSideLoader.cleanup();
this.otherSideLoader = undefined;
}
}
}