Golang ΡΠ°Ρ-Π±ΠΎΡ Π΄Π»Ρ ΠΎΡΠΈΡΠΈΠ°Π»ΡΠ½ΠΎΠ³ΠΎ API MAX#
maxbot-chatbot-library β ΡΡΠΎ ΡΡΠ΅ΠΉΠΌΠ²ΠΎΡΠΊ Π΄Π»Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ ΠΌΠ°ΡΡΡΠ°Π±ΠΈΡΡΠ΅ΠΌΡΡ Π±ΠΎΡΠΎΠ² Π΄Π»Ρ MAX Bot API Π½Π° ΡΠ·ΡΠΊΠ΅ Go. ΠΠΎΡΡΡΠΎΠ΅Π½Π½Π°Ρ Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ maxbot-api-client-go. ΠΡΠ° Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠ° ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΠ΅Ρ ΡΠΈΡΡΡΠΉ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡ, Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠ΅ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ Π΄Π°Π½Π½ΡΡ ΠΈ Π½Π°Π΄Π΅ΠΆΠ½ΡΠΉ ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ ΡΠΎΡΡΠΎΡΠ½ΠΈΠΉ (FSM) Π΄Π»Ρ ΠΏΠΎΡΡΡΠΎΠ΅Π½ΠΈΡ ΠΌΠ½ΠΎΠ³ΠΎΡΠ°Π³ΠΎΠ²ΡΡ Π΄ΠΈΠ°Π»ΠΎΠ³ΠΎΠ²ΡΡ ΡΡΠ΅Π½Π°ΡΠΈΠ΅Π².
API#
ΠΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ ΠΊ REST API MAX Π½Π°Ρ ΠΎΠ΄ΠΈΡΡΡ ΠΏΠΎ ΡΡΡΠ»ΠΊΠ΅. ΠΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠ° ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΎΠ±ΡΡΡΠΊΠΎΠΉ ΠΊ REST API, ΠΏΠΎΡΡΠΎΠΌΡ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΡ ΠΏΠΎ ΡΡΡΠ»ΠΊΠ΅ Π²ΡΡΠ΅ ΠΏΡΠΈΠΌΠ΅Π½ΠΈΠΌΠ° ΠΈ ΠΊ ΡΠ°ΠΌΠΎΠΉ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠ΅.
ΠΠ²ΡΠΎΡΠΈΠ·Π°ΡΠΈΡ#
ΠΠ»Ρ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ ΠΏΠΎΡΡΠ΅Π±ΡΠ΅ΡΡΡ ΠΏΠΎΠ»ΡΡΠΈΡΡ ΡΠΎΠΊΠ΅Π½ Π±ΠΎΡΠ° Π² ΠΊΠΎΠ½ΡΠΎΠ»ΠΈ ΡΠ°Π·ΡΠ°Π±ΠΎΡΡΠΈΠΊΠ° MAX API.
Π‘ΠΎΠ΄Π΅ΡΠΆΠ°Π½ΠΈΠ΅#
ΠΠ°ΠΊ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°ΡΡ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΡ ΠΈ ΠΈΠ½ΠΈΡΠΈΠΈΡΠΎΠ²Π°ΡΡ ΡΠ²ΠΎΠ΅Π³ΠΎ Π±ΠΎΡΠ°#
ΠΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΈΡ ΠΊΠΎΠΌΠ°Π½Π΄ ΠΈ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠΉ#
Π£ΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΡΠΌΠΈ ΠΈ ΡΡΠ΅Π½Ρ#
ΠΡΠ²Π΅Ρ Ρ ΠΌΠ΅Π΄ΠΈΠ°ΡΠ°ΠΉΠ»Π°ΠΌΠΈ#
ΠΡΠΈΠΌΠ΅Ρ ΡΡ ΠΎ-Π±ΠΎΡΠ°#
ΠΠΈΡΠ΅Π½Π·ΠΈΡ#
ΠΠ°ΠΊ ΠΈΠΌΠΏΠΎΡΡΠΈΡΠΎΠ²Π°ΡΡ Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΡ ΠΈ ΠΈΠ½ΠΈΡΠΈΠΈΡΠΎΠ²Π°ΡΡ ΡΠ²ΠΎΠ΅Π³ΠΎ Π±ΠΎΡΠ°#
Π£Π±Π΅Π΄ΠΈΡΠ΅ΡΡ, ΡΡΠΎ Ρ Π²Π°Ρ ΡΡΡΠ°Π½ΠΎΠ²Π»Π΅Π½Π° Π²Π΅ΡΡΠΈΡ Go Π½Π΅ Π½ΠΈΠΆΠ΅ 1.25:
go version
ΠΠ΅ Π·Π°Π±ΡΠ΄ΡΡΠ΅ ΡΠΎΠ·Π΄Π°ΡΡ ΠΌΠΎΠ΄ΡΠ»Ρ:
go mod init my-bot-project
Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ:
go get github.com/green-api/maxbot-chatbot-library
ΠΠ°ΠΊ ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡ ΠΎΠ±ΡΠ΅ΠΊΡ#
ΠΠ»Ρ ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΠΈ Π±ΠΎΡΠ° Π½ΡΠΆΠ½ΠΎ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡΡΡ ΠΌΠ΅ΡΠΎΠ΄ΠΎΠΌ NewBot ΠΈΠ· Π±ΠΈΠ±Π»ΠΈΠΎΡΠ΅ΠΊΠΈ ΠΈ ΡΠΊΠ°Π·Π°ΡΡ Π°Π΄ΡΠ΅Ρ ΠΏΠ»Π°ΡΡΠΎΡΠΌΡ MaxBot, MAX token, Π° ΡΠ°ΠΊ ΠΆΠ΅ Π·Π°Π΄Π°ΡΡ Π·Π½Π°ΡΠ΅Π½ΠΈΡ Π΄Π»Ρ RateLimiter ΠΈ Timeout.
ΠΠ°ΡΠ°ΠΌΠ΅ΡΡΡ ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ:
BaseURL- ΠΠ°Π·ΠΎΠ²ΡΠΉ URL-Π°Π΄ΡΠ΅Ρ ΡΠ΅ΡΠ²Π΅ΡΠΎΠ² ΠΏΠ»Π°ΡΡΠΎΡΠΌΡ MaxBot. ΠΡΠ΅ ΠΌΠ΅ΡΠΎΠ΄Ρ API Π±ΡΠ΄ΡΡ ΠΌΠ°ΡΡΡΡΡΠΈΠ·ΠΈΡΠΎΠ²Π°ΡΡΡΡ ΠΏΠΎ ΡΡΠΎΠΌΡ ΠΊΠΎΡΠ½Π΅Π²ΠΎΠΌΡ Π°Π΄ΡΠ΅ΡΡ. ΠΠΊΡΡΠ°Π»ΡΠ½ΡΠΉ Π°Π΄ΡΠ΅Ρ ΡΠΊΠ°Π·Π°Π½ Π² ΠΎΡΠΈΡΠΈΠ°Π»ΡΠ½ΠΎΠΉ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ.Token- Π£Π½ΠΈΠΊΠ°Π»ΡΠ½ΡΠΉ ΡΠ΅ΠΊΡΠ΅ΡΠ½ΡΠΉ ΠΊΠ»ΡΡ Π°Π²ΡΠΎΡΠΈΠ·Π°ΡΠΈΠΈ (API-ΠΊΠ»ΡΡ) Π²Π°ΡΠ΅Π³ΠΎ Π±ΠΎΡΠ°. ΠΠΎΠ»ΡΡΠΈΡΡ Π΅Π³ΠΎ ΠΌΠΎΠΆΠ½ΠΎ Π² Π»ΠΈΡΠ½ΠΎΠΌ ΠΊΠ°Π±ΠΈΠ½Π΅ΡΠ΅ ΠΏΠΎΡΠ»Π΅ ΡΠ΅Π³ΠΈΡΡΡΠ°ΡΠΈΠΈ ΠΈΠ»ΠΈ ΡΠΎΠ·Π΄Π°Π½ΠΈΠΈ Π±ΠΎΡΠ° Π½Π° ΠΏΠ»Π°ΡΡΠΎΡΠΌΠ΅ business.max.ru. Π§ΠΈΡΠ°ΠΉΡΠ΅ ΠΏΠ΅ΡΠ΅Π΄ Π½Π°ΡΠ°Π»ΠΎΠΌ ΡΠ°Π±ΠΎΡΡ, ΡΡΠΎΠ±Ρ ΠΏΠΎΠ»ΡΡΠΈΡΡ ΡΠΎΠΊΠ΅Π½.RateLimiter- ΠΡΡΡΠΎΠ΅Π½Π½ΡΠΉ ΠΎΠ³ΡΠ°Π½ΠΈΡΠΈΡΠ΅Π»Ρ ΡΠ°ΡΡΠΎΡΡ Π·Π°ΠΏΡΠΎΡΠΎΠ². ΠΠ½ ΠΊΠΎΠ½ΡΡΠΎΠ»ΠΈΡΡΠ΅Ρ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ ΠΈΡΡ ΠΎΠ΄ΡΡΠΈΡ Π·Π°ΠΏΡΠΎΡΠΎΠ² Π² ΡΠ΅ΠΊΡΠ½Π΄Ρ (RPS), Π·Π°ΡΠΈΡΠ°Ρ Π±ΠΎΡΠ° ΠΎΡ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΠΈ ΡΠΎ ΡΡΠΎΡΠΎΠ½Ρ ΡΠ΅ΡΠ²Π΅ΡΠ° Π·Π° ΠΏΡΠ΅Π²ΡΡΠ΅Π½ΠΈΠ΅ Π»ΠΈΠΌΠΈΡΠΎΠ². Π Π΅ΠΊΠΎΠΌΠ΅Π½Π΄ΡΠ΅ΠΌΠΎΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ β Π½Π΅ ΠΌΠ΅Π½Π΅Π΅ 25, Π½ΠΎ Π½Π΅ Π±ΠΎΠ»Π΅Π΅ 30.Timeout- ΠΠ°ΠΊΡΠΈΠΌΠ°Π»ΡΠ½ΠΎΠ΅ Π²ΡΠ΅ΠΌΡ ΠΎΠΆΠΈΠ΄Π°Π½ΠΈΡ ΠΎΡΠ²Π΅ΡΠ° ΠΎΡ ΡΠ΅ΡΠ²Π΅ΡΠ° (Π² ΡΠ΅ΠΊΡΠ½Π΄Π°Ρ ). ΠΡΠ»ΠΈ ΡΠ΅ΡΠ²Π΅Ρ Π½Π΅ ΠΎΡΠ²Π΅ΡΠΈΡ Π² ΡΠ΅ΡΠ΅Π½ΠΈΠ΅ ΡΡΠΎΠ³ΠΎ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ, Π·Π°ΠΏΡΠΎΡ Π±ΡΠ΄Π΅Ρ Π·Π°Π²Π΅ΡΡΠ΅Π½ Ρ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ. ΠΠΏΡΠΈΠΌΠ°Π»ΡΠ½ΠΎΠ΅ Π·Π½Π°ΡΠ΅Π½ΠΈΠ΅ β 30 ΡΠ΅ΠΊΡΠ½Π΄.
myBot, err := bot.NewBot(client.Config{
BaseURL: "https://platform-api.max.ru",
Token: "YOUR_BOT_TOKEN", // ΠΠ°ΠΌΠ΅Π½ΠΈΡΠ΅ Π½Π° Π²Π°Ρ ΡΠΎΠΊΠ΅Π½
RateLimiter: 25,
Timeout: 30 * time.Second,
})
ΠΡΠΈΠΌΠ΅Ρ ΠΈΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΠΈ Π±ΠΎΡΠ°#
Π§ΡΠΎΠ±Ρ Π½Π°ΡΠ°ΡΡ ΠΏΠΎΠ»ΡΡΠ°ΡΡ ΠΈ ΠΎΡΠ²Π΅ΡΠ°ΡΡ Π½Π° ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΡ, Π½Π°ΡΡΡΠΎΠΉΡΠ΅ Π±ΠΎΡΠ°, ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΡ Π²Π°Ρ BaseURL ΠΈ Token, Π·Π°ΡΠ΅ΠΌ Π·Π°ΠΏΡΡΡΠΈΡΠ΅ ΠΌΠ΅Ρ Π°Π½ΠΈΠ·ΠΌ ΠΎΠΏΡΠΎΡΠ°.
package main
import (
"context"
"time"
"github.com/green-api/maxbot-api-client-go/pkg/client"
"github.com/green-api/maxbot-chatbot-library/pkg/bot"
)
func main() {
myBot, err := bot.NewBot(client.Config{
BaseURL: "https://platform-api.max.ru",
Token: "YOUR_BOT_TOKEN", // ΠΠ°ΠΌΠ΅Π½ΠΈΡΠ΅ Π½Π° Π²Π°Ρ ΡΠΎΠΊΠ΅Π½
RateLimiter: 25,
Timeout: 30 * time.Second,
})
if err != nil {
panic(err)
}
myBot.StartPolling(context.Background())
}
ΠΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΈΡ ΠΊΠΎΠΌΠ°Π½Π΄ ΠΈ ΡΠΎΠΎΠ±ΡΠ΅Π½ΠΈΠΉ#
ΠΡΡΡΠΎΠ΅Π½Π½ΡΠΉ ΠΌΠ°ΡΡΡΡΡΠΈΠ·Π°ΡΠΎΡ ΡΠΏΡΠΎΡΠ°Π΅Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΡ ΠΊΠΎΠΌΠ°Π½Π΄, ΠΎΠ±ΡΠ°ΡΠ½ΡΡ Π²ΡΠ·ΠΎΠ²ΠΎΠ² ΠΈ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠΉ.
package main
import (
"context"
"github.com/green-api/maxbot-api-client-go/pkg/client"
"github.com/green-api/maxbot-api-client-go/pkg/models"
"github.com/green-api/maxbot-chatbot-library/pkg/bot"
n "github.com/green-api/maxbot-chatbot-library/pkg/notification"
)
func main() {
myBot, err := bot.NewBot(client.Config{
BaseURL: "https://platform-api.max.ru",
Token: "YOUR_BOT_TOKEN", // ΠΠ°ΠΌΠ΅Π½ΠΈΡΠ΅ Π½Π° Π²Π°Ρ ΡΠΎΠΊΠ΅Π½
RateLimiter: 25,
Timeout: 30 * time.Second,
})
myBot.Router.Command("/start", func(n *n.Notification) {
n.Reply("Hello! Welcome to the MAX Bot.")
})
myBot.Router.Register(models.TypeMessageCreated, func(n *n.Notification) {
text, err := n.Text()
if err == nil && text == "ping" {
n.Reply("pong", m.Format(""))
}
})
myBot.Router.Callback("accept_rules_payload", func(n *n.Notification) {
n.Reply("*Thank you for accepting the rules!*", m.Markdown)
})
myBot.StartPolling(context.Background())
}
Π£ΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠΎΡΡΠΎΡΠ½ΠΈΡΠΌΠΈ ΠΈ ΡΡΠ΅Π½Ρ#
ΠΠ»Ρ ΡΠ»ΠΎΠΆΠ½ΡΡ Π±ΠΎΡΠΎΠ² (Π½Π°ΠΏΡΠΈΠΌΠ΅Ρ, Π΄Π»Ρ ΡΠ΅Π³ΠΈΡΡΡΠ°ΡΠΈΠΈ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΉ, Π²ΠΈΠΊΡΠΎΡΠΈΠ½ ΠΈΠ»ΠΈ ΠΏΠΎΡΠ°Π³ΠΎΠ²ΡΡ ΡΠΎΡΠΌ) ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠΉΡΠ΅ Π½Π°Ρ ΠΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ ΡΠΎΡΡΠΎΡΠ½ΠΈΠΉ ΠΈ ΡΡΠ΅Π½Ρ. Π‘ΡΠ΅Π½Π° ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π»ΡΠ΅Ρ ΡΠΎΠ±ΠΎΠΉ ΠΊΠΎΠ½ΠΊΡΠ΅ΡΠ½ΡΡ ΡΠΈΡΡΠ°ΡΠΈΡ Π² Π΄ΠΈΠ°Π»ΠΎΠ³Π΅, ΠΈΠ·ΠΎΠ»ΠΈΡΡΡ Π΅Π³ΠΎ Π»ΠΎΠ³ΠΈΠΊΡ.
package main
import (
"context"
"fmt"
"github.com/green-api/maxbot-api-client-go/pkg/models"
"github.com/green-api/maxbot-chatbot-library/pkg/bot"
n "github.com/green-api/maxbot-chatbot-library/pkg/notification"
"github.com/green-api/maxbot-chatbot-library/pkg/state"
)
func main() {
myBot, err := bot.NewBot(client.Config{
BaseURL: "https://platform-api.max.ru",
Token: "YOUR_BOT_TOKEN", // ΠΠ°ΠΌΠ΅Π½ΠΈΡΠ΅ Π½Π° Π²Π°Ρ ΡΠΎΠΊΠ΅Π½
RateLimiter: 25,
Timeout: 30 * time.Second,
})
myBot.StateManager = state.NewMapStateManager(map[string]any{
"step": "start",
})
myBot.StateManager.SetStartScene(RegistrationScene{})
myBot.Router.Register(models.TypeMessageCreated, func(n *n.Notification) {
currentScene := n.GetCurrentScene()
if currentScene != nil {
currentScene.Start(n)
}
})
myBot.StartPolling(context.Background())
}
type RegistrationScene struct{}
func (s RegistrationScene) Start(n *n.Notification) {
text, _ := n.Text()
if text == "/start" {
n.Reply("Let's register! What is your *login*?", m.Markdown)
return
}
if len(text) >= 4 {
n.StateManager.UpdateStateData(n.StateId, map[string]any{"login": text})
n.Reply(fmt.Sprintf("**Login** `%s` accepted. Now enter your **password**:", text), m.Markdown)
n.ActivateNextScene(PasswordScene{})
} else {
n.Reply("Login must be **at least 4 characters long**.", m.Markdown)
}
}
type PasswordScene struct{}
func (s PasswordScene) Start(n *n.Notification) {
password, _ := n.Text()
stateData := n.StateManager.GetStateData(n.StateId)
login := stateData["login"].(string)
n.Reply(fmt.Sprintf("Success! Profile created.\nLogin: `%s`\nPass: `%s`", login, password), m.Markdown)
n.ActivateNextScene(RegistrationScene{})
}
ΠΡΠ²Π΅Ρ Ρ ΠΌΠ΅Π΄ΠΈΠ°ΡΠ°ΠΉΠ»Π°ΠΌΠΈ#
ΠΠ±ΠΎΠ»ΠΎΡΠΊΠ° Notification ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΠ΅Ρ ΡΠ΄ΠΎΠ±Π½ΡΠ΅ ΠΌΠ΅ΡΠΎΠ΄Ρ Π΄Π»Ρ ΠΎΡΠ²Π΅ΡΠ° ΡΠ΅ΠΊΡΡΠΎΠΌ ΠΈ Π²Π»ΠΎΠΆΠ΅Π½ΠΈΡΠΌΠΈ.
myBot.Router.Command("/photo", func(n *n.Notification) {
n.ReplyWithMedia(
"Check out this image!",
m.Markdown,
"https://example.com/image.png"
)
})
ΠΡΠΈΠΌΠ΅Ρ ΡΡ ΠΎ-Π±ΠΎΡΠ°#
ΠΠ»Ρ ΠΏΡΠΈΠΌΠ΅ΡΠ° ΡΠΎΠ³ΠΎ, ΠΊΠ°ΠΊ ΡΠ°Π±ΠΎΡΠ°ΡΡ Ρ MAX BOT API, ΠΏΡΠ΅Π΄Π»Π°Π³Π°Π΅ΠΌ Π²Π°ΠΌ ΠΊΠΎΠ΄ ΡΡ ΠΎ ΡΠ°Ρ-Π±ΠΎΡΠ°:
func main() {
myBot, err := bot.NewBot(client.Config{
BaseURL: "https://platform-api.max.ru",
Token: "YOUR_BOT_TOKEN", // ΠΠ°ΠΌΠ΅Π½ΠΈΡΠ΅ Π½Π° Π²Π°Ρ ΡΠΎΠΊΠ΅Π½
RateLimiter: 25,
Timeout: 30 * time.Second,
})
if err != nil {
log.Error().Msgf("Bot initialization error: %v", err)
}
myBot.StateManager = state.NewMapStateManager(map[string]any{})
myBot.Router.Register(models.TypeMessageCreated, func(n *c.Notification) {
text, err := n.Text()
if err != nil {
return
}
n.Reply("Echo: " + text, "")
})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go myBot.StartPolling(ctx)
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
<-stop
log.Info().Msg("Bot shutting down...")
}
ΠΠΈΡΠ΅Π½Π·ΠΈΡ#
ΠΠΈΡΠ΅Π½Π·ΠΈΡΠΎΠ²Π°Π½ΠΎ Π² ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΠΈΠΈ Ρ MIT.