Properly handle blacklisted claims. #7665

Merged
Ruk33 merged 2 commits from 7663-support-hub-operated-dmcablocking into master 2022-08-09 17:19:23 +02:00
7 changed files with 142 additions and 47 deletions

View file

@ -5,7 +5,7 @@
// involve moving it from 'extras' to 'ui' (big change). // involve moving it from 'extras' to 'ui' (big change).
import { createCachedSelector } from 're-reselect'; import { createCachedSelector } from 're-reselect';
import { selectClaimForUri } from 'redux/selectors/claims'; import { selectClaimForUri, makeSelectIsBlacklisted } from 'redux/selectors/claims';
import { selectMutedChannels } from 'redux/selectors/blocked'; import { selectMutedChannels } from 'redux/selectors/blocked';
import { selectModerationBlockList } from 'redux/selectors/comments'; import { selectModerationBlockList } from 'redux/selectors/comments';
import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc'; import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc';
@ -18,7 +18,8 @@ export const selectBanStateForUri = createCachedSelector(
selectFilteredOutpointMap, selectFilteredOutpointMap,
selectMutedChannels, selectMutedChannels,
selectModerationBlockList, selectModerationBlockList,
(claim, blackListedOutpointMap, filteredOutpointMap, mutedChannelUris, personalBlocklist) => { (state, uri) => makeSelectIsBlacklisted(uri)(state),
(claim, blackListedOutpointMap, filteredOutpointMap, mutedChannelUris, personalBlocklist, isBlacklisted) => {
const banState = {}; const banState = {};
if (!claim) { if (!claim) {
@ -27,6 +28,10 @@ export const selectBanStateForUri = createCachedSelector(
const channelClaim = getChannelFromClaim(claim); const channelClaim = getChannelFromClaim(claim);
if (isBlacklisted) {
banState['blacklisted'] = true;
}
// This will be replaced once blocking is done at the wallet server level. // This will be replaced once blocking is done at the wallet server level.
if (blackListedOutpointMap) { if (blackListedOutpointMap) {
if ( if (

37
flow-typed/Claim.js vendored
View file

@ -145,12 +145,49 @@ declare type PurchaseReceipt = {
type: 'purchase', type: 'purchase',
}; };
declare type ClaimErrorCensor = {
address: string,
amount: string,
canonical_url: string,
claim_id: string,
claim_op: string,
confirmations: number,
has_signing_key: boolean,
height: number,
meta: {
activation_height: number,
claims_in_channel: number,
creation_height: number,
creation_timestamp: number,
effective_amount: string,
expiration_height: number,
is_controlling: boolean,
reposted: number,
support_amount: string,
take_over_height: number,
},
name: string,
normalized_name: string,
nout: number,
permanent_url: string,
short_url: string,
timestamp: number,
txid: string,
type: string,
value: {
public_key: string,
public_key_id: string,
},
value_type: string,
}
declare type ClaimActionResolveInfo = { declare type ClaimActionResolveInfo = {
[string]: { [string]: {
stream: ?StreamClaim, stream: ?StreamClaim,
channel: ?ChannelClaim, channel: ?ChannelClaim,
claimsInChannel: ?number, claimsInChannel: ?number,
collection: ?CollectionClaim, collection: ?CollectionClaim,
errorCensor: ?ClaimErrorCensor,
}, },
} }

View file

@ -10,6 +10,9 @@ import {
selectTitleForUri, selectTitleForUri,
selectClaimIsMine, selectClaimIsMine,
makeSelectClaimIsPending, makeSelectClaimIsPending,
makeSelectIsBlacklisted,
makeSelectBlacklistedDueToDMCA,
makeSelectClaimErrorCensor,
} from 'redux/selectors/claims'; } from 'redux/selectors/claims';
import { import {
makeSelectCollectionForId, makeSelectCollectionForId,
@ -23,7 +26,6 @@ import { normalizeURI } from 'util/lbryURI';
import * as COLLECTIONS_CONSTS from 'constants/collections'; import * as COLLECTIONS_CONSTS from 'constants/collections';
import { push } from 'connected-react-router'; import { push } from 'connected-react-router';
import { makeSelectChannelInSubscriptions } from 'redux/selectors/subscriptions'; import { makeSelectChannelInSubscriptions } from 'redux/selectors/subscriptions';
import { selectBlackListedOutpoints } from 'lbryinc';
import ShowPage from './view'; import ShowPage from './view';
const select = (state, props) => { const select = (state, props) => {
@ -72,7 +74,6 @@ const select = (state, props) => {
uri, uri,
claim, claim,
isResolvingUri: selectIsUriResolving(state, uri), isResolvingUri: selectIsUriResolving(state, uri),
blackListedOutpoints: selectBlackListedOutpoints(state),
totalPages: makeSelectTotalPagesForChannel(uri, PAGE_SIZE)(state), totalPages: makeSelectTotalPagesForChannel(uri, PAGE_SIZE)(state),
isSubscribed: makeSelectChannelInSubscriptions(uri)(state), isSubscribed: makeSelectChannelInSubscriptions(uri)(state),
title: selectTitleForUri(state, uri), title: selectTitleForUri(state, uri),
@ -82,6 +83,9 @@ const select = (state, props) => {
collectionId: collectionId, collectionId: collectionId,
collectionUrls: makeSelectUrlsForCollectionId(collectionId)(state), collectionUrls: makeSelectUrlsForCollectionId(collectionId)(state),
isResolvingCollection: makeSelectIsResolvingCollectionForId(collectionId)(state), isResolvingCollection: makeSelectIsResolvingCollectionForId(collectionId)(state),
isBlacklisted: makeSelectIsBlacklisted(uri)(state),
isBlacklistedDueToDMCA: makeSelectBlacklistedDueToDMCA(uri)(state),
errorCensor: makeSelectClaimErrorCensor(uri)(state),
}; };
}; };

View file

@ -22,10 +22,6 @@ type Props = {
uri: string, uri: string,
claim: StreamClaim, claim: StreamClaim,
location: UrlLocation, location: UrlLocation,
blackListedOutpoints: Array<{
txid: string,
nout: number,
}>,
title: string, title: string,
claimIsMine: boolean, claimIsMine: boolean,
claimIsPending: boolean, claimIsPending: boolean,
@ -35,6 +31,9 @@ type Props = {
collectionUrls: Array<string>, collectionUrls: Array<string>,
isResolvingCollection: boolean, isResolvingCollection: boolean,
fetchCollectionItems: (string) => void, fetchCollectionItems: (string) => void,
isBlacklisted: boolean,
isBlacklistedDueToDMCA: boolean,
errorCensor: ?ClaimErrorCensor,
}; };
function ShowPage(props: Props) { function ShowPage(props: Props) {
@ -43,7 +42,6 @@ function ShowPage(props: Props) {
resolveUri, resolveUri,
uri, uri,
claim, claim,
blackListedOutpoints,
location, location,
claimIsMine, claimIsMine,
isSubscribed, isSubscribed,
@ -54,11 +52,13 @@ function ShowPage(props: Props) {
collection, collection,
collectionUrls, collectionUrls,
isResolvingCollection, isResolvingCollection,
isBlacklisted,
isBlacklistedDueToDMCA,
errorCensor,
} = props; } = props;
const { search } = location; const { search } = location;
const signingChannel = claim && claim.signing_channel;
const canonicalUrl = claim && claim.canonical_url; const canonicalUrl = claim && claim.canonical_url;
const claimExists = claim !== null && claim !== undefined; const claimExists = claim !== null && claim !== undefined;
const haventFetchedYet = claim === undefined; const haventFetchedYet = claim === undefined;
@ -103,7 +103,7 @@ function ShowPage(props: Props) {
} }
let innerContent = ''; let innerContent = '';
if (!claim || (claim && !claim.name)) { if ((!claim || (claim && !claim.name)) && !isBlacklisted) {
innerContent = ( innerContent = (
<Page> <Page>
{(claim === undefined || {(claim === undefined ||
@ -142,38 +142,42 @@ function ShowPage(props: Props) {
{!isResolvingUri && isSubscribed && claim === null && <AbandonedChannelPreview uri={uri} type={'large'} />} {!isResolvingUri && isSubscribed && claim === null && <AbandonedChannelPreview uri={uri} type={'large'} />}
</Page> </Page>
); );
} else if (claim.name.length && claim.name[0] === '@') { } else if (claim && claim.name.length && claim.name[0] === '@') {
innerContent = <ChannelPage uri={uri} location={location} />; innerContent = <ChannelPage uri={uri} location={location} />;
} else if (isBlacklisted && !claimIsMine) {
innerContent = isBlacklistedDueToDMCA ? (
<Page>
<Card
title={uri}
subtitle={__(
'In response to a complaint we received under the US Digital Millennium Copyright Act, hub have blocked access to this content from our applications.'
)}
actions={
<div className="section__actions">
<Button button="link" href="https://lbry.com/faq/dmca" label={__('Read More')} />
</div>
}
/>
</Page>
) : (
<Page>
<Card
title={uri}
subtitle={
<>
{__('Your hub has blocked this content because it has subscribed to the following channel:')}{' '}
<Button
button="link"
href={errorCensor && errorCensor.canonical_url}
label={errorCensor && errorCensor.name}
/>
</>
}
/>
</Page>
);
} else if (claim) { } else if (claim) {
let isClaimBlackListed = false; innerContent = <FilePage uri={uri} location={location} />;
isClaimBlackListed =
blackListedOutpoints &&
blackListedOutpoints.some(
(outpoint) =>
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
);
if (isClaimBlackListed && !claimIsMine) {
innerContent = (
<Page>
<Card
title={uri}
subtitle={__(
'In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications.'
)}
actions={
<div className="section__actions">
<Button button="link" href="https://lbry.com/faq/dmca" label={__('Read More')} />
</div>
}
/>
</Page>
);
} else {
innerContent = <FilePage uri={uri} location={location} />;
}
} }
return <React.Fragment>{innerContent}</React.Fragment>; return <React.Fragment>{innerContent}</React.Fragment>;

View file

@ -87,7 +87,7 @@ export function doResolveUris(
if (uriResolveInfo) { if (uriResolveInfo) {
if (uriResolveInfo.error) { if (uriResolveInfo.error) {
// $FlowFixMe // $FlowFixMe
resolveInfo[uri] = { ...fallbackResolveInfo }; resolveInfo[uri] = { ...fallbackResolveInfo, errorCensor: uriResolveInfo.error.censor };
} else { } else {
if (checkReposts) { if (checkReposts) {
if (uriResolveInfo.reposted_claim) { if (uriResolveInfo.reposted_claim) {

View file

@ -16,6 +16,7 @@ type State = {
createCollectionError: ?string, createCollectionError: ?string,
channelClaimCounts: { [string]: number }, channelClaimCounts: { [string]: number },
claimsByUri: { [string]: string }, claimsByUri: { [string]: string },
blacklistedByUri: { [string]: ClaimErrorCensor },
byId: { [string]: Claim }, byId: { [string]: Claim },
pendingById: { [string]: Claim }, // keep pending claims pendingById: { [string]: Claim }, // keep pending claims
resolvingUris: Array<string>, resolvingUris: Array<string>,
@ -67,6 +68,7 @@ type State = {
const reducers = {}; const reducers = {};
const defaultState = { const defaultState = {
blacklistedByUri: {},
byId: {}, byId: {},
claimsByUri: {}, claimsByUri: {},
paginatedClaimsByChannel: {}, paginatedClaimsByChannel: {},
@ -118,6 +120,7 @@ const defaultState = {
function handleClaimAction(state: State, action: any): State { function handleClaimAction(state: State, action: any): State {
const { resolveInfo }: ClaimActionResolveInfo = action.data; const { resolveInfo }: ClaimActionResolveInfo = action.data;
const blacklistedByUri = Object.assign({}, state.blacklistedByUri);
const byUri = Object.assign({}, state.claimsByUri); const byUri = Object.assign({}, state.claimsByUri);
const byId = Object.assign({}, state.byId); const byId = Object.assign({}, state.byId);
const channelClaimCounts = Object.assign({}, state.channelClaimCounts); const channelClaimCounts = Object.assign({}, state.channelClaimCounts);
@ -127,9 +130,11 @@ function handleClaimAction(state: State, action: any): State {
Object.entries(resolveInfo).forEach(([url, resolveResponse]) => { Object.entries(resolveInfo).forEach(([url, resolveResponse]) => {
// $FlowFixMe // $FlowFixMe
const { claimsInChannel, stream, channel: channelFromResolve, collection } = resolveResponse; const { claimsInChannel, stream, channel: channelFromResolve, collection, errorCensor } = resolveResponse;
const channel = channelFromResolve || (stream && stream.signing_channel); const channel = channelFromResolve || (stream && stream.signing_channel);
blacklistedByUri[url] = errorCensor;
if (stream) { if (stream) {
if (pendingById[stream.claim_id]) { if (pendingById[stream.claim_id]) {
byId[stream.claim_id] = mergeClaim(stream, byId[stream.claim_id]); byId[stream.claim_id] = mergeClaim(stream, byId[stream.claim_id]);
@ -197,6 +202,7 @@ function handleClaimAction(state: State, action: any): State {
}); });
return Object.assign({}, state, { return Object.assign({}, state, {
blacklistedByUri,
byId, byId,
claimsByUri: byUri, claimsByUri: byUri,
channelClaimCounts, channelClaimCounts,
@ -518,10 +524,8 @@ reducers[ACTIONS.UPDATE_PENDING_CLAIMS] = (state: State, action: any): State =>
}; };
reducers[ACTIONS.UPDATE_CONFIRMED_CLAIMS] = (state: State, action: any): State => { reducers[ACTIONS.UPDATE_CONFIRMED_CLAIMS] = (state: State, action: any): State => {
const { const { claims: confirmedClaims, pending: pendingClaims }: { claims: Array<Claim>, pending: { [string]: Claim } } =
claims: confirmedClaims, action.data;
pending: pendingClaims,
}: { claims: Array<Claim>, pending: { [string]: Claim } } = action.data;
const byId = Object.assign({}, state.byId); const byId = Object.assign({}, state.byId);
const byUri = Object.assign({}, state.claimsByUri); const byUri = Object.assign({}, state.claimsByUri);
// //

View file

@ -4,6 +4,7 @@ import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { createCachedSelector } from 're-reselect'; import { createCachedSelector } from 're-reselect';
import { isClaimNsfw, filterClaims } from 'util/claim'; import { isClaimNsfw, filterClaims } from 'util/claim';
import { selectBlackListedOutpoints } from 'lbryinc';
import * as CLAIM from 'constants/claim'; import * as CLAIM from 'constants/claim';
type State = { claims: any }; type State = { claims: any };
@ -83,6 +84,46 @@ export const selectClaimIdForUri = (state: State, uri: string) => selectClaimIds
export const selectReflectingById = (state: State) => selectState(state).reflectingById; export const selectReflectingById = (state: State) => selectState(state).reflectingById;
export const makeSelectBlacklistedDueToDMCA = (claimUri: string) =>
createSelector(makeSelectClaimErrorCensor(claimUri), (claimError) => {
if (!claimError) {
return false;
}
return claimError.name === '@LBRY-DMCA';
});
export const makeSelectClaimErrorCensor = (claimUri: string) =>
createSelector(selectState, (state) => state.blacklistedByUri[claimUri]);
export const makeSelectIsBlacklisted = (claimUri: string) =>
createSelector(
makeSelectClaimErrorCensor(claimUri),
selectBlackListedOutpoints,
makeSelectClaimForUri(claimUri),
(errorCensor, legacyBlacklistedList, claim) => {
if (errorCensor) {
return true;
}
// Fallback to legacy just in case.
if (!claim) {
return false;
}
if (!legacyBlacklistedList) {
return false;
}
const signingChannel = claim.signing_channel;
if (!signingChannel) {
return false;
}
const isInLegacyBlacklist = legacyBlacklistedList.some(
(outpoint) =>
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
);
return isInLegacyBlacklist;
}
);
export const makeSelectClaimForClaimId = (claimId: string) => createSelector(selectClaimsById, (byId) => byId[claimId]); export const makeSelectClaimForClaimId = (claimId: string) => createSelector(selectClaimsById, (byId) => byId[claimId]);
export const selectClaimForUri = createCachedSelector( export const selectClaimForUri = createCachedSelector(