lbry-desktop/ui/redux/selectors/search.js

244 lines
9.3 KiB
JavaScript
Raw Normal View History

2020-07-27 22:04:12 +02:00
// @flow
2021-03-31 22:55:26 +02:00
import { selectShowMatureContent } from 'redux/selectors/settings';
import {
selectClaimsByUri,
makeSelectClaimForUri,
makeSelectClaimForClaimId,
selectClaimIsNsfwForUri,
makeSelectPendingClaimForUri,
selectIsUriResolving,
} from 'redux/selectors/claims';
import { parseURI } from 'util/lbryURI';
import { isClaimNsfw } from 'util/claim';
2020-07-27 22:04:12 +02:00
import { createSelector } from 'reselect';
import { createCachedSelector } from 're-reselect';
import { createNormalizedSearchKey, getRecommendationSearchKey, getRecommendationSearchOptions } from 'util/search';
import { selectMutedChannels } from 'redux/selectors/blocked';
import { selectHistory } from 'redux/selectors/content';
import { selectAllCostInfoByUri } from 'lbryinc';
2020-07-27 22:04:12 +02:00
2022-03-17 05:55:40 +01:00
type State = { claims: any, search: SearchState, user: UserState };
2020-07-27 22:04:12 +02:00
export const selectState = (state: State): SearchState => state.search;
// $FlowFixMe - 'searchQuery' is never populated. Something lost in a merge?
export const selectSearchValue: (state: State) => string = (state) => selectState(state).searchQuery;
export const selectSearchOptions: (state: State) => SearchOptions = (state) => selectState(state).options;
export const selectIsSearching: (state: State) => boolean = (state) => selectState(state).searching;
export const selectSearchResultByQuery: (state: State) => { [string]: Array<string> } = (state) =>
selectState(state).resultsByQuery;
export const selectHasReachedMaxResultsLength: (state: State) => { [boolean]: Array<boolean> } = (state) =>
selectState(state).hasReachedMaxResultsLength;
2021-12-07 15:51:55 +01:00
export const selectMentionSearchResults: (state: State) => Array<string> = (state) => selectState(state).results;
2021-12-07 19:17:29 +01:00
export const selectMentionQuery: (state: State) => string = (state) => selectState(state).mentionQuery;
export const selectPersonalRecommendations = (state: State) => selectState(state).personalRecommendations;
2021-03-26 09:33:30 +01:00
export const makeSelectSearchUrisForQuery = (query: string): ((state: State) => Array<string>) =>
createSelector(selectSearchResultByQuery, (byQuery) => {
if (!query) return;
// replace statement below is kind of ugly, and repeated in doSearch action
query = query.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
const normalizedQuery = createNormalizedSearchKey(query);
return byQuery[normalizedQuery] && byQuery[normalizedQuery]['uris'];
2021-03-26 09:33:30 +01:00
});
export const makeSelectHasReachedMaxResultsLength = (query: string): ((state: State) => boolean) =>
createSelector(selectHasReachedMaxResultsLength, (hasReachedMaxResultsLength) => {
if (query) {
query = query.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
const normalizedQuery = createNormalizedSearchKey(query);
return hasReachedMaxResultsLength[normalizedQuery];
}
return hasReachedMaxResultsLength[query];
});
2020-07-27 22:04:12 +02:00
export const selectRecommendedContentForUri = createCachedSelector(
(state, uri) => uri,
selectHistory,
selectClaimsByUri,
selectShowMatureContent,
selectMutedChannels,
selectAllCostInfoByUri,
selectSearchResultByQuery,
selectClaimIsNsfwForUri, // (state, uri)
(uri, history, claimsByUri, matureEnabled, blockedChannels, costInfoByUri, searchUrisByQuery, isMature) => {
const claim = claimsByUri[uri];
if (!claim) return;
let recommendedContent;
// always grab the claimId - this value won't change for filtering
const currentClaimId = claim.claim_id;
const { title } = claim.value;
if (!title) return;
const options = getRecommendationSearchOptions(matureEnabled, isMature, claim.claim_id);
const normalizedSearchQuery = getRecommendationSearchKey(title, options);
let searchResult = searchUrisByQuery[normalizedSearchQuery];
if (searchResult) {
// Filter from recommended: The same claim and blocked channels
recommendedContent = searchResult['uris'].filter((searchUri) => {
const searchClaim = claimsByUri[searchUri];
2020-07-27 22:04:12 +02:00
if (!searchClaim) {
return true;
}
2020-07-27 22:04:12 +02:00
const signingChannel = searchClaim && searchClaim.signing_channel;
const channelUri = signingChannel && signingChannel.canonical_url;
const blockedMatch = blockedChannels.some((blockedUri) => blockedUri.includes(channelUri));
2020-07-27 22:04:12 +02:00
let isEqualUri;
try {
const { claimId: searchId } = parseURI(searchUri);
isEqualUri = searchId === currentClaimId;
} catch (e) {}
return !isEqualUri && !blockedMatch;
});
// Claim to play next: playable and free claims not played before in history
for (let i = 0; i < recommendedContent.length; ++i) {
const nextRecommendedUri = recommendedContent[i];
const costInfo = costInfoByUri[nextRecommendedUri] && costInfoByUri[nextRecommendedUri].cost;
const recommendedClaim = claimsByUri[nextRecommendedUri];
const isVideo = recommendedClaim && recommendedClaim.value && recommendedClaim.value.stream_type === 'video';
const isAudio = recommendedClaim && recommendedClaim.value && recommendedClaim.value.stream_type === 'audio';
let historyMatch = false;
try {
const { claimId: nextRecommendedId } = parseURI(nextRecommendedUri);
historyMatch = history.some(
(historyItem) =>
(claimsByUri[historyItem.uri] && claimsByUri[historyItem.uri].claim_id) === nextRecommendedId
);
} catch (e) {}
if (!historyMatch && costInfo === 0 && (isVideo || isAudio)) {
// Better next-uri found, swap with top entry:
if (i > 0) {
const a = recommendedContent[0];
recommendedContent[0] = nextRecommendedUri;
recommendedContent[i] = a;
}
break;
}
2020-07-27 22:04:12 +02:00
}
}
return recommendedContent;
}
)((state, uri) => String(uri));
export const makeSelectRecommendedRecsysIdForClaimId = (claimId: string) =>
createSelector(
makeSelectClaimForClaimId(claimId),
selectShowMatureContent,
selectSearchResultByQuery,
(claim, matureEnabled, searchUrisByQuery) => {
// TODO: DRY this out.
let poweredBy;
if (claim && claimId) {
const isMature = isClaimNsfw(claim);
const { title } = claim.value;
if (!title) {
return;
}
const options = getRecommendationSearchOptions(matureEnabled, isMature, claimId);
const normalizedSearchQuery = getRecommendationSearchKey(title, options);
const searchResult = searchUrisByQuery[normalizedSearchQuery];
if (searchResult) {
poweredBy = searchResult.recsys;
} else {
return normalizedSearchQuery;
}
}
return poweredBy;
}
);
export const makeSelectWinningUriForQuery = (query: string) => {
const uriFromQuery = `lbry://${query}`;
let channelUriFromQuery = '';
try {
const { isChannel } = parseURI(uriFromQuery);
if (!isChannel) {
channelUriFromQuery = `lbry://@${query}`;
}
} catch (e) {}
return createSelector(
2021-03-31 22:55:26 +02:00
selectShowMatureContent,
makeSelectPendingClaimForUri(uriFromQuery),
makeSelectClaimForUri(uriFromQuery),
makeSelectClaimForUri(channelUriFromQuery),
(matureEnabled, pendingClaim, claim1, claim2) => {
2020-12-03 18:29:47 +01:00
const claim1Mature = claim1 && isClaimNsfw(claim1);
const claim2Mature = claim2 && isClaimNsfw(claim2);
let pendingAmount = pendingClaim && pendingClaim.amount;
2020-12-03 18:29:47 +01:00
if (!claim1 && !claim2) {
return undefined;
} else if (!claim1 && claim2) {
2020-12-03 18:29:47 +01:00
return matureEnabled ? claim2.canonical_url : claim2Mature ? undefined : claim2.canonical_url;
} else if (claim1 && !claim2) {
return matureEnabled
? claim1.repost_url || claim1.canonical_url
: claim1Mature
? undefined
: claim1.repost_url || claim1.canonical_url;
}
const effectiveAmount1 = claim1 && (claim1.repost_bid_amount || claim1.meta.effective_amount);
// claim2 will never have a repost_bid_amount because reposts never start with "@"
const effectiveAmount2 = claim2 && claim2.meta.effective_amount;
2020-11-04 20:39:16 +01:00
2020-12-03 18:29:47 +01:00
if (!matureEnabled) {
if (claim1Mature && !claim2Mature) {
return claim2.canonical_url;
} else if (claim2Mature && !claim1Mature) {
return claim1.repost_url || claim1.canonical_url;
2020-12-03 18:29:47 +01:00
} else if (claim1Mature && claim2Mature) {
return undefined;
}
}
const returnBeforePending =
2021-01-13 16:44:44 +01:00
Number(effectiveAmount1) > Number(effectiveAmount2)
? claim1.repost_url || claim1.canonical_url
: claim2.canonical_url;
if (pendingAmount && pendingAmount > effectiveAmount1 && pendingAmount > effectiveAmount2) {
return pendingAmount.permanent_url;
} else {
return returnBeforePending;
}
}
);
};
2020-12-16 19:31:07 +01:00
export const selectIsResolvingWinningUri = (state: State, query: string = '') => {
2020-12-16 19:31:07 +01:00
const uriFromQuery = `lbry://${query}`;
let channelUriFromQuery;
try {
const { isChannel } = parseURI(uriFromQuery);
if (!isChannel) {
channelUriFromQuery = `lbry://@${query}`;
}
} catch (e) {}
const claim1IsResolving = selectIsUriResolving(state, uriFromQuery);
const claim2IsResolving = channelUriFromQuery ? selectIsUriResolving(state, channelUriFromQuery) : false;
return claim1IsResolving || claim2IsResolving;
2020-12-16 19:31:07 +01:00
};
export const makeSelectUrlForClaimId = (claimId: string) =>
createSelector(makeSelectClaimForClaimId(claimId), (claim) =>
claim ? claim.canonical_url || claim.permanent_url : null
);