/**
* WordPress dependencies
*/
import { _x, sprintf } from '@safe-wordpress/i18n';
/**
* External dependencies
*/
import { keyBy, filter, find, map, without, isArray, uniq } from 'lodash';
import twitter from 'twitter-text';
import type {
AndArray,
Maybe,
MediaItem,
NonEmptyArray,
OrArray,
RequirementsErrorAttributes,
SocialKind,
SocialNetwork,
SocialNetworkName,
SocialNetworkSupport,
Url,
} from '@nelio-content/types';
const BASIC_AUTOMATION_FREQS: SocialNetwork[ 'automationFreqs' ] = {
reshare: 0,
publication: 1,
};
const GENERIC_ERRORS = {
/* translators: %s: Social network name. */
missingImage: _x( 'Image required by %s', 'text', 'nelio-content' ),
/* translators: %s: Social network name. */
missingMedia: _x(
'Image or video required by %s',
'text',
'nelio-content'
),
/* translators: %s: Social network name. */
missingRelatedPost: _x(
'Add a related post on %s',
'user',
'nelio-content'
),
/* translators: %s: Social network name. */
missingText: _x(
'Please write status update on %s',
'user',
'nelio-content'
),
/* translators: %s: Social network name. */
missingVideo: _x( 'Video required by %s', 'text', 'nelio-content' ),
textTooLong: _x(
'Please write a shorter social message',
'user',
'nelio-content'
),
};
const DEFAULT_SUPPORTS: NonEmptyArray< SocialNetworkSupport > = [
'automations',
'image',
'network-image',
'network-template',
'preview',
'profile-template',
'recurrence',
'related-post',
'reusability',
'text',
];
const DEFAULT_REQUIRES: OrArray< AndArray< SocialNetworkSupport > > = [
[ 'text' ],
];
const NETWORKS: NonEmptyArray< SocialNetwork > = [
{
id: 'twitter' as const,
active: true,
automationFreqs: {
reshare: 8,
publication: 2,
},
labels: {
add: _x( 'Add X Profile', 'command', 'nelio-content' ),
name: _x( 'X', 'text', 'nelio-content' ),
settings: _x( 'X Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 280,
supports: [
...DEFAULT_SUPPORTS,
'multi-image',
'video',
'buffer-connection',
'hootsuite-connection',
'link-shortener',
],
supportedImageTypes: [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
'image/webp',
],
supportedVideoTypes: [ 'video' ],
requires: DEFAULT_REQUIRES,
validators: {
image: ( media ) => {
if ( ! media ) {
return undefined;
}
if (
! [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
'image/webp',
].includes( media.mime )
) {
return _x(
'Unsupported image type in X (Twitter)',
'text',
'nelio-content'
);
}
if (
media.mime !== 'image/gif' &&
megas( media.filesizeInBytes ) > 5
) {
return _x(
'X (Twitter) image should be smaller than 5MB',
'text',
'nelio-content'
);
}
if (
media.mime === 'image/gif' &&
megas( media.filesizeInBytes ) > 15
) {
return _x(
'X (Twitter) GIF image should be smaller than 15MB',
'text',
'nelio-content'
);
}
return undefined;
},
video: ( media ) => {
if ( ! media ) {
return undefined;
}
if ( media.duration < 0.5 ) {
return _x(
'X (Twitter) video must be at least half a second long',
'text',
'nelio-content'
);
}
if ( media.duration > 140 ) {
return _x(
'X (Twitter) video must be shorter than 140 seconds',
'text',
'nelio-content'
);
}
if ( megas( media.filesizeInBytes ) > 512 ) {
return _x(
'X (Twitter) video should be smaller than 512MB',
'text',
'nelio-content'
);
}
return undefined;
},
},
},
{
id: 'facebook' as const,
active: true,
automationFreqs: {
reshare: 1,
publication: 1,
},
labels: {
add: _x( 'Add Facebook Profile', 'command', 'nelio-content' ),
name: _x( 'Facebook', 'text', 'nelio-content' ),
settings: _x( 'Facebook Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 10000,
supports: [
...DEFAULT_SUPPORTS,
'buffer-connection',
'hootsuite-connection',
'video',
'link-shortener',
],
supportedImageTypes: [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
'image/tiff',
],
supportedVideoTypes: [ 'video/mp4' ],
requires: DEFAULT_REQUIRES,
validators: {
image: ( media ) => {
if ( ! media ) {
return undefined;
}
if ( megas( media.filesizeInBytes ) > 4 ) {
return _x(
'Facebook image should be smaller than 4MB',
'text',
'nelio-content'
);
}
return undefined;
},
video: ( media ) => {
if ( ! media ) {
return undefined;
}
if ( 'video/mp4' !== media.mime ) {
return _x(
'Unsupported video type in Facebook',
'text',
'nelio-content'
);
}
return undefined;
},
},
kinds: [
{
id: 'page',
label: _x( 'Page', 'facebook profile', 'nelio-content' ),
},
],
},
{
id: 'linkedin' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add LinkedIn Profile', 'command', 'nelio-content' ),
name: _x( 'LinkedIn', 'text', 'nelio-content' ),
settings: _x( 'LinkedIn Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 3000,
supports: [
...DEFAULT_SUPPORTS,
'buffer-connection',
'hootsuite-connection',
'multi-image',
'video',
'link-shortener',
],
supportedImageTypes: [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
],
supportedVideoTypes: [ 'video/mp4' ],
requires: DEFAULT_REQUIRES,
validators: {
image: ( media ) => {
if ( ! media ) {
return undefined;
}
if (
! [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
'image/tiff',
].includes( media.mime )
) {
return _x(
'Unsupported image type in LinkedIn',
'text',
'nelio-content'
);
}
return undefined;
},
video: ( media ) => {
if ( ! media ) {
return undefined;
}
if ( media.mime !== 'video/mp4' ) {
return _x(
'Unsupported video type in LinkedIn',
'text',
'nelio-content'
);
}
if ( media.duration < 3 ) {
return _x(
'LinkedIn video must be at least 3 seconds long',
'text',
'nelio-content'
);
}
if ( media.duration > 30 * 60 ) {
return _x(
'LinkedIn video must be shorter than 30 minutes',
'text',
'nelio-content'
);
}
if ( kilos( media.filesizeInBytes ) < 75 ) {
return _x(
'LinkedIn video must be at least 75KB',
'text',
'nelio-content'
);
}
if ( megas( media.filesizeInBytes ) > 500 ) {
return _x(
'LinkedIn video should be smaller than 500MB',
'text',
'nelio-content'
);
}
return undefined;
},
},
kinds: [
{
id: 'single',
label: _x( 'Personal', 'linkedin profile', 'nelio-content' ),
},
{
id: 'company',
label: _x( 'Company', 'linkedin profile', 'nelio-content' ),
},
],
},
{
id: 'gmb' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x(
'Add Google My Business Location',
'command',
'nelio-content'
),
name: _x( 'Google My Business', 'text', 'nelio-content' ),
settings: _x(
'Google My Business Settings',
'text',
'nelio-content'
),
},
icon: (
),
limit: 1500,
supports: [
...DEFAULT_SUPPORTS,
'buffer-connection',
'link-shortener',
],
supportedImageTypes: [ 'image/jpeg', 'image/jpg', 'image/png' ],
requires: DEFAULT_REQUIRES,
validators: {
image: ( media ) => {
if ( ! media ) {
return undefined;
}
if (
! [ 'image/jpeg', 'image/jpg', 'image/png' ].includes(
media.mime
)
) {
return _x(
'Unsupported image type in Google My Business',
'text',
'nelio-content'
);
}
if ( megas( media.filesizeInBytes ) > 5 ) {
return _x(
'Google My Business image should be smaller than 5MB',
'text',
'nelio-content'
);
}
if ( kilos( media.filesizeInBytes ) < 10 ) {
return _x(
'Google My Business image should be at least 10KB',
'text',
'nelio-content'
);
}
if ( media.height < 250 ) {
return _x(
'Google My Business image height must be at least 250px',
'text',
'nelio-content'
);
}
if ( media.width < 250 ) {
return _x(
'Google My Business image width must be at least 250px',
'text',
'nelio-content'
);
}
return undefined;
},
},
kinds: [
{
id: 'location',
label: _x(
'Location',
'Google My Business profile',
'nelio-content'
),
},
],
},
{
id: 'pinterest' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add Pinterest Profile', 'command', 'nelio-content' ),
name: _x( 'Pinterest', 'text', 'nelio-content' ),
settings: _x( 'Pinterest Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 500,
supports: [
...without( DEFAULT_SUPPORTS, 'network-template' ),
'multi-image',
'multi-target',
'buffer-connection',
'link-shortener',
],
supportedImageTypes: [ 'image' ],
requires: [ [ 'multi-target', 'text', 'image' ] ],
validators: {
images: ( media ) => {
if (
uniq( media.map( ( m ) => `${ m.width }-${ m.height }` ) )
.length !== 1
) {
return _x(
'Images in a multi-image Pinterest pin must have the same width/height ratios.',
'text',
'nelio-content'
);
}
return undefined;
},
},
},
{
id: 'reddit' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add Subreddit', 'command', 'nelio-content' ),
name: _x( 'Reddit', 'text', 'nelio-content' ),
settings: _x( 'Reddit Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 500,
supports: without( DEFAULT_SUPPORTS, 'network-image' ),
supportedImageTypes: [ 'image' ],
requires: DEFAULT_REQUIRES,
kinds: [
{
id: 'subreddit',
label: _x( 'Subreddit', 'Reddit profile', 'nelio-content' ),
},
],
},
{
id: 'instagram' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add Instagram Profile', 'command', 'nelio-content' ),
name: _x( 'Instagram', 'text', 'nelio-content' ),
settings: _x( 'Instagram Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 2000,
supports: [
...DEFAULT_SUPPORTS,
'buffer-connection',
'hootsuite-connection',
'multi-image',
'multi-media',
'video',
'link-shortener',
],
supportedImageTypes: [ 'image/jpeg', 'image/jpg' ],
supportedVideoTypes: [ 'video/mp4', 'video/quicktime' ],
requires: [ [ 'text', 'image' ] ],
validators: {
image: ( media ) => {
if ( ! media ) {
return undefined;
}
if ( ! [ 'image/jpeg', 'image/jpg' ].includes( media.mime ) ) {
return _x(
'Unsupported image type in Instagram',
'text',
'nelio-content'
);
}
if ( megas( media.filesizeInBytes ) > 8 ) {
return _x(
'Instagram image should be smaller than 8MB',
'text',
'nelio-content'
);
}
return undefined;
},
video: ( media ) => {
if ( ! media ) {
return undefined;
}
if (
! [ 'video/mp4', 'video/quicktime' ].includes( media.mime )
) {
return _x(
'Unsupported video type in Instagram',
'text',
'nelio-content'
);
}
return undefined;
},
},
},
{
id: 'telegram' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add Telegram Connection', 'command', 'nelio-content' ),
name: _x( 'Telegram', 'text', 'nelio-content' ),
settings: _x( 'Telegram Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 4096,
supports: [
...DEFAULT_SUPPORTS,
'multi-image',
'video',
'multi-media',
'link-shortener',
],
supportedImageTypes: [ 'image' ],
supportedVideoTypes: [ 'video' ],
requires: DEFAULT_REQUIRES,
validators: {
image: ( media ) => {
if ( ! media ) {
return undefined;
}
if ( megas( media.filesizeInBytes ) > 5 ) {
return _x(
'Telegram image should be smaller than 5MB',
'text',
'nelio-content'
);
}
return undefined;
},
video: ( media ) => {
if ( ! media ) {
return undefined;
}
if ( megas( media.filesizeInBytes ) > 20 ) {
return _x(
'Telegram video should be smaller than 20MB',
'text',
'nelio-content'
);
}
return undefined;
},
},
},
{
id: 'medium' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add Medium Connection', 'command', 'nelio-content' ),
name: _x( 'Medium', 'text', 'nelio-content' ),
settings: _x( 'Medium Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 4096,
tagsLimit: 5,
supports: [ 'preview', 'related-post', 'medium-tags' ],
requires: [ [ 'preview', 'related-post' ] ],
kinds: [
{
id: 'single',
label: _x( 'Personal', 'medium profile', 'nelio-content' ),
},
{
id: 'publication',
label: _x( 'Publication', 'medium profile', 'nelio-content' ),
},
],
},
{
id: 'blogger' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add Blogger Connection', 'command', 'nelio-content' ),
name: _x( 'Blogger', 'text', 'nelio-content' ),
settings: _x( 'Blogger Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 10000,
supports: [ 'preview', 'related-post' ],
requires: [ [ 'preview', 'related-post' ] ],
kinds: [
{
id: 'blog',
label: _x( 'Blog', 'Blogger blog', 'nelio-content' ),
},
],
},
{
id: 'tiktok' as const,
active: true,
automationFreqs: {
reshare: 0,
publication: 0,
},
labels: {
add: _x( 'Add TikTok Profile', 'command', 'nelio-content' ),
name: _x( 'TikTok', 'text', 'nelio-content' ),
settings: _x( 'TikTok Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 2000,
supports: [
'recurrence',
'reusability',
'text',
'preview',
'video',
'image',
'multi-image',
'link-shortener',
],
supportedImageTypes: [ 'image/jpeg', 'image/jpg', 'image/webp' ],
supportedVideoTypes: [ 'video/mp4', 'video/webm', 'video/quicktime' ],
requires: [ [ 'video' ], [ 'image' ], [ 'multi-image' ] ],
validators: {
image: ( media ) => {
if ( ! media ) {
return undefined;
}
if (
! [ 'image/jpeg', 'image/jpg', 'image/webp' ].includes(
media.mime
)
) {
return _x(
'Unsupported image type in TikTok',
'text',
'nelio-content'
);
}
if ( megas( media.filesizeInBytes ) > 20 ) {
return _x(
'TikTok image should be smaller than 20MB',
'text',
'nelio-content'
);
}
if ( media.height > 1080 ) {
return _x(
'TikTok image height must be less than or equal to 1080px',
'text',
'nelio-content'
);
}
return undefined;
},
video: ( media ) => {
if ( ! media ) {
return undefined;
}
if (
! [ 'video/mp4', 'video/webm', 'video/quicktime' ].includes(
media.mime
)
) {
return _x(
'Unsupported video type in TikTok',
'text',
'nelio-content'
);
}
if ( media.duration > 10 * 60 ) {
return _x(
'TikTok video must be shorter than 10 minutes',
'text',
'nelio-content'
);
}
return undefined;
},
general: ( attrs ) => {
if (
attrs.network === 'tiktok' &&
attrs.tiktokSettings?.discloseContent &&
! attrs.tiktokSettings.isBrandContent &&
! attrs.tiktokSettings.isBrandOrganic
) {
return _x(
'You need to indicate if your content promotes yourself, a third party, or both',
'user',
'nelio-content'
);
}
return undefined;
},
},
},
{
id: 'tumblr' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add Tumblr Profile', 'command', 'nelio-content' ),
name: _x( 'Tumblr', 'text', 'nelio-content' ),
settings: _x( 'Tumblr Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 500,
supports: DEFAULT_SUPPORTS,
supportedImageTypes: [ 'image' ],
requires: DEFAULT_REQUIRES,
},
{
id: 'mastodon' as const,
active: true,
automationFreqs: {
reshare: 8,
publication: 2,
},
labels: {
add: _x( 'Add Mastodon Profile', 'command', 'nelio-content' ),
name: _x( 'Mastodon', 'text', 'nelio-content' ),
settings: _x( 'Mastodon Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 500,
supports: [
...DEFAULT_SUPPORTS,
'multi-image',
'video',
'buffer-connection',
'link-shortener',
],
supportedImageTypes: [ 'image' ],
supportedVideoTypes: [
'video/mp4',
'video/x-m4v',
'video/quicktime',
'video/webm',
],
requires: DEFAULT_REQUIRES,
validators: {
image: ( media ) => {
if ( ! media ) {
return undefined;
}
if ( megas( media.filesizeInBytes ) > 16 ) {
return _x(
'Mastodon image should be smaller than 16MB',
'text',
'nelio-content'
);
}
return undefined;
},
video: ( media ) => {
if ( ! media ) {
return undefined;
}
if (
! [
'video/mp4',
'video/x-m4v',
'video/quicktime',
'video/webm',
].includes( media.mime )
) {
return _x(
'Unsupported video type in Mastodon',
'text',
'nelio-content'
);
}
if ( megas( media.filesizeInBytes ) > 99 ) {
return _x(
'Mastodon video should be smaller than 99MB',
'text',
'nelio-content'
);
}
return undefined;
},
},
},
{
id: 'bluesky' as const,
active: true,
automationFreqs: {
reshare: 8,
publication: 2,
},
labels: {
add: _x( 'Add Bluesky Profile', 'command', 'nelio-content' ),
name: _x( 'Bluesky', 'text', 'nelio-content' ),
settings: _x( 'Bluesky Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 300,
supports: [ ...DEFAULT_SUPPORTS, 'multi-image', 'link-shortener' ],
supportedImageTypes: [ 'image' ],
requires: DEFAULT_REQUIRES,
validators: {
image: ( media ) => {
if ( ! media ) {
return undefined;
}
if ( megas( media.filesizeInBytes ) > 1 ) {
return _x(
'Bluesky image should be smaller than 1MB',
'text',
'nelio-content'
);
}
return undefined;
},
},
},
{
id: 'threads' as const,
active: true,
automationFreqs: {
reshare: 8,
publication: 2,
},
labels: {
add: _x( 'Add Threads Profile', 'command', 'nelio-content' ),
name: _x( 'Threads', 'text', 'nelio-content' ),
settings: _x( 'Threads Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 300,
supports: [
...DEFAULT_SUPPORTS,
'hootsuite-connection',
'video',
'multi-image',
'multi-media',
'link-shortener',
],
supportedImageTypes: [ 'image/jpeg', 'image/jpg', 'image/png' ],
supportedVideoTypes: [ 'video/mp4', 'video/quicktime' ],
requires: DEFAULT_REQUIRES,
validators: {
image: ( media ) => {
if ( ! media ) {
return undefined;
}
if ( megas( media.filesizeInBytes ) > 8 ) {
return _x(
'Threads image should be smaller than 8MB',
'text',
'nelio-content'
);
}
return undefined;
},
video: ( media ) => {
if ( ! media ) {
return undefined;
}
if (
! [ 'video/mp4', 'video/quicktime' ].includes( media.mime )
) {
return _x(
'Unsupported video type in Threads',
'text',
'nelio-content'
);
}
if ( media.duration > 300 ) {
return _x(
'TikTok video must be shorter than 300 seconds',
'text',
'nelio-content'
);
}
return undefined;
},
},
},
{
id: 'vk' as const,
active: true,
automationFreqs: {
reshare: 1,
publication: 1,
},
labels: {
add: _x( 'Add VK Profile', 'command', 'nelio-content' ),
name: _x( 'VK', 'text', 'nelio-content' ),
settings: _x( 'VK Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 10000,
supports: [
...DEFAULT_SUPPORTS,
'video',
'multi-image',
'multi-media',
'link-shortener',
],
supportedImageTypes: [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
],
supportedVideoTypes: [
'video/x-msvideo',
'video/mp4',
'video/3gpp',
'video/mpeg',
'video/quicktime',
'video/x-flv',
'video/x-ms-wmv',
],
requires: DEFAULT_REQUIRES,
validators: {
image: ( media ) => {
if ( ! media ) {
return undefined;
}
if (
! [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
].includes( media.mime )
) {
return _x(
'Unsupported image type in VK',
'text',
'nelio-content'
);
}
if ( megas( media.filesizeInBytes ) > 50 ) {
return _x(
'VK image should be smaller than 50MB',
'text',
'nelio-content'
);
}
if ( media.height + media.width > 14000 ) {
return _x(
'Sum of the height and width is no more than 14,000 pixels for VK image',
'text',
'nelio-content'
);
}
return undefined;
},
video: ( media ) => {
if ( ! media ) {
return undefined;
}
if (
! [
'video/x-msvideo', // AVI
'video/mp4', // MP4
'video/3gpp', // 3GP
'video/mpeg', // MPEG
'video/quicktime', // MOV
'video/x-flv', // FLV
'video/x-ms-wmv', // WMV
].includes( media.mime )
) {
return _x(
'Unsupported video type in VK',
'text',
'nelio-content'
);
}
return undefined;
},
},
kinds: [
{
id: 'single',
label: _x( 'Personal', 'vk profile', 'nelio-content' ),
},
{
id: 'group',
label: _x( 'Community', 'vk profile', 'nelio-content' ),
},
],
},
{
id: 'plurk' as const,
active: true,
automationFreqs: {
reshare: 8,
publication: 2,
},
labels: {
add: _x( 'Add Plurk Profile', 'command', 'nelio-content' ),
name: _x( 'Plurk', 'text', 'nelio-content' ),
settings: _x( 'Plurk Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 280,
supports: [
'automations',
'network-template',
'profile-template',
'recurrence',
'related-post',
'reusability',
'text',
'preview',
'link-shortener',
],
requires: DEFAULT_REQUIRES,
},
{
id: 'discord' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add Discord Channel', 'command', 'nelio-content' ),
name: _x( 'Discord', 'text', 'nelio-content' ),
settings: _x( 'Discord Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 2000,
supports: [ ...DEFAULT_SUPPORTS, 'multi-image', 'link-shortener' ],
supportedImageTypes: [ 'image' ],
requires: DEFAULT_REQUIRES,
},
{
id: 'ok' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add OK.ru Profile', 'command', 'nelio-content' ),
name: _x( 'OK.ru', 'text', 'nelio-content' ),
settings: _x( 'OK.ru Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 3000,
supports: [
...DEFAULT_SUPPORTS,
'multi-image',
'link-shortener',
/* 'video'*/
],
supportedImageTypes: [ 'image' ],
//supportedVideoTypes: [ 'video' ],
requires: DEFAULT_REQUIRES,
// validators: {
// video: ( media ) => {
// if ( ! media ) {
// return undefined;
// } //end if
// if ( kilos( media.filesizeInBytes ) < 16 ) {
// return _x(
// 'OK.ru video must be at least 16KB',
// 'text',
// 'nelio-content'
// );
// } //end if
// if ( megas( media.filesizeInBytes ) > 1024 ) {
// return _x(
// 'OK.ru video should be smaller than 1GB',
// 'text',
// 'nelio-content'
// );
// } //end if
// return undefined;
// },
// },
kinds: [
{
id: 'single',
label: _x( 'Personal', 'ok.ru profile', 'nelio-content' ),
},
{
id: 'group',
label: _x( 'Group', 'ok.ru profile', 'nelio-content' ),
},
],
},
{
id: 'slack' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add Slack Channel', 'command', 'nelio-content' ),
name: _x( 'Slack', 'text', 'nelio-content' ),
settings: _x( 'Slack Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 2000,
supports: [ ...DEFAULT_SUPPORTS, 'multi-image', 'link-shortener' ],
supportedImageTypes: [ 'image' ],
requires: DEFAULT_REQUIRES,
},
{
id: 'band' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add Band', 'command', 'nelio-content' ),
name: _x( 'Band', 'text', 'nelio-content' ),
settings: _x( 'Band Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 2000,
supports: [
'automations',
'network-template',
'preview',
'profile-template',
'recurrence',
'related-post',
'reusability',
'text',
'link-shortener',
],
requires: DEFAULT_REQUIRES,
},
{
id: 'webhook' as const,
active: true,
automationFreqs: BASIC_AUTOMATION_FREQS,
labels: {
add: _x( 'Add Webhook', 'command', 'nelio-content' ),
name: _x( 'Webhook', 'text', 'nelio-content' ),
settings: _x( 'Webhook Settings', 'text', 'nelio-content' ),
},
icon: (
),
limit: 10000,
supports: [ 'automations', 'related-post', 'reusability' ],
requires: [],
validators: {
general: ( attrs ) => {
if (
attrs.network === 'webhook' &&
! isUrl( attrs.webhookSettings?.url )
) {
return _x(
'Provide a fully qualified URL to send the webhook data',
'user',
'nelio-content'
);
}
if (
attrs.network === 'webhook' &&
attrs.webhookSettings?.bodyType === 'json' &&
! isValidJSON( attrs.webhookSettings?.bodyContent )
) {
return _x(
'Provide a valid JSON body for the webhook',
'user',
'nelio-content'
);
}
return undefined;
},
},
},
];
const ACTIVE_NETWORKS = filter( NETWORKS, { active: true } );
const MAX_CHAR_LIMIT: number = Math.max( ...map( ACTIVE_NETWORKS, 'limit' ) );
const MIN_CHAR_LIMIT: number = Math.min( ...map( ACTIVE_NETWORKS, 'limit' ) );
type TargetLabel = {
readonly explanation: string;
readonly loading: string;
readonly noTargetsExplanation: string;
readonly selectTarget: string;
readonly selectTargetError: string;
readonly selectTargets: string;
readonly targetLabel: string;
readonly title: string;
};
const TARGET_LABELS: Record< string, TargetLabel > = {
default: {
explanation: _x(
'Please select where your message will be shared on:',
'user',
'nelio-content'
),
loading: _x( 'Loading…', 'text', 'nelio-content' ),
noTargetsExplanation: _x(
'Your profile does not have any locations yet. Please configure one first.',
'user',
'nelio-content'
),
selectTarget: _x( 'Select a location…', 'user', 'nelio-content' ),
selectTargets: _x( 'Select locations…', 'user', 'nelio-content' ),
selectTargetError: _x(
'Please select a location',
'user',
'nelio-content'
),
targetLabel: _x( 'Location', 'text', 'nelio-content' ),
title: _x( 'Select Location', 'text', 'nelio-content' ),
},
pinterest: {
explanation: _x(
'Please select the boards your message will be shared on:',
'user (pinterest boards)',
'nelio-content'
),
loading: _x(
'Loading boards…',
'text (pinterest boards)',
'nelio-content'
),
noTargetsExplanation: _x(
'Your Pinterest profile does not have any boards yet. Go to Pinterest and create one first.',
'user (no pinterest boards)',
'nelio-content'
),
selectTarget: _x( 'Select a board…', 'user', 'nelio-content' ),
selectTargets: _x( 'Select boards…', 'user', 'nelio-content' ),
selectTargetError: _x(
'Please select a board',
'user',
'nelio-content'
),
targetLabel: _x(
'Board',
'text (pinterest target name)',
'nelio-content'
),
title: _x(
'Select Boards',
'text (pinterest boards)',
'nelio-content'
),
},
reddit: {
explanation: _x(
'Please select where your message will be shared on:',
'user',
'nelio-content'
),
loading: _x( 'Loading…', 'text', 'nelio-content' ),
noTargetsExplanation: _x(
'Your profile does not have any subreddits yet. Please configure one first.',
'user',
'nelio-content'
),
selectTarget: _x( 'Select a subreddit…', 'user', 'nelio-content' ),
selectTargets: _x( 'Select subreddits…', 'user', 'nelio-content' ),
selectTargetError: _x(
'Please select a subreddit',
'user',
'nelio-content'
),
targetLabel: _x( 'Subreddit', 'text', 'nelio-content' ),
title: _x( 'Select Subreddit', 'text', 'nelio-content' ),
},
};
const _supportedNetworks = map( ACTIVE_NETWORKS, 'id' );
export const getSupportedNetworks = (): Array< SocialNetworkName > =>
_supportedNetworks;
const _networksById = keyBy( ACTIVE_NETWORKS, 'id' );
export const getCharLimitInNetwork = (
networkName: SocialNetworkName
): number => {
const network = _networksById[ networkName ] || { limit: MAX_CHAR_LIMIT };
return network.limit;
};
export const getMinCharLimit = (): number => MIN_CHAR_LIMIT;
export const getMaxCharLimit = (): number => MAX_CHAR_LIMIT;
export const getTagsLimitInNetwork = (
networkName: SocialNetworkName
): number => {
const network = _networksById[ networkName ] || { tagsLimit: 0 };
return network.tagsLimit ?? 0;
};
const LINK_25CH = 'aaaabaaaabaaaabaaaabaaaab';
export function getMessageLength( text: string ): number {
text = ` ${ text } `;
text = text.replaceAll( /(\s)https?:\/\/[^\s]+/g, `$1${ LINK_25CH }` );
text = text.trim();
return twitter.parseTweet( text ).weightedLength;
}
export const getDefaultPublicationValue = (
network: SocialNetworkName
): number => _networksById[ network ]?.automationFreqs?.publication || 0;
export const getDefaultReshareValue = ( network: SocialNetworkName ): number =>
_networksById[ network ]?.automationFreqs?.reshare || 0;
export const doesNetworkSupport = (
support: SocialNetworkSupport,
networkName: Maybe< SocialNetworkName >
): boolean =>
!! networkName &&
!! _networksById[ networkName ]?.supports.includes( support );
export const doesNetworkRequire = (
support: SocialNetworkSupport,
networkName: Maybe< SocialNetworkName >
): boolean =>
!! networkName &&
!! _networksById[ networkName ]?.requires.some( ( r ) =>
r.includes( support )
);
export const getNetworkLabel = (
label: keyof SocialNetwork[ 'labels' ],
networkName: SocialNetworkName
): string => {
const network = _networksById[ networkName ];
return network?.labels[ label ] || '';
};
export const getSupportedImageTypes = (
networkName: SocialNetworkName
): ReadonlyArray< string > => {
const network = _networksById[ networkName ];
return network?.supportedImageTypes ?? [];
};
export const getSupportedVideoTypes = (
networkName: SocialNetworkName
): ReadonlyArray< string > => {
const network = _networksById[ networkName ];
return network?.supportedVideoTypes ?? [];
};
export const getTargetLabel = (
label: keyof TargetLabel,
networkName: SocialNetworkName
): string => {
const labels = TARGET_LABELS[ networkName ] || TARGET_LABELS.default;
return labels?.[ label ] || '';
};
export const getNetworkKinds = (
networkName: SocialNetworkName
): ReadonlyArray< SocialKind > => {
const network = _networksById[ networkName ];
return network?.kinds || [];
};
export const getNetworkIcon = ( networkName: SocialNetworkName ): JSX.Element =>
_networksById[ networkName ]?.icon || (
);
export function getRequirementsError(
attrs: RequirementsErrorAttributes
): Maybe< string > {
const network = _networksById[ attrs.network ];
if ( ! network ) {
return _x( 'Unknown error', 'text', 'nelio-content' );
}
const getReqErr = ( req: SocialNetworkSupport ): string => {
switch ( req ) {
case 'image':
case 'multi-image':
if (
[ 'image', 'auto-image' ].includes( attrs.type ) ||
( network.supports.includes( 'video' ) &&
'video' === attrs.type ) ||
( network.supports.includes( 'multi-media' ) &&
'multi-media' === attrs.type )
) {
return '';
}
return network.supports.includes( 'video' ) ||
network.supports.includes( 'multi-media' )
? GENERIC_ERRORS.missingMedia
: GENERIC_ERRORS.missingImage;
case 'multi-media':
if (
[ 'image', 'auto-image', 'video', 'multi-media' ].includes(
attrs.type
)
) {
return '';
}
return GENERIC_ERRORS.missingMedia;
case 'multi-target':
return ! attrs.targetName
? getTargetLabel( 'selectTargetError', attrs.network )
: '';
case 'text':
if ( attrs.textComputed === '' ) {
return GENERIC_ERRORS.missingText;
}
return getMessageLength( attrs.textComputed ) > network.limit
? GENERIC_ERRORS.textTooLong
: '';
case 'video':
if (
'video' === attrs.type ||
( network.supports.includes( 'image' ) &&
'image' === attrs.type ) ||
( network.supports.includes( 'multi-media' ) &&
'multi-media' === attrs.type )
) {
return '';
}
return network.supports.includes( 'image' )
? GENERIC_ERRORS.missingMedia
: GENERIC_ERRORS.missingVideo;
case 'related-post':
if ( attrs.postId ) {
return '';
}
return GENERIC_ERRORS.missingRelatedPost;
case 'automations':
case 'buffer-connection':
case 'hootsuite-connection':
case 'link-shortener':
case 'medium-tags':
case 'network-image':
case 'network-template':
case 'preview':
case 'profile-template':
case 'recurrence':
case 'reusability':
return '';
}
};
const getMediaErr = (): string => {
if ( network.supports.includes( 'image' ) && 'image' === attrs.type ) {
return isArray( attrs.image )
? attrs.image
.map(
( img: MediaItem ) =>
network.validators?.image?.( img ) ?? ''
)
.filter( ( m ) => !! m )[ 0 ] ??
network.validators?.images?.( attrs.image ) ??
''
: network.validators?.image?.(
attrs.image as Maybe< MediaItem >
) ?? '';
}
if ( network.supports.includes( 'video' ) && 'video' === attrs.type ) {
return isArray( attrs.video )
? attrs.video
.map(
( v: MediaItem ) =>
network.validators?.video?.( v ) ?? ''
)
.filter( ( v ) => !! v )[ 0 ] ?? ''
: network.validators?.video?.(
attrs.video as Maybe< MediaItem >
) ?? '';
}
return '';
};
const getNetworkErr = (): string =>
network.validators?.general?.( attrs ) ?? '';
const errors = network.requires.map( ( reqs ) =>
reqs.reduce( ( e, r ) => e || getReqErr( r ), '' )
);
/* eslint-disable @wordpress/valid-sprintf */
const err = sprintf(
find( errors, ( e ) => e !== '' ) ?? '',
network.labels.name
);
/* eslint-enable @wordpress/valid-sprintf */
return err || getMediaErr() || getNetworkErr() || undefined;
}
// =======
// HELPERS
// =======
const kilos = ( bytes: number ) => Math.round( bytes / 1024 );
const megas = ( bytes: number ) => Math.round( kilos( bytes ) / 1024 );
const URL_REGEX =
/^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/;
const isUrl = ( url?: string | Url ): url is Url => {
return !! url && URL_REGEX.test( url );
};
const isValidJSON = ( value?: string ): boolean => {
if ( value === undefined ) {
return false;
}
try {
JSON.parse( value );
return true;
} catch {
return false;
}
};