2020-07-27 22:04:12 +02:00
|
|
|
// @flow
|
|
|
|
import { getSearchQueryString } from 'util/query-params';
|
2021-03-31 22:55:26 +02:00
|
|
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
2021-09-16 22:00:44 +02:00
|
|
|
import { SEARCH_OPTIONS } from 'constants/search';
|
2020-12-04 01:10:23 +01:00
|
|
|
import {
|
|
|
|
parseURI,
|
2021-09-16 22:00:44 +02:00
|
|
|
selectClaimsByUri,
|
2020-12-04 01:10:23 +01:00
|
|
|
makeSelectClaimForUri,
|
2021-08-17 16:03:25 +02:00
|
|
|
makeSelectClaimForClaimId,
|
2020-12-04 01:10:23 +01:00
|
|
|
makeSelectClaimIsNsfw,
|
|
|
|
isClaimNsfw,
|
|
|
|
makeSelectPendingClaimForUri,
|
2020-12-16 19:31:07 +01:00
|
|
|
makeSelectIsUriResolving,
|
2020-12-04 01:10:23 +01:00
|
|
|
} from 'lbry-redux';
|
2020-07-27 22:04:12 +02:00
|
|
|
import { createSelector } from 'reselect';
|
2021-03-26 09:33:30 +01:00
|
|
|
import { createNormalizedSearchKey } from 'util/search';
|
2021-09-16 22:00:44 +02:00
|
|
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
|
|
|
import { selectHistory } from 'redux/selectors/content';
|
|
|
|
import { selectAllCostInfoByUri } from 'lbryinc';
|
|
|
|
import { SIMPLE_SITE } from 'config';
|
2020-07-27 22:04:12 +02:00
|
|
|
|
|
|
|
type State = { search: SearchState };
|
|
|
|
|
|
|
|
export const selectState = (state: State): SearchState => state.search;
|
|
|
|
|
2021-02-16 22:09:20 +01:00
|
|
|
export const selectSearchValue: (state: State) => string = createSelector(selectState, (state) => state.searchQuery);
|
2020-07-27 22:04:12 +02:00
|
|
|
|
2021-02-16 22:09:20 +01:00
|
|
|
export const selectSearchOptions: (state: State) => SearchOptions = createSelector(
|
|
|
|
selectState,
|
|
|
|
(state) => state.options
|
|
|
|
);
|
2020-07-27 22:04:12 +02:00
|
|
|
|
2021-02-16 22:09:20 +01:00
|
|
|
export const selectIsSearching: (state: State) => boolean = createSelector(selectState, (state) => state.searching);
|
2020-07-27 22:04:12 +02:00
|
|
|
|
2021-08-17 16:03:25 +02:00
|
|
|
export const selectSearchResultByQuery: (state: State) => { [string]: Array<string> } = createSelector(
|
2020-07-27 22:04:12 +02:00
|
|
|
selectState,
|
2021-08-17 16:03:25 +02:00
|
|
|
(state) => state.resultsByQuery
|
2020-07-27 22:04:12 +02:00
|
|
|
);
|
|
|
|
|
2021-03-26 09:33:30 +01:00
|
|
|
export const selectHasReachedMaxResultsLength: (state: State) => { [boolean]: Array<boolean> } = createSelector(
|
|
|
|
selectState,
|
|
|
|
(state) => state.hasReachedMaxResultsLength
|
|
|
|
);
|
|
|
|
|
2021-08-17 16:03:25 +02:00
|
|
|
export const makeSelectSearchUrisForQuery = (query: string): ((state: State) => Array<string>) =>
|
|
|
|
createSelector(selectSearchResultByQuery, (byQuery) => {
|
2021-09-16 22:00:44 +02:00
|
|
|
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 makeSelectRecommendedContentForUri = (uri: string) =>
|
|
|
|
createSelector(
|
2021-09-16 22:00:44 +02:00
|
|
|
selectHistory,
|
|
|
|
selectClaimsByUri,
|
|
|
|
selectShowMatureContent,
|
|
|
|
selectMutedChannels,
|
|
|
|
selectAllCostInfoByUri,
|
2021-08-17 16:03:25 +02:00
|
|
|
selectSearchResultByQuery,
|
2020-07-27 22:04:12 +02:00
|
|
|
makeSelectClaimIsNsfw(uri),
|
2021-09-16 22:00:44 +02:00
|
|
|
(history, claimsByUri, matureEnabled, blockedChannels, costInfoByUri, searchUrisByQuery, isMature) => {
|
|
|
|
const claim = claimsByUri[uri];
|
|
|
|
|
|
|
|
if (!claim) return;
|
|
|
|
|
2020-07-27 22:04:12 +02:00
|
|
|
let recommendedContent;
|
2021-09-16 22:00:44 +02:00
|
|
|
// always grab the claimId - this value won't change for filtering
|
|
|
|
const currentClaimId = claim.claim_id;
|
2020-07-27 22:04:12 +02:00
|
|
|
|
2021-09-16 22:00:44 +02:00
|
|
|
const { title } = claim.value;
|
2020-07-27 22:04:12 +02:00
|
|
|
|
2021-09-16 22:00:44 +02:00
|
|
|
if (!title) return;
|
|
|
|
|
|
|
|
const options: {
|
|
|
|
size: number,
|
|
|
|
nsfw?: boolean,
|
|
|
|
isBackgroundSearch?: boolean,
|
|
|
|
} = { size: 20, nsfw: matureEnabled, isBackgroundSearch: true };
|
|
|
|
|
|
|
|
if (SIMPLE_SITE) {
|
|
|
|
options[SEARCH_OPTIONS.CLAIM_TYPE] = SEARCH_OPTIONS.INCLUDE_FILES;
|
|
|
|
options[SEARCH_OPTIONS.MEDIA_VIDEO] = true;
|
|
|
|
options[SEARCH_OPTIONS.PRICE_FILTER_FREE] = true;
|
|
|
|
}
|
|
|
|
if (matureEnabled || (!matureEnabled && !isMature)) {
|
|
|
|
options[SEARCH_OPTIONS.RELATED_TO] = claim.claim_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
const searchQuery = getSearchQueryString(title.replace(/\//, ' '), options);
|
|
|
|
const normalizedSearchQuery = createNormalizedSearchKey(searchQuery);
|
|
|
|
|
|
|
|
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
|
|
|
|
2021-09-16 22:00:44 +02:00
|
|
|
if (!searchClaim) return;
|
2020-07-27 22:04:12 +02:00
|
|
|
|
2021-09-16 22:00:44 +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
|
|
|
|
2021-09-16 22:00:44 +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
|
|
|
|
const nextUriToPlay = recommendedContent.filter((nextRecommendedUri) => {
|
|
|
|
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) {}
|
|
|
|
|
|
|
|
return !historyMatch && costInfo === 0 && (isVideo || isAudio);
|
|
|
|
})[0];
|
|
|
|
|
|
|
|
const index = recommendedContent.indexOf(nextUriToPlay);
|
|
|
|
if (index > 0) {
|
|
|
|
const a = recommendedContent[0];
|
|
|
|
recommendedContent[0] = nextUriToPlay;
|
|
|
|
recommendedContent[index] = a;
|
2020-07-27 22:04:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return recommendedContent;
|
|
|
|
}
|
|
|
|
);
|
2020-10-28 20:18:58 +01:00
|
|
|
|
2021-08-17 16:03:25 +02:00
|
|
|
export const makeSelectRecommendedRecsysIdForClaimId = (claimId: string) =>
|
|
|
|
createSelector(makeSelectClaimForClaimId(claimId), selectSearchResultByQuery, (claim, searchUrisByQuery) => {
|
|
|
|
// TODO: DRY this out.
|
|
|
|
let poweredBy;
|
2021-09-03 00:39:40 +02:00
|
|
|
if (claim && claimId) {
|
2021-08-17 16:03:25 +02:00
|
|
|
const isMature = isClaimNsfw(claim);
|
|
|
|
const { title } = claim.value;
|
|
|
|
|
|
|
|
if (!title) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const options: {
|
|
|
|
related_to?: string,
|
|
|
|
nsfw?: boolean,
|
|
|
|
isBackgroundSearch?: boolean,
|
|
|
|
} = { related_to: claim.claim_id, isBackgroundSearch: true };
|
|
|
|
|
|
|
|
options['nsfw'] = isMature;
|
|
|
|
const searchQuery = getSearchQueryString(title.replace(/\//, ' '), options);
|
|
|
|
const normalizedSearchQuery = createNormalizedSearchKey(searchQuery);
|
|
|
|
|
|
|
|
let searchResult = searchUrisByQuery[normalizedSearchQuery];
|
|
|
|
if (searchResult) {
|
|
|
|
poweredBy = searchResult.recsys;
|
|
|
|
} else {
|
|
|
|
return normalizedSearchQuery;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return poweredBy;
|
|
|
|
});
|
|
|
|
|
2020-10-28 20:18:58 +01:00
|
|
|
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,
|
2020-12-04 01:10:23 +01:00
|
|
|
makeSelectPendingClaimForUri(uriFromQuery),
|
2020-10-28 20:18:58 +01:00
|
|
|
makeSelectClaimForUri(uriFromQuery),
|
|
|
|
makeSelectClaimForUri(channelUriFromQuery),
|
2020-12-04 01:10:23 +01:00
|
|
|
(matureEnabled, pendingClaim, claim1, claim2) => {
|
2020-12-03 18:29:47 +01:00
|
|
|
const claim1Mature = claim1 && isClaimNsfw(claim1);
|
|
|
|
const claim2Mature = claim2 && isClaimNsfw(claim2);
|
2020-12-04 01:10:23 +01:00
|
|
|
let pendingAmount = pendingClaim && pendingClaim.amount;
|
2020-12-03 18:29:47 +01:00
|
|
|
|
2020-10-28 20:18:58 +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;
|
2020-10-28 20:18:58 +01:00
|
|
|
} else if (claim1 && !claim2) {
|
2020-12-22 16:16:39 +01:00
|
|
|
return matureEnabled
|
|
|
|
? claim1.repost_url || claim1.canonical_url
|
|
|
|
: claim1Mature
|
|
|
|
? undefined
|
|
|
|
: claim1.repost_url || claim1.canonical_url;
|
2020-10-28 20:18:58 +01:00
|
|
|
}
|
|
|
|
|
2021-01-08 17:17:04 +01:00
|
|
|
const effectiveAmount1 = claim1 && (claim1.repost_bid_amount || claim1.meta.effective_amount);
|
|
|
|
// claim2 will never have a repost_bid_amount because reposts never start with "@"
|
2020-10-28 20:18:58 +01:00
|
|
|
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) {
|
2020-12-22 16:16:39 +01:00
|
|
|
return claim1.repost_url || claim1.canonical_url;
|
2020-12-03 18:29:47 +01:00
|
|
|
} else if (claim1Mature && claim2Mature) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-04 01:10:23 +01:00
|
|
|
const returnBeforePending =
|
2021-01-13 16:44:44 +01:00
|
|
|
Number(effectiveAmount1) > Number(effectiveAmount2)
|
|
|
|
? claim1.repost_url || claim1.canonical_url
|
|
|
|
: claim2.canonical_url;
|
2020-12-04 01:10:23 +01:00
|
|
|
if (pendingAmount && pendingAmount > effectiveAmount1 && pendingAmount > effectiveAmount2) {
|
|
|
|
return pendingAmount.permanent_url;
|
|
|
|
} else {
|
|
|
|
return returnBeforePending;
|
|
|
|
}
|
2020-10-28 20:18:58 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
2020-12-16 19:31:07 +01:00
|
|
|
|
|
|
|
export const makeSelectIsResolvingWinningUri = (query: string = '') => {
|
|
|
|
const uriFromQuery = `lbry://${query}`;
|
|
|
|
let channelUriFromQuery;
|
|
|
|
try {
|
|
|
|
const { isChannel } = parseURI(uriFromQuery);
|
|
|
|
if (!isChannel) {
|
|
|
|
channelUriFromQuery = `lbry://@${query}`;
|
|
|
|
}
|
|
|
|
} catch (e) {}
|
|
|
|
|
|
|
|
return createSelector(
|
|
|
|
makeSelectIsUriResolving(uriFromQuery),
|
|
|
|
channelUriFromQuery ? makeSelectIsUriResolving(channelUriFromQuery) : () => {},
|
|
|
|
(claim1IsResolving, claim2IsResolving) => {
|
|
|
|
return claim1IsResolving || claim2IsResolving;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
2021-09-03 00:39:40 +02:00
|
|
|
|
|
|
|
export const makeSelectUrlForClaimId = (claimId: string) =>
|
2021-09-16 22:00:44 +02:00
|
|
|
createSelector(makeSelectClaimForClaimId(claimId), (claim) =>
|
|
|
|
claim ? claim.canonical_url || claim.permanent_url : null
|
2021-09-03 00:39:40 +02:00
|
|
|
);
|