lbry-desktop/ui/redux/actions/comments.js
Sean Yesmunt 2e87b2fd22 odysee
Naomi

comment websockets

increase slow mode time to 5 seconds

fix to prevent duplicate comments

update livestream details

fix channel

pin electron boom

fix rebase

prune unused icons

updating meme

updating meme

update livestream for naomi

fix rebase

DigitalCashNetwork

remove electroboom pin

Slavguns

Joel

So he can edit his claims

add streamTypes param to claimTilesDiscover so following section can search for all types of content

fix typo

update meme

fixes

publish page fixes

pending

fix notifications

fix comments finally

fix claim preview

no mature for simplesite

Revert "no mature for simplesite"

This reverts commit 9f89242d85.

fix livestream preview click

no mature on simple site

try fixing invite page crash

probably needs more changes.
2021-03-26 18:47:28 -04:00

647 lines
19 KiB
JavaScript

// @flow
import * as ACTIONS from 'constants/action_types';
import * as REACTION_TYPES from 'constants/reactions';
import { Lbry, parseURI, buildURI, selectClaimsByUri, selectMyChannelClaims } from 'lbry-redux';
// import { BANNED_LIVESTREAM_WORDS } from 'constants/comment';
import { doToast, doSeeNotifications } from 'redux/actions/notifications';
import {
makeSelectCommentIdsForUri,
makeSelectMyReactionsForComment,
makeSelectOthersReactionsForComment,
selectPendingCommentReacts,
selectModerationBlockList,
} 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';
export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) {
return (dispatch: Dispatch, getState: GetState) => {
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;
}
dispatch({
type: ACTIONS.COMMENT_LIST_STARTED,
});
return Comments.comment_list({
page,
claim_id: claimId,
page_size: pageSize,
})
.then((result: CommentListResponse) => {
const { items: comments } = result;
dispatch({
type: ACTIONS.COMMENT_LIST_COMPLETED,
data: {
comments,
claimId: claimId,
uri: uri,
},
});
return result;
})
.catch((error) => {
dispatch({
type: ACTIONS.COMMENT_LIST_FAILED,
data: error,
});
});
};
}
export function doCommentReactList(uri: string | null, commentId?: string) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const activeChannelClaim = selectActiveChannelClaim(state);
const commentIds = uri ? makeSelectCommentIdsForUri(uri)(state) : [commentId];
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_STARTED,
});
const params: { comment_ids: string, channel_name?: string, channel_id?: string } = {
comment_ids: commentIds.join(','),
};
if (activeChannelClaim) {
params['channel_name'] = activeChannelClaim.name;
params['channel_id'] = activeChannelClaim.claim_id;
}
return Lbry.comment_react_list(params)
.then((result: CommentReactListResponse) => {
const { my_reactions: myReactions, others_reactions: othersReactions } = result;
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_COMPLETED,
data: {
myReactions: myReactions || {},
othersReactions,
},
});
})
.catch((error) => {
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_FAILED,
data: error,
});
});
};
}
export function doCommentReact(commentId: string, type: string) {
return (dispatch: Dispatch, getState: GetState) => {
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;
}
let myReacts = makeSelectMyReactionsForComment(commentId)(state);
const othersReacts = makeSelectOthersReactionsForComment(commentId)(state);
const params: CommentReactParams = {
comment_ids: commentId,
channel_name: activeChannelClaim.name,
channel_id: activeChannelClaim.claim_id,
react_type: type,
};
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);
}
}
}
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;
}, {});
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_COMPLETED,
data: {
myReactions: { [commentId]: myReactsObj },
othersReactions: { [commentId]: othersReacts },
},
});
Lbry.comment_react(params)
.then((result: CommentReactListResponse) => {
dispatch({
type: ACTIONS.COMMENT_REACT_COMPLETED,
data: commentId + type,
});
})
.catch((error) => {
dispatch({
type: ACTIONS.COMMENT_REACT_FAILED,
data: commentId + type,
});
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 },
},
});
});
};
}
export function doCommentCreate(
comment: string = '',
claim_id: string = '',
parent_id?: string,
uri: string,
livestream?: boolean = false
) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const activeChannelClaim = selectActiveChannelClaim(state);
if (!activeChannelClaim) {
console.error('Unable to create comment. No activeChannel is set.'); // eslint-disable-line
return;
}
dispatch({
type: ACTIONS.COMMENT_CREATE_STARTED,
});
// if (livestream) {
// const strippedCommentText = comment.trim().toLowerCase().replace(/\s/g, '');
// for (var i = 0; i < BANNED_LIVESTREAM_WORDS.length; i++) {
// const bannedWord = BANNED_LIVESTREAM_WORDS[i];
// if (strippedCommentText.includes(bannedWord)) {
// dispatch({
// type: ACTIONS.COMMENT_CREATE_FAILED,
// });
//
// dispatch(
// doToast({
// message: 'Unable to create comment.',
// isError: true,
// })
// );
//
// return;
// }
// }
// }
if (parent_id) {
const notification = makeSelectNotificationForCommentId(parent_id)(state);
if (notification && !notification.is_seen) {
dispatch(doSeeNotifications([notification.id]));
}
}
return Lbry.comment_create({
comment: comment,
claim_id: claim_id,
channel_id: activeChannelClaim.claim_id,
parent_id: parent_id,
})
.then((result: CommentCreateResponse) => {
dispatch({
type: ACTIONS.COMMENT_CREATE_COMPLETED,
data: {
uri,
livestream,
comment: result,
claimId: claim_id,
},
});
return result;
})
.catch((error) => {
dispatch({
type: ACTIONS.COMMENT_CREATE_FAILED,
data: error,
});
let toastMessage = __('Unable to create comment, please try again later.');
if (error && error.message === 'channel is blocked by publisher') {
toastMessage = __('Unable to comment. This channel has blocked you.');
}
dispatch(
doToast({
message: toastMessage,
isError: true,
})
);
});
};
}
export function doCommentPin(commentId: string, remove: boolean) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const activeChannel = selectActiveChannelClaim(state);
if (!activeChannel) {
console.error('Unable to pin comment. No activeChannel is set.'); // eslint-disable-line
return;
}
dispatch({
type: ACTIONS.COMMENT_PIN_STARTED,
});
return Lbry.comment_pin({
comment_id: commentId,
channel_name: activeChannel.name,
channel_id: activeChannel.claim_id,
...(remove ? { remove: true } : {}),
})
.then((result: CommentPinResponse) => {
dispatch({
type: ACTIONS.COMMENT_PIN_COMPLETED,
data: result,
});
})
.catch((error) => {
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);
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 || {}),
})
.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,
},
});
} 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) => {
dispatch({
type: ACTIONS.COMMENT_ABANDON_FAILED,
data: error,
});
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 (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.COMMENT_UPDATE_STARTED,
});
return Lbry.comment_update({
comment_id: comment_id,
comment: comment,
})
.then((result: CommentUpdateResponse) => {
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) => {
dispatch({
type: ACTIONS.COMMENT_UPDATE_FAILED,
data: error,
});
dispatch(
doToast({
message: 'Unable to edit this comment, please try again later.',
isError: true,
})
);
});
};
}
}
// Hides a users comments from all creator's claims and prevent them from commenting in the future
export function doCommentModToggleBlock(channelUri: string, unblock: boolean = false) {
return async (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const myChannels = selectMyChannelClaims(state);
const claim = selectClaimsByUri(state)[channelUri];
if (!claim) {
console.error("Can't find claim to block"); // eslint-disable-line
return;
}
dispatch({
type: unblock ? ACTIONS.COMMENT_MODERATION_UN_BLOCK_STARTED : ACTIONS.COMMENT_MODERATION_BLOCK_STARTED,
data: {
uri: channelUri,
},
});
const creatorIdForAction = claim ? claim.claim_id : null;
const creatorNameForAction = claim ? claim.name : null;
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) {}
}
}
const sharedModBlockParams = unblock
? {
un_blocked_channel_id: creatorIdForAction,
un_blocked_channel_name: creatorNameForAction,
}
: {
blocked_channel_id: creatorIdForAction,
blocked_channel_name: creatorNameForAction,
};
const commentAction = unblock ? Comments.moderation_unblock : Comments.moderation_block;
return Promise.all(
channelSignatures.map((signatureData) =>
commentAction({
mod_channel_id: signatureData.claim_id,
mod_channel_name: signatureData.name,
signature: signatureData.signature,
signing_ts: signatureData.signing_ts,
...sharedModBlockParams,
})
)
)
.then(() => {
dispatch({
type: unblock ? ACTIONS.COMMENT_MODERATION_UN_BLOCK_COMPLETE : ACTIONS.COMMENT_MODERATION_BLOCK_COMPLETE,
data: { channelUri },
});
if (!unblock) {
dispatch(doToast({ message: __('Channel blocked. You will not see them again.') }));
}
})
.catch(() => {
dispatch({
type: unblock ? ACTIONS.COMMENT_MODERATION_UN_BLOCK_FAILED : ACTIONS.COMMENT_MODERATION_BLOCK_FAILED,
});
});
};
}
export function doCommentModBlock(commentAuthor: string) {
return (dispatch: Dispatch) => {
return dispatch(doCommentModToggleBlock(commentAuthor));
};
}
export function doCommentModUnBlock(commentAuthor: string) {
return (dispatch: Dispatch) => {
return dispatch(doCommentModToggleBlock(commentAuthor, true));
};
}
export function doFetchModBlockedList() {
return async (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const myChannels = selectMyChannelClaims(state);
dispatch({
type: ACTIONS.COMMENT_MODERATION_BLOCK_LIST_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.moderation_block_list({
mod_channel_id: signatureData.claim_id,
mod_channel_name: signatureData.name,
signature: signatureData.signature,
signing_ts: signatureData.signing_ts,
})
)
)
.then((blockLists) => {
let globalBlockList = [];
blockLists
.sort((a, b) => {
return 1;
})
.forEach((channelBlockListData) => {
const blockListForChannel = channelBlockListData && channelBlockListData.blocked_channels;
if (blockListForChannel) {
blockListForChannel.forEach((blockedChannel) => {
if (blockedChannel.blocked_channel_name) {
const channelUri = buildURI({
channelName: blockedChannel.blocked_channel_name,
claimId: blockedChannel.blocked_channel_id,
});
if (!globalBlockList.find((blockedChannel) => blockedChannel.channelUri === channelUri)) {
globalBlockList.push({ channelUri, blockedAt: blockedChannel.blocked_at });
}
}
});
}
});
dispatch({
type: ACTIONS.COMMENT_MODERATION_BLOCK_LIST_COMPLETED,
data: {
blockList:
globalBlockList.length > 0
? globalBlockList
.sort((a, b) => new Date(a.blockedAt) - new Date(b.blockedAt))
.map((blockedChannel) => blockedChannel.channelUri)
: null,
},
});
})
.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);
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,
});
})
);
};
};