lbry-desktop/ui/redux/actions/comments.js

1727 lines
53 KiB
JavaScript
Raw Normal View History

2020-06-23 19:38:18 +02:00
// @flow
import * as ACTIONS from 'constants/action_types';
2020-09-29 16:10:23 +02:00
import * as REACTION_TYPES from 'constants/reactions';
import * as PAGES from 'constants/pages';
import { SORT_BY, BLOCK_LEVEL } from 'constants/comment';
import Lbry from 'lbry';
import { parseURI, buildURI, isURIEqual } from 'util/lbryURI';
import { selectClaimsByUri, selectMyChannelClaims } from 'redux/selectors/claims';
import { doClaimSearch } from 'redux/actions/claims';
import { doToast, doSeeNotifications } from 'redux/actions/notifications';
import {
selectMyReactsForComment,
selectOthersReactsForComment,
selectPendingCommentReacts,
selectModerationBlockList,
2021-05-25 08:17:36 +02:00
selectModerationDelegatorsById,
} from 'redux/selectors/comments';
import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import { toHex } from 'util/hex';
import Comments from 'comments';
import { selectPrefsReady } from 'redux/selectors/sync';
import { doAlertWaitingForSync } from 'redux/actions/app';
2020-06-23 19:38:18 +02:00
const isDev = process.env.NODE_ENV !== 'production';
const FETCH_API_FAILED_TO_FETCH = 'Failed to fetch';
const PROMISE_FULFILLED = 'fulfilled';
declare type CommentronErrorMap = {
[string]: {
commentron: string | RegExp,
replacement: string,
linkText?: string,
linkTarget?: string,
},
};
// prettier-ignore
const ERR_MAP: CommentronErrorMap = {
SIMILAR_NAME: {
commentron: /^your user name (.*) is too close to the creator's user name (.*) and may cause confusion. Please use another identity.$/,
replacement: 'Your user name "%1%" is too close to the creator\'s user name "%2%" and may cause confusion. Please use another identity.',
},
SLOW_MODE_IS_ON: {
commentron: /^Slow mode is on. Please wait at most (.*) seconds before commenting again.$/,
replacement: 'Slow mode is on. Please wait up to %1% seconds before commenting again.',
},
HAS_MUTED_WORDS: {
commentron: /^the comment contents are blocked by (.*)$/,
replacement: 'The comment contains contents that are blocked by %1%.',
},
BLOCKED_BY_CREATOR: {
commentron: 'channel is blocked by publisher',
replacement: 'Unable to comment. This channel has blocked you.',
},
BLOCKED_BY_ADMIN: {
commentron: 'channel is not allowed to post comments',
replacement: 'Unable to comment. Your channel has been blocked by an admin.',
},
CREATOR_DISABLED: {
commentron: 'comments are disabled by the creator',
replacement: 'Unable to comment. The content owner has disabled comments.',
},
STOP_SPAMMING: {
commentron: 'duplicate comment!',
replacement: 'Please do not spam.',
},
};
function devToast(dispatch, msg) {
if (isDev) {
console.error(msg); // eslint-disable-line
dispatch(doToast({ isError: true, message: `DEV: ${msg}` }));
}
}
function resolveCommentronError(commentronMsg: string) {
for (const key in ERR_MAP) {
// noinspection JSUnfilteredForInLoop
const data = ERR_MAP[key];
if (typeof data.commentron === 'string') {
if (data.commentron === commentronMsg) {
return {
message: __(data.replacement),
linkText: data.linkText ? __(data.linkText) : undefined,
linkTarget: data.linkTarget,
isError: true,
};
}
} else {
const match = commentronMsg.match(data.commentron);
if (match) {
const subs = {};
for (let i = 1; i < match.length; ++i) {
subs[`${i}`] = match[i];
}
return {
message: __(data.replacement, subs),
linkText: data.linkText ? __(data.linkText) : undefined,
linkTarget: data.linkTarget,
isError: true,
};
}
}
}
return {
// Fallback to commentron original message. It will be in English
// only and most likely not capitalized correctly.
message: commentronMsg,
isError: true,
};
}
export function doCommentList(
uri: string,
parentId: string,
page: number = 1,
pageSize: number = 99999,
sortBy: number = SORT_BY.NEWEST
) {
2020-11-16 20:09:00 +01:00
return (dispatch: Dispatch, getState: GetState) => {
2020-06-23 19:38:18 +02:00
const state = getState();
const claim = selectClaimsByUri(state)[uri];
const claimId = claim ? claim.claim_id : null;
if (!claimId) {
dispatch({
type: ACTIONS.COMMENT_LIST_FAILED,
data: 'unable to find claim for uri',
});
return;
}
2020-06-23 19:38:18 +02:00
dispatch({
type: ACTIONS.COMMENT_LIST_STARTED,
data: {
parentId,
},
2020-06-23 19:38:18 +02:00
});
// Adding 'channel_id' and 'channel_name' enables "CreatorSettings > commentsEnabled".
const creatorChannelClaim = claim.value_type === 'channel' ? claim : claim.signing_channel;
return Comments.comment_list({
2020-06-23 19:38:18 +02:00
page,
claim_id: claimId,
2020-06-23 19:38:18 +02:00
page_size: pageSize,
parent_id: parentId || undefined,
top_level: !parentId,
channel_id: creatorChannelClaim ? creatorChannelClaim.claim_id : undefined,
channel_name: creatorChannelClaim ? creatorChannelClaim.name : undefined,
sort_by: sortBy,
2020-06-23 19:38:18 +02:00
})
.then((result: CommentListResponse) => {
const { items: comments, total_items, total_filtered_items, total_pages } = result;
2020-06-23 19:38:18 +02:00
dispatch({
type: ACTIONS.COMMENT_LIST_COMPLETED,
data: {
comments,
parentId,
totalItems: total_items,
totalFilteredItems: total_filtered_items,
totalPages: total_pages,
2020-06-23 19:38:18 +02:00
claimId: claimId,
creatorClaimId: creatorChannelClaim ? creatorChannelClaim.claim_id : undefined,
2020-06-23 19:38:18 +02:00
uri: uri,
},
});
2020-10-20 05:20:38 +02:00
return result;
2020-06-23 19:38:18 +02:00
})
.catch((error) => {
switch (error.message) {
case 'comments are disabled by the creator':
dispatch({
type: ACTIONS.COMMENT_LIST_COMPLETED,
data: {
creatorClaimId: creatorChannelClaim ? creatorChannelClaim.claim_id : undefined,
disabled: true,
},
});
break;
case FETCH_API_FAILED_TO_FETCH:
dispatch(
doToast({
isError: true,
message: Comments.isCustomServer
? __('Failed to fetch comments. Verify custom server settings.')
: __('Failed to fetch comments.'),
})
);
dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: error });
break;
default:
dispatch(doToast({ isError: true, message: `${error.message}` }));
dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: error });
}
2020-06-23 19:38:18 +02:00
});
};
}
export function doCommentListOwn(
channelId: string,
page: number = 1,
pageSize: number = 99999,
sortBy: number = SORT_BY.NEWEST_NO_PINS
) {
return async (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const myChannelClaims = selectMyChannelClaims(state);
if (!myChannelClaims) {
console.error('Failed to fetch channel list.'); // eslint-disable-line
return;
}
const channelClaim = myChannelClaims.find((x) => x.claim_id === channelId);
if (!channelClaim) {
console.error('You do not own this channel.'); // eslint-disable-line
return;
}
const channelSignature = await channelSignName(channelClaim.claim_id, channelClaim.name);
if (!channelSignature) {
console.error('Failed to sign channel name.'); // eslint-disable-line
return;
}
dispatch({
type: ACTIONS.COMMENT_LIST_STARTED,
data: {},
});
return Comments.comment_list({
page,
page_size: pageSize,
sort_by: sortBy,
author_claim_id: channelId,
requestor_channel_name: channelClaim.name,
requestor_channel_id: channelClaim.claim_id,
signature: channelSignature.signature,
signing_ts: channelSignature.signing_ts,
})
.then((result: CommentListResponse) => {
const { items: comments, total_items, total_filtered_items, total_pages } = result;
if (!comments) {
dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: 'No more comments.' });
return;
}
dispatch(
doClaimSearch({
page: 1,
page_size: 20,
no_totals: true,
claim_ids: comments.map((c) => c.claim_id),
})
)
.then((result) => {
dispatch({
type: ACTIONS.COMMENT_LIST_COMPLETED,
data: {
comments,
totalItems: total_items,
totalFilteredItems: total_filtered_items,
totalPages: total_pages,
uri: channelClaim.canonical_url, // hijack "Discussion Page"
claimId: channelClaim.claim_id, // hijack "Discussion Page"
},
});
})
.catch((err) => {
dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: err });
});
})
.catch((error) => {
switch (error.message) {
case FETCH_API_FAILED_TO_FETCH:
dispatch(
doToast({
isError: true,
message: Comments.isCustomServer
? __('Failed to fetch comments. Verify custom server settings.')
: __('Failed to fetch comments.'),
})
);
dispatch(doToast({ isError: true, message: `${error.message}` }));
dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: error });
break;
default:
dispatch(doToast({ isError: true, message: `${error.message}` }));
dispatch({ type: ACTIONS.COMMENT_LIST_FAILED, data: error });
}
});
};
}
export function doCommentById(commentId: string, toastIfNotFound: boolean = true) {
return (dispatch: Dispatch, getState: GetState) => {
dispatch({
type: ACTIONS.COMMENT_BY_ID_STARTED,
});
return Comments.comment_by_id({ comment_id: commentId, with_ancestors: true })
.then((result: CommentByIdResponse) => {
const { item, items, ancestors } = result;
dispatch({
type: ACTIONS.COMMENT_BY_ID_COMPLETED,
data: {
comment: item || items, // Requested a change to rename it to 'item'. This covers both.
ancestors: ancestors,
},
});
return result;
})
.catch((error) => {
const ID_NOT_FOUND_REGEX = /^comment for id (.*) could not be found$/;
if (ID_NOT_FOUND_REGEX.test(error.message) && toastIfNotFound) {
dispatch(
doToast({
isError: true,
message: __('The requested comment is no longer available.'),
})
);
} else {
devToast(dispatch, error.message);
}
2021-08-27 14:03:29 +02:00
return error;
});
};
}
export function doCommentReset(claimId: string) {
return (dispatch: Dispatch) => {
if (!claimId) {
console.error(`Failed to reset comments`); //eslint-disable-line
return;
}
dispatch({
type: ACTIONS.COMMENT_LIST_RESET,
data: {
claimId,
},
});
};
}
2021-04-23 21:59:48 +02:00
export function doSuperChatList(uri: string) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const claim = selectClaimsByUri(state)[uri];
const claimId = claim ? claim.claim_id : null;
if (!claimId) {
console.error('No claimId found for uri: ', uri); //eslint-disable-line
return;
}
dispatch({
type: ACTIONS.COMMENT_SUPER_CHAT_LIST_STARTED,
});
return Comments.super_list({
claim_id: claimId,
})
.then((result: SuperListResponse) => {
2021-04-23 21:59:48 +02:00
const { items: comments, total_amount: totalAmount } = result;
dispatch({
type: ACTIONS.COMMENT_SUPER_CHAT_LIST_COMPLETED,
data: {
comments,
totalAmount,
uri: uri,
},
});
})
.catch((error) => {
dispatch({
type: ACTIONS.COMMENT_SUPER_CHAT_LIST_FAILED,
data: error,
});
});
};
}
export function doCommentReactList(commentIds: Array<string>) {
return async (dispatch: Dispatch, getState: GetState) => {
2020-09-29 16:10:23 +02:00
const state = getState();
const activeChannelClaim = selectActiveChannelClaim(state);
2020-09-29 16:10:23 +02:00
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_STARTED,
});
const params: ReactionListParams = {
2020-09-29 16:10:23 +02:00
comment_ids: commentIds.join(','),
};
if (activeChannelClaim) {
const signatureData = await channelSignName(activeChannelClaim.claim_id, activeChannelClaim.name);
if (!signatureData) {
return dispatch(doToast({ isError: true, message: __('Unable to verify your channel. Please try again.') }));
}
params.channel_name = activeChannelClaim.name;
params.channel_id = activeChannelClaim.claim_id;
params.signature = signatureData.signature;
params.signing_ts = signatureData.signing_ts;
}
return Comments.reaction_list(params)
.then((result: ReactionListResponse) => {
2020-09-29 16:10:23 +02:00
const { my_reactions: myReactions, others_reactions: othersReactions } = result;
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_COMPLETED,
data: {
myReactions,
2020-09-29 16:10:23 +02:00
othersReactions,
channelId: activeChannelClaim ? activeChannelClaim.claim_id : undefined,
commentIds,
2020-09-29 16:10:23 +02:00
},
});
})
.catch((error) => {
2020-09-29 16:10:23 +02:00
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_FAILED,
data: error,
2020-09-29 16:10:23 +02:00
});
});
};
}
export function doCommentReact(commentId: string, type: string) {
return async (dispatch: Dispatch, getState: GetState) => {
2020-09-29 16:10:23 +02:00
const state = getState();
const activeChannelClaim = selectActiveChannelClaim(state);
const pendingReacts = selectPendingCommentReacts(state);
const notification = makeSelectNotificationForCommentId(commentId)(state);
if (!activeChannelClaim) {
console.error('Unable to react to comment. No activeChannel is set.'); // eslint-disable-line
return;
}
if (notification && !notification.is_seen) {
dispatch(doSeeNotifications([notification.id]));
}
const exclusiveTypes = {
[REACTION_TYPES.LIKE]: REACTION_TYPES.DISLIKE,
[REACTION_TYPES.DISLIKE]: REACTION_TYPES.LIKE,
};
if (pendingReacts.includes(commentId + exclusiveTypes[type]) || pendingReacts.includes(commentId + type)) {
// ignore dislikes during likes, for example
return;
}
const reactKey = `${commentId}:${activeChannelClaim.claim_id}`;
const myReacts = selectMyReactsForComment(state, reactKey) || [];
const othersReacts = selectOthersReactsForComment(state, reactKey) || {};
const signatureData = await channelSignName(activeChannelClaim.claim_id, activeChannelClaim.name);
if (!signatureData) {
return dispatch(doToast({ isError: true, message: __('Unable to verify your channel. Please try again.') }));
}
const params: ReactionReactParams = {
2020-09-29 16:10:23 +02:00
comment_ids: commentId,
channel_name: activeChannelClaim.name,
channel_id: activeChannelClaim.claim_id,
signature: signatureData.signature,
signing_ts: signatureData.signing_ts,
type: type,
2020-09-29 16:10:23 +02:00
};
2020-09-29 16:10:23 +02:00
if (myReacts.includes(type)) {
params['remove'] = true;
myReacts.splice(myReacts.indexOf(type), 1);
} else {
myReacts.push(type);
if (Object.keys(exclusiveTypes).includes(type)) {
params['clear_types'] = exclusiveTypes[type];
if (myReacts.indexOf(exclusiveTypes[type]) !== -1) {
myReacts.splice(myReacts.indexOf(exclusiveTypes[type]), 1);
}
}
2020-09-29 16:10:23 +02:00
}
dispatch({
type: ACTIONS.COMMENT_REACT_STARTED,
data: commentId + type,
});
// simulate api return shape: ['like'] -> { 'like': 1 }
const myReactsObj = myReacts.reduce((acc, el) => {
acc[el] = 1;
return acc;
}, {});
2020-09-29 16:10:23 +02:00
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_COMPLETED,
data: {
myReactions: { [reactKey]: myReactsObj },
othersReactions: { [reactKey]: othersReacts },
},
});
Comments.reaction_react(params)
.then((result: ReactionReactResponse) => {
2020-09-29 16:10:23 +02:00
dispatch({
type: ACTIONS.COMMENT_REACT_COMPLETED,
data: commentId + type,
});
2020-09-29 16:10:23 +02:00
})
.catch((error) => {
2020-09-29 16:10:23 +02:00
dispatch({
type: ACTIONS.COMMENT_REACT_FAILED,
data: commentId + type,
2020-09-29 16:10:23 +02:00
});
const myRevertedReactsObj = myReacts
.filter((el) => el !== type)
.reduce((acc, el) => {
acc[el] = 1;
return acc;
}, {});
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_COMPLETED,
data: {
myReactions: { [commentId]: myRevertedReactsObj },
othersReactions: { [commentId]: othersReacts },
},
});
2020-09-29 16:10:23 +02:00
});
};
}
/**
*
* @param comment
* @param claim_id - File claim id
* @param parent_id - What is this?
* @param uri
Bringing in emotes, stickers, and refactors from ody (#7435) * [New Feature] Comment Emotes (#125) * Refactor form-field * Create new Emote Menu * Add Emotes * Add Emote Selector and Emote Comment creation ability * Fix and Split CSS * [New Feature] Stickers (#131) * Refactor filePrice * Refactor Wallet Tip Components * Add backend sticker support for comments * Add stickers * Refactor commentCreate * Add Sticker Selector and sticker comment creation * Add stickers display to comments and hyperchats * Fix wrong checks for total Super Chats * Stickers/emojis fall out / improvements (#220) * Fix error logs * Improve LBC sticker flow/clarity * Show inline error if custom sticker amount below min * Sort emojis alphabetically * Improve loading of Images * Improve quality and display of emojis and fix CSS * Display both USD and LBC prices * Default to LBC tip if creator can't receive USD * Don't clear text-field after sticker is sent * Refactor notification component * Handle notifications * Don't show profile pic on sticker livestream comments * Change Sticker icon * Fix wording and number rounding * Fix blurring emojis * Disable non functional emote buttons * new Stickers! (#248) * Add new stickers (#347) * Fix cancel sending sticker (#447) * Refactor scrollbar CSS for portal components outside of main Refactor channelMention suggestions into new textareaSuggestions component Install @mui/material packages Move channel mentioning to use @mui/Autocomplete combobox without search functionality Add support for suggesting Emotes while typing ':' Improve label to display matching term Add back and improved support for searching while mentioning Add support for suggesting emojis Fix non concatenated strings Add key to groups and options Fix dispatch props Fix Popper positioning to be consistent Fix and Improve searching Add back support for Winning Uri Filter default emojis with the same name as emotes Remove unused topSuggestion component Fix text color on darkmode Fix livestream updating state from both websocket and reducer and causing double of the same comments to appear Fix blur and focus commentCreate events Fix no name after @ error * desktop tweaks Co-authored-by: saltrafael <76502841+saltrafael@users.noreply.github.com> Co-authored-by: Thomas Zarebczan <tzarebczan@users.noreply.github.com> Co-authored-by: Rafael <rafael.saes@odysee.com>
2022-01-24 17:07:09 +01:00
* @param sticker
* @param {string} [txid] Optional transaction id
* @param {string} [payment_intent_id] Optional transaction id
* @param {string} [environment] Optional environment for Stripe (test|live)
* @returns {(function(Dispatch, GetState): Promise<undefined|void|*>)|*}
*/
export function doCommentCreate(
comment: string = '',
claim_id: string = '',
parent_id?: string,
Bringing in emotes, stickers, and refactors from ody (#7435) * [New Feature] Comment Emotes (#125) * Refactor form-field * Create new Emote Menu * Add Emotes * Add Emote Selector and Emote Comment creation ability * Fix and Split CSS * [New Feature] Stickers (#131) * Refactor filePrice * Refactor Wallet Tip Components * Add backend sticker support for comments * Add stickers * Refactor commentCreate * Add Sticker Selector and sticker comment creation * Add stickers display to comments and hyperchats * Fix wrong checks for total Super Chats * Stickers/emojis fall out / improvements (#220) * Fix error logs * Improve LBC sticker flow/clarity * Show inline error if custom sticker amount below min * Sort emojis alphabetically * Improve loading of Images * Improve quality and display of emojis and fix CSS * Display both USD and LBC prices * Default to LBC tip if creator can't receive USD * Don't clear text-field after sticker is sent * Refactor notification component * Handle notifications * Don't show profile pic on sticker livestream comments * Change Sticker icon * Fix wording and number rounding * Fix blurring emojis * Disable non functional emote buttons * new Stickers! (#248) * Add new stickers (#347) * Fix cancel sending sticker (#447) * Refactor scrollbar CSS for portal components outside of main Refactor channelMention suggestions into new textareaSuggestions component Install @mui/material packages Move channel mentioning to use @mui/Autocomplete combobox without search functionality Add support for suggesting Emotes while typing ':' Improve label to display matching term Add back and improved support for searching while mentioning Add support for suggesting emojis Fix non concatenated strings Add key to groups and options Fix dispatch props Fix Popper positioning to be consistent Fix and Improve searching Add back support for Winning Uri Filter default emojis with the same name as emotes Remove unused topSuggestion component Fix text color on darkmode Fix livestream updating state from both websocket and reducer and causing double of the same comments to appear Fix blur and focus commentCreate events Fix no name after @ error * desktop tweaks Co-authored-by: saltrafael <76502841+saltrafael@users.noreply.github.com> Co-authored-by: Thomas Zarebczan <tzarebczan@users.noreply.github.com> Co-authored-by: Rafael <rafael.saes@odysee.com>
2022-01-24 17:07:09 +01:00
uri: string, // REMOVE ed livestream
txid?: string,
payment_intent_id?: string,
Bringing in emotes, stickers, and refactors from ody (#7435) * [New Feature] Comment Emotes (#125) * Refactor form-field * Create new Emote Menu * Add Emotes * Add Emote Selector and Emote Comment creation ability * Fix and Split CSS * [New Feature] Stickers (#131) * Refactor filePrice * Refactor Wallet Tip Components * Add backend sticker support for comments * Add stickers * Refactor commentCreate * Add Sticker Selector and sticker comment creation * Add stickers display to comments and hyperchats * Fix wrong checks for total Super Chats * Stickers/emojis fall out / improvements (#220) * Fix error logs * Improve LBC sticker flow/clarity * Show inline error if custom sticker amount below min * Sort emojis alphabetically * Improve loading of Images * Improve quality and display of emojis and fix CSS * Display both USD and LBC prices * Default to LBC tip if creator can't receive USD * Don't clear text-field after sticker is sent * Refactor notification component * Handle notifications * Don't show profile pic on sticker livestream comments * Change Sticker icon * Fix wording and number rounding * Fix blurring emojis * Disable non functional emote buttons * new Stickers! (#248) * Add new stickers (#347) * Fix cancel sending sticker (#447) * Refactor scrollbar CSS for portal components outside of main Refactor channelMention suggestions into new textareaSuggestions component Install @mui/material packages Move channel mentioning to use @mui/Autocomplete combobox without search functionality Add support for suggesting Emotes while typing ':' Improve label to display matching term Add back and improved support for searching while mentioning Add support for suggesting emojis Fix non concatenated strings Add key to groups and options Fix dispatch props Fix Popper positioning to be consistent Fix and Improve searching Add back support for Winning Uri Filter default emojis with the same name as emotes Remove unused topSuggestion component Fix text color on darkmode Fix livestream updating state from both websocket and reducer and causing double of the same comments to appear Fix blur and focus commentCreate events Fix no name after @ error * desktop tweaks Co-authored-by: saltrafael <76502841+saltrafael@users.noreply.github.com> Co-authored-by: Thomas Zarebczan <tzarebczan@users.noreply.github.com> Co-authored-by: Rafael <rafael.saes@odysee.com>
2022-01-24 17:07:09 +01:00
environment?: string,
sticker: boolean
) {
2021-04-23 21:59:48 +02:00
return async (dispatch: Dispatch, getState: GetState) => {
2020-06-23 19:38:18 +02:00
const state = getState();
// get active channel that will receive comment and optional tip
const activeChannelClaim = selectActiveChannelClaim(state);
if (!activeChannelClaim) {
console.error('Unable to create comment. No activeChannel is set.'); // eslint-disable-line
return;
}
Bringing in emotes, stickers, and refactors from ody (#7435) * [New Feature] Comment Emotes (#125) * Refactor form-field * Create new Emote Menu * Add Emotes * Add Emote Selector and Emote Comment creation ability * Fix and Split CSS * [New Feature] Stickers (#131) * Refactor filePrice * Refactor Wallet Tip Components * Add backend sticker support for comments * Add stickers * Refactor commentCreate * Add Sticker Selector and sticker comment creation * Add stickers display to comments and hyperchats * Fix wrong checks for total Super Chats * Stickers/emojis fall out / improvements (#220) * Fix error logs * Improve LBC sticker flow/clarity * Show inline error if custom sticker amount below min * Sort emojis alphabetically * Improve loading of Images * Improve quality and display of emojis and fix CSS * Display both USD and LBC prices * Default to LBC tip if creator can't receive USD * Don't clear text-field after sticker is sent * Refactor notification component * Handle notifications * Don't show profile pic on sticker livestream comments * Change Sticker icon * Fix wording and number rounding * Fix blurring emojis * Disable non functional emote buttons * new Stickers! (#248) * Add new stickers (#347) * Fix cancel sending sticker (#447) * Refactor scrollbar CSS for portal components outside of main Refactor channelMention suggestions into new textareaSuggestions component Install @mui/material packages Move channel mentioning to use @mui/Autocomplete combobox without search functionality Add support for suggesting Emotes while typing ':' Improve label to display matching term Add back and improved support for searching while mentioning Add support for suggesting emojis Fix non concatenated strings Add key to groups and options Fix dispatch props Fix Popper positioning to be consistent Fix and Improve searching Add back support for Winning Uri Filter default emojis with the same name as emotes Remove unused topSuggestion component Fix text color on darkmode Fix livestream updating state from both websocket and reducer and causing double of the same comments to appear Fix blur and focus commentCreate events Fix no name after @ error * desktop tweaks Co-authored-by: saltrafael <76502841+saltrafael@users.noreply.github.com> Co-authored-by: Thomas Zarebczan <tzarebczan@users.noreply.github.com> Co-authored-by: Rafael <rafael.saes@odysee.com>
2022-01-24 17:07:09 +01:00
dispatch({ type: ACTIONS.COMMENT_CREATE_STARTED });
2020-06-23 19:38:18 +02:00
2021-04-23 21:59:48 +02:00
let signatureData;
if (activeChannelClaim) {
try {
signatureData = await Lbry.channel_sign({
channel_id: activeChannelClaim.claim_id,
hexdata: toHex(comment),
});
} catch (e) {}
}
// send a notification
Bringing in emotes, stickers, and refactors from ody (#7435) * [New Feature] Comment Emotes (#125) * Refactor form-field * Create new Emote Menu * Add Emotes * Add Emote Selector and Emote Comment creation ability * Fix and Split CSS * [New Feature] Stickers (#131) * Refactor filePrice * Refactor Wallet Tip Components * Add backend sticker support for comments * Add stickers * Refactor commentCreate * Add Sticker Selector and sticker comment creation * Add stickers display to comments and hyperchats * Fix wrong checks for total Super Chats * Stickers/emojis fall out / improvements (#220) * Fix error logs * Improve LBC sticker flow/clarity * Show inline error if custom sticker amount below min * Sort emojis alphabetically * Improve loading of Images * Improve quality and display of emojis and fix CSS * Display both USD and LBC prices * Default to LBC tip if creator can't receive USD * Don't clear text-field after sticker is sent * Refactor notification component * Handle notifications * Don't show profile pic on sticker livestream comments * Change Sticker icon * Fix wording and number rounding * Fix blurring emojis * Disable non functional emote buttons * new Stickers! (#248) * Add new stickers (#347) * Fix cancel sending sticker (#447) * Refactor scrollbar CSS for portal components outside of main Refactor channelMention suggestions into new textareaSuggestions component Install @mui/material packages Move channel mentioning to use @mui/Autocomplete combobox without search functionality Add support for suggesting Emotes while typing ':' Improve label to display matching term Add back and improved support for searching while mentioning Add support for suggesting emojis Fix non concatenated strings Add key to groups and options Fix dispatch props Fix Popper positioning to be consistent Fix and Improve searching Add back support for Winning Uri Filter default emojis with the same name as emotes Remove unused topSuggestion component Fix text color on darkmode Fix livestream updating state from both websocket and reducer and causing double of the same comments to appear Fix blur and focus commentCreate events Fix no name after @ error * desktop tweaks Co-authored-by: saltrafael <76502841+saltrafael@users.noreply.github.com> Co-authored-by: Thomas Zarebczan <tzarebczan@users.noreply.github.com> Co-authored-by: Rafael <rafael.saes@odysee.com>
2022-01-24 17:07:09 +01:00
const notification = parent_id && makeSelectNotificationForCommentId(parent_id)(state);
if (notification && !notification.is_seen) dispatch(doSeeNotifications([notification.id]));
2021-04-23 21:59:48 +02:00
if (!signatureData) {
return dispatch(doToast({ isError: true, message: __('Unable to verify your channel. Please try again.') }));
}
// Comments is a function which helps make calls to the backend
// these params passed in POST call.
2021-04-23 21:59:48 +02:00
return Comments.comment_create({
2020-06-23 19:38:18 +02:00
comment: comment,
claim_id: claim_id,
channel_id: activeChannelClaim.claim_id,
2021-04-23 21:59:48 +02:00
channel_name: activeChannelClaim.name,
2020-06-23 19:38:18 +02:00
parent_id: parent_id,
2021-04-23 21:59:48 +02:00
signature: signatureData.signature,
signing_ts: signatureData.signing_ts,
Bringing in emotes, stickers, and refactors from ody (#7435) * [New Feature] Comment Emotes (#125) * Refactor form-field * Create new Emote Menu * Add Emotes * Add Emote Selector and Emote Comment creation ability * Fix and Split CSS * [New Feature] Stickers (#131) * Refactor filePrice * Refactor Wallet Tip Components * Add backend sticker support for comments * Add stickers * Refactor commentCreate * Add Sticker Selector and sticker comment creation * Add stickers display to comments and hyperchats * Fix wrong checks for total Super Chats * Stickers/emojis fall out / improvements (#220) * Fix error logs * Improve LBC sticker flow/clarity * Show inline error if custom sticker amount below min * Sort emojis alphabetically * Improve loading of Images * Improve quality and display of emojis and fix CSS * Display both USD and LBC prices * Default to LBC tip if creator can't receive USD * Don't clear text-field after sticker is sent * Refactor notification component * Handle notifications * Don't show profile pic on sticker livestream comments * Change Sticker icon * Fix wording and number rounding * Fix blurring emojis * Disable non functional emote buttons * new Stickers! (#248) * Add new stickers (#347) * Fix cancel sending sticker (#447) * Refactor scrollbar CSS for portal components outside of main Refactor channelMention suggestions into new textareaSuggestions component Install @mui/material packages Move channel mentioning to use @mui/Autocomplete combobox without search functionality Add support for suggesting Emotes while typing ':' Improve label to display matching term Add back and improved support for searching while mentioning Add support for suggesting emojis Fix non concatenated strings Add key to groups and options Fix dispatch props Fix Popper positioning to be consistent Fix and Improve searching Add back support for Winning Uri Filter default emojis with the same name as emotes Remove unused topSuggestion component Fix text color on darkmode Fix livestream updating state from both websocket and reducer and causing double of the same comments to appear Fix blur and focus commentCreate events Fix no name after @ error * desktop tweaks Co-authored-by: saltrafael <76502841+saltrafael@users.noreply.github.com> Co-authored-by: Thomas Zarebczan <tzarebczan@users.noreply.github.com> Co-authored-by: Rafael <rafael.saes@odysee.com>
2022-01-24 17:07:09 +01:00
sticker: sticker,
...(txid ? { support_tx_id: txid } : {}), // add transaction id if it exists
...(payment_intent_id ? { payment_intent_id } : {}), // add payment_intent_id if it exists
...(environment ? { environment } : {}), // add environment for stripe if it exists
2020-06-23 19:38:18 +02:00
})
.then((result: CommentCreateResponse) => {
dispatch({
type: ACTIONS.COMMENT_CREATE_COMPLETED,
data: {
uri,
2020-06-23 19:38:18 +02:00
comment: result,
claimId: claim_id,
},
});
2020-09-30 02:11:48 +02:00
return result;
2020-06-23 19:38:18 +02:00
})
.catch((error) => {
dispatch({ type: ACTIONS.COMMENT_CREATE_FAILED, data: error });
dispatch(doToast(resolveCommentronError(error.message)));
2021-04-23 21:59:48 +02:00
return Promise.reject(error);
2020-06-23 19:38:18 +02:00
});
};
}
export function doCommentPin(commentId: string, claimId: string, remove: boolean) {
return async (dispatch: Dispatch, getState: GetState) => {
2020-10-20 05:20:38 +02:00
const state = getState();
const activeChannel = selectActiveChannelClaim(state);
if (!activeChannel) {
console.error('Unable to pin comment. No activeChannel is set.'); // eslint-disable-line
return;
}
2020-10-20 05:20:38 +02:00
const signedCommentId = await channelSignData(activeChannel.claim_id, commentId);
if (!signedCommentId) {
return dispatch(doToast({ isError: true, message: __('Unable to verify your channel. Please try again.') }));
}
2020-10-20 05:20:38 +02:00
dispatch({
type: ACTIONS.COMMENT_PIN_STARTED,
});
const params: CommentPinParams = {
comment_id: commentId,
channel_id: activeChannel.claim_id,
channel_name: activeChannel.name,
remove: remove,
signature: signedCommentId.signature,
signing_ts: signedCommentId.signing_ts,
};
return Comments.comment_pin(params)
2020-10-20 05:20:38 +02:00
.then((result: CommentPinResponse) => {
dispatch({
type: ACTIONS.COMMENT_PIN_COMPLETED,
data: {
pinnedComment: result.items,
claimId,
unpin: remove,
},
2020-10-20 05:20:38 +02:00
});
})
.catch((error) => {
2020-10-20 05:20:38 +02:00
dispatch({
type: ACTIONS.COMMENT_PIN_FAILED,
data: error,
});
dispatch(
doToast({
message: 'Unable to pin this comment, please try again later.',
isError: true,
})
);
});
};
}
export function doCommentAbandon(commentId: string, creatorChannelUri?: string) {
return async (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const claim = creatorChannelUri ? selectClaimsByUri(state)[creatorChannelUri] : undefined;
const creatorChannelId = claim ? claim.claim_id : null;
const creatorChannelName = claim ? claim.name : null;
const activeChannelClaim = selectActiveChannelClaim(state);
2020-06-23 19:38:18 +02:00
dispatch({
type: ACTIONS.COMMENT_ABANDON_STARTED,
});
let commentIdSignature;
if (activeChannelClaim) {
try {
commentIdSignature = await Lbry.channel_sign({
channel_id: activeChannelClaim.claim_id,
hexdata: toHex(commentId),
});
} catch (e) {}
}
return Comments.comment_abandon({
comment_id: commentId,
...(creatorChannelId ? { creator_channel_id: creatorChannelId } : {}),
...(creatorChannelName ? { creator_channel_name: creatorChannelName } : {}),
...(commentIdSignature || {}),
2020-06-23 19:38:18 +02:00
})
.then((result: CommentAbandonResponse) => {
// Comment may not be deleted if the signing channel can't be signed.
// This will happen if the channel was recently created or abandoned.
if (result.abandoned) {
dispatch({
type: ACTIONS.COMMENT_ABANDON_COMPLETED,
data: {
comment_id: commentId,
2020-06-23 19:38:18 +02:00
},
});
} else {
dispatch({
type: ACTIONS.COMMENT_ABANDON_FAILED,
});
dispatch(
doToast({
message: 'Your channel is still being setup, try again in a few moments.',
isError: true,
})
);
}
})
.catch((error) => {
2020-06-23 19:38:18 +02:00
dispatch({
type: ACTIONS.COMMENT_ABANDON_FAILED,
data: error,
});
2020-06-23 19:38:18 +02:00
dispatch(
doToast({
message: 'Unable to delete this comment, please try again later.',
isError: true,
})
);
});
};
}
export function doCommentUpdate(comment_id: string, comment: string) {
// if they provided an empty string, they must have wanted to abandon
if (comment === '') {
return doCommentAbandon(comment_id);
} else {
return async (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const activeChannelClaim = selectActiveChannelClaim(state);
if (!activeChannelClaim) {
return dispatch(doToast({ isError: true, message: __('No active channel selected.') }));
}
const signedComment = await channelSignData(activeChannelClaim.claim_id, comment);
if (!signedComment) {
return dispatch(doToast({ isError: true, message: __('Unable to verify your channel. Please try again.') }));
}
2020-06-23 19:38:18 +02:00
dispatch({
type: ACTIONS.COMMENT_UPDATE_STARTED,
});
return Comments.comment_edit({
2020-06-23 19:38:18 +02:00
comment_id: comment_id,
comment: comment,
signature: signedComment.signature,
signing_ts: signedComment.signing_ts,
2020-06-23 19:38:18 +02:00
})
.then((result: CommentEditResponse) => {
2020-06-23 19:38:18 +02:00
if (result != null) {
dispatch({
type: ACTIONS.COMMENT_UPDATE_COMPLETED,
data: {
comment: result,
},
});
} else {
// the result will return null
dispatch({
type: ACTIONS.COMMENT_UPDATE_FAILED,
});
dispatch(
doToast({
message: 'Your channel is still being setup, try again in a few moments.',
isError: true,
})
);
}
})
.catch((error) => {
2020-06-23 19:38:18 +02:00
dispatch({
type: ACTIONS.COMMENT_UPDATE_FAILED,
data: error,
});
dispatch(
doToast({
message: 'Unable to edit this comment, please try again later.',
isError: true,
})
);
});
};
}
}
async function channelSignName(channelClaimId: string, channelName: string) {
let signedObject;
try {
signedObject = await Lbry.channel_sign({
channel_id: channelClaimId,
hexdata: toHex(channelName),
});
signedObject['claim_id'] = channelClaimId;
signedObject['name'] = channelName;
} catch (e) {}
return signedObject;
}
async function channelSignData(channelClaimId: string, data: string) {
let signedObject;
try {
signedObject = await Lbry.channel_sign({
channel_id: channelClaimId,
hexdata: toHex(data),
});
} catch (e) {}
return signedObject;
}
function safeParseURI(uri) {
try {
return parseURI(uri);
} catch {
return {};
}
}
// Hides a users comments from all creator's claims and prevent them from commenting in the future
2021-05-25 08:17:36 +02:00
function doCommentModToggleBlock(
unblock: boolean,
commenterUri: string,
creatorUri: string,
2021-05-25 08:17:36 +02:00
blockerIds: Array<string>, // [] = use all my channels
blockLevel: string,
timeoutSec?: number,
2021-05-25 08:17:36 +02:00
showLink: boolean = false
) {
return async (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const ready = selectPrefsReady(state);
2021-05-25 08:17:36 +02:00
let blockerChannelClaims = selectMyChannelClaims(state);
if (!ready) {
return dispatch(doAlertWaitingForSync());
}
if (!blockerChannelClaims) {
return dispatch(
doToast({
message: __('Create a channel to change this setting.'),
isError: false,
})
);
}
const { channelName, channelClaimId } = parseURI(commenterUri);
const { channelName: creatorName, channelClaimId: creatorId } = safeParseURI(creatorUri);
2021-05-25 08:17:36 +02:00
if (blockerIds.length === 0) {
// Specific blockers not provided, so find one based on block-level.
switch (blockLevel) {
case BLOCK_LEVEL.MODERATOR:
{
// Find the first channel that is a moderator for 'creatorId'.
const delegatorsById = selectModerationDelegatorsById(state);
blockerChannelClaims = [
blockerChannelClaims.find((x) => {
const delegatorDataForId = delegatorsById[x.claim_id];
return delegatorDataForId && Object.values(delegatorDataForId.delegators).includes(creatorId);
}),
];
}
break;
case BLOCK_LEVEL.ADMIN:
{
// Find the first admin channel and use that.
const delegatorsById = selectModerationDelegatorsById(state);
blockerChannelClaims = [
blockerChannelClaims.find((x) => delegatorsById[x.claim_id] && delegatorsById[x.claim_id].global),
];
}
break;
}
} else {
// Client wants to block for specific channels only. Ensure we own those channels.
2021-05-25 08:17:36 +02:00
blockerChannelClaims = blockerChannelClaims.filter((x) => blockerIds.includes(x.claim_id));
}
dispatch({
type: unblock ? ACTIONS.COMMENT_MODERATION_UN_BLOCK_STARTED : ACTIONS.COMMENT_MODERATION_BLOCK_STARTED,
data: {
2021-05-25 08:17:36 +02:00
blockedUri: commenterUri,
creatorUri: creatorUri || undefined,
2021-05-25 08:17:36 +02:00
blockLevel: blockLevel,
},
});
const commenterIdForAction = channelClaimId;
const commenterNameForAction = channelName;
let channelSignatures = [];
const sharedModBlockParams = unblock
? {
2021-05-25 08:17:36 +02:00
un_blocked_channel_id: commenterIdForAction,
un_blocked_channel_name: commenterNameForAction,
}
: {
2021-05-25 08:17:36 +02:00
blocked_channel_id: commenterIdForAction,
blocked_channel_name: commenterNameForAction,
};
const commentAction = unblock ? Comments.moderation_unblock : Comments.moderation_block;
2021-05-25 08:17:36 +02:00
return Promise.all(blockerChannelClaims.map((x) => channelSignName(x.claim_id, x.name)))
.then((response) => {
channelSignatures = response;
// $FlowFixMe
return Promise.allSettled(
channelSignatures
.filter((x) => x !== undefined && x !== null)
.map((signatureData) =>
commentAction({
2021-05-25 08:17:36 +02:00
// $FlowFixMe
mod_channel_id: signatureData.claim_id,
2021-05-25 08:17:36 +02:00
// $FlowFixMe
mod_channel_name: signatureData.name,
2021-05-25 08:17:36 +02:00
// $FlowFixMe
signature: signatureData.signature,
2021-05-25 08:17:36 +02:00
// $FlowFixMe
signing_ts: signatureData.signing_ts,
creator_channel_id: creatorUri ? creatorId : undefined,
creator_channel_name: creatorUri ? creatorName : undefined,
2021-05-25 08:17:36 +02:00
block_all: unblock ? undefined : blockLevel === BLOCK_LEVEL.ADMIN,
global_un_block: unblock ? blockLevel === BLOCK_LEVEL.ADMIN : undefined,
...sharedModBlockParams,
time_out: unblock ? undefined : timeoutSec,
})
)
)
2021-05-25 08:17:36 +02:00
.then((response) => {
const failures = [];
response.forEach((res, index) => {
if (res.status === 'rejected') {
// TODO: This should be error codes
if (res.reason.message !== 'validation is disallowed for non controlling channels') {
// $FlowFixMe
failures.push(channelSignatures[index].name + ': ' + res.reason.message);
}
}
});
if (failures.length !== 0) {
dispatch(doToast({ message: failures.join(), isError: true }));
dispatch({
type: unblock ? ACTIONS.COMMENT_MODERATION_UN_BLOCK_FAILED : ACTIONS.COMMENT_MODERATION_BLOCK_FAILED,
data: {
blockedUri: commenterUri,
creatorUri: creatorUri || undefined,
2021-05-25 08:17:36 +02:00
blockLevel: blockLevel,
},
});
return;
}
dispatch({
type: unblock ? ACTIONS.COMMENT_MODERATION_UN_BLOCK_COMPLETE : ACTIONS.COMMENT_MODERATION_BLOCK_COMPLETE,
2021-05-25 08:17:36 +02:00
data: {
blockedUri: commenterUri,
creatorUri: creatorUri || undefined,
2021-05-25 08:17:36 +02:00
blockLevel: blockLevel,
},
});
2021-05-25 08:17:36 +02:00
dispatch(
doToast({
message: unblock
? __('Channel unblocked!')
: __('Channel "%channel%" blocked.', { channel: commenterNameForAction }),
linkText: __(showLink ? 'See All' : ''),
linkTarget: '/settings/block_and_mute',
})
);
})
.catch(() => {
dispatch({
type: unblock ? ACTIONS.COMMENT_MODERATION_UN_BLOCK_FAILED : ACTIONS.COMMENT_MODERATION_BLOCK_FAILED,
2021-05-25 08:17:36 +02:00
data: {
blockedUri: commenterUri,
creatorUri: creatorUri || undefined,
2021-05-25 08:17:36 +02:00
blockLevel: blockLevel,
},
});
});
})
.catch(() => {
dispatch({
type: unblock ? ACTIONS.COMMENT_MODERATION_UN_BLOCK_FAILED : ACTIONS.COMMENT_MODERATION_BLOCK_FAILED,
2021-05-25 08:17:36 +02:00
data: {
blockedUri: commenterUri,
creatorUri: creatorUri || undefined,
2021-05-25 08:17:36 +02:00
blockLevel: blockLevel,
},
});
});
};
}
2021-05-25 08:17:36 +02:00
/**
* Blocks the commenter for all channels that I own.
*
* @param commenterUri
* @param timeoutSec
2021-05-25 08:17:36 +02:00
* @param showLink
* @returns {function(Dispatch): *}
*/
export function doCommentModBlock(commenterUri: string, timeoutSec?: number, showLink: boolean = true) {
2021-05-25 08:17:36 +02:00
return (dispatch: Dispatch) => {
return dispatch(doCommentModToggleBlock(false, commenterUri, '', [], BLOCK_LEVEL.SELF, timeoutSec, showLink));
2021-05-25 08:17:36 +02:00
};
}
/**
* Blocks the commenter using the given channel that has Global privileges.
*
* @param commenterUri
* @param blockerId
* @param timeoutSec
2021-05-25 08:17:36 +02:00
* @returns {function(Dispatch): *}
*/
export function doCommentModBlockAsAdmin(commenterUri: string, blockerId: string, timeoutSec?: number) {
2021-05-25 08:17:36 +02:00
return (dispatch: Dispatch) => {
return dispatch(
doCommentModToggleBlock(false, commenterUri, '', blockerId ? [blockerId] : [], BLOCK_LEVEL.ADMIN, timeoutSec)
);
2021-05-25 08:17:36 +02:00
};
}
/**
* Blocks the commenter using the given channel that has been granted
* moderation rights by the creator.
*
* @param commenterUri
* @param creatorUri
2021-05-25 08:17:36 +02:00
* @param blockerId
* @param timeoutSec
2021-05-25 08:17:36 +02:00
* @returns {function(Dispatch): *}
*/
export function doCommentModBlockAsModerator(
commenterUri: string,
creatorUri: string,
blockerId: string,
timeoutSec?: number
) {
return (dispatch: Dispatch) => {
2021-05-25 08:17:36 +02:00
return dispatch(
doCommentModToggleBlock(
false,
commenterUri,
creatorUri,
blockerId ? [blockerId] : [],
BLOCK_LEVEL.MODERATOR,
timeoutSec
)
2021-05-25 08:17:36 +02:00
);
};
}
2021-05-25 08:17:36 +02:00
/**
* Unblocks the commenter for all channels that I own.
*
* @param commenterUri
* @param showLink
* @returns {function(Dispatch): *}
*/
export function doCommentModUnBlock(commenterUri: string, showLink: boolean = true) {
return (dispatch: Dispatch) => {
return dispatch(doCommentModToggleBlock(true, commenterUri, '', [], BLOCK_LEVEL.SELF, undefined, showLink));
2021-05-25 08:17:36 +02:00
};
}
/**
* Unblocks the commenter using the given channel that has Global privileges.
*
* @param commenterUri
* @param blockerId
* @returns {function(Dispatch): *}
*/
export function doCommentModUnBlockAsAdmin(commenterUri: string, blockerId: string) {
return (dispatch: Dispatch) => {
return dispatch(doCommentModToggleBlock(true, commenterUri, '', blockerId ? [blockerId] : [], BLOCK_LEVEL.ADMIN));
};
}
/**
* Unblocks the commenter using the given channel that has been granted
* moderation rights by the creator.
*
* @param commenterUri
* @param creatorUri
2021-05-25 08:17:36 +02:00
* @param blockerId
* @returns {function(Dispatch): *}
*/
export function doCommentModUnBlockAsModerator(commenterUri: string, creatorUri: string, blockerId: string) {
2021-05-25 08:17:36 +02:00
return (dispatch: Dispatch) => {
return dispatch(
doCommentModToggleBlock(true, commenterUri, creatorUri, blockerId ? [blockerId] : [], BLOCK_LEVEL.MODERATOR)
2021-05-25 08:17:36 +02:00
);
};
}
export function doFetchModBlockedList() {
return async (dispatch: Dispatch, getState: GetState) => {
const LOOP_CHUNK_SIZE = 1;
const yieldThread = () => new Promise((resolve) => setTimeout(resolve));
const state = getState();
const myChannels = selectMyChannelClaims(state);
if (!myChannels) {
dispatch({ type: ACTIONS.COMMENT_MODERATION_BLOCK_LIST_FAILED });
return;
}
dispatch({
type: ACTIONS.COMMENT_MODERATION_BLOCK_LIST_STARTED,
});
let channelSignatures = [];
return Promise.all(myChannels.map((channel) => channelSignName(channel.claim_id, channel.name)))
.then((response) => {
channelSignatures = response;
// $FlowFixMe
return Promise.allSettled(
channelSignatures
.filter((x) => x !== undefined && x !== null)
.map((signatureData) =>
Comments.moderation_block_list({
mod_channel_id: signatureData.claim_id,
mod_channel_name: signatureData.name,
signature: signatureData.signature,
signing_ts: signatureData.signing_ts,
})
)
)
.then(async (res) => {
2021-05-25 08:17:36 +02:00
let personalBlockList = [];
let adminBlockList = [];
let moderatorBlockList = [];
let moderatorBlockListDelegatorsMap = {};
// These should just be part of the block list above, but it is
// separated for now because there are too many clients that we need
// to update.
const personalTimeoutMap = {};
const adminTimeoutMap = {};
const moderatorTimeoutMap = {};
const blockListsPerChannel = [];
for (let i = 0; i < res.length; ++i) {
blockListsPerChannel.push(res[i].value);
if (i % 2 === 0) {
await yieldThread();
}
}
for (let i = 0; i < blockListsPerChannel.length; ++i) {
const storeList = async (fetchedList, blockedList, timeoutMap, blockedByMap) => {
if (fetchedList) {
for (let j = 0; j < fetchedList.length; ++j) {
const blockedChannel = fetchedList[j];
if (j > 0 && i % LOOP_CHUNK_SIZE === 0) {
await yieldThread();
}
if (blockedChannel.blocked_channel_name) {
const channelUri = buildURI({
channelName: blockedChannel.blocked_channel_name,
channelClaimId: blockedChannel.blocked_channel_id,
});
if (!blockedList.find((blockedChannel) => isURIEqual(blockedChannel.channelUri, channelUri))) {
blockedList.push({ channelUri, blockedAt: blockedChannel.blocked_at });
if (blockedChannel.banned_for) {
timeoutMap[channelUri] = {
blockedAt: blockedChannel.blocked_at,
bannedFor: blockedChannel.banned_for,
banRemaining: blockedChannel.ban_remaining,
};
2021-05-25 08:17:36 +02:00
}
}
2021-05-25 08:17:36 +02:00
if (blockedByMap !== undefined) {
const blockedByChannelUri = buildURI({
channelName: blockedChannel.blocked_by_channel_name,
channelClaimId: blockedChannel.blocked_by_channel_id,
});
if (blockedByMap[channelUri]) {
if (!blockedByMap[channelUri].includes(blockedByChannelUri)) {
blockedByMap[channelUri].push(blockedByChannelUri);
2021-05-25 08:17:36 +02:00
}
} else {
blockedByMap[channelUri] = [blockedByChannelUri];
2021-05-25 08:17:36 +02:00
}
}
}
2021-05-25 08:17:36 +02:00
}
}
};
const channelBlockLists = blockListsPerChannel[i];
const blocked_channels = channelBlockLists && channelBlockLists.blocked_channels;
const globally_blocked_channels = channelBlockLists && channelBlockLists.globally_blocked_channels;
const delegated_blocked_channels = channelBlockLists && channelBlockLists.delegated_blocked_channels;
if (i > 0 && i % LOOP_CHUNK_SIZE === 0) {
await yieldThread();
}
await storeList(blocked_channels, personalBlockList, personalTimeoutMap);
await storeList(globally_blocked_channels, adminBlockList, adminTimeoutMap);
await storeList(
delegated_blocked_channels,
moderatorBlockList,
moderatorTimeoutMap,
moderatorBlockListDelegatorsMap
);
}
dispatch({
type: ACTIONS.COMMENT_MODERATION_BLOCK_LIST_COMPLETED,
data: {
2021-05-25 08:17:36 +02:00
personalBlockList:
personalBlockList.length > 0
? personalBlockList
.sort((a, b) => new Date(a.blockedAt) - new Date(b.blockedAt))
.map((blockedChannel) => blockedChannel.channelUri)
: null,
2021-05-25 08:17:36 +02:00
adminBlockList:
adminBlockList.length > 0
? adminBlockList
.sort((a, b) => new Date(a.blockedAt) - new Date(b.blockedAt))
.map((blockedChannel) => blockedChannel.channelUri)
: null,
moderatorBlockList:
moderatorBlockList.length > 0
? moderatorBlockList
.sort((a, b) => new Date(a.blockedAt) - new Date(b.blockedAt))
.map((blockedChannel) => blockedChannel.channelUri)
: null,
moderatorBlockListDelegatorsMap: moderatorBlockListDelegatorsMap,
personalTimeoutMap,
adminTimeoutMap,
moderatorTimeoutMap,
},
});
})
.catch(() => {
dispatch({
type: ACTIONS.COMMENT_MODERATION_BLOCK_LIST_FAILED,
});
});
})
.catch(() => {
dispatch({
type: ACTIONS.COMMENT_MODERATION_BLOCK_LIST_FAILED,
});
});
};
}
export const doUpdateBlockListForPublishedChannel = (channelClaim: ChannelClaim) => {
return async (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const blockedUris = selectModerationBlockList(state);
let channelSignature: ?{
signature: string,
signing_ts: string,
};
try {
channelSignature = await Lbry.channel_sign({
channel_id: channelClaim.claim_id,
hexdata: toHex(channelClaim.name),
});
} catch (e) {}
if (!channelSignature) {
return;
}
return Promise.all(
blockedUris.map((uri) => {
const { channelName, channelClaimId } = parseURI(uri);
if (channelName && channelClaimId) {
return Comments.moderation_block({
mod_channel_id: channelClaim.claim_id,
mod_channel_name: channelClaim.name,
// $FlowFixMe
signature: channelSignature.signature,
// $FlowFixMe
signing_ts: channelSignature.signing_ts,
blocked_channel_id: channelClaimId,
blocked_channel_name: channelName,
});
}
})
);
};
};
2021-04-20 10:40:53 +02:00
2021-05-25 08:17:36 +02:00
export function doCommentModAddDelegate(
modChannelId: string,
modChannelName: string,
creatorChannelClaim: ChannelClaim,
showToast: boolean = false
2021-05-25 08:17:36 +02:00
) {
return async (dispatch: Dispatch, getState: GetState) => {
let signature: ?{
signature: string,
signing_ts: string,
};
try {
signature = await Lbry.channel_sign({
channel_id: creatorChannelClaim.claim_id,
hexdata: toHex(creatorChannelClaim.name),
});
} catch (e) {}
if (!signature) {
return;
}
return Comments.moderation_add_delegate({
mod_channel_id: modChannelId,
mod_channel_name: modChannelName,
creator_channel_id: creatorChannelClaim.claim_id,
creator_channel_name: creatorChannelClaim.name,
signature: signature.signature,
signing_ts: signature.signing_ts,
})
.then(() => {
if (showToast) {
dispatch(
doToast({
message: __('Added %user% as moderator for %myChannel%', {
user: modChannelName,
myChannel: creatorChannelClaim.name,
}),
linkText: __('Manage'),
linkTarget: `/${PAGES.SETTINGS_CREATOR}`,
})
);
}
})
.catch((err) => {
dispatch(
doToast({
message: err.message,
isError: true,
})
);
});
2021-05-25 08:17:36 +02:00
};
}
export function doCommentModRemoveDelegate(
modChannelId: string,
modChannelName: string,
creatorChannelClaim: ChannelClaim
) {
return async (dispatch: Dispatch, getState: GetState) => {
let signature: ?{
signature: string,
signing_ts: string,
};
try {
signature = await Lbry.channel_sign({
channel_id: creatorChannelClaim.claim_id,
hexdata: toHex(creatorChannelClaim.name),
});
} catch (e) {}
if (!signature) {
return;
}
return Comments.moderation_remove_delegate({
mod_channel_id: modChannelId,
mod_channel_name: modChannelName,
creator_channel_id: creatorChannelClaim.claim_id,
creator_channel_name: creatorChannelClaim.name,
signature: signature.signature,
signing_ts: signature.signing_ts,
}).catch((err) => {
dispatch(
doToast({
message: err.message,
isError: true,
})
);
});
};
}
export function doCommentModListDelegates(channelClaim: ChannelClaim) {
return async (dispatch: Dispatch, getState: GetState) => {
dispatch({
type: ACTIONS.COMMENT_FETCH_MODERATION_DELEGATES_STARTED,
});
let signature: ?{
signature: string,
signing_ts: string,
};
try {
signature = await Lbry.channel_sign({
channel_id: channelClaim.claim_id,
hexdata: toHex(channelClaim.name),
});
} catch (e) {}
if (!signature) {
dispatch({
type: ACTIONS.COMMENT_FETCH_MODERATION_DELEGATES_FAILED,
});
return;
}
return Comments.moderation_list_delegates({
creator_channel_id: channelClaim.claim_id,
creator_channel_name: channelClaim.name,
signature: signature.signature,
signing_ts: signature.signing_ts,
})
.then((response) => {
dispatch({
type: ACTIONS.COMMENT_FETCH_MODERATION_DELEGATES_COMPLETED,
data: {
id: channelClaim.claim_id,
delegates: response.Delegates,
},
});
})
.catch((err) => {
dispatch({
type: ACTIONS.COMMENT_FETCH_MODERATION_DELEGATES_FAILED,
});
});
};
}
export function doFetchCommentModAmIList(channelClaim: ChannelClaim) {
return async (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const myChannels = selectMyChannelClaims(state);
if (!myChannels) {
dispatch({ type: ACTIONS.COMMENT_MODERATION_AM_I_LIST_FAILED });
return;
}
2021-05-25 08:17:36 +02:00
dispatch({ type: ACTIONS.COMMENT_MODERATION_AM_I_LIST_STARTED });
2021-05-25 08:17:36 +02:00
let channelSignatures = [];
return Promise.all(myChannels.map((channel) => channelSignName(channel.claim_id, channel.name)))
.then((response) => {
channelSignatures = response;
// $FlowFixMe
return Promise.allSettled(
channelSignatures
.filter((x) => x !== undefined && x !== null)
.map((signatureData) =>
Comments.moderation_am_i({
channel_name: signatureData.name,
channel_id: signatureData.claim_id,
signature: signatureData.signature,
signing_ts: signatureData.signing_ts,
})
)
)
.then((results) => {
2021-05-25 08:17:36 +02:00
const delegatorsById = {};
results.forEach((result, index) => {
if (result.status === PROMISE_FULFILLED) {
const value = result.value;
delegatorsById[value.channel_id] = {
2021-05-25 08:17:36 +02:00
global: value ? value.type === 'Global' : false,
delegators: value && value.authorized_channels ? value.authorized_channels : {},
};
}
});
dispatch({
type: ACTIONS.COMMENT_MODERATION_AM_I_LIST_COMPLETED,
data: delegatorsById,
});
})
.catch((err) => {
devToast(dispatch, `AmI: ${err}`);
dispatch({ type: ACTIONS.COMMENT_MODERATION_AM_I_LIST_FAILED });
2021-05-25 08:17:36 +02:00
});
})
.catch(() => {
dispatch({ type: ACTIONS.COMMENT_MODERATION_AM_I_LIST_FAILED });
2021-05-25 08:17:36 +02:00
});
};
}
export const doFetchCreatorSettings = (channelId: string) => {
2021-04-20 10:40:53 +02:00
return async (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const myChannels = selectMyChannelClaims(state);
dispatch({
type: ACTIONS.COMMENT_FETCH_SETTINGS_STARTED,
});
let signedName;
2021-04-20 10:40:53 +02:00
if (myChannels) {
const index = myChannels.findIndex((myChannel) => myChannel.claim_id === channelId);
if (index > -1) {
signedName = await channelSignName(channelId, myChannels[index].name);
2021-04-20 10:40:53 +02:00
}
}
const cmd = signedName ? Comments.setting_list : Comments.setting_get;
2021-04-20 10:40:53 +02:00
return cmd({
channel_id: channelId,
channel_name: (signedName && signedName.name) || undefined,
signature: (signedName && signedName.signature) || undefined,
signing_ts: (signedName && signedName.signing_ts) || undefined,
})
.then((response: SettingsResponse) => {
2021-04-20 10:40:53 +02:00
dispatch({
type: ACTIONS.COMMENT_FETCH_SETTINGS_COMPLETED,
data: {
channelId: channelId,
settings: response,
partialUpdate: !signedName,
},
2021-04-20 10:40:53 +02:00
});
})
.catch((err) => {
if (err.message === 'validation is disallowed for non controlling channels') {
dispatch({
type: ACTIONS.COMMENT_FETCH_SETTINGS_COMPLETED,
data: {
channelId: channelId,
settings: null,
partialUpdate: !signedName,
},
});
} else {
devToast(dispatch, `Creator: ${err}`);
dispatch({
type: ACTIONS.COMMENT_FETCH_SETTINGS_FAILED,
});
}
2021-04-20 10:40:53 +02:00
});
};
};
/**
* Updates creator settings, except for 'Words', which will be handled by
* 'doCommentWords, doCommentBlockWords, etc.'
*
* @param channelClaim
* @param settings
* @returns {function(Dispatch, GetState): any}
2021-04-20 10:40:53 +02:00
*/
export const doUpdateCreatorSettings = (channelClaim: ChannelClaim, settings: PerChannelSettings) => {
return async (dispatch: Dispatch, getState: GetState) => {
2021-07-16 10:11:02 +02:00
const channelSignature = await channelSignName(channelClaim.claim_id, channelClaim.name);
2021-04-20 10:40:53 +02:00
if (!channelSignature) {
2021-07-16 10:11:02 +02:00
devToast(dispatch, 'doUpdateCreatorSettings: failed to sign channel name');
2021-04-20 10:40:53 +02:00
return;
}
return Comments.setting_update({
channel_name: channelClaim.name,
channel_id: channelClaim.claim_id,
signature: channelSignature.signature,
signing_ts: channelSignature.signing_ts,
...settings,
}).catch((err) => {
2021-07-16 10:11:02 +02:00
dispatch(doToast({ message: err.message, isError: true }));
2021-04-20 10:40:53 +02:00
});
};
};
export const doCommentWords = (channelClaim: ChannelClaim, words: Array<string>, isUnblock: boolean) => {
return async (dispatch: Dispatch, getState: GetState) => {
let channelSignature: ?{
signature: string,
signing_ts: string,
};
try {
channelSignature = await Lbry.channel_sign({
channel_id: channelClaim.claim_id,
hexdata: toHex(channelClaim.name),
});
} catch (e) {}
if (!channelSignature) {
return;
}
const cmd = isUnblock ? Comments.setting_unblock_word : Comments.setting_block_word;
return cmd({
channel_name: channelClaim.name,
channel_id: channelClaim.claim_id,
words: words.join(','),
signature: channelSignature.signature,
signing_ts: channelSignature.signing_ts,
}).catch((err) => {
dispatch(
doToast({
message: err.message,
isError: true,
})
);
});
};
};
export const doCommentBlockWords = (channelClaim: ChannelClaim, words: Array<string>) => {
return (dispatch: Dispatch) => {
return dispatch(doCommentWords(channelClaim, words, false));
};
};
export const doCommentUnblockWords = (channelClaim: ChannelClaim, words: Array<string>) => {
return (dispatch: Dispatch) => {
return dispatch(doCommentWords(channelClaim, words, true));
};
};
export const doFetchBlockedWords = () => {
return async (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const myChannels = selectMyChannelClaims(state);
dispatch({
type: ACTIONS.COMMENT_FETCH_BLOCKED_WORDS_STARTED,
});
let channelSignatures = [];
if (myChannels) {
for (const channelClaim of myChannels) {
try {
const channelSignature = await Lbry.channel_sign({
channel_id: channelClaim.claim_id,
hexdata: toHex(channelClaim.name),
});
channelSignatures.push({ ...channelSignature, claim_id: channelClaim.claim_id, name: channelClaim.name });
} catch (e) {}
}
}
return Promise.all(
channelSignatures.map((signatureData) =>
Comments.setting_list_blocked_words({
channel_name: signatureData.name,
channel_id: signatureData.claim_id,
signature: signatureData.signature,
signing_ts: signatureData.signing_ts,
})
)
)
.then((blockedWords) => {
const blockedWordsByChannelId = {};
for (let i = 0; i < channelSignatures.length; ++i) {
const claim_id = channelSignatures[i].claim_id;
blockedWordsByChannelId[claim_id] = blockedWords[i].word_list;
}
dispatch({
type: ACTIONS.COMMENT_FETCH_BLOCKED_WORDS_COMPLETED,
data: blockedWordsByChannelId,
});
})
.catch(() => {
dispatch({
type: ACTIONS.COMMENT_FETCH_BLOCKED_WORDS_FAILED,
});
});
};
};