Closed gerushenka closed 1 month ago
Покажи пример, как ты хочешь соединять свои абстракции
Вот что, я имею в виду
Пример как это может выглядеть
https://github.com/Adobe-for-Alex/adobe-api/pull/5#issuecomment-2446095897
Примерно так это вижу формируем сумму --> ищем админа --> запрашиваем верификацию --> назначаем сессию пользователю
async function processPayment(userId: TelegramId) {
const payment = new ScreenPayment(userName, context.image());
const price = await payment.price();
const amount = await price.amount();
const admin = await admins.anyNotBusy();
if (!admin) {
throw new Error("нет доступных админов");
}
const verification = await admin.requestPaymentVerification(payment);
if (verification === 'approved') {
const user = await users.withId(userId);
if (user) {
const session = await service.createSession(userId, period);
await user.setSubscription(session);
}
} else {
console.log("REJECT");
}
}
получается что оплата будет формироваться в price а amount будет итоговой суммой со скидкой
Это то, что нужно, опиши пожалуйста больше сценариев. Например отправка сообщений пользователю и приветствие нового. Каждая реакция бота - это какой-то объект
регестрируем пользователя --> получаем имя и приветствуем --> предлагаем оформить подписку если её нет
async function newUser(userId: TelegramId, chatId: ChatId, nickname: TelegramNickname) {
const users = await service.users();
let user = await users.withId(userId);
if (!user) {
user = await users.register(userId, chatId);
}
const userName = await user.name();
const welcomeMessage = `Привет, ${userName}! Добро пожаловать в наш сервис подписок.`;
await sendTelegramMessage(chatId, welcomeMessage);
const currentSubscription = await user.subscription();
if (!currentSubscription) {
const offerMessage = "У вас пока нет активной подписки. Хотите оформить её сейчас?";
await sendTelegramMessage(chatId, offerMessage);
}
}
проверяем сколько дней до конца подписки и отправляем сообщение о скором истечении её
async function sendSubscriptionReminder(user: User, chatId: ChatId): Promise<void> {
const session = await user.subscription();
if (!session) {
return;
}
const endDate = await session.endDate();
const currentDate = new Date();
const daysRemaining = Math.ceil((endDate.getTime() - currentDate.getTime()) / (1000 * 60 * 60 * 24));
if (daysRemaining <= 7) {
const reminderMessage = `Ваша подписка заканчивается через ${daysRemaining} дней.'
await sendTelegramMessage(chatId, reminderMessage);
}
}
вместо текста можно в целом использовать кнопки
async function handleSubscriptionEnd(userId: TelegramId) {
const user = await users.withId(userId);
const currentSession = await user.subscription();
const hasExpired = !currentSession || new Date() > await currentSession.endDate();
if (!hasExpired) {
return;
}
const endMessage = "Ваша подписка закончилась. Хотите продлить её, чтобы сохранить доступ?";
await sendTelegramMessage(userId, endMessage);
const wantsToRenew = await waitForUserResponse(userId);
if (wantsToRenew.toLowerCase().includes("да")) {
const period = 30;
const session = await service.createSession(userId, period);
await user.setSubscription(session);
const endDate = await session.endDate();
const renewalMessage = `Подписка успешно продлена и будет действовать до ${endDate.toDateString()}. Спасибо, что остаетесь с нами!`;
await sendTelegramMessage(userId, renewalMessage);
} else {
// Пользователь отказался от продления, предлагаем скидку
const price = await getDiscountedPrice(userId);
const discount = await price.discount();
const discountedAmount = await price.amount();
const discountMessage = `Продлите подписку сейчас и получите скидку ${discount}%! Всего за ${discountedAmount} рублей. Хотите воспользоваться предложением?`;
await sendTelegramMessage(userId, discountMessage);
const response = await waitForUserResponse(userId);
if (response.toLowerCase().includes("да")) {
const session = await service.createSession(userId, 30); // Подписка на 30 дней со скидкой
await user.setSubscription(session);
const endDate = await session.endDate();
const finalMessage = `Спасибо за выбор нашего сервиса! Ваша подписка активирована до ${endDate.toDateString()}.`;
await sendTelegramMessage(userId, finalMessage);
} else {
await sendTelegramMessage(userId, "Спасибо за использование нашего сервиса! Мы всегда рады вам.");
}
}
}
@gerushenka пиши код, пожалуйста, в таких кавычках: ```ts code ``` так он будет красиво подсвечиваться. ts - это TypeScript. Github поддерживает многие популярные языки
Это то, что я хотел (хотя и не в точности правильно в моем понимании)! Общая рекомендация: лучше не пладить кучу констант, а стараться писать все в одно выражение. Если становится сложно ориентироваться в этом выражении, это сигнал того, что стоит переформулировать его или разбить на отдельные самостоятельные части. Такой подход позволяет избегать портянок кода практически везде
По поводу этого сценария
Декомпозиция - в общем случае плохо, хотя и является самым простым с ходу решением
Например, лучше вместо payment.price()
сделать так, что бы price
передавался в конструкторе и был инкапсулирован внутри payment
. Пример:
const price = new DiscountPrice(new DollarPrice(42), 10)
user.sendMessage(new StringMessage(`Новая подписка обойдется вам в ${price.asString()}`))
const answer = user.answer()
admin.approve(
userName
new GeneralPayment(
price
answer.file()
)
).extend(user.session())
admin.approve(payment: Payment): Promise<Payment>
- заворачивает оплату в декоратор, что позволяет избежать ветвления в пользу полиморфизма
@gerushenka тебе понятна последняя пара рекомендация? Если да, то перепиши пожалуйста сценарии с учетом их. Еще, думаю лучше будет, если с пользователем ты будешь общаться через интерфейс User
, что-то вроде:
user.send(message)
message = user.answer()
Причем пускай message
будет тоже интерфейсом, что бы мы могли прикреплять к сообщению картинки и файлы через декораторы: new MessageWithImage(new TextMessage('Привет, землянин!'), new Image('/assets/hello-cat.png'))
@gerushenka тебе понятна последняя пара рекомендация? Если да, то перепиши пожалуйста сценарии с учетом их. Еще, думаю лучше будет, если с пользователем ты будешь общаться через интерфейс
User
, что-то вроде:
user.send(message)
message = user.answer()
Причем пускай
message
будет тоже интерфейсом, что бы мы могли прикреплять к сообщению картинки и файлы через декораторы:new MessageWithImage(new TextMessage('Привет, землянин!'), new Image('/assets/hello-cat.png'))
Понял, сейчас сделаю
async function processPayment(userId: TelegramId) {
const price = new DiscountPrice(new DollarPrice(42), 10);
user.send(new StringMessage(`Новая подписка обойдется вам в ${price.asString()}`))
const admin = await admins.anyNotBusy();
admin.approve(
userName
new GeneralPayment(
price
answer.file()
)
).extend(user.session())
const user = await users.withId(userId);
if (verification === 'approved' && user) {
const session = await service.createSession(userId, period);
await user.setSubscription(session);
await user.send(new TextMessage(`Оплата одобрена. Подписка активирована до ${session.endDate()}.`));
} else {
await user.send(new TextMessage("Извините, ваша оплата отклонена."));
}
}
регестрируем пользователя --> получаем имя и приветствуем --> предлагаем оформить подписку если её нет
async function newUser(userId: TelegramId, chatId: ChatId, nickname: TelegramNickname) {
const users = await service.users();
let user = await users.withId(userId);
if (!user) {
user = await users.register(userId, chatId);
}
const userName = await user.name();
await user.send( new TextMessage(`Привет, ${userName}! Добро пожаловать в наш сервис.`));
const currentSubscription = await user.subscription();
if (!currentSubscription) {
await user.send(new TextMessage("У вас пока нет активной подписки. Хотите оформить её сейчас?"));
}
}
проверяем сколько дней до конца подписки и отправляем сообщение о скором истечении её
async function sendSubscriptionReminder(user: User): Promise<void> {
const session = await user.subscription();
if (!session) {
return;
}
const endDate = await session.endDate();
const currentDate = new Date();
const daysRemaining = Math.ceil((endDate.getTime() - currentDate.getTime()) / (1000 * 60 * 60 * 24));
if (daysRemaining <= 3) {
await user.send(new TextMessage(`Ваша подписка заканчивается через ${daysRemaining} дней.`));
}
}
вместо текста можно в целом использовать кнопки
async function handleSubscriptionEnd(userId: TelegramId) {
const user = await users.withId(userId);
if (!user) return;
const currentSession = await user.subscription();
const hasExpired = !currentSession || new Date() > await currentSession.endDate();
if (!hasExpired) {
return;
}
await user.send(new TextMessage("Ваша подписка закончилась. Хотите продлить её, чтобы сохранить доступ?"));
const wantsToRenew = await user.answer();
if (wantsToRenew.asString().toLowerCase().includes("да")) {
const period = 30;
const session = await service.createSession(userId, period);
await user.setSubscription(session);
await user.send(new TextMessage(`Подписка успешно продлена и будет действовать до ${await session.endDate()}. Спасибо, что остаетесь с нами!`));
} else {
const discount = await price.discount();
const discountedAmount = await price.discountedAmount();
await user.send(new TextMessage(`Продлите подписку сейчас и получите скидку ${discount}%! Всего за ${discountedAmount} рублей. Хотите воспользоваться предложением?`));
const response = await user.answer();
if (response.asString().toLowerCase().includes("да")) {
const session = await service.createSession(userId, 30);
await user.setSubscription(session);
await user.send(new TextMessage(`Ваша подписка активирована до ${await session.endDate()}.`));
} else {
await user.send(new TextMessage("Спасибо за использование нашего сервиса! Мы всегда рады вам."));
}
}
}
После изменений, раскидай все интерфейсы по файлам по шаблону: Payment
-> src/payment/Payment.ts
. Это нужно для группировки реализаций интерфейсов по папкам
В TS можно писать
export default interface A {}
вместо
interface A{}
export default A
Второй вариант менее предпочтителен из-за дублирования кода
Последние стилистические штрихи. Убери везде из объявления интерфейсов ;
, но при этом оставь в строчках с import
. Так же перемести все as*
методы в конец объявленя интерфейса.
В будущем будет автоматический линтер, который сделает эту часть работы за тебя и мне не надо будет писать об этом. Оставить ;
в строке import
я сказал что бы было сейчас меньше гемороя, а стиль проекта выдерживался. Я так понял, что твой редактор (как и мой :)) автоматически добавляет импорт именно в таком формате
Fixes #7