// @flow import React from 'react'; import Button from 'component/button'; import { Form, FormField } from 'component/common/form'; import Card from 'component/common/card'; import ClaimPreview from 'component/claimPreview'; import Comment from 'component/comment'; import ChannelSelector from 'component/channelSelector'; import Spinner from 'component/spinner'; import ErrorText from 'component/common/error-text'; import Icon from 'component/common/icon'; import { COUNTRIES } from 'util/country'; import { URL } from 'config'; import { EMAIL_REGEX } from 'constants/email'; import { FF_MAX_CHARS_REPORT_CONTENT_DETAILS, FF_MAX_CHARS_REPORT_CONTENT_SHORT, FF_MAX_CHARS_REPORT_CONTENT_ADDRESS, } from 'constants/form-field'; import * as REPORT_API from 'constants/report_content'; import * as ICONS from 'constants/icons'; import { useHistory } from 'react-router-dom'; const PAGE_TYPE = 'page--type'; const PAGE_CATEGORY = 'page--category'; const PAGE_INFRINGEMENT_DETAILS = 'page--infringement-details'; const PAGE_SUBMITTER_DETAILS = 'page--submitter-details'; const PAGE_SUBMITTER_DETAILS_ADDRESS = 'page--submitter-details-address'; const PAGE_CONFIRM = 'page--confirm'; const PAGE_SENT = 'page--sent'; const isDev = process.env.NODE_ENV !== 'production'; const DEFAULT_INPUT_DATA = { // page: string, type: '', category: '', timestamp: '', additionalDetails: '', email: '', additional_email: '', phone_number: '', country: '', street_address: '', city: '', state_or_province: '', zip_code: '', signature: '', reporter_name: '', acting_on_behalf_of: REPORT_API.BEHALF_SELF, client_name: '', specific_law: '', law_url: '', clarification: '', affected_party: REPORT_API.PARTY_SELF, copyright_owner_name: '', relationship_to_copyrighted_content: '', remove_now: true, }; type Props = { // --- urlParams --- claimId: string, commentId?: string, // --- redux --- claim: StreamClaim, comment?: Comment, isReporting: boolean, error: string, activeChannelClaim: ?ChannelClaim, incognito: boolean, doClaimSearch: (any) => Promise, doCommentById: (string, boolean) => Promise, doReportContent: (string, string) => void, }; export default function ReportContent(props: Props) { const { isReporting, error, activeChannelClaim, incognito, claimId, commentId, claim, comment, doClaimSearch, doCommentById, doReportContent, } = props; const [input, setInput] = React.useState({ ...DEFAULT_INPUT_DATA }); const [page, setPage] = React.useState(PAGE_TYPE); const [timestampInvalid, setTimestampInvalid] = React.useState(false); const [isResolvingClaim, setIsResolvingClaim] = React.useState(false); const [isResolvingComment, setIsResolvingComment] = React.useState(false); const { goBack } = useHistory(); // Resolve claim if URL is entered directly or if page is reloaded. React.useEffect(() => { if (!claim) { setIsResolvingClaim(true); doClaimSearch({ page_size: 20, page: 1, no_totals: true, claim_ids: [claimId], }).finally(() => { setIsResolvingClaim(false); }); } }, [claim, claimId, doClaimSearch]); // Fetch comment if `commentId` is provided React.useEffect(() => { if (commentId) { setIsResolvingComment(true); doCommentById(commentId, false).finally(() => { setIsResolvingComment(false); }); } }, [commentId, doCommentById]); // On mount, pause player and get the timestamp, if applicable. React.useEffect(() => { if (window.player) { window.player.pause(); const seconds = window.player.currentTime(); const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor((seconds % 3600) % 60); const str = (n) => n.toLocaleString('en-US', { minimumIntegerDigits: 2, useGrouping: false }); updateInput('timestamp', str(h) + ':' + str(m) + ':' + str(s)); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); function getCommentUrl(contentClaim, commentId) { if (commentId) { if (contentClaim && contentClaim.canonical_url) { const canonical = contentClaim.canonical_url.replace(/#/g, ':'); const commentUrl = `${canonical.replace('lbry://', `${URL}/`)}?lc=${commentId}`; return commentUrl + '\n\n'; } else { return commentId + '\n\n'; } } else { return ''; } } function onSubmit() { if (!claim) { if (isDev) throw new Error('ReportContent::onSubmit -- null claim'); return; } const pushParam = (params, label, value, encode = true) => { if (encode) { params.push(`${label}=${encodeURIComponent(value)}`); } else { params.push(`${label}=${value}`); } }; const params = []; pushParam(params, 'primary_email', input.email); pushParam(params, 'claim_id', claim.claim_id, false); pushParam(params, 'transaction_id', claim.txid, false); pushParam(params, 'vout', claim.nout.toString(), false); if (!incognito && activeChannelClaim) { pushParam(params, 'channel_name', activeChannelClaim.name); pushParam(params, 'channel_claim_id', activeChannelClaim.claim_id, false); } switch (input.type) { case REPORT_API.INFRINGES_MY_RIGHTS: pushParam(params, 'signature', input.signature); switch (input.category) { case REPORT_API.COPYRIGHT_ISSUES: pushParam(params, 'additional_email', input.additional_email); pushParam(params, 'phone_number', input.phone_number); pushParam(params, 'country', input.country); pushParam(params, 'street_address', input.street_address); pushParam(params, 'city', input.city); pushParam(params, 'state_or_province', input.state_or_province); pushParam(params, 'zip_code', input.zip_code); pushParam(params, 'affected_party', input.affected_party); pushParam(params, 'copyright_owner_name', input.copyright_owner_name); pushParam(params, 'relationship_to_copyrighted_content', input.relationship_to_copyrighted_content); pushParam(params, 'remove_now', input.remove_now.toString(), false); break; case REPORT_API.OTHER_LEGAL_ISSUES: pushParam(params, 'reporter_name', input.reporter_name); pushParam(params, 'acting_on_behalf_of', input.acting_on_behalf_of); pushParam(params, 'specific_law', input.specific_law); pushParam(params, 'law_url', input.law_url); pushParam(params, 'clarification', input.clarification); if (input.acting_on_behalf_of === REPORT_API.BEHALF_CLIENT) { pushParam(params, 'client_name', input.client_name); } break; } break; default: pushParam(params, 'type', input.type); pushParam(params, 'category', input.category); pushParam(params, 'additional_details', getCommentUrl(claim, commentId) + input.additionalDetails); if (includeTimestamp(claim, commentId)) { pushParam(params, 'timestamp', input.timestamp); } break; } doReportContent(input.category, params.join('&')); } function updateInput(field: string, value: any) { let newInput = input; if (isDev && newInput[field] === undefined) { throw new Error('Unexpected field: ' + field); } newInput[field] = value; if (field === 'type') { newInput['category'] = ''; } setInput({ ...newInput }); } function isInfringementDetailsValid(type: string, category: string) { switch (type) { case REPORT_API.INFRINGES_MY_RIGHTS: switch (category) { case REPORT_API.COPYRIGHT_ISSUES: return ( input.affected_party && input.copyright_owner_name && input.relationship_to_copyrighted_content && input.relationship_to_copyrighted_content.length > REPORT_API.RELATIONSHIP_FIELD_MIN_WIDTH ); case REPORT_API.OTHER_LEGAL_ISSUES: return ( input.reporter_name && input.specific_law && input.law_url && input.acting_on_behalf_of && (input.acting_on_behalf_of === REPORT_API.BEHALF_SELF || input.client_name) && input.clarification ); default: return false; } default: return (!includeTimestamp(claim, commentId) || isTimestampValid(input.timestamp)) && input.additionalDetails; } } function isSubmitterDetailsValid(type: string, category: string) { if (category === REPORT_API.COPYRIGHT_ISSUES) { return ( input.email.match(EMAIL_REGEX) && (!input.additional_email || input.additional_email.match(EMAIL_REGEX)) && input.phone_number ); } return input.email.match(EMAIL_REGEX); } function isSubmitterDetailsAddressValid() { return input.street_address && input.city && input.state_or_province && input.zip_code && input.country; } function isTimestampValid(timestamp: string) { if (timestamp === '0') { return true; } const length = timestamp.length; if (length <= 4) { return timestamp.match(/\d:[0-5]\d/); } else if (length <= 5) { return timestamp.match(/[0-5]\d:[0-5]\d/); } else if (length <= 8) { return timestamp.match(/\d{1,2}:[0-5]\d:\d\d/); } else { return false; } } function includeTimestamp(claim: StreamClaim, commentId: ?string) { return ( !commentId && claim.value_type === 'stream' && (claim.value.stream_type === 'video' || claim.value.stream_type === 'audio') ); } function getActionElem() { let body; switch (page) { case PAGE_TYPE: return ( <>
{Object.keys(REPORT_API.PARAMETERS['type']).map((x) => { return ( updateInput('type', x)} /> ); })}
); case PAGE_CATEGORY: if (!input.type) { return null; } else { return ( <>
{__(input.type)}
{REPORT_API.PARAMETERS['type'][input.type][REPORT_API.CATEGORIES].map((x) => { return ( updateInput('category', x)} /> ); })}
); } case PAGE_INFRINGEMENT_DETAILS: switch (input.type) { case REPORT_API.INFRINGES_MY_RIGHTS: switch (input.category) { case REPORT_API.COPYRIGHT_ISSUES: body = (
updateInput('affected_party', e.target.value)} blockWrap={false} > updateInput('copyright_owner_name', e.target.value)} /> updateInput('relationship_to_copyrighted_content', e.target.value)} charCount={input.relationship_to_copyrighted_content.length} textAreaMaxLength={FF_MAX_CHARS_REPORT_CONTENT_DETAILS} noEmojis /> updateInput('remove_now', !input.remove_now)} />
); break; case REPORT_API.OTHER_LEGAL_ISSUES: body = (
updateInput('reporter_name', e.target.value)} /> updateInput('acting_on_behalf_of', e.target.value)} blockWrap={false} > {input.acting_on_behalf_of === REPORT_API.BEHALF_CLIENT && ( updateInput('client_name', e.target.value)} /> )} updateInput('specific_law', e.target.value)} /> updateInput('law_url', e.target.value)} /> updateInput('clarification', e.target.value)} noEmojis />
); break; default: if (isDev) throw new Error('Unhandled category for DMCA: ' + input.category); body = null; break; } break; default: body = ( <> {includeTimestamp(claim, commentId) && (
{ updateInput('timestamp', e.target.value); setTimestampInvalid(!isTimestampValid(e.target.value)); }} error={timestampInvalid && input.timestamp ? 'Invalid timestamp (e.g. 05:23, 00:23:59)' : ''} />
)}
updateInput('additionalDetails', e.target.value)} charCount={input.additionalDetails.length} textAreaMaxLength={FF_MAX_CHARS_REPORT_CONTENT_DETAILS} placeholder={__('Provide additional details')} noEmojis />
); } return ( <> {body}
); case PAGE_SUBMITTER_DETAILS: return ( <>
updateInput('email', e.target.value)} /> {input.category === REPORT_API.COPYRIGHT_ISSUES && ( <> updateInput('additional_email', e.target.value)} /> updateInput('phone_number', e.target.value)} /> )}
); case PAGE_SUBMITTER_DETAILS_ADDRESS: return ( <>
updateInput('street_address', e.target.value)} /> updateInput('city', e.target.value)} /> updateInput('state_or_province', e.target.value)} /> updateInput('zip_code', e.target.value)} /> updateInput('country', e.target.value)} > {COUNTRIES.map((country) => ( ))}
); case PAGE_CONFIRM: return ( <>
{__('Contact details')}
{input.email}
{input.type === REPORT_API.INFRINGES_MY_RIGHTS && ( updateInput('signature', e.target.value)} /> )}
{__('Send report?')}
); case PAGE_SENT: if (isReporting) { body = ; } else if (error) { body = (
{error}
); } else { body = ( <>
{__('Report submitted')}
{__('We will review and respond shortly.')}
); } return ( <>
{body}
{error &&
); } } function getClaimPreview(claim: StreamClaim) { return claim ? (
) : null; } function getCommentPreviews(comment: ?Comment) { return comment ? (
) : null; } // ************************************************************************** // ************************************************************************** // --- Report comment --- if (commentId) { if (!claimId) { return ; } else { return (
: __('Invalid comment ID')} /> ); } } // --- Report content --- if (claimId) { return (
: __('Invalid claim ID')} /> ); } // --- Invalid --- return ; }