Skip to content

Commit c1990b6

Browse files
committed
Add shop module
1 parent 18863cf commit c1990b6

21 files changed

+516
-2
lines changed

.deploy/lambda/lib/JProfByBotStack.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {Construct} from 'constructs';
33
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
44
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
55
import * as lambda from 'aws-cdk-lib/aws-lambda';
6+
import {Architecture} from 'aws-cdk-lib/aws-lambda';
7+
import * as secrets from 'aws-cdk-lib/aws-secretsmanager';
68
import * as sfn from 'aws-cdk-lib/aws-stepfunctions';
79
import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks';
810
import * as ses from 'aws-cdk-lib/aws-ses';
@@ -13,6 +15,8 @@ export class JProfByBotStack extends cdk.Stack {
1315
constructor(scope: Construct, id: string, props: JProfByBotStackProps) {
1416
super(scope, id, props);
1517

18+
const secretPaymentProviderTokens = new secrets.Secret(this, 'jprof-by-bot-secret-payment-provider-tokens');
19+
1620
const votesTable = new dynamodb.Table(this, 'jprof-by-bot-table-votes', {
1721
tableName: 'jprof-by-bot-table-votes',
1822
partitionKey: {name: 'id', type: dynamodb.AttributeType.STRING},
@@ -123,13 +127,20 @@ export class JProfByBotStack extends cdk.Stack {
123127
});
124128

125129
const layerLibGL = new lambda.LayerVersion(this, 'jprof-by-bot-lambda-layer-libGL', {
130+
layerVersionName: 'libGL',
126131
code: lambda.Code.fromAsset('layers/libGL.zip'),
127-
compatibleRuntimes: [lambda.Runtime.JAVA_11],
132+
compatibleArchitectures: [Architecture.ARM_64],
128133
});
129134
const layerLibfontconfig = new lambda.LayerVersion(this, 'jprof-by-bot-lambda-layer-libfontconfig', {
135+
layerVersionName: 'libfontconfig',
130136
code: lambda.Code.fromAsset('layers/libfontconfig.zip'),
131-
compatibleRuntimes: [lambda.Runtime.JAVA_11],
137+
compatibleArchitectures: [Architecture.ARM_64],
132138
});
139+
const layerParametersAndSecretsLambdaExtension = lambda.LayerVersion.fromLayerVersionArn(
140+
this,
141+
'jprof-by-bot-lambda-layer-parametersAndSecretsLambdaExtension',
142+
'arn:aws:lambda:us-east-1:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:2'
143+
)
133144

134145
const lambdaWebhookTimeout = cdk.Duration.seconds(29);
135146
const lambdaWebhook = new lambda.Function(this, 'jprof-by-bot-lambda-webhook', {
@@ -138,6 +149,7 @@ export class JProfByBotStack extends cdk.Stack {
138149
layers: [
139150
layerLibGL,
140151
layerLibfontconfig,
152+
layerParametersAndSecretsLambdaExtension,
141153
],
142154
timeout: lambdaWebhookTimeout,
143155
maxEventAge: cdk.Duration.minutes(5),
@@ -198,6 +210,8 @@ export class JProfByBotStack extends cdk.Stack {
198210
],
199211
});
200212

213+
secretPaymentProviderTokens.grantRead(lambdaWebhook);
214+
201215
votesTable.grantReadWriteData(lambdaWebhook);
202216

203217
youtubeChannelsWhitelistTable.grantReadData(lambdaWebhook);

README.adoc

+2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ Official Telegram bot of Java Professionals BY community.
1212
* Converts some currencies to EUR and USD
1313
* Posts scheduled messages from this repo's `posts` branch
1414
* Expands LeetCode links
15+
* Tells users' local times
1516
* Regulates our English Rooms & teaches us new English words
17+
* Sells pins and custom statuses 🤑
1618

1719
So, it just brings some fun and interactivity in our chat.
1820

gradle/libs.versions.toml

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ aws-lambda-java-events = { group = "com.amazonaws", name = "aws-lambda-java-even
3737
aws-lambda-java-core = { group = "com.amazonaws", name = "aws-lambda-java-core", version.ref = "aws-lambda-java-core" }
3838
aws-lambda-java-log4j2 = { group = "com.amazonaws", name = "aws-lambda-java-log4j2", version.ref = "aws-lambda-java-log4j2" }
3939
dynamodb = { group = "software.amazon.awssdk", name = "dynamodb", version.ref = "awssdk" }
40+
secretsmanager = { group = "software.amazon.awssdk", name = "secretsmanager", version.ref = "awssdk" }
4041
sfn = { group = "software.amazon.awssdk", name = "sfn", version.ref = "awssdk" }
4142

4243
koin-core = { group = "io.insert-koin", name = "koin-core", version.ref = "koin" }

launchers/lambda/build.gradle.kts

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ plugins {
55

66
dependencies {
77
implementation(libs.bundles.aws.lambda)
8+
implementation(libs.secretsmanager)
89
implementation(libs.koin.core)
910
implementation(libs.tgbotapi)
1011
implementation(libs.bundles.log4j)
@@ -28,4 +29,6 @@ dependencies {
2829
implementation(project.projects.english.urbanWordOfTheDay.dynamodb)
2930
implementation(project.projects.english.urbanDictionary)
3031
implementation(project.projects.english.dictionaryapiDev)
32+
implementation(project.projects.shop.provider)
33+
implementation(project.projects.shop)
3134
}

launchers/lambda/src/main/kotlin/by/jprof/telegram/bot/launchers/lambda/JProf.kt

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import by.jprof.telegram.bot.launchers.lambda.config.dictionaryApiDevModule
77
import by.jprof.telegram.bot.launchers.lambda.config.envModule
88
import by.jprof.telegram.bot.launchers.lambda.config.jsonModule
99
import by.jprof.telegram.bot.launchers.lambda.config.pipelineModule
10+
import by.jprof.telegram.bot.launchers.lambda.config.secretsModule
1011
import by.jprof.telegram.bot.launchers.lambda.config.sfnModule
1112
import by.jprof.telegram.bot.launchers.lambda.config.telegramModule
1213
import by.jprof.telegram.bot.launchers.lambda.config.urbanDictionaryModule
@@ -45,6 +46,7 @@ class JProf : RequestHandler<APIGatewayV2HTTPEvent, APIGatewayV2HTTPResponse>, K
4546
init {
4647
startKoin {
4748
modules(
49+
secretsModule,
4850
envModule,
4951
databaseModule,
5052
jsonModule,

launchers/lambda/src/main/kotlin/by/jprof/telegram/bot/launchers/lambda/config/env.kt

+16
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package by.jprof.telegram.bot.launchers.lambda.config
22

3+
import by.jprof.telegram.bot.shop.provider.ChatProviderTokens
4+
import kotlinx.serialization.decodeFromString
5+
import kotlinx.serialization.json.Json
36
import org.koin.core.qualifier.named
47
import org.koin.dsl.module
8+
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient
9+
10+
private const val SECRET_PAYMENT_PROVIDER_TOKENS = "jprof-by-bot-secret-payment-provider-tokens"
511

612
const val TOKEN_TELEGRAM_BOT = "TOKEN_TELEGRAM_BOT"
713
const val TOKEN_YOUTUBE_API = "TOKEN_YOUTUBE_API"
@@ -42,4 +48,14 @@ val envModule = module {
4248
single(named(TIMEOUT)) {
4349
System.getenv(TIMEOUT)!!.toLong()
4450
}
51+
52+
single {
53+
val json: Json = get()
54+
val secrets: SecretsManagerClient = get()
55+
val secret = secrets.getSecretValue {
56+
it.secretId(SECRET_PAYMENT_PROVIDER_TOKENS)
57+
}
58+
59+
json.decodeFromString<ChatProviderTokens>(secret.secretString())
60+
}
4561
}

launchers/lambda/src/main/kotlin/by/jprof/telegram/bot/launchers/lambda/config/pipeline.kt

+52
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ import by.jprof.telegram.bot.quizoji.QuizojiOptionUpdateProcessor
2121
import by.jprof.telegram.bot.quizoji.QuizojiQuestionUpdateProcessor
2222
import by.jprof.telegram.bot.quizoji.QuizojiStartCommandUpdateProcessor
2323
import by.jprof.telegram.bot.quizoji.QuizojiVoteUpdateProcessor
24+
import by.jprof.telegram.bot.shop.ForwardedPaymentStartCommandUpdateProcessor
25+
import by.jprof.telegram.bot.shop.PinsPreCheckoutQueryUpdateProcessor
26+
import by.jprof.telegram.bot.shop.RichCommandUpdateProcessor
27+
import by.jprof.telegram.bot.shop.RichPreCheckoutQueryUpdateProcessor
28+
import by.jprof.telegram.bot.shop.SupportCommandUpdateProcessor
29+
import by.jprof.telegram.bot.shop.SupportPreCheckoutQueryUpdateProcessor
2430
import by.jprof.telegram.bot.times.TimeCommandUpdateProcessor
2531
import by.jprof.telegram.bot.times.TimeZoneCommandUpdateProcessor
2632
import by.jprof.telegram.bot.youtube.YouTubeUpdateProcessor
@@ -116,6 +122,8 @@ val pipelineModule = module {
116122
pinDAO = get(),
117123
unpinScheduler = get(),
118124
bot = get(),
125+
providerTokens = get(),
126+
json = get(),
119127
)
120128
}
121129

@@ -191,4 +199,48 @@ val pipelineModule = module {
191199
bot = get(),
192200
)
193201
}
202+
203+
single<UpdateProcessor>(named("RichCommandUpdateProcessor")) {
204+
RichCommandUpdateProcessor(
205+
bot = get(),
206+
providerTokens = get(),
207+
json = get(),
208+
)
209+
}
210+
211+
single<UpdateProcessor>(named("RichPreCheckoutQueryUpdateProcessor")) {
212+
RichPreCheckoutQueryUpdateProcessor(
213+
bot = get(),
214+
json = get(),
215+
)
216+
}
217+
218+
single<UpdateProcessor>(named("SupportCommandUpdateProcessor")) {
219+
SupportCommandUpdateProcessor(
220+
bot = get(),
221+
providerTokens = get(),
222+
json = get(),
223+
)
224+
}
225+
226+
single<UpdateProcessor>(named("SupportPreCheckoutQueryUpdateProcessor")) {
227+
SupportPreCheckoutQueryUpdateProcessor(
228+
bot = get(),
229+
json = get(),
230+
)
231+
}
232+
233+
single<UpdateProcessor>(named("ForwardedPaymentStartCommandUpdateProcessor")) {
234+
ForwardedPaymentStartCommandUpdateProcessor(
235+
bot = get(),
236+
)
237+
}
238+
239+
single<UpdateProcessor>(named("PinsPreCheckoutQueryUpdateProcessor")) {
240+
PinsPreCheckoutQueryUpdateProcessor(
241+
bot = get(),
242+
json = get(),
243+
moniesDAO = get(),
244+
)
245+
}
194246
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package by.jprof.telegram.bot.launchers.lambda.config
2+
3+
import kotlinx.serialization.ExperimentalSerializationApi
4+
import org.koin.dsl.module
5+
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient
6+
7+
@ExperimentalSerializationApi
8+
val secretsModule = module {
9+
single {
10+
SecretsManagerClient.create()
11+
}
12+
}

pins/build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ dependencies {
66
api(project.projects.core)
77
api(libs.tgbotapi)
88
api(project.projects.monies)
9+
implementation(project.projects.shop.provider)
10+
implementation(project.projects.shop.payload)
911
implementation(project.projects.pins.dto)
1012
implementation(project.projects.pins.scheduler)
1113
implementation(libs.log4j.api)

pins/src/main/kotlin/by/jprof/telegram/bot/pins/PinCommandUpdateProcessor.kt

+39
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import by.jprof.telegram.bot.pins.dao.PinDAO
88
import by.jprof.telegram.bot.pins.dto.Unpin
99
import by.jprof.telegram.bot.pins.model.Pin
1010
import by.jprof.telegram.bot.pins.model.PinDuration
11+
import by.jprof.telegram.bot.pins.model.PinRequest
1112
import by.jprof.telegram.bot.pins.scheduler.UnpinScheduler
1213
import by.jprof.telegram.bot.pins.utils.PinRequestFinder
1314
import by.jprof.telegram.bot.pins.utils.beggar
@@ -16,13 +17,20 @@ import by.jprof.telegram.bot.pins.utils.negativeDuration
1617
import by.jprof.telegram.bot.pins.utils.tooManyPinnedMessages
1718
import by.jprof.telegram.bot.pins.utils.tooPositiveDuration
1819
import by.jprof.telegram.bot.pins.utils.unrecognizedDuration
20+
import by.jprof.telegram.bot.shop.payload.PinsPayload
21+
import by.jprof.telegram.bot.shop.provider.ChatProviderTokens
1922
import dev.inmo.tgbotapi.bot.RequestsExecutor
2023
import dev.inmo.tgbotapi.extensions.api.chat.modify.pinChatMessage
24+
import dev.inmo.tgbotapi.extensions.api.send.payments.sendInvoice
2125
import dev.inmo.tgbotapi.extensions.api.send.reply
2226
import dev.inmo.tgbotapi.types.message.MarkdownV2
27+
import dev.inmo.tgbotapi.types.payments.LabeledPrice
2328
import dev.inmo.tgbotapi.types.update.abstracts.Update
2429
import dev.inmo.tgbotapi.utils.PreviewFeature
2530
import java.time.Duration
31+
import kotlin.random.Random
32+
import kotlinx.serialization.encodeToString
33+
import kotlinx.serialization.json.Json
2634
import org.apache.logging.log4j.LogManager
2735

2836
@PreviewFeature
@@ -31,6 +39,8 @@ class PinCommandUpdateProcessor(
3139
private val pinDAO: PinDAO,
3240
private val unpinScheduler: UnpinScheduler,
3341
private val bot: RequestsExecutor,
42+
private val providerTokens: ChatProviderTokens,
43+
private val json: Json,
3444
private val pinRequestFinder: PinRequestFinder = PinRequestFinder.DEFAULT
3545
) : UpdateProcessor {
3646
companion object {
@@ -47,6 +57,7 @@ class PinCommandUpdateProcessor(
4757

4858
if (pin.message == null) {
4959
bot.reply(to = pin.request, text = help(pins), parseMode = MarkdownV2)
60+
pinsShop(pin)
5061

5162
return
5263
}
@@ -71,6 +82,7 @@ class PinCommandUpdateProcessor(
7182

7283
if (pins < pin.price) {
7384
bot.reply(to = pin.request, text = beggar(pins, pin.price), parseMode = MarkdownV2)
85+
pinsShop(pin)
7486

7587
return
7688
}
@@ -90,9 +102,36 @@ class PinCommandUpdateProcessor(
90102
chatId = pin.chat.id.chatId
91103
ttl = duration.duration.seconds
92104
})
105+
if (Random.nextInt(4) == 0) {
106+
pinsShop(pin)
107+
}
93108
} catch (e: Exception) {
94109
logger.error("Failed to pin a message", e)
95110
}
96111
}
97112
}
113+
114+
private suspend fun pinsShop(pin: PinRequest) {
115+
val chatProviderToken = providerTokens[pin.request.chat.id.chatId]
116+
117+
if (chatProviderToken != null) {
118+
bot.sendInvoice(
119+
chatId = pin.request.chat.id,
120+
title = "168 пинов",
121+
description = "Неделя закрепа",
122+
payload = json.encodeToString(PinsPayload(
123+
pins = 168,
124+
chat = pin.request.chat.id.chatId,
125+
)),
126+
providerToken = chatProviderToken,
127+
currency = "USD",
128+
prices = listOf(
129+
LabeledPrice("Пины × 168", 200)
130+
),
131+
startParameter = "forwarded_payment",
132+
replyToMessageId = pin.request.messageId,
133+
allowSendingWithoutReply = true,
134+
)
135+
}
136+
}
98137
}

settings.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,5 @@ include(":english:urban-word-of-the-day-formatter")
4545
include(":english:urban-dictionary-daily")
4646
include(":shop:provider")
4747
include(":shop:payload")
48+
include(":shop")
4849
include(":launchers:lambda")

shop/README.adoc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
= Shop
2+
3+
This feature allows users to buy link:../pins[pins] and custom titles.

shop/build.gradle.kts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
plugins {
2+
kotlin("jvm")
3+
}
4+
5+
dependencies {
6+
api(project.projects.core)
7+
api(libs.tgbotapi)
8+
implementation(project.projects.shop.provider)
9+
implementation(project.projects.shop.payload)
10+
implementation(project.projects.monies)
11+
implementation(libs.log4j.api)
12+
13+
testImplementation(libs.junit.jupiter.api)
14+
testImplementation(libs.junit.jupiter.params)
15+
testImplementation(libs.mockk)
16+
testRuntimeOnly(libs.junit.jupiter.engine)
17+
testRuntimeOnly(libs.log4j.core)
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package by.jprof.telegram.bot.shop
2+
3+
import by.jprof.telegram.bot.core.UpdateProcessor
4+
import by.jprof.telegram.bot.shop.utils.forwardedPaymentsAreNotSupported
5+
import dev.inmo.tgbotapi.bot.RequestsExecutor
6+
import dev.inmo.tgbotapi.extensions.api.send.sendMessage
7+
import dev.inmo.tgbotapi.extensions.utils.asMessageUpdate
8+
import dev.inmo.tgbotapi.extensions.utils.asPrivateChat
9+
import dev.inmo.tgbotapi.extensions.utils.asPrivateContentMessage
10+
import dev.inmo.tgbotapi.extensions.utils.asTextContent
11+
import dev.inmo.tgbotapi.types.message.MarkdownV2ParseMode
12+
import dev.inmo.tgbotapi.types.update.abstracts.Update
13+
import dev.inmo.tgbotapi.utils.PreviewFeature
14+
15+
@PreviewFeature
16+
class ForwardedPaymentStartCommandUpdateProcessor(
17+
private val bot: RequestsExecutor,
18+
) : UpdateProcessor {
19+
override suspend fun process(update: Update) {
20+
val message = update.asMessageUpdate()?.data?.asPrivateContentMessage() ?: return
21+
val chat = message.chat.asPrivateChat() ?: return
22+
val content = message.content.asTextContent() ?: return
23+
24+
if (content.text != "/start forwarded_payment") {
25+
return
26+
}
27+
28+
bot.sendMessage(
29+
chat = chat,
30+
text = forwardedPaymentsAreNotSupported(),
31+
parseMode = MarkdownV2ParseMode,
32+
)
33+
}
34+
}

0 commit comments

Comments
 (0)