Management: Allow to edit bot info (#4178)
Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>
This commit is contained in:
parent
13846c0d42
commit
21aefeffef
@ -71,6 +71,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
type: userType,
|
type: userType,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
|
canEditBot: Boolean(mtpUser.botCanEdit),
|
||||||
...(userType === 'userTypeBot' && { canBeInvitedToGroup: !mtpUser.botNochats }),
|
...(userType === 'userTypeBot' && { canBeInvitedToGroup: !mtpUser.botNochats }),
|
||||||
...(usernames && { usernames }),
|
...(usernames && { usernames }),
|
||||||
phoneNumber: mtpUser.phone || '',
|
phoneNumber: mtpUser.phone || '',
|
||||||
|
|||||||
@ -564,3 +564,27 @@ function addPhotoToLocalDb(photo: GramJs.Photo) {
|
|||||||
function addWebDocumentToLocalDb(webDocument: GramJs.TypeWebDocument) {
|
function addWebDocumentToLocalDb(webDocument: GramJs.TypeWebDocument) {
|
||||||
localDb.webDocuments[webDocument.url] = webDocument;
|
localDb.webDocuments[webDocument.url] = webDocument;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setBotInfo({
|
||||||
|
bot,
|
||||||
|
langCode,
|
||||||
|
name,
|
||||||
|
about,
|
||||||
|
description,
|
||||||
|
}: {
|
||||||
|
bot: ApiUser;
|
||||||
|
langCode: string;
|
||||||
|
name?: string;
|
||||||
|
about?: string;
|
||||||
|
description?: string;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.bots.SetBotInfo({
|
||||||
|
bot: buildInputPeer(bot.id, bot.accessHash),
|
||||||
|
langCode,
|
||||||
|
name: name || '',
|
||||||
|
about: about || '',
|
||||||
|
description: description || '',
|
||||||
|
}), {
|
||||||
|
shouldReturnTrue: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -75,7 +75,8 @@ export {
|
|||||||
} from './twoFaSettings';
|
} from './twoFaSettings';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot,
|
answerCallbackButton, setBotInfo, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults,
|
||||||
|
sendInlineBotResult, startBot,
|
||||||
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachBots, toggleAttachBot, fetchBotApp,
|
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachBots, toggleAttachBot, fetchBotApp,
|
||||||
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth, loadAttachBot, requestAppWebView,
|
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth, loadAttachBot, requestAppWebView,
|
||||||
allowBotSendMessages, fetchBotCanSendMessage, invokeWebViewCustomMethod,
|
allowBotSendMessages, fetchBotCanSendMessage, invokeWebViewCustomMethod,
|
||||||
|
|||||||
@ -109,9 +109,12 @@ export async function updateProfilePhoto(photo?: ApiPhoto, isFallback?: boolean)
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uploadProfilePhoto(file: File, isFallback?: boolean, isVideo = false, videoTs = 0) {
|
export async function uploadProfilePhoto(
|
||||||
|
file: File, isFallback?: boolean, isVideo = false, videoTs = 0, bot?: ApiUser,
|
||||||
|
) {
|
||||||
const inputFile = await uploadFile(file);
|
const inputFile = await uploadFile(file);
|
||||||
const result = await invokeRequest(new GramJs.photos.UploadProfilePhoto({
|
const result = await invokeRequest(new GramJs.photos.UploadProfilePhoto({
|
||||||
|
...(bot ? { bot: buildInputPeer(bot.id, bot.accessHash) } : undefined),
|
||||||
...(isVideo ? { video: inputFile, videoStartTs: videoTs } : { file: inputFile }),
|
...(isVideo ? { video: inputFile, videoStartTs: videoTs } : { file: inputFile }),
|
||||||
...(isFallback ? { fallback: true } : undefined),
|
...(isFallback ? { fallback: true } : undefined),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -37,6 +37,7 @@ export interface ApiUser {
|
|||||||
hasUnreadStories?: boolean;
|
hasUnreadStories?: boolean;
|
||||||
maxStoryId?: number;
|
maxStoryId?: number;
|
||||||
color?: ApiPeerColor;
|
color?: ApiPeerColor;
|
||||||
|
canEditBot?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiUserFullInfo {
|
export interface ApiUserFullInfo {
|
||||||
|
|||||||
@ -31,6 +31,7 @@ import useFlag from '../../hooks/useFlag';
|
|||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
|
||||||
|
import Icon from '../common/Icon';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import ConfirmDialog from '../ui/ConfirmDialog';
|
import ConfirmDialog from '../ui/ConfirmDialog';
|
||||||
import SearchInput from '../ui/SearchInput';
|
import SearchInput from '../ui/SearchInput';
|
||||||
@ -75,6 +76,7 @@ type StateProps = {
|
|||||||
currentInviteInfo?: ApiExportedInvite;
|
currentInviteInfo?: ApiExportedInvite;
|
||||||
shouldSkipHistoryAnimations?: boolean;
|
shouldSkipHistoryAnimations?: boolean;
|
||||||
isBot?: boolean;
|
isBot?: boolean;
|
||||||
|
canEditBot?: boolean;
|
||||||
isInsideTopic?: boolean;
|
isInsideTopic?: boolean;
|
||||||
canEditTopic?: boolean;
|
canEditTopic?: boolean;
|
||||||
};
|
};
|
||||||
@ -157,6 +159,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
canEditTopic,
|
canEditTopic,
|
||||||
onClose,
|
onClose,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
|
canEditBot,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
setLocalTextSearchQuery,
|
setLocalTextSearchQuery,
|
||||||
@ -494,6 +497,17 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
<i className="icon icon-edit" />
|
<i className="icon icon-edit" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{canEditBot && (
|
||||||
|
<Button
|
||||||
|
round
|
||||||
|
color="translucent"
|
||||||
|
size="smaller"
|
||||||
|
ariaLabel={lang('Edit')}
|
||||||
|
onClick={handleToggleManagement}
|
||||||
|
>
|
||||||
|
<Icon name="edit" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{canEditTopic && (
|
{canEditTopic && (
|
||||||
<Button
|
<Button
|
||||||
round
|
round
|
||||||
@ -580,6 +594,7 @@ export default withGlobal<OwnProps>(
|
|||||||
const topic = isInsideTopic ? chat.topics?.[threadId!] : undefined;
|
const topic = isInsideTopic ? chat.topics?.[threadId!] : undefined;
|
||||||
const canEditTopic = isInsideTopic && topic && getCanManageTopic(chat, topic);
|
const canEditTopic = isInsideTopic && topic && getCanManageTopic(chat, topic);
|
||||||
const isBot = user && isUserBot(user);
|
const isBot = user && isUserBot(user);
|
||||||
|
const canEditBot = isBot && user?.canEditBot;
|
||||||
|
|
||||||
const canAddContact = user && getCanAddContact(user);
|
const canAddContact = user && getCanAddContact(user);
|
||||||
const canManage = Boolean(!isManagement && isProfile && chatId && selectCanManage(global, chatId));
|
const canManage = Boolean(!isManagement && isProfile && chatId && selectCanManage(global, chatId));
|
||||||
@ -607,6 +622,7 @@ export default withGlobal<OwnProps>(
|
|||||||
isEditingInvite,
|
isEditingInvite,
|
||||||
currentInviteInfo,
|
currentInviteInfo,
|
||||||
shouldSkipHistoryAnimations: tabState.shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations: tabState.shouldSkipHistoryAnimations,
|
||||||
|
canEditBot,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(RightHeader);
|
)(RightHeader);
|
||||||
|
|||||||
267
src/components/right/management/ManageBot.tsx
Normal file
267
src/components/right/management/ManageBot.tsx
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
import type { ChangeEvent } from 'react';
|
||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React, {
|
||||||
|
memo, useEffect, useMemo, useRef, useState,
|
||||||
|
} from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiBotInfo, ApiUser } from '../../../api/types';
|
||||||
|
import { ApiMediaFormat } from '../../../api/types';
|
||||||
|
import { ManagementProgress } from '../../../types';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getChatAvatarHash, getMainUsername, getUserFirstOrLastName,
|
||||||
|
} from '../../../global/helpers';
|
||||||
|
import {
|
||||||
|
selectBot,
|
||||||
|
selectTabState,
|
||||||
|
selectUserFullInfo,
|
||||||
|
} from '../../../global/selectors';
|
||||||
|
import { selectCurrentLimit } from '../../../global/selectors/limits';
|
||||||
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useMedia from '../../../hooks/useMedia';
|
||||||
|
|
||||||
|
import Icon from '../../common/Icon';
|
||||||
|
import AvatarEditable from '../../ui/AvatarEditable';
|
||||||
|
import FloatingActionButton from '../../ui/FloatingActionButton';
|
||||||
|
import InputText from '../../ui/InputText';
|
||||||
|
import ListItem from '../../ui/ListItem';
|
||||||
|
import SelectAvatar from '../../ui/SelectAvatar';
|
||||||
|
import Spinner from '../../ui/Spinner';
|
||||||
|
import TextArea from '../../ui/TextArea';
|
||||||
|
|
||||||
|
import './Management.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
userId: string;
|
||||||
|
onClose: NoneToVoidFunction;
|
||||||
|
isActive: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
userId?: string;
|
||||||
|
user?: ApiUser;
|
||||||
|
chatBot?: ApiBotInfo;
|
||||||
|
currentBio?: string;
|
||||||
|
progress?: ManagementProgress;
|
||||||
|
isMuted?: boolean;
|
||||||
|
maxBioLength: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ERROR_NAME_MISSING = 'Please provide name';
|
||||||
|
|
||||||
|
const ManageBot: FC<OwnProps & StateProps> = ({
|
||||||
|
userId,
|
||||||
|
user,
|
||||||
|
progress,
|
||||||
|
onClose,
|
||||||
|
currentBio,
|
||||||
|
isActive,
|
||||||
|
maxBioLength,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
setBotInfo,
|
||||||
|
uploadProfilePhoto,
|
||||||
|
uploadContactProfilePhoto,
|
||||||
|
startBotFatherConversation,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
|
const [isFieldTouched, markFieldTouched, unmarkProfileTouched] = useFlag(false);
|
||||||
|
const [isAvatarTouched, markAvatarTouched, unmarkAvatarTouched] = useFlag(false);
|
||||||
|
const [error, setError] = useState<string | undefined>();
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const username = useMemo(() => (user ? getMainUsername(user) : undefined), [user]);
|
||||||
|
|
||||||
|
useHistoryBack({
|
||||||
|
isActive,
|
||||||
|
onBack: onClose,
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentName = user ? getUserFirstOrLastName(user) : '';
|
||||||
|
|
||||||
|
const [photo, setPhoto] = useState<File | undefined>();
|
||||||
|
const [name, setName] = useState(currentName || '');
|
||||||
|
const [bio, setBio] = useState(currentBio || '');
|
||||||
|
|
||||||
|
const currentAvatarHash = user && getChatAvatarHash(user);
|
||||||
|
const currentAvatarBlobUrl = useMedia(currentAvatarHash, false, ApiMediaFormat.BlobUrl);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
unmarkProfileTouched();
|
||||||
|
unmarkAvatarTouched();
|
||||||
|
}, [userId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setName(currentName || '');
|
||||||
|
setBio(currentBio || '');
|
||||||
|
}, [currentName, currentBio, user]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPhoto(undefined);
|
||||||
|
}, [currentAvatarBlobUrl]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (progress === ManagementProgress.Complete) {
|
||||||
|
unmarkProfileTouched();
|
||||||
|
unmarkAvatarTouched();
|
||||||
|
setError(undefined);
|
||||||
|
}
|
||||||
|
}, [progress]);
|
||||||
|
|
||||||
|
const handleNameChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setName(e.target.value);
|
||||||
|
markFieldTouched();
|
||||||
|
|
||||||
|
if (error === ERROR_NAME_MISSING) {
|
||||||
|
setError(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleBioChange = useLastCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
setBio(e.target.value);
|
||||||
|
markFieldTouched();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePhotoChange = useLastCallback((newPhoto: File) => {
|
||||||
|
setPhoto(newPhoto);
|
||||||
|
markAvatarTouched();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleProfileSave = useLastCallback(() => {
|
||||||
|
const trimmedName = name.trim();
|
||||||
|
const trimmedBio = bio.trim();
|
||||||
|
|
||||||
|
if (!trimmedName.length) {
|
||||||
|
setError(ERROR_NAME_MISSING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setBotInfo({
|
||||||
|
...(isFieldTouched && {
|
||||||
|
bot: user,
|
||||||
|
name: trimmedName,
|
||||||
|
description: trimmedBio,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (photo) {
|
||||||
|
uploadProfilePhoto({
|
||||||
|
file: photo,
|
||||||
|
...(isAvatarTouched && { bot: user }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChangeEditIntro = useLastCallback(() => {
|
||||||
|
startBotFatherConversation({ param: `${username}-intro` });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChangeEditCommands = useLastCallback(() => {
|
||||||
|
startBotFatherConversation({ param: `${username}-commands` });
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChangeSettings = useLastCallback(() => {
|
||||||
|
startBotFatherConversation({ param: `${username}` });
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const isSuggestRef = useRef(false);
|
||||||
|
|
||||||
|
const handleSelectAvatar = useLastCallback((file: File) => {
|
||||||
|
markAvatarTouched();
|
||||||
|
uploadContactProfilePhoto({ userId, file, isSuggest: isSuggestRef.current });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLoading = progress === ManagementProgress.InProgress;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Management">
|
||||||
|
<div className="custom-scroll">
|
||||||
|
<div className="section">
|
||||||
|
<AvatarEditable
|
||||||
|
currentAvatarBlobUrl={currentAvatarBlobUrl}
|
||||||
|
onChange={handlePhotoChange}
|
||||||
|
title={lang('ChatSetPhotoOrVideo')}
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
<InputText
|
||||||
|
id="user-name"
|
||||||
|
label={lang('PaymentCheckoutName')}
|
||||||
|
onChange={handleNameChange}
|
||||||
|
value={name}
|
||||||
|
error={error === ERROR_NAME_MISSING ? error : undefined}
|
||||||
|
teactExperimentControlled
|
||||||
|
/>
|
||||||
|
<TextArea
|
||||||
|
value={bio}
|
||||||
|
onChange={handleBioChange}
|
||||||
|
label={lang('DescriptionPlaceholder')}
|
||||||
|
disabled={isLoading}
|
||||||
|
maxLength={maxBioLength}
|
||||||
|
maxLengthIndicator={maxBioLength ? (maxBioLength - bio.length).toString() : undefined}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="section">
|
||||||
|
<div className="dialog-buttons">
|
||||||
|
<ListItem icon="bot-commands-filled" ripple onClick={handleChangeEditIntro}>
|
||||||
|
<span>{lang('BotEditIntro')}</span>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem icon="bot-command" ripple onClick={handleChangeEditCommands}>
|
||||||
|
<span>{lang('BotEditCommands')}</span>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem icon="bots" ripple onClick={handleChangeSettings}>
|
||||||
|
<span>{lang('BotChangeSettings')}</span>
|
||||||
|
</ListItem>
|
||||||
|
<div className="section-info section-info_push">
|
||||||
|
{renderText(lang('BotManageInfo'), ['links'])}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<FloatingActionButton
|
||||||
|
isShown={isFieldTouched || isAvatarTouched}
|
||||||
|
onClick={handleProfileSave}
|
||||||
|
disabled={isLoading}
|
||||||
|
ariaLabel={lang('Save')}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<Spinner color="white" />
|
||||||
|
) : (
|
||||||
|
<Icon name="check" />
|
||||||
|
)}
|
||||||
|
</FloatingActionButton>
|
||||||
|
<SelectAvatar
|
||||||
|
onChange={handleSelectAvatar}
|
||||||
|
inputRef={inputRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global, { userId }): StateProps => {
|
||||||
|
const user = selectBot(global, userId);
|
||||||
|
const userFullInfo = selectUserFullInfo(global, userId);
|
||||||
|
const { progress } = selectTabState(global).management;
|
||||||
|
const maxBioLength = selectCurrentLimit(global, 'aboutLength');
|
||||||
|
|
||||||
|
return {
|
||||||
|
userId,
|
||||||
|
user,
|
||||||
|
progress,
|
||||||
|
currentBio: userFullInfo?.bio,
|
||||||
|
maxBioLength,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(ManageBot));
|
||||||
@ -161,6 +161,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-position {
|
||||||
|
justify-content: initial;
|
||||||
|
}
|
||||||
|
|
||||||
&__filter {
|
&__filter {
|
||||||
padding: 0 1rem 0.25rem 0.75rem;
|
padding: 0 1rem 0.25rem 0.75rem;
|
||||||
margin-bottom: 0.625rem;
|
margin-bottom: 0.625rem;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { ManagementScreens } from '../../../types';
|
|||||||
|
|
||||||
import { selectCurrentManagementType } from '../../../global/selectors';
|
import { selectCurrentManagementType } from '../../../global/selectors';
|
||||||
|
|
||||||
|
import ManageBot from './ManageBot';
|
||||||
import ManageChannel from './ManageChannel';
|
import ManageChannel from './ManageChannel';
|
||||||
import ManageChatAdministrators from './ManageChatAdministrators';
|
import ManageChatAdministrators from './ManageChatAdministrators';
|
||||||
import ManageChatPrivacyType from './ManageChatPrivacyType';
|
import ManageChatPrivacyType from './ManageChatPrivacyType';
|
||||||
@ -54,6 +55,15 @@ const Management: FC<OwnProps & StateProps> = ({
|
|||||||
switch (currentScreen) {
|
switch (currentScreen) {
|
||||||
case ManagementScreens.Initial: {
|
case ManagementScreens.Initial: {
|
||||||
switch (managementType) {
|
switch (managementType) {
|
||||||
|
case 'bot':
|
||||||
|
return (
|
||||||
|
<ManageBot
|
||||||
|
key={chatId}
|
||||||
|
userId={chatId}
|
||||||
|
onClose={onClose}
|
||||||
|
isActive={isActive}
|
||||||
|
/>
|
||||||
|
);
|
||||||
case 'user':
|
case 'user':
|
||||||
return (
|
return (
|
||||||
<ManageUser
|
<ManageUser
|
||||||
|
|||||||
@ -272,6 +272,7 @@ export const RE_TG_LINK = /^tg:(\/\/)?/i;
|
|||||||
export const RE_TME_LINK = /^(https?:\/\/)?([-a-zA-Z0-9@:%_+~#=]{1,32}\.)?t\.me/i;
|
export const RE_TME_LINK = /^(https?:\/\/)?([-a-zA-Z0-9@:%_+~#=]{1,32}\.)?t\.me/i;
|
||||||
export const RE_TELEGRAM_LINK = /^(https?:\/\/)?telegram\.org\//i;
|
export const RE_TELEGRAM_LINK = /^(https?:\/\/)?telegram\.org\//i;
|
||||||
export const TME_LINK_PREFIX = 'https://t.me/';
|
export const TME_LINK_PREFIX = 'https://t.me/';
|
||||||
|
export const BOT_FATHER_USERNAME = 'botfather';
|
||||||
export const USERNAME_PURCHASE_ERROR = 'USERNAME_PURCHASE_AVAILABLE';
|
export const USERNAME_PURCHASE_ERROR = 'USERNAME_PURCHASE_AVAILABLE';
|
||||||
export const PURCHASE_USERNAME = 'auction';
|
export const PURCHASE_USERNAME = 'auction';
|
||||||
export const TME_WEB_DOMAINS = new Set(['t.me', 'web.t.me', 'a.t.me', 'k.t.me', 'z.t.me']);
|
export const TME_WEB_DOMAINS = new Set(['t.me', 'web.t.me', 'a.t.me', 'k.t.me', 'z.t.me']);
|
||||||
|
|||||||
@ -5,8 +5,9 @@ import {
|
|||||||
type ApiChat, type ApiChatType, type ApiContact, type ApiInputMessageReplyInfo, type ApiPeer, type ApiUrlAuthResult,
|
type ApiChat, type ApiChatType, type ApiContact, type ApiInputMessageReplyInfo, type ApiPeer, type ApiUrlAuthResult,
|
||||||
MAIN_THREAD_ID,
|
MAIN_THREAD_ID,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
import { ManagementProgress } from '../../../types';
|
||||||
|
|
||||||
import { GENERAL_REFETCH_INTERVAL } from '../../../config';
|
import { BOT_FATHER_USERNAME, GENERAL_REFETCH_INTERVAL } from '../../../config';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||||
import { translate } from '../../../util/langProvider';
|
import { translate } from '../../../util/langProvider';
|
||||||
@ -19,17 +20,21 @@ import { callApi } from '../../../api/gramjs';
|
|||||||
import {
|
import {
|
||||||
addActionHandler, getGlobal, setGlobal,
|
addActionHandler, getGlobal, setGlobal,
|
||||||
} from '../../index';
|
} from '../../index';
|
||||||
import { addChats, addUsers, removeBlockedUser } from '../../reducers';
|
import {
|
||||||
|
addChats, addUsers, removeBlockedUser, updateManagementProgress, updateUser, updateUserFullInfo,
|
||||||
|
} from '../../reducers';
|
||||||
import { replaceInlineBotSettings, replaceInlineBotsIsLoading } from '../../reducers/bots';
|
import { replaceInlineBotSettings, replaceInlineBotsIsLoading } from '../../reducers/bots';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
selectBot, selectChat, selectChatMessage, selectCurrentChat, selectCurrentMessageList, selectDraft,
|
selectBot, selectChat, selectChatMessage, selectCurrentChat, selectCurrentMessageList, selectDraft,
|
||||||
selectIsTrustedBot, selectMessageReplyInfo, selectSendAs, selectTabState, selectUser, selectUserFullInfo,
|
selectIsTrustedBot, selectMessageReplyInfo, selectSendAs, selectTabState, selectUser, selectUserFullInfo,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
import { fetchChatByUsername } from './chats';
|
||||||
|
|
||||||
const GAMEE_URL = 'https://prizes.gamee.com/';
|
const GAMEE_URL = 'https://prizes.gamee.com/';
|
||||||
const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
|
const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
|
||||||
const runDebouncedForSearch = debounce((cb) => cb(), 500, false);
|
const runDebouncedForSearch = debounce((cb) => cb(), 500, false);
|
||||||
|
let botFatherId: string | null;
|
||||||
|
|
||||||
addActionHandler('clickBotInlineButton', (global, actions, payload): ActionReturnType => {
|
addActionHandler('clickBotInlineButton', (global, actions, payload): ActionReturnType => {
|
||||||
const { messageId, button, tabId = getCurrentTabId() } = payload;
|
const { messageId, button, tabId = getCurrentTabId() } = payload;
|
||||||
@ -1128,3 +1133,66 @@ async function answerCallbackButton<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addActionHandler('setBotInfo', async (global, actions, payload): Promise<void> => {
|
||||||
|
const {
|
||||||
|
bot, name, description: about,
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
let { langCode } = payload;
|
||||||
|
if (!langCode) langCode = global.settings.byKey.language;
|
||||||
|
|
||||||
|
const { currentUserId } = global;
|
||||||
|
if (!currentUserId || !bot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateManagementProgress(global, ManagementProgress.InProgress, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
if (name || about) {
|
||||||
|
const result = await callApi('setBotInfo', {
|
||||||
|
bot, langCode, name, about,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateUser(
|
||||||
|
global,
|
||||||
|
bot.id,
|
||||||
|
{
|
||||||
|
firstName: name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
global = updateUserFullInfo(global, bot.id, { bio: about });
|
||||||
|
setGlobal(global);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
global = updateManagementProgress(global, ManagementProgress.Complete, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('startBotFatherConversation', async (global, actions, payload): Promise<void> => {
|
||||||
|
const {
|
||||||
|
param,
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
if (!botFatherId) {
|
||||||
|
const chat = await fetchChatByUsername(global, BOT_FATHER_USERNAME);
|
||||||
|
if (!chat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
botFatherId = chat.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param) {
|
||||||
|
actions.startBot({ botId: botFatherId, param });
|
||||||
|
}
|
||||||
|
|
||||||
|
actions.openChat({ id: botFatherId, tabId });
|
||||||
|
});
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { ActionReturnType } from '../../types';
|
import type { ActionReturnType } from '../../types';
|
||||||
|
import { ManagementProgress } from '../../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CUSTOM_BG_CACHE_NAME,
|
CUSTOM_BG_CACHE_NAME,
|
||||||
@ -34,7 +35,9 @@ import { serializeGlobal } from '../../cache';
|
|||||||
import {
|
import {
|
||||||
addActionHandler, getGlobal, setGlobal,
|
addActionHandler, getGlobal, setGlobal,
|
||||||
} from '../../index';
|
} from '../../index';
|
||||||
import { addUsers, clearGlobalForLockScreen, updatePasscodeSettings } from '../../reducers';
|
import {
|
||||||
|
addUsers, clearGlobalForLockScreen, updateManagementProgress, updatePasscodeSettings,
|
||||||
|
} from '../../reducers';
|
||||||
|
|
||||||
addActionHandler('initApi', async (global, actions): Promise<void> => {
|
addActionHandler('initApi', async (global, actions): Promise<void> => {
|
||||||
if (!IS_TEST) {
|
if (!IS_TEST) {
|
||||||
@ -100,14 +103,19 @@ addActionHandler('setAuthPassword', (global, actions, payload): ActionReturnType
|
|||||||
|
|
||||||
addActionHandler('uploadProfilePhoto', async (global, actions, payload): Promise<void> => {
|
addActionHandler('uploadProfilePhoto', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
file, isFallback, isVideo, videoTs,
|
file, isFallback, isVideo, videoTs, bot,
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
} = payload!;
|
} = payload!;
|
||||||
|
|
||||||
const result = await callApi('uploadProfilePhoto', file, isFallback, isVideo, videoTs);
|
global = updateManagementProgress(global, ManagementProgress.InProgress, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
const result = await callApi('uploadProfilePhoto', file, isFallback, isVideo, videoTs, bot);
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||||
|
global = updateManagementProgress(global, ManagementProgress.Complete, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
actions.loadFullUser({ userId: global.currentUserId! });
|
actions.loadFullUser({ userId: global.currentUserId! });
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export function getUserFirstOrLastName(user?: ApiUser) {
|
|||||||
|
|
||||||
switch (user.type) {
|
switch (user.type) {
|
||||||
case 'userTypeBot':
|
case 'userTypeBot':
|
||||||
|
return user.firstName;
|
||||||
case 'userTypeRegular': {
|
case 'userTypeRegular': {
|
||||||
return user.firstName || user.lastName;
|
return user.firstName || user.lastName;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
import { selectChat, selectIsChatWithSelf } from './chats';
|
import { selectChat, selectIsChatWithSelf } from './chats';
|
||||||
import { selectCurrentMessageList } from './messages';
|
import { selectCurrentMessageList } from './messages';
|
||||||
import { selectTabState } from './tabs';
|
import { selectTabState } from './tabs';
|
||||||
import { selectUser } from './users';
|
import { selectBot, selectUser } from './users';
|
||||||
|
|
||||||
export function selectManagement<T extends GlobalState>(
|
export function selectManagement<T extends GlobalState>(
|
||||||
global: T, chatId: string,
|
global: T, chatId: string,
|
||||||
@ -39,10 +39,16 @@ export function selectCurrentManagementType<T extends GlobalState>(
|
|||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
) {
|
) {
|
||||||
const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
||||||
|
|
||||||
if (!chatId || !threadId) {
|
if (!chatId || !threadId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chatBot = selectBot(global, chatId);
|
||||||
|
if (chatBot) {
|
||||||
|
return 'bot';
|
||||||
|
}
|
||||||
|
|
||||||
if (isUserId(chatId)) {
|
if (isUserId(chatId)) {
|
||||||
return 'user';
|
return 'user';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1020,6 +1020,8 @@ export interface ActionPayloads {
|
|||||||
isFallback?: boolean;
|
isFallback?: boolean;
|
||||||
videoTs?: number;
|
videoTs?: number;
|
||||||
isVideo?: boolean;
|
isVideo?: boolean;
|
||||||
|
bot?: ApiUser;
|
||||||
|
tabId?: number;
|
||||||
};
|
};
|
||||||
goToAuthQrCode: undefined;
|
goToAuthQrCode: undefined;
|
||||||
|
|
||||||
@ -2274,6 +2276,22 @@ export interface ActionPayloads {
|
|||||||
bio?: string;
|
bio?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
updateBotProfile: {
|
||||||
|
photo?: File;
|
||||||
|
firstName?: string;
|
||||||
|
bio?: string;
|
||||||
|
} & WithTabId;
|
||||||
|
setBotInfo: {
|
||||||
|
bot?: ApiUser | undefined;
|
||||||
|
langCode?: string;
|
||||||
|
name?: string | undefined;
|
||||||
|
about?: string | undefined;
|
||||||
|
description?: string | undefined;
|
||||||
|
isMuted?: boolean;
|
||||||
|
} & WithTabId;
|
||||||
|
startBotFatherConversation: {
|
||||||
|
param: string;
|
||||||
|
} & WithTabId;
|
||||||
checkUsername: {
|
checkUsername: {
|
||||||
username: string;
|
username: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|||||||
@ -1464,6 +1464,7 @@ channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = U
|
|||||||
channels.clickSponsoredMessage#18afbc93 channel:InputChannel random_id:bytes = Bool;
|
channels.clickSponsoredMessage#18afbc93 channel:InputChannel random_id:bytes = Bool;
|
||||||
channels.toggleViewForumAsMessages#9738bb15 channel:InputChannel enabled:Bool = Updates;
|
channels.toggleViewForumAsMessages#9738bb15 channel:InputChannel enabled:Bool = Updates;
|
||||||
channels.getChannelRecommendations#83b70d97 channel:InputChannel = messages.Chats;
|
channels.getChannelRecommendations#83b70d97 channel:InputChannel = messages.Chats;
|
||||||
|
bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:flags.3?string about:flags.0?string description:flags.1?string = Bool;
|
||||||
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
||||||
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
||||||
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
||||||
|
|||||||
@ -218,6 +218,7 @@
|
|||||||
"bots.canSendMessage",
|
"bots.canSendMessage",
|
||||||
"bots.allowSendMessage",
|
"bots.allowSendMessage",
|
||||||
"bots.invokeWebViewCustomMethod",
|
"bots.invokeWebViewCustomMethod",
|
||||||
|
"bots.setBotInfo",
|
||||||
"payments.getPaymentForm",
|
"payments.getPaymentForm",
|
||||||
"payments.getPaymentReceipt",
|
"payments.getPaymentReceipt",
|
||||||
"payments.validateRequestedInfo",
|
"payments.validateRequestedInfo",
|
||||||
|
|||||||
@ -414,7 +414,7 @@ export enum ManagementScreens {
|
|||||||
JoinRequests,
|
JoinRequests,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ManagementType = 'user' | 'group' | 'channel';
|
export type ManagementType = 'user' | 'group' | 'channel' | 'bot';
|
||||||
|
|
||||||
export type NotifyException = {
|
export type NotifyException = {
|
||||||
isMuted: boolean;
|
isMuted: boolean;
|
||||||
|
|||||||
@ -139,6 +139,8 @@ export default {
|
|||||||
PrivacySettings: 'Privacy and Security',
|
PrivacySettings: 'Privacy and Security',
|
||||||
Language: 'Language',
|
Language: 'Language',
|
||||||
FirstName: 'First name (required)',
|
FirstName: 'First name (required)',
|
||||||
|
PaymentCheckoutName: 'Name',
|
||||||
|
ChatSetPhotoOrVideo: 'Set Photo',
|
||||||
LastName: 'Last name (optional)',
|
LastName: 'Last name (optional)',
|
||||||
UserBio: 'Bio',
|
UserBio: 'Bio',
|
||||||
lng_settings_about_bio: 'Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco',
|
lng_settings_about_bio: 'Any details such as age, occupation or city.\nExample: 23 y.o. designer from San Francisco',
|
||||||
@ -176,6 +178,9 @@ export default {
|
|||||||
AccDescrChannel: 'Channel',
|
AccDescrChannel: 'Channel',
|
||||||
AccDescrGroup: 'Group',
|
AccDescrGroup: 'Group',
|
||||||
Bot: 'bot',
|
Bot: 'bot',
|
||||||
|
BotChangeSettings: 'Change Bot Settings',
|
||||||
|
BotEditCommands: 'Edit Commands',
|
||||||
|
BotEditIntro: 'Edit Intro',
|
||||||
ServiceNotifications: 'Service notifications',
|
ServiceNotifications: 'Service notifications',
|
||||||
'LastSeen.TodayAt': 'last seen today at %@',
|
'LastSeen.TodayAt': 'last seen today at %@',
|
||||||
ALongTimeAgo: 'last seen a long time ago',
|
ALongTimeAgo: 'last seen a long time ago',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user