Initial commit

This commit is contained in:
zavolo
2026-06-05 00:53:35 +03:00
commit e59e4424a4
7686 changed files with 352420 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
# Docker ignore file - exclude unnecessary files from build context
# Node.js
**/node_modules/
**/npm-debug.log
**/.npm
**/yarn-error.log
**/yarn.lock
**/package-lock.json
# .NET build artifacts
**/bin/
**/obj/
**/.vs/
**/.vscode/
# Git
**/.git/
**/.gitignore
**/.gitattributes
# Docker
**/docker-compose*.yml
**/Dockerfile*
**/.dockerignore
# Documentation
**/*.md
**/docs/
# IDE
**/.idea/
**/.vscode/
**/*.suo
**/*.user
**/*.userosscache
**/*.sln.docstates
# Logs
**/logs/
**/*.log
# Temporary files
**/tmp/
**/temp/
**/.tmp/
# Specific exclusions
stargift-admin/
docker/compose/data/
scripts/
# Keep source code and build files for Docker
!source/
!build/
+372
View File
@@ -0,0 +1,372 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
out/
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
.cr/
.idea/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# --- Project-specific secrets and local artifacts ---
# Приватные ключи и сертификаты
*.pem
*.key
*.pfx
# Файлы сессий Telethon/Pyrogram
*.session
# Резервные копии паролей и временные секреты
*.passwords.*
.passwords.tmp
.env.secure
# Логи Node-сервисов
*.log
# Загружаемые в рантайме медиа админ-панели
stargift-admin/backend/uploads/*
!stargift-admin/backend/uploads/.gitkeep
+92
View File
@@ -0,0 +1,92 @@
# Opengram
Opengram — это самостоятельный сервер Telegram, написанный на C# (.NET 9). Проект является форком [mytelegram](https://github.com/loyldg/mytelegram) и реализует серверную часть API Telegram (MTProto), которую можно развернуть на собственной инфраструктуре.
## Возможности
- MTProto-транспорты (Abridged, Intermediate);
- личные чаты, группы, супергруппы и каналы;
- секретные (end-to-end) чаты;
- голосовые и видеозвонки (через TURN/STUN и SFU mediasoup);
- боты и Bot API;
- настройки приватности и двухфакторная аутентификация;
- стикеры, реакции, кастомные эмодзи;
- звёзды (Stars) и подарки (Star Gifts), включая перепродажу и апгрейд;
- истории (Stories), темы оформления и обои;
- запланированные и самоудаляющиеся сообщения.
## Архитектура
Сервер состоит из набора микросервисов, которые запускаются через Docker Compose:
| Сервис | Назначение |
| --- | --- |
| `gateway-server` | Точка входа для MTProto-подключений клиентов |
| `auth-server` | Авторизация и обмен ключами |
| `session-server` | Хранение сессий и маршрутизация обновлений |
| `messenger-command-server` | Обработка команд (запись, CQRS) |
| `messenger-query-server` | Обработка запросов (чтение, CQRS) |
| `bot-api-server` | HTTP Bot API |
| `admin-api` | Служебный API администрирования |
| `file-server` / `file-merge-proxy` | Хранение и раздача файлов |
| `turn-server` | TURN/STUN для звонков |
| `sms-sender` | Отправка кодов подтверждения |
| `data-seeder` | Первичное наполнение базы данных |
Инфраструктура: MongoDB (хранилище и event store), Redis (кеш), RabbitMQ (шина событий), MinIO (объектное хранилище файлов).
Дополнительные компоненты репозитория:
- `mediasoup-server/` — SFU для групповых видеозвонков (Node.js);
- `stargift-admin/` — веб-панель управления подарками (backend на Node.js, frontend на React);
- `scripts/` — вспомогательные скрипты запуска и тестовые боты.
## Быстрый старт (Docker)
Потребуются Docker и Docker Compose.
1. Перейдите в каталог с compose-файлом:
```bash
cd docker/compose
```
2. Откройте файл `.env` и задайте свои значения вместо плейсхолдеров `CHANGE_ME`
(пароли MongoDB, Redis, RabbitMQ, MinIO, ключ Admin API), а также укажите внешний
IP-адрес сервера в параметрах `App__WebRtcConnections` и `App__DcOptions`.
3. Сгенерируйте RSA-ключи MTProto и положите их в `docker/compose/secrets/mtproto/`
(см. `secrets/mtproto/README.md`).
4. Запустите сервисы:
```bash
docker compose up -d
```
После старта подключите клиент Telegram, прописав адрес вашего дата-центра.
## Сборка из исходников
Для сборки нужен .NET 9 SDK.
```bash
cd source
dotnet build MyTelegram.sln -c Release
```
Скрипты сборки Docker-образов лежат в каталоге `build/`.
## Конфигурация
Все настройки задаются через переменные окружения (файл `.env`) либо через
`appsettings.json` отдельных сервисов. В репозитории все значения паролей и ключей
заменены на плейсхолдеры `CHANGE_ME` — перед запуском замените их на свои.
Не храните реальные пароли и приватные ключи в репозитории.
## Лицензия и происхождение
Проект основан на [mytelegram](https://github.com/loyldg/mytelegram). Все права на
оригинальный код принадлежат его авторам; уважайте условия лицензии исходного проекта
и товарные знаки Telegram.
+45
View File
@@ -0,0 +1,45 @@
$version=Get-Content "./version.txt"
$date = Get-Date -Format "Mdd"
$version = $version+$date
$currentDir=(Get-Item .).FullName
$parentFolder=(Get-Item $currentDir).Parent
$outputRootFolder=Join-Path $parentFolder "out/local" $version
$sourceRootFolder=Join-Path $parentFolder "./source/src"
$authServerOutputFolder=Join-Path $outputRootFolder "auth"
$gatewayOutputFolder=Join-Path $outputRootFolder "gateway"
$messengerProCommandOutputFolder=Join-Path $outputRootFolder "command-server"
$messengerProQueryOutputFolder=Join-Path $outputRootFolder "query-server"
$messengerGrpcOutputFolder=Join-Path $outputRootFolder "messenger-grpc"
$smsOutputFolder=Join-Path $outputRootFolder "sms"
$dataSeederOutputFolder=Join-Path $outputRootFolder "data-seeder"
Write-Output "Current dir:$currentDir"
Write-Output "OutputFolder is:$outputRootFolder"
function CreateFolderIfNotExists([System.String] $folder){
if(![System.IO.Directory]::Exists($folder)){
Write-Output "Create new folder:$folder"
[System.IO.Directory]::CreateDirectory($folder)
}
}
function Build-Server([System.String]$srcFolder,[System.String] $outputFolder) {
$sourceFolder=Join-Path $sourceRootFolder $srcFolder
dotnet publish $sourceFolder -c Release -o $outputFolder
}
# Set-Location ../source/
#dotnet restore ./MyTelegram.sln
Build-Server "./MyTelegram.AuthServer" $authServerOutputFolder
Build-Server "./MyTelegram.GatewayServer" $gatewayOutputFolder
Build-Server "./MyTelegram.Messenger.CommandServer" $messengerProCommandOutputFolder
Build-Server "./MyTelegram.Messenger.QueryServer" $messengerProQueryOutputFolder
#Build-Server "./MyTelegram.MessengerServer.GrpcService" $messengerGrpcOutputFolder
Build-Server "./MyTelegram.SmsSender" $smsOutputFolder
Build-Server "./MyTelegram.DataSeeder" $dataSeederOutputFolder
Set-Location $currentDir
+45
View File
@@ -0,0 +1,45 @@
#!/bin/bash
v=$(head -n 1 ./version.txt)
currentDate=`date +%-m%d`
version=$v.$currentDate
currentDir=$(pwd)
parentFolder=$(dirname "$currentDir")
outputRootFolder="$parentFolder/out/local/$version"
sourceRootFolder="$parentFolder/source/src"
authServerOutputFolder="$outputRootFolder/auth"
gatewayOutputFolder="$outputRootFolder/gateway"
messengerProCommandOutputFolder="$outputRootFolder/command-server"
messengerProQueryOutputFolder="$outputRootFolder/query-server"
messengerGrpcOutputFolder="$outputRootFolder/messenger-grpc"
smsOutputFolder="$outputRootFolder/sms"
dataSeederOutputFolder="$outputRootFolder/data-seeder"
echo "Current dir: $currentDir"
echo "OutputFolder is: $outputRootFolder"
CreateFolderIfNotExists() {
folder="$1"
if [ ! -d "$folder" ]; then
echo "Create new folder: $folder"
mkdir -p "$folder"
fi
}
Build-Server() {
srcFolder="$1"
outputFolder="$2"
sourceFolder="$sourceRootFolder/$srcFolder"
dotnet publish "$sourceFolder" -c Release -o "$outputFolder"
}
Build-Server "./MyTelegram.AuthServer" "$authServerOutputFolder"
Build-Server "./MyTelegram.GatewayServer" "$gatewayOutputFolder"
Build-Server "./MyTelegram.Messenger.CommandServer" "$messengerProCommandOutputFolder"
Build-Server "./MyTelegram.Messenger.QueryServer" "$messengerProQueryOutputFolder"
#Build-Server "./MyTelegram.MessengerServer.GrpcService" "$messengerGrpcOutputFolder"
Build-Server "./MyTelegram.SmsSender" "$smsOutputFolder"
Build-Server "./MyTelegram.DataSeeder" "$dataSeederOutputFolder"
cd "$currentDir"
@@ -0,0 +1,5 @@
#!/bin/sh
export MYTELEGRAM_SERVER_NAME="mytelegram-messenger-command-server"
. ./helper.sh
$BUILD_ALL_COMMAND
@@ -0,0 +1,5 @@
#!/bin/sh
export MYTELEGRAM_SERVER_NAME="mytelegram-messenger-query-server"
. ./helper.sh
$BUILD_ALL_COMMAND
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
export MYTELEGRAM_SERVER_NAME="mytelegram-sms-sender"
. ./helper.sh
$BUILD_ALL_COMMAND
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
export MYTELEGRAM_SERVER_NAME="mytelegram-gateway-server"
. ./helper.sh
$BUILD_ALL_COMMAND
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
export MYTELEGRAM_SERVER_NAME="mytelegram-auth-server"
. ./helper.sh
$BUILD_ALL_COMMAND
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
export MYTELEGRAM_SERVER_NAME="mytelegram-data-seeder"
. ./helper.sh
$BUILD_ALL_COMMAND
@@ -0,0 +1,46 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/runtime:9.0 AS base
USER $APP_UID
WORKDIR /app
# This stage is used to build the service project
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
ARG TARGETARCH
WORKDIR /src
COPY ["Directory.Packages.props", "."]
COPY ["Directory.Build.props", "."]
COPY ["nuget.config", "."]
COPY ["src/MyTelegram.DataSeeder/MyTelegram.DataSeeder.csproj", "src/MyTelegram.DataSeeder/"]
COPY ["src/MyTelegram.Domain/MyTelegram.Domain.csproj", "src/MyTelegram.Domain/"]
COPY ["src/MyTelegram.Domain.Shared/MyTelegram.Domain.Shared.csproj", "src/MyTelegram.Domain.Shared/"]
COPY ["src/MyTelegram.Abstractions/MyTelegram.Abstractions.csproj", "src/MyTelegram.Abstractions/"]
COPY ["src/MyTelegram.Schema/MyTelegram.Schema.csproj", "src/MyTelegram.Schema/"]
COPY ["src/MyTelegram.EventFlow/MyTelegram.EventFlow.csproj", "src/MyTelegram.EventFlow/"]
COPY ["src/MyTelegram.QueryHandlers.MongoDB/MyTelegram.QueryHandlers.MongoDB.csproj", "src/MyTelegram.QueryHandlers.MongoDB/"]
COPY ["src/MyTelegram.Queries/MyTelegram.Queries.csproj", "src/MyTelegram.Queries/"]
COPY ["src/MyTelegram.ReadModel/MyTelegram.ReadModel.csproj", "src/MyTelegram.ReadModel/"]
COPY ["src/MyTelegram.ReadModel.Interfaces/MyTelegram.ReadModel.Interfaces.csproj", "src/MyTelegram.ReadModel.Interfaces/"]
COPY ["src/MyTelegram.ReadModel.MongoDB/MyTelegram.ReadModel.MongoDB.csproj", "src/MyTelegram.ReadModel.MongoDB/"]
COPY ["src/MyTelegram.EventFlow.MongoDB/MyTelegram.EventFlow.MongoDB.csproj", "src/MyTelegram.EventFlow.MongoDB/"]
COPY ["src/MyTelegram.Services/MyTelegram.Services.csproj", "src/MyTelegram.Services/"]
COPY ["src/MyTelegram.Core/MyTelegram.Core.csproj", "src/MyTelegram.Core/"]
COPY ["src/MyTelegram.EventBus/MyTelegram.EventBus.csproj", "src/MyTelegram.EventBus/"]
RUN dotnet restore -a $TARGETARCH "./src/MyTelegram.DataSeeder/MyTelegram.DataSeeder.csproj"
COPY . .
WORKDIR "/src/src/MyTelegram.DataSeeder"
RUN dotnet build "./MyTelegram.DataSeeder.csproj" -a $TARGETARCH -c $BUILD_CONFIGURATION -o /app/build
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./MyTelegram.DataSeeder.csproj" -a $TARGETARCH -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyTelegram.DataSeeder.dll"]
@@ -0,0 +1,40 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/runtime:9.0 AS base
USER $APP_UID
WORKDIR /app
# This stage is used to build the service project
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
ARG TARGETARCH
WORKDIR /src
COPY ["Directory.Packages.props", "."]
COPY ["Directory.Build.props", "."]
COPY ["nuget.config", "."]
COPY ["src/MyTelegram.SmsSender/MyTelegram.SmsSender.csproj", "src/MyTelegram.SmsSender/"]
COPY ["src/MyTelegram.Core/MyTelegram.Core.csproj", "src/MyTelegram.Core/"]
COPY ["src/MyTelegram.Abstractions/MyTelegram.Abstractions.csproj", "src/MyTelegram.Abstractions/"]
COPY ["src/MyTelegram.Domain.Shared/MyTelegram.Domain.Shared.csproj", "src/MyTelegram.Domain.Shared/"]
COPY ["src/MyTelegram.Schema/MyTelegram.Schema.csproj", "src/MyTelegram.Schema/"]
COPY ["src/MyTelegram.EventBus.RabbitMQ/MyTelegram.EventBus.RabbitMQ.csproj", "src/MyTelegram.EventBus.RabbitMQ/"]
COPY ["src/MyTelegram.EventBus/MyTelegram.EventBus.csproj", "src/MyTelegram.EventBus/"]
COPY ["src/MyTelegram.Services/MyTelegram.Services.csproj", "src/MyTelegram.Services/"]
COPY ["src/MyTelegram.EventFlow/MyTelegram.EventFlow.csproj", "src/MyTelegram.EventFlow/"]
RUN dotnet restore -a $TARGETARCH "./src/MyTelegram.SmsSender/MyTelegram.SmsSender.csproj"
COPY . .
WORKDIR "/src/src/MyTelegram.SmsSender"
RUN dotnet build "./MyTelegram.SmsSender.csproj" -a $TARGETARCH -c $BUILD_CONFIGURATION -o /app/build
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
ARG TARGETARCH
RUN dotnet publish "./MyTelegram.SmsSender.csproj" -a $TARGETARCH -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyTelegram.SmsSender.dll"]
+13
View File
@@ -0,0 +1,13 @@
### Run the following scripts to build docker images
### linux/amd64 (build)
```
build-all-amd64.sh
```
### linux/arm64 (build)
```
build-all-arm64.sh
```
### linux/amd64 & linux/arm64 (build and push)
```
build-and-push-all-amd64-arm64.sh
```
+7
View File
@@ -0,0 +1,7 @@
#!/bin/sh
./1.build-messenger-command-server.sh
./2.build-messenger-query-server.sh
./4.build-sms-sender.sh
./5.build-gateway-server.sh
./6.build-auth-server.sh
./7.build-data-seeder.sh
+9
View File
@@ -0,0 +1,9 @@
#!/bin/sh
export BUILDPLATFORM="linux/arm64"
export TARGETARCH="linux/arm64"
./1.build-messenger-command-server.sh
./2.build-messenger-query-server.sh
./4.build-sms-sender.sh
./5.build-gateway-server.sh
./6.build-auth-server.sh
./7.build-data-seeder.sh
@@ -0,0 +1,11 @@
#!/bin/sh
export BUILDPLATFORM="linux/amd64,linux/arm64"
export TARGETARCH="linux/amd64,linux/arm64"
#export REGISTRY_URL="localhost:8500"
export BUILD_COMMAND="docker buildx build --push"
./1.build-messenger-command-server.sh
./2.build-messenger-query-server.sh
./4.build-sms-sender.sh
./5.build-gateway-server.sh
./6.build-auth-server.sh
./7.build-data-seeder.sh
@@ -0,0 +1,11 @@
#!/bin/sh
export BUILDPLATFORM="linux/amd64"
export TARGETARCH="linux/amd64"
export REGISTRY_URL="localhost:8500"
export BUILD_COMMAND="docker buildx build --push"
./1.build-messenger-command-server.sh
./2.build-messenger-query-server.sh
./4.build-sms-sender.sh
./5.build-gateway-server.sh
./6.build-auth-server.sh
./7.build-data-seeder.sh
+31
View File
@@ -0,0 +1,31 @@
#!/bin/sh
v=$(head -n 1 ../version.txt)
currentDate=`date +%-m%d`
export DEFAULT_VERSION=$v.$currentDate
export DEFAULT_IMAGE_VERSION=$DEFAULT_VERSION
DEFAULT_PLATFORM="linux/amd64"
: ${VERSION:=$DEFAULT_VERSION}
: ${IMAGE_VERSION:=$DEFAULT_IMAGE_VERSION}
: ${TARGETARCH:=$DEFAULT_PLATFORM}
: ${BUILDPLATFORM:=$DEFAULT_PLATFORM}
: ${BUILD_CONFIGURATION:="Release"}
: ${REGISTRY_URL:="mytelegram"}
: ${BUILD_COMMAND:="docker build" }
: ${MYTELEGRAM_SERVER_NAME:="mytelegram-messenger-command-server"}
: ${IMAGE_NAME:=$REGISTRY_URL/$MYTELEGRAM_SERVER_NAME}
: ${DOCKER_FILENAME:="./Dockerfile-$MYTELEGRAM_SERVER_NAME"}
echo Version: $VERSION
echo Docker image version: $IMAGE_VERSION
echo Target architecture: $TARGETARCH
echo Buld configuration: $BUILD_CONFIGURATION
: ${BUILD_ALL_COMMAND:=$BUILD_COMMAND \
--platform $BUILDPLATFORM \
--build-arg BUILD_CONFIGURATION=$BUILD_CONFIGURATION \
--tag $IMAGE_NAME:$IMAGE_VERSION \
--tag $IMAGE_NAME:latest \
--file $DOCKER_FILENAME \
../../source \
--network=host}
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
export REGISTRY_URL="mytelegram"
./1.build-messenger-command-server.sh
./2.build-messenger-query-server.sh
./4.build-sms-sender.sh
./5.build-gateway-server.sh
./6.build-auth-server.sh
+1
View File
@@ -0,0 +1 @@
0.32.206
+247
View File
@@ -0,0 +1,247 @@
# MyTelegram servers version
MyTelegramVersion=0.32.206.802
MyTelegramRegistry=mytelegram
# Build configuration for compiling from source
BUILD_CONFIGURATION=Release
# RabbitMQ
RabbitMQ__Connections__Default__HostName=rabbitmq
RabbitMQ__Connections__Default__Port=5672
RabbitMQ__Connections__Default__UserName=test
RabbitMQ__Connections__Default__Password=test
RabbitMQ__EventBus__ExchangeName=mytelegram_exchange
# Redis
Redis__Configuration=redis:6379,password=CHANGE_ME
# MongoDB
ConnectionStrings__Default=mongodb://mongodb:27017
# Minio
Minio__Endpoint=minio:9000
Minio__AccessKey=minioadmin
Minio__SecretKey=CHANGE_ME
Minio__BucketName=tg-files
# Minio__CreateBucketIfNotExists=True
# TwilioSms
# TwilioSms__Enabled=False
# TwilioSms__AccountSId=
# TwilioSms__AuthToken=
# TwilioSms__FromNumber=
# TwilioSms__MessagingServiceSId=
# VonageSms
# VonageSms__Enabled=False
# VonageSms__BrandName=
# VonageSms__ApiKey=
# VonageSms__ApiSecret=
# EmailSenderOptions
# EmailSenderOptions__FromAddress=
# EmailSenderOptions__FromDisplayName=MyTelegram
# EmailSenderOptions__SmtpEmailOptions__Host=
# EmailSenderOptions__SmtpEmailOptions__Port=
# EmailSenderOptions__SmtpEmailOptions__UserName=
# EmailSenderOptions__SmtpEmailOptions__Password=
# EmailSenderOptions__SmtpEmailOptions__Domain=localhost
# EmailSenderOptions__SmtpEmailOptions__EnableSsl=False
# EmailSenderOptions__MailKitOptions__SecureSocketOptions=0
# App
# App__PrivateKeyFilePath=private.pkcs8.key
# App__IdGeneratorGrpcServiceUrl=http://localhost:10002
App__DatabaseName=tg
App__ReadModelDatabaseName=tg
App__CreateTestUsers=False
App__AccessHashSecretKey=CHANGE_ME
# App__MyTelegramBotOptions__BotApiBaseUrl=http://bot-server:8080
# App__MyTelegramBotOptions__Token=
# App__MyTelegramBotOptions__WebHookUrl=http://localhost:10004/bot/ProcessBotCommand
# App__SubscribeLocalRequest=True
# App__SubscribeRemoteRequest=True
# App__UseExternalWebHookSender=False
# App__WebHookSenderUrl=https://xxx/api/bot/sendWebHook
# App=
App__ThisDcId=1
App__MediaDcId=2
# App__UploadRootPath=uploads
# App__MediaOnly=False
App__Servers__0__Enabled=True
# App__Servers__0__ServerType=0
# App__Servers__0__Ip=
# App__Servers__0__Ipv6=True
App__Servers__0__Port=20443
# App__Servers__0__Ssl=False
# App__Servers__0__CertPemFilePath=
# App__Servers__0__KeyPemFilePath=
# App__Servers__0__EnableProxyProtocolV2=False
# App__Servers__0__MediaOnly=False
App__Servers__1__Enabled=True
# App__Servers__1__ServerType=0
# App__Servers__1__Ip=
# App__Servers__1__Ipv6=True
App__Servers__1__Port=20543
# App__Servers__1__Ssl=False
# App__Servers__1__CertPemFilePath=
# App__Servers__1__KeyPemFilePath=
# App__Servers__1__EnableProxyProtocolV2=False
# App__Servers__1__MediaOnly=False
# App__Servers__2__Enabled=True
# App__Servers__2__ServerType=0
# App__Servers__2__Ip=
# App__Servers__2__Ipv6=True
App__Servers__2__Port=20643
# App__Servers__2__Ssl=False
# App__Servers__2__CertPemFilePath=
# App__Servers__2__KeyPemFilePath=
# App__Servers__2__EnableProxyProtocolV2=False
# App__Servers__2__MediaOnly=True
App__Servers__3__Enabled=True
# App__Servers__3__ServerType=0
# App__Servers__3__Ip=
# App__Servers__3__Ipv6=True
App__Servers__3__Port=20644
# App__Servers__3__Ssl=False
# App__Servers__3__CertPemFilePath=
# App__Servers__3__KeyPemFilePath=
# App__Servers__3__EnableProxyProtocolV2=False
# App__Servers__3__MediaOnly=True
App__Servers__4__Enabled=True
# App__Servers__4__ServerType=1
# App__Servers__4__Ip=
# App__Servers__4__Ipv6=True
App__Servers__4__Port=30443
# App__Servers__4__Ssl=True
# App__Servers__4__CertPemFilePath=_wildcard.telegram2.com.pem
# App__Servers__4__KeyPemFilePath=_wildcard.telegram2.com-key.pem
# App__Servers__4__EnableProxyProtocolV2=False
# App__Servers__4__MediaOnly=False
App__Servers__5__Enabled=True
# App__Servers__5__ServerType=1
# App__Servers__5__Ip=
# App__Servers__5__Ipv6=True
App__Servers__5__Port=30444
# App__Servers__5__Ssl=False
# App__Servers__5__CertPemFilePath=
# App__Servers__5__KeyPemFilePath=
# App__Servers__5__EnableProxyProtocolV2=False
# App__Servers__5__MediaOnly=False
App__QueryServerEventStoreDatabaseName=tg
App__QueryServerReadModelDatabaseName=tg
App__BotDatabaseName=tg
App__FileServerGrpcServiceUrl=http://file-server:8080
App__FixedVerifyCode=22222
# App__FixedEmailVerificationCode=
App__VerificationCodeLength=5
App__VerificationCodeExpirationSeconds=300
# App__JoinChatDomain=https://t.me
# App__ChannelGetDifferenceIntervalSeconds=60
# App__UseInMemoryFilters=True
# App__IsMediaDc=False
# App__AutoCreateSuperGroup=True
# App__EnableFutureAuthToken=True
# App__SetPremiumToTrueAfterUserCreated=True
# App__SendWelcomeMessageAfterUserSignIn=False
# App__SetupPasswordRequired=False
App__EnableEmailLogin=False
# App__SupportUserId=
App__CheckPhoneNumberFormat=True
App__EnableSearchNonContacts=True
# WebRTC / TURN / STUN configuration for voice and video calls
# TURN/STUN enabled for voice and video calls
App__WebRtcConnections__0__Ip=127.0.0.1
App__WebRtcConnections__0__Ipv6=::
App__WebRtcConnections__0__Port=3478
App__WebRtcConnections__0__Turn=true
App__WebRtcConnections__0__Stun=true
App__WebRtcConnections__0__UserName=mytelegram
App__WebRtcConnections__0__Password=CHANGE_ME
TURN_EXTERNAL_IP=127.0.0.1
App__DcOptions__0__Id=1
App__DcOptions__0__IpAddress=127.0.0.1
App__DcOptions__0__Port=20443
# App__DcOptions__0__TcpoOnly=True
# App__DcOptions__0__ThisPortOnly=True
# App__DcOptions__0__Ipv6=False
# App__DcOptions__0__Cdn=False
# App__DcOptions__0__MediaOnly=False
# App__DcOptions__0__Static=False
App__DcOptions__1__Id=2
App__DcOptions__1__IpAddress=127.0.0.1
App__DcOptions__1__Port=20543
# App__DcOptions__1__TcpoOnly=True
# App__DcOptions__1__ThisPortOnly=True
# App__DcOptions__1__Ipv6=False
# App__DcOptions__1__Cdn=False
# App__DcOptions__1__MediaOnly=False
# App__DcOptions__1__Static=False
App__DcOptions__2__Id=2
App__DcOptions__2__IpAddress=127.0.0.1
App__DcOptions__2__Port=20643
# App__DcOptions__2__TcpoOnly=True
# App__DcOptions__2__ThisPortOnly=True
# App__DcOptions__2__Ipv6=False
# App__DcOptions__2__Cdn=False
# App__DcOptions__2__MediaOnly=True
# App__DcOptions__2__Static=False
App__DcOptions__0__Enabled=True
App__DcOptions__1__Enabled=True
App__DcOptions__2__Enabled=True
App__DcOptions__3__Enabled=True
App__DcOptions__3__Id=2
App__DcOptions__3__IpAddress=127.0.0.1
App__DcOptions__3__Port=20644
# App__DcOptions__3__TcpoOnly=False
# App__DcOptions__3__ThisPortOnly=True
# App__DcOptions__3__Ipv6=False
# App__DcOptions__3__Cdn=False
# App__DcOptions__3__MediaOnly=True
# App__DcOptions__3__Static=False
# App__ServiceAccountKeyJsonFileName=serviceAccountKey.json
# App__MessengerServerGrpcServiceUrl=http://messenger-grpc-service:8080
# App__DispatchUnknownObjectToMessengerServer=True
# App__TempAuthKeyExpirationMinutes=720
# App__CommandServerObjectIds=
# App__ChannelMemberMaxCountAllowedForPush=100
# App__EnablePushNotification=True
# App__ForceDownload=False
# App__AllowedUserIds__0=2000001
# App__AllowedUserIds__1=2000004
# App__OfficialTelegramBotOptions__Token=
# App__OfficialTelegramBotOptions__ProxyUrl=socks5://localhost:10808
# App__OfficialTelegramBotOptions__UseProxy=False
# App__LoadAllStickersIntoMemory=True
# Serilog
# Serilog__Using__0=Serilog.Sinks.Console
# Serilog__Using__1=Serilog.Sinks.File
# Serilog__Using__2=Serilog.Sinks.Async
Serilog__MinimumLevel__Default=Information
Serilog__MinimumLevel__Override__Microsoft=Warning
# Serilog__WriteTo__0__Name=Async
# Serilog__WriteTo__0__Args__configure__0__Name=Console
# Serilog__WriteTo__0__Args__configure__0__Args__MinimumLevel=Information
# Serilog__WriteTo__0__Args__configure__0__Args__outputTemplate=[{Timestamp:yyyy-MM-dd HH:mm:ss:ffffff} [{Level:u3}] {Message}{NewLine}{Exception}
# Serilog__WriteTo__0__Args__configure__0__Args__theme=Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console
# Serilog__WriteTo__1__Name=Async
# Serilog__WriteTo__1__Args__configure__0__Name=File
# Serilog__WriteTo__1__Args__configure__0__Args__rollingInterval=Day
# Serilog__WriteTo__1__Args__configure__0__Args__path=./Logs/log-.txt
# Serilog__WriteTo__1__Args__configure__0__Args__outputTemplate={Timestamp:o} [{Level:u3}] {Message}{NewLine}{Exception}
# Serilog__MinimumLevel__Override__Grpc=Debug
# Serilog__WriteTo__1__Args__configure__0__Args__MinimumLevel=Debug
# Serilog__MinimumLevel__Override__System.Net.Http.HttpClient.Default=Warning
# Serilog__MinimumLevel__Override__EventFlow=Information
# Fragment
FRAGMENT_BOT_TOKEN=
# Database Passwords
MONGODB_PASSWORD=CHANGE_ME
RABBITMQ_PASSWORD=CHANGE_ME
REDIS_PASSWORD=CHANGE_ME
MINIO_PASSWORD=CHANGE_ME
# Admin API Key
ADMIN_API_KEY=CHANGE_ME
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env powershell
# Полный перезапуск MongoDB с включённой аутентификацией
# Скрипт корректно сбрасывает состояние MongoDB и поднимает его заново
Write-Host "======================================" -ForegroundColor Green
Write-Host "MongoDB Authentication Fix - Restart" -ForegroundColor Green
Write-Host "======================================" -ForegroundColor Green
Write-Host ""
$compose_dir = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location $compose_dir
Write-Host "[1/5] Stopping all services..." -ForegroundColor Yellow
docker-compose down
Write-Host "[2/5] Removing MongoDB data volume to reinitialize..." -ForegroundColor Yellow
if (Test-Path "./data/mongo") {
Remove-Item -Recurse -Force "./data/mongo"
Write-Host " MongoDB data directory removed" -ForegroundColor Green
} else {
Write-Host " MongoDB data directory not found (this is fine)" -ForegroundColor Cyan
}
Write-Host "[3/5] Waiting a moment before starting services..." -ForegroundColor Yellow
Start-Sleep -Seconds 2
Write-Host "[4/5] Building and starting services with MongoDB authentication..." -ForegroundColor Yellow
docker-compose up --build -d
Write-Host "[5/5] Waiting for MongoDB to initialize..." -ForegroundColor Yellow
Start-Sleep -Seconds 10
Write-Host ""
Write-Host "======================================" -ForegroundColor Green
Write-Host "Restart Complete!" -ForegroundColor Green
Write-Host "======================================" -ForegroundColor Green
Write-Host ""
Write-Host "Next steps:" -ForegroundColor Cyan
Write-Host "1. Check MongoDB initialization: docker-compose logs mongodb"
Write-Host "2. Check data-seeder: docker-compose logs data-seeder"
Write-Host "3. Check admin-api: docker-compose logs admin-api"
Write-Host "4. Monitor bot-api: docker-compose logs bot-api-server"
Write-Host ""
Write-Host "MongoDB Authentication Details:" -ForegroundColor Cyan
Write-Host " Username: admin" -ForegroundColor Gray
Write-Host " Auth Database: admin" -ForegroundColor Gray
Write-Host " Target Database: tg" -ForegroundColor Gray
Write-Host ""
+39
View File
@@ -0,0 +1,39 @@
# Simple script to create chat themes in MongoDB
Write-Host ""
Write-Host "Creating chat themes in MongoDB..." -ForegroundColor Cyan
Write-Host ""
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
# Copy seed script to MongoDB container
Write-Host "Copying seed script to container..." -ForegroundColor Gray
docker cp "$scriptDir\seed-chat-themes-unicode.js" compose-mongodb-1:/tmp/seed-themes.js
if ($LASTEXITCODE -ne 0) {
Write-Host "Failed to copy seed script!" -ForegroundColor Red
exit 1
}
# Execute seed script
Write-Host "Creating themes..." -ForegroundColor Gray
docker exec compose-mongodb-1 mongosh tg /tmp/seed-themes.js
if ($LASTEXITCODE -ne 0) {
Write-Host "Failed to create themes!" -ForegroundColor Red
exit 1
}
Write-Host ""
Write-Host "Done! 8 chat themes created successfully!" -ForegroundColor Green
Write-Host ""
Write-Host "Themes:" -ForegroundColor Cyan
Write-Host " - Home (Blue)" -ForegroundColor White
Write-Host " - Love (Pink)" -ForegroundColor White
Write-Host " - Party (Orange)" -ForegroundColor White
Write-Host " - Ocean (Cyan)" -ForegroundColor White
Write-Host " - Flower (Rose)" -ForegroundColor White
Write-Host " - Night (Purple)" -ForegroundColor White
Write-Host " - Fire (Red)" -ForegroundColor White
Write-Host " - Green (Emerald)" -ForegroundColor White
Write-Host ""
@@ -0,0 +1,11 @@
// Script to delete old StarsSnapshot from MongoDB
// Run this in MongoDB shell or via mongosh
use MyTelegram;
// Delete all StarsSnapshots with old version
db['eventflow.snapshots'].deleteMany({
'Metadata.AggregateId': { $regex: '^stars-' }
});
print('Old StarsSnapshots deleted');
+653
View File
@@ -0,0 +1,653 @@
version: "3.9"
networks:
tg-net:
driver: bridge
services:
### Redis
redis:
image: redis:alpine
restart: always
networks:
- tg-net
#ports:
#- "6379:6379"
volumes:
- ./data/redis:/data
### RabbitMq
rabbitmq:
image: rabbitmq:3-management-alpine
restart: always
networks:
- tg-net
#ports:
# - "5672:5672"
# - "15672:15672"
volumes:
- ./data/rabbitmq:/var/lib/rabbitmq
environment:
# RABBITMQ_DEFAULT_USER: ${RabbitMQ__Connections__Default__UserName}
# RABBITMQ_DEFAULT_PASS: ${RabbitMQ__Connections__Default__Password}
RABBITMQ_DEFAULT_USER: test
RABBITMQ_DEFAULT_PASS: test
### MongoDb
mongodb:
image: mongo:latest
restart: always
networks:
- tg-net
ports:
- "27017:27017"
volumes:
- ./data/mongo/:/data
#- ./data/mongo/db:/data/db
#- ./data/mongo/configdb:/data/configdb
#environment:
# MONGO_INITDB_ROOT_USERNAME: admin
# MONGO_INITDB_ROOT_PASSWORD: admin
### Minio
minio:
image: minio/minio:latest
restart: always
networks:
- tg-net
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: ${Minio__AccessKey}
MINIO_ROOT_PASSWORD: ${Minio__SecretKey}
volumes:
- ./data/minio:/data
#- /docker/minio/config:/root/./minio/
command: server /data --console-address ":9001"
#privileged: true
################### MyTelegram server
### Data seeder
data-seeder:
build:
context: ../../source
dockerfile: src/MyTelegram.DataSeeder/Dockerfile
restart: always
networks:
- tg-net
environment:
Serilog__MinimumLevel__Default: ${Serilog__MinimumLevel__Default}
ConnectionStrings__Default: ${ConnectionStrings__Default}
App__DatabaseName: tg
App__ReadModelDatabaseName: tg
App__CreateTestUsers: ${App__CreateTestUsers}
# App__LoadAllStickersIntoMemory: ${App__LoadAllStickersIntoMemory}
RabbitMQ__Connections__Default__HostName: ${RabbitMQ__Connections__Default__HostName}
RabbitMQ__Connections__Default__Port: ${RabbitMQ__Connections__Default__Port}
RabbitMQ__Connections__Default__UserName: ${RabbitMQ__Connections__Default__UserName}
RabbitMQ__Connections__Default__Password: ${RabbitMQ__Connections__Default__Password}
# RabbitMQ__EventBus__ClientName: ${RabbitMQ__EventBus__ClientName}
# RabbitMQ__EventBus__ExchangeName: ${RabbitMQ__EventBus__ExchangeName}
volumes:
- ./data/mytelegram/data-seeder/logs:/app/Logs
depends_on:
- mongodb
### Admin API
admin-api:
build:
context: ../../source
dockerfile: src/MyTelegram.Admin.Api/Dockerfile
restart: always
networks:
- tg-net
ports:
- "5555:5000"
environment:
ASPNETCORE_URLS: http://+:5000
Serilog__MinimumLevel__Default: ${Serilog__MinimumLevel__Default}
ConnectionStrings__Default: ${ConnectionStrings__Default}
App__DatabaseName: tg
App__ReadModelDatabaseName: tg
RabbitMQ__Connections__Default__HostName: ${RabbitMQ__Connections__Default__HostName}
RabbitMQ__Connections__Default__Port: ${RabbitMQ__Connections__Default__Port}
RabbitMQ__Connections__Default__UserName: ${RabbitMQ__Connections__Default__UserName}
RabbitMQ__Connections__Default__Password: ${RabbitMQ__Connections__Default__Password}
ADMIN_API_KEY: ${ADMIN_API_KEY}
volumes:
- ./data/mytelegram/admin-api/logs:/app/Logs
depends_on:
- mongodb
- rabbitmq
### 1.Gateway server
gateway-server:
build:
context: ../../source
dockerfile: src/MyTelegram.GatewayServer/Dockerfile
args:
BUILD_CONFIGURATION: ${BUILD_CONFIGURATION}
restart: always
networks:
- tg-net
ports:
- "${App__Servers__0__Port}:${App__Servers__0__Port}"
- "${App__Servers__1__Port}:${App__Servers__1__Port}"
- "${App__Servers__2__Port}:${App__Servers__2__Port}"
- "${App__Servers__3__Port}:${App__Servers__3__Port}"
- "${App__Servers__4__Port}:${App__Servers__4__Port}"
- "${App__Servers__5__Port}:${App__Servers__5__Port}"
volumes:
- ./data/mytelegram/gateway/logs:/app/Logs
- ./secrets/mtproto:/app/secrets/mtproto:ro
environment:
Serilog__MinimumLevel__Default: ${Serilog__MinimumLevel__Default}
App__ThisDcId: ${App__ThisDcId}
App__MediaDcId: ${App__MediaDcId}
# App__MediaOnly: ${App__MediaOnly}
# App__Servers__0__Enabled: ${App__Servers__0__Enabled}
# App__Servers__0__ServerType: ${App__Servers__0__ServerType}
# App__Servers__0__Ip: ${App__Servers__0__Ip}
# App__Servers__0__Ipv6: ${App__Servers__0__Ipv6}
App__Servers__0__Port: ${App__Servers__0__Port}
# App__Servers__0__Ssl: ${App__Servers__0__Ssl}
# App__Servers__0__CertPemFilePath: ${App__Servers__0__CertPemFilePath}
# App__Servers__0__KeyPemFilePath: ${App__Servers__0__KeyPemFilePath}
# App__Servers__0__EnableProxyProtocolV2: ${App__Servers__0__EnableProxyProtocolV2}
# App__Servers__0__MediaOnly: ${App__Servers__0__MediaOnly}
App__Servers__1__Enabled: ${App__Servers__1__Enabled}
# App__Servers__1__ServerType: ${App__Servers__1__ServerType}
# App__Servers__1__Ip: ${App__Servers__1__Ip}
# App__Servers__1__Ipv6: ${App__Servers__1__Ipv6}
App__Servers__1__Port: ${App__Servers__1__Port}
# App__Servers__1__Ssl: ${App__Servers__1__Ssl}
# App__Servers__1__CertPemFilePath: ${App__Servers__1__CertPemFilePath}
# App__Servers__1__KeyPemFilePath: ${App__Servers__1__KeyPemFilePath}
# App__Servers__1__EnableProxyProtocolV2: ${App__Servers__1__EnableProxyProtocolV2}
# App__Servers__1__MediaOnly: ${App__Servers__1__MediaOnly}
App__Servers__2__Enabled: ${App__Servers__2__Enabled}
# App__Servers__2__ServerType: ${App__Servers__2__ServerType}
# App__Servers__2__Ip: ${App__Servers__2__Ip}
# App__Servers__2__Ipv6: ${App__Servers__2__Ipv6}
App__Servers__2__Port: ${App__Servers__2__Port}
# App__Servers__2__Ssl: ${App__Servers__2__Ssl}
# App__Servers__2__CertPemFilePath: ${App__Servers__2__CertPemFilePath}
# App__Servers__2__KeyPemFilePath: ${App__Servers__2__KeyPemFilePath}
# App__Servers__2__EnableProxyProtocolV2: ${App__Servers__2__EnableProxyProtocolV2}
# App__Servers__2__MediaOnly: ${App__Servers__2__MediaOnly}
App__Servers__3__Enabled: ${App__Servers__3__Enabled}
# App__Servers__3__ServerType: ${App__Servers__3__ServerType}
# App__Servers__3__Ip: ${App__Servers__3__Ip}
# App__Servers__3__Ipv6: ${App__Servers__3__Ipv6}
App__Servers__3__Port: ${App__Servers__3__Port}
# App__Servers__3__Ssl: ${App__Servers__3__Ssl}
# App__Servers__3__CertPemFilePath: ${App__Servers__3__CertPemFilePath}
# App__Servers__3__KeyPemFilePath: ${App__Servers__3__KeyPemFilePath}
# App__Servers__3__EnableProxyProtocolV2: ${App__Servers__3__EnableProxyProtocolV2}
# App__Servers__3__MediaOnly: ${App__Servers__3__MediaOnly}
# App__Servers__4__Enabled: ${App__Servers__4__Enabled}
# App__Servers__4__ServerType: ${App__Servers__4__ServerType}
# App__Servers__4__Ip: ${App__Servers__4__Ip}
# App__Servers__4__Ipv6: ${App__Servers__4__Ipv6}
App__Servers__4__Port: ${App__Servers__4__Port}
# App__Servers__4__Ssl: ${App__Servers__4__Ssl}
# App__Servers__4__CertPemFilePath: ${App__Servers__4__CertPemFilePath}
# App__Servers__4__KeyPemFilePath: ${App__Servers__4__KeyPemFilePath}
# App__Servers__4__EnableProxyProtocolV2: ${App__Servers__4__EnableProxyProtocolV2}
# App__Servers__4__MediaOnly: ${App__Servers__4__MediaOnly}
# App__Servers__5__Enabled: ${App__Servers__5__Enabled}
# App__Servers__5__ServerType: ${App__Servers__5__ServerType}
# App__Servers__5__Ip: ${App__Servers__5__Ip}
# App__Servers__5__Ipv6: ${App__Servers__5__Ipv6}
App__Servers__5__Port: ${App__Servers__5__Port}
# App__Servers__5__Ssl: ${App__Servers__5__Ssl}
# App__Servers__5__CertPemFilePath: ${App__Servers__5__CertPemFilePath}
# App__Servers__5__KeyPemFilePath: ${App__Servers__5__KeyPemFilePath}
# App__Servers__5__EnableProxyProtocolV2: ${App__Servers__5__EnableProxyProtocolV2}
# App__Servers__5__MediaOnly: ${App__Servers__5__MediaOnly}
RabbitMQ__Connections__Default__HostName: ${RabbitMQ__Connections__Default__HostName}
RabbitMQ__Connections__Default__Port: ${RabbitMQ__Connections__Default__Port}
RabbitMQ__Connections__Default__UserName: ${RabbitMQ__Connections__Default__UserName}
RabbitMQ__Connections__Default__Password: ${RabbitMQ__Connections__Default__Password}
# RabbitMQ__EventBus__ClientName: ${RabbitMQ__EventBus__ClientName}
RabbitMQ__EventBus__ExchangeName: ${RabbitMQ__EventBus__ExchangeName}
depends_on:
- rabbitmq
- mongodb
- redis
# ### 2.Seq server
# seq-server:
# image: mytelegram/mytelegram-seq-server:${MyTelegramVersion}
# restart: always
# networks:
# - tg-net
# #ports:
# # - "10001:80"
# environment:
# ConnectionStrings__Default: ${DefaultConnectionStrings}
# Serilog__MinimumLevel__Default: ${DefaultLogLevel}
# volumes:
# - ./data/mytelegram/seq/logs:/data/Logs
# depends_on:
# - mongodb
### 2.Auth server
auth-server:
build:
context: ../../source
dockerfile: src/MyTelegram.AuthServer/Dockerfile
args:
BUILD_CONFIGURATION: ${BUILD_CONFIGURATION}
restart: always
networks:
- tg-net
environment:
Serilog__MinimumLevel__Default: ${Serilog__MinimumLevel__Default}
App__PrivateKeyFilePath: /app/secrets/mtproto/rsa_private.pem
RabbitMQ__Connections__Default__HostName: ${RabbitMQ__Connections__Default__HostName}
RabbitMQ__Connections__Default__Port: ${RabbitMQ__Connections__Default__Port}
RabbitMQ__Connections__Default__UserName: ${RabbitMQ__Connections__Default__UserName}
RabbitMQ__Connections__Default__Password: ${RabbitMQ__Connections__Default__Password}
# RabbitMQ__EventBus__ClientName: ${RabbitMQ__EventBus__ClientName}
RabbitMQ__EventBus__ExchangeName: ${RabbitMQ__EventBus__ExchangeName}
Redis__Configuration: ${Redis__Configuration}
volumes:
- ./data/mytelegram/auth/logs:/app/Logs
- ./secrets/mtproto:/app/secrets/mtproto:ro
depends_on:
- mongodb
- rabbitmq
- redis
### 3.Session server
session-server:
image: ${MyTelegramRegistry}/mytelegram-session-server:${MyTelegramVersion}
restart: always
networks:
- tg-net
environment:
Serilog__MinimumLevel__Default: ${Serilog__MinimumLevel__Default}
ConnectionStrings__Default: ${ConnectionStrings__Default}
# App__ThisDcId: ${App__ThisDcId}
# App__MediaOnly: ${App__MediaOnly}
App__DatabaseName: tg
App__ReadModelDatabaseName: tg
App__AccessHashSecretKey: ${App__AccessHashSecretKey}
# App__IdGeneratorGrpcServiceUrl: ${App__IdGeneratorGrpcServiceUrl}
App__MessengerServerGrpcServiceUrl: ${App__MessengerServerGrpcServiceUrl}
# App__DispatchUnknownObjectToMessengerServer: ${App__DispatchUnknownObjectToMessengerServer}
# App__TempAuthKeyExpirationMinutes: ${App__TempAuthKeyExpirationMinutes}
# App__CommandServerObjectIds: ${App__CommandServerObjectIds}
RabbitMQ__Connections__Default__HostName: ${RabbitMQ__Connections__Default__HostName}
RabbitMQ__Connections__Default__Port: ${RabbitMQ__Connections__Default__Port}
RabbitMQ__Connections__Default__UserName: ${RabbitMQ__Connections__Default__UserName}
RabbitMQ__Connections__Default__Password: ${RabbitMQ__Connections__Default__Password}
# RabbitMQ__EventBus__ClientName: ${RabbitMQ__EventBus__ClientName}
# RabbitMQ__EventBus__ExchangeName: ${RabbitMQ__EventBus__ExchangeName}
Redis__Configuration: ${Redis__Configuration}
volumes:
- ./data/mytelegram/session/logs:/app/Logs
depends_on:
- mongodb
- rabbitmq
- redis
# ### 5.Messenger grpc server
# messenger-grpc-service:
# image: mytelegram/mytelegram-messenger-grpc-service:${MyTelegramVersion}
# restart: always
# networks:
# - tg-net
# environment:
# Serilog__MinimumLevel__Default: ${Serilog__MinimumLevel__Default}
# ConnectionStrings__Default: ${ConnectionStrings__Default}
# App__DatabaseName: tg
# App__ReadModelDatabaseName: tg
# volumes:
# - ./data/mytelegram/messenger-grpc-service/logs:/app/Logs
# depends_on:
# - mongodb
### 4.File server (original - for Telegram)
file-server:
image: ${MyTelegramRegistry}/mytelegram-file-server:${MyTelegramVersion}
restart: always
user: "0:0" # Run as root to avoid permission issues
networks:
- tg-net
volumes:
- ./data/mytelegram/file/uploads:/app/uploads
- ./data/mytelegram/file:/app/Logs
environment:
Serilog__MinimumLevel__Default: ${Serilog__MinimumLevel__Default}
ConnectionStrings__Default: ${ConnectionStrings__Default}
App__DatabaseName: tg
App__ReadModelDatabaseName: tg
App__AccessHashSecretKey: ${App__AccessHashSecretKey}
Minio__Endpoint: ${Minio__Endpoint}
Minio__AccessKey: ${Minio__AccessKey}
Minio__SecretKey: ${Minio__SecretKey}
RabbitMQ__Connections__Default__HostName: ${RabbitMQ__Connections__Default__HostName}
RabbitMQ__Connections__Default__Port: ${RabbitMQ__Connections__Default__Port}
RabbitMQ__Connections__Default__UserName: ${RabbitMQ__Connections__Default__UserName}
RabbitMQ__Connections__Default__Password: ${RabbitMQ__Connections__Default__Password}
Redis__Configuration: ${Redis__Configuration}
depends_on:
- mongodb
- rabbitmq
### 4.5 File merge proxy (HTTP API for merging file parts from local filesystem)
file-merge-proxy:
build:
context: ../../file-merge-proxy
dockerfile: Dockerfile
restart: always
networks:
- tg-net
volumes:
- ./data/mytelegram/file/uploads:/app/uploads
environment:
PORT: 5000
UPLOAD_PATH: /app/uploads
ports:
- "5000:5000"
### 5.Messenger command server
messenger-command-server:
build:
context: ../../source
dockerfile: src/MyTelegram.Messenger.CommandServer/Dockerfile
restart: always
networks:
- tg-net
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
Serilog__MinimumLevel__Default: ${Serilog__MinimumLevel__Default}
ConnectionStrings__Default: ${ConnectionStrings__Default}
App__DatabaseName: tg
App__ReadModelDatabaseName: tg
App__AccessHashSecretKey: ${App__AccessHashSecretKey}
App__FileServerGrpcServiceUrl: ${App__FileServerGrpcServiceUrl}
App__FixedVerifyCode: ${App__FixedVerifyCode}
App__VerificationCodeLength: ${App__VerificationCodeLength}
App__VerificationCodeExpirationSeconds: ${App__VerificationCodeExpirationSeconds}
App__CheckPhoneNumberFormat: ${App__CheckPhoneNumberFormat}
App__EnableSearchNonContacts: ${App__EnableSearchNonContacts}
# App__JoinChatDomain: ${App__JoinChatDomain}
# App__ChannelGetDifferenceIntervalSeconds: ${App__ChannelGetDifferenceIntervalSeconds}
# App__UseInMemoryFilters: ${App__UseInMemoryFilters}
# App__IsMediaDc: ${App__IsMediaDc}
# App__AutoCreateSuperGroup: ${App__AutoCreateSuperGroup}
# App__EnableFutureAuthToken: ${App__EnableFutureAuthToken}
# App__SetPremiumToTrueAfterUserCreated: ${App__SetPremiumToTrueAfterUserCreated}
# App__SendWelcomeMessageAfterUserSignIn: ${App__SendWelcomeMessageAfterUserSignIn}
App__WebRtcConnections__0__Ip: ${App__WebRtcConnections__0__Ip}
App__WebRtcConnections__0__Ipv6: ${App__WebRtcConnections__0__Ipv6}
App__WebRtcConnections__0__Port: ${App__WebRtcConnections__0__Port}
App__WebRtcConnections__0__Turn: ${App__WebRtcConnections__0__Turn}
App__WebRtcConnections__0__Stun: ${App__WebRtcConnections__0__Stun}
App__WebRtcConnections__0__UserName: ${App__WebRtcConnections__0__UserName}
App__WebRtcConnections__0__Password: ${App__WebRtcConnections__0__Password}
# App__ThisDcId: ${App__ThisDcId}
# App__DcOptions__0__Enabled: ${App__DcOptions__0__Enabled}
# App__DcOptions__0__Id: ${App__DcOptions__0__Id}
App__DcOptions__0__IpAddress: ${App__DcOptions__0__IpAddress}
# App__DcOptions__0__Port: ${App__DcOptions__0__Port}
# App__DcOptions__0__TcpoOnly: ${App__DcOptions__0__TcpoOnly}
# App__DcOptions__0__ThisPortOnly: ${App__DcOptions__0__ThisPortOnly}
# App__DcOptions__0__Ipv6: ${App__DcOptions__0__Ipv6}
# App__DcOptions__0__Cdn: ${App__DcOptions__0__Cdn}
# App__DcOptions__0__MediaOnly: ${App__DcOptions__0__MediaOnly}
# App__DcOptions__0__Static: ${App__DcOptions__0__Static}
App__DcOptions__1__Enabled: ${App__DcOptions__1__Enabled}
# App__DcOptions__1__Id: ${App__DcOptions__1__Id}
App__DcOptions__1__IpAddress: ${App__DcOptions__1__IpAddress}
App__DcOptions__1__Port: ${App__DcOptions__1__Port}
# App__DcOptions__1__TcpoOnly: ${App__DcOptions__1__TcpoOnly}
# App__DcOptions__1__ThisPortOnly: ${App__DcOptions__1__ThisPortOnly}
# App__DcOptions__1__Ipv6: ${App__DcOptions__1__Ipv6}
# App__DcOptions__1__Cdn: ${App__DcOptions__1__Cdn}
# App__DcOptions__1__MediaOnly: ${App__DcOptions__1__MediaOnly}
# App__DcOptions__1__Static: ${App__DcOptions__1__Static}
App__DcOptions__2__Enabled: ${App__DcOptions__2__Enabled}
# App__DcOptions__2__Id: ${App__DcOptions__2__Id}
App__DcOptions__2__IpAddress: ${App__DcOptions__2__IpAddress}
# App__DcOptions__2__Port: ${App__DcOptions__2__Port}
# App__DcOptions__2__TcpoOnly: ${App__DcOptions__2__TcpoOnly}
# App__DcOptions__2__ThisPortOnly: ${App__DcOptions__2__ThisPortOnly}
# App__DcOptions__2__Ipv6: ${App__DcOptions__2__Ipv6}
# App__DcOptions__2__Cdn: ${App__DcOptions__2__Cdn}
# App__DcOptions__2__MediaOnly: ${App__DcOptions__2__MediaOnly}
# App__DcOptions__2__Static: ${App__DcOptions__2__Static}
App__DcOptions__3__Enabled: ${App__DcOptions__3__Enabled}
# App__DcOptions__3__Id: ${App__DcOptions__3__Id}
App__DcOptions__3__IpAddress: ${App__DcOptions__3__IpAddress}
# App__DcOptions__3__Port: ${App__DcOptions__3__Port}
# App__DcOptions__3__TcpoOnly: ${App__DcOptions__3__TcpoOnly}
# App__DcOptions__3__ThisPortOnly: ${App__DcOptions__3__ThisPortOnly}
# App__DcOptions__3__Ipv6: ${App__DcOptions__3__Ipv6}
# App__DcOptions__3__Cdn: ${App__DcOptions__3__Cdn}
# App__DcOptions__3__MediaOnly: ${App__DcOptions__3__MediaOnly}
# App__DcOptions__3__Static: ${App__DcOptions__3__Static}
RabbitMQ__Connections__Default__HostName: ${RabbitMQ__Connections__Default__HostName}
RabbitMQ__Connections__Default__Port: ${RabbitMQ__Connections__Default__Port}
RabbitMQ__Connections__Default__UserName: ${RabbitMQ__Connections__Default__UserName}
RabbitMQ__Connections__Default__Password: ${RabbitMQ__Connections__Default__Password}
# RabbitMQ__EventBus__ClientName: ${RabbitMQ__EventBus__ClientName}
# RabbitMQ__EventBus__ExchangeName: ${RabbitMQ__EventBus__ExchangeName}
Redis__Configuration: ${Redis__Configuration}
volumes:
- ./data/mytelegram/file/uploads:/app/uploads
- ./data/mytelegram/command-server/logs:/app/Logs
depends_on:
- mongodb
- rabbitmq
- redis
### 6.Messenger query server
messenger-query-server:
# CHANGED: Build locally from source instead of using remote image
build:
context: ../../source
dockerfile: src/MyTelegram.Messenger.QueryServer/Dockerfile
image: mytelegram-messenger-query-server-local:latest
restart: always
networks:
- tg-net
environment:
Serilog__MinimumLevel__Default: ${Serilog__MinimumLevel__Default}
ConnectionStrings__Default: ${ConnectionStrings__Default}
App__DatabaseName: tg
App__ReadModelDatabaseName: tg
App__AccessHashSecretKey: ${App__AccessHashSecretKey}
App__FileServerGrpcServiceUrl: ${App__FileServerGrpcServiceUrl}
App__FixedVerifyCode: ${App__FixedVerifyCode}
App__VerificationCodeLength: ${App__VerificationCodeLength}
App__VerificationCodeExpirationSeconds: ${App__VerificationCodeExpirationSeconds}
App__EnableSearchNonContacts: ${App__EnableSearchNonContacts}
# App__JoinChatDomain: ${App__JoinChatDomain}
# App__ChannelGetDifferenceIntervalSeconds: ${App__ChannelGetDifferenceIntervalSeconds}
# App__UseInMemoryFilters: ${App__UseInMemoryFilters}
# App__IsMediaDc: ${App__IsMediaDc}
# App__AutoCreateSuperGroup: ${App__AutoCreateSuperGroup}
# App__EnableFutureAuthToken: ${App__EnableFutureAuthToken}
# App__SetPremiumToTrueAfterUserCreated: ${App__SetPremiumToTrueAfterUserCreated}
# App__SendWelcomeMessageAfterUserSignIn: ${App__SendWelcomeMessageAfterUserSignIn}
App__WebRtcConnections__0__Ip: ${App__WebRtcConnections__0__Ip}
App__WebRtcConnections__0__Ipv6: ${App__WebRtcConnections__0__Ipv6}
App__WebRtcConnections__0__Port: ${App__WebRtcConnections__0__Port}
App__WebRtcConnections__0__Turn: ${App__WebRtcConnections__0__Turn}
App__WebRtcConnections__0__Stun: ${App__WebRtcConnections__0__Stun}
App__WebRtcConnections__0__UserName: ${App__WebRtcConnections__0__UserName}
App__WebRtcConnections__0__Password: ${App__WebRtcConnections__0__Password}
# App__ThisDcId: ${App__ThisDcId}
# App__DcOptions__0__Enabled: ${App__DcOptions__0__Enabled}
# App__DcOptions__0__Id: ${App__DcOptions__0__Id}
App__DcOptions__0__IpAddress: ${App__DcOptions__0__IpAddress}
App__DcOptions__0__Port: ${App__DcOptions__0__Port}
# App__DcOptions__0__TcpoOnly: ${App__DcOptions__0__TcpoOnly}
# App__DcOptions__0__ThisPortOnly: ${App__DcOptions__0__ThisPortOnly}
# App__DcOptions__0__Ipv6: ${App__DcOptions__0__Ipv6}
# App__DcOptions__0__Cdn: ${App__DcOptions__0__Cdn}
# App__DcOptions__0__MediaOnly: ${App__DcOptions__0__MediaOnly}
# App__DcOptions__0__Static: ${App__DcOptions__0__Static}
App__DcOptions__1__Enabled: ${App__DcOptions__1__Enabled}
# App__DcOptions__1__Id: ${App__DcOptions__1__Id}
App__DcOptions__1__IpAddress: ${App__DcOptions__1__IpAddress}
App__DcOptions__1__Port: ${App__DcOptions__1__Port}
# App__DcOptions__1__TcpoOnly: ${App__DcOptions__1__TcpoOnly}
# App__DcOptions__1__ThisPortOnly: ${App__DcOptions__1__ThisPortOnly}
# App__DcOptions__1__Ipv6: ${App__DcOptions__1__Ipv6}
# App__DcOptions__1__Cdn: ${App__DcOptions__1__Cdn}
# App__DcOptions__1__MediaOnly: ${App__DcOptions__1__MediaOnly}
# App__DcOptions__1__Static: ${App__DcOptions__1__Static}
App__DcOptions__2__Enabled: ${App__DcOptions__2__Enabled}
# App__DcOptions__2__Id: ${App__DcOptions__2__Id}
App__DcOptions__2__IpAddress: ${App__DcOptions__2__IpAddress}
App__DcOptions__2__Port: ${App__DcOptions__2__Port}
# App__DcOptions__2__TcpoOnly: ${App__DcOptions__2__TcpoOnly}
# App__DcOptions__2__ThisPortOnly: ${App__DcOptions__2__ThisPortOnly}
# App__DcOptions__2__Ipv6: ${App__DcOptions__2__Ipv6}
# App__DcOptions__2__Cdn: ${App__DcOptions__2__Cdn}
# App__DcOptions__2__MediaOnly: ${App__DcOptions__2__MediaOnly}
# App__DcOptions__2__Static: ${App__DcOptions__2__Static}
App__DcOptions__3__Enabled: ${App__DcOptions__3__Enabled}
# App__DcOptions__3__Id: ${App__DcOptions__3__Id}
App__DcOptions__3__IpAddress: ${App__DcOptions__3__IpAddress}
# App__DcOptions__3__Port: ${App__DcOptions__3__Port}
# App__DcOptions__3__TcpoOnly: ${App__DcOptions__3__TcpoOnly}
# App__DcOptions__3__ThisPortOnly: ${App__DcOptions__3__ThisPortOnly}
# App__DcOptions__3__Ipv6: ${App__DcOptions__3__Ipv6}
# App__DcOptions__3__Cdn: ${App__DcOptions__3__Cdn}
# App__DcOptions__3__MediaOnly: ${App__DcOptions__3__MediaOnly}
# App__DcOptions__3__Static: ${App__DcOptions__3__Static}
Minio__Endpoint: ${Minio__Endpoint}
Minio__AccessKey: ${Minio__AccessKey}
Minio__SecretKey: ${Minio__SecretKey}
Minio__BucketName: ${Minio__BucketName}
RabbitMQ__Connections__Default__HostName: ${RabbitMQ__Connections__Default__HostName}
RabbitMQ__Connections__Default__Port: ${RabbitMQ__Connections__Default__Port}
RabbitMQ__Connections__Default__UserName: ${RabbitMQ__Connections__Default__UserName}
RabbitMQ__Connections__Default__Password: ${RabbitMQ__Connections__Default__Password}
# RabbitMQ__EventBus__ClientName: ${RabbitMQ__EventBus__ClientName}
# RabbitMQ__EventBus__ExchangeName: ${RabbitMQ__EventBus__ExchangeName}
Redis__Configuration: ${Redis__Configuration}
volumes:
- ./data/mytelegram/query-server/logs:/app/Logs
depends_on:
- mongodb
- rabbitmq
- redis
### 7.Bot API Server
bot-api-server:
build:
context: ../../source
dockerfile: src/MyTelegram.BotApi/Dockerfile
restart: always
networks:
- tg-net
ports:
- "8081:8081"
environment:
ASPNETCORE_URLS: http://+:8081
Serilog__MinimumLevel__Default: ${Serilog__MinimumLevel__Default}
ConnectionStrings__Default: ${ConnectionStrings__Default}
App__DatabaseName: tg
App__ReadModelDatabaseName: tg
RabbitMQ__Connections__Default__HostName: ${RabbitMQ__Connections__Default__HostName}
RabbitMQ__Connections__Default__Port: ${RabbitMQ__Connections__Default__Port}
RabbitMQ__Connections__Default__UserName: ${RabbitMQ__Connections__Default__UserName}
RabbitMQ__Connections__Default__Password: ${RabbitMQ__Connections__Default__Password}
RabbitMQ__EventBus__ClientProvidedName: MyTelegram.BotApi
RabbitMQ__EventBus__ExchangeName: ${RabbitMQ__EventBus__ExchangeName}
MongoDB__ConnectionString: ${ConnectionStrings__Default}
MongoDB__DatabaseName: tg
BotApi__Port: 8081
BotApi__LocalMode: true
BotApi__MaxFileSize: 2147483648
volumes:
- ./data/mytelegram/bot-api/logs:/app/Logs
depends_on:
- mongodb
- rabbitmq
- redis
### 8.TURN/STUN server for VoIP
turn-server:
image: coturn/coturn:latest
restart: always
network_mode: host
# networks:
# - tg-net
# ports:
# - "3478:3478/udp"
# - "3478:3478/tcp"
# - "5349:5349/tcp"
# Note: network_mode: host exposes all ports directly, including relay ports 49152-65535
volumes:
- ./turnserver.conf:/etc/coturn/turnserver.conf:ro
- ./data/coturn/logs:/var/log/coturn
environment:
TURN_EXTERNAL_IP: "${TURN_EXTERNAL_IP:-auto-detect}"
command: ["-c", "/etc/coturn/turnserver.conf"]
### 9.Sms sender
sms-sender:
image: ${MyTelegramRegistry}/mytelegram-sms-sender:${MyTelegramVersion}
restart: always
networks:
- tg-net
environment:
Serilog__MinimumLevel__Default: ${Serilog__MinimumLevel__Default}
# TwilioSms__Enabled: ${TwilioSms__Enabled}
# TwilioSms__AccountSId: ${TwilioSms__AccountSId}
# TwilioSms__AuthToken: ${TwilioSms__AuthToken}
# TwilioSms__FromNumber: ${TwilioSms__FromNumber}
# TwilioSms__MessagingServiceSId: ${TwilioSms__MessagingServiceSId}
# VonageSms__Enabled: ${VonageSms__Enabled}
# VonageSms__BrandName: ${VonageSms__BrandName}
# VonageSms__ApiKey: ${VonageSms__ApiKey}
# VonageSms__ApiSecret: ${VonageSms__ApiSecret}
RabbitMQ__Connections__Default__HostName: ${RabbitMQ__Connections__Default__HostName}
RabbitMQ__Connections__Default__Port: ${RabbitMQ__Connections__Default__Port}
RabbitMQ__Connections__Default__UserName: ${RabbitMQ__Connections__Default__UserName}
RabbitMQ__Connections__Default__Password: ${RabbitMQ__Connections__Default__Password}
# RabbitMQ__EventBus__ClientName: ${RabbitMQ__EventBus__ClientName}
# RabbitMQ__EventBus__ExchangeName: ${RabbitMQ__EventBus__ExchangeName}
volumes:
- ./data/mytelegram/sms-sender/logs:/app/Logs
depends_on:
- rabbitmq
### 10.Fragment Backend API (Run locally with npm run dev)
# fragment-backend:
# build:
# context: ../../fragment/fragment-backend
# dockerfile: Dockerfile
# restart: always
# networks:
# - tg-net
# ports:
# - "3001:3001"
# environment:
# PORT: 3001
# MONGODB_URI: mongodb://mongodb:27017/tg
# JWT_SECRET: fragment_secret_key_change_in_production
# BOT_API_URL: http://bot-api-server:8081
# FRAGMENT_BOT_TOKEN: ${FRAGMENT_BOT_TOKEN}
# volumes:
# - ./data/fragment/backend/logs:/app/logs
# depends_on:
# - mongodb
# - bot-api-server
+31
View File
@@ -0,0 +1,31 @@
// Скрипт инициализации MongoDB
// Запускается при старте MongoDB с включённой аутентификацией
// Гарантирует, что пользователь admin существует с нужными правами
// Учтите: скрипт выполняется в контексте базы admin
// Создаём пользователя admin, если его ещё нет
try {
db.createUser({
user: "admin",
pwd: "CHANGE_ME",
roles: [
{ role: "root", db: "admin" },
{ role: "dbOwner", db: "tg" },
{ role: "readWrite", db: "tg" }
]
});
print("Пользователь admin создан");
} catch (e) {
if (e.code === 48) {
// Пользователь уже существует - это нормально
print("Пользователь admin уже существует");
} else {
print("Ошибка при создании пользователя admin: " + e.message);
throw e;
}
}
// Создаём базу tg и обеспечиваем доступ к ней
db.getSiblingDB("tg").createCollection("dummy", { capped: false });
print("База 'tg' готова");
+19
View File
@@ -0,0 +1,19 @@
@echo off
echo Stopping session-server container...
docker-compose stop session-server
echo Removing session-server container...
docker-compose rm -f session-server
echo Removing old session server image (0.33.211.906)...
docker rmi mytelegram/mytelegram-session-server:0.33.211.906
echo Pulling session server image v0.32.206.802...
docker pull mytelegram/mytelegram-session-server:0.32.206.802
echo Starting session-server with version 0.32.206.802...
set MyTelegramVersion=0.32.206.802
docker-compose up -d session-server
echo Done.
pause
+47
View File
@@ -0,0 +1,47 @@
#!/bin/bash
# Полный перезапуск MongoDB с включённой аутентификацией
# Скрипт корректно сбрасывает состояние MongoDB и поднимает его заново
echo "======================================"
echo "MongoDB Authentication Fix - Restart"
echo "======================================"
echo ""
cd "$(dirname "$0")"
echo "[1/5] Stopping all services..."
docker-compose down
echo "[2/5] Removing MongoDB data volume to reinitialize..."
if [ -d "./data/mongo" ]; then
rm -rf "./data/mongo"
echo " MongoDB data directory removed"
else
echo " MongoDB data directory not found (this is fine)"
fi
echo "[3/5] Waiting a moment before starting services..."
sleep 2
echo "[4/5] Building and starting services with MongoDB authentication..."
docker-compose up --build -d
echo "[5/5] Waiting for MongoDB to initialize..."
sleep 10
echo ""
echo "======================================"
echo "Restart Complete!"
echo "======================================"
echo ""
echo "Next steps:"
echo "1. Check MongoDB initialization: docker-compose logs mongodb"
echo "2. Check data-seeder: docker-compose logs data-seeder"
echo "3. Check admin-api: docker-compose logs admin-api"
echo "4. Monitor bot-api: docker-compose logs bot-api-server"
echo ""
echo "MongoDB Authentication Details:"
echo " Username: admin"
echo " Auth Database: admin"
echo " Target Database: tg"
echo ""
+23
View File
@@ -0,0 +1,23 @@
# RSA-ключи MTProto
Здесь должны лежать RSA-ключи сервера. Приватные ключи намеренно не хранятся в
репозитории — сгенерируйте свою пару перед запуском.
```bash
# Приватный ключ (PKCS#8)
openssl genrsa -out rsa_private.pem 2048
# Приватный ключ в формате PKCS#1 (используется сервером)
openssl rsa -in rsa_private.pem -traditional -out rsa_private_pkcs1.pem
# Публичный ключ
openssl rsa -in rsa_private.pem -pubout -out rsa_public.pem
```
После генерации в этом каталоге должны появиться файлы:
- `rsa_private.pem`
- `rsa_private_pkcs1.pem`
- `rsa_public.pem`
Эти файлы не коммитьте.
+31
View File
@@ -0,0 +1,31 @@
// Скрипт MongoDB: добавляет упрощённые темы оформления чата (только эмодзи-маркер)
// Сделано по образцу официального конструктора chatTheme из Telegram
// Запуск: docker exec -i compose-mongodb-1 mongosh tg
db = db.getSiblingDB('tg');
// Проверяем, не созданы ли темы оформления ранее
const existingCount = db.chat_themes.countDocuments();
if (existingCount > 0) {
print(`Темы оформления уже есть (${existingCount}), удаляем...`);
db.chat_themes.deleteMany({});
}
print('Добавляем упрощённые темы оформления чата (только эмодзи-маркер)...');
// Официальные темы оформления Telegram - только эмодзи-маркеры
const themes = [
{ _id: "1", Emoticon: "🏠" }, // Home
{ _id: "2", Emoticon: "❤️" }, // Love
{ _id: "3", Emoticon: "🎉" }, // Party
{ _id: "4", Emoticon: "🌊" }, // Ocean
{ _id: "5", Emoticon: "🌸" }, // Flower
{ _id: "6", Emoticon: "🌙" }, // Night
{ _id: "7", Emoticon: "🔥" }, // Fire
{ _id: "8", Emoticon: "💚" } // Green
];
db.chat_themes.insertMany(themes);
print(`Добавлено упрощённых тем оформления чата: ${themes.length}`);
print(`Total chat themes in database: ${db.chat_themes.countDocuments()}`);
@@ -0,0 +1,31 @@
// Скрипт MongoDB: добавляет темы оформления чата, используя Unicode-escape-последовательности
// Так удаётся избежать проблем с кодировкой при передаче через docker exec
// Запуск: docker exec -i compose-mongodb-1 mongosh tg < seed-chat-themes-unicode.js
db = db.getSiblingDB('tg');
// Удаляем существующие темы
print('Удаляем существующие темы оформления чата...');
db.chat_themes.deleteMany({});
print('Добавляем темы оформления через Unicode-escape...');
// Используем Unicode-escape-последовательности, чтобы избежать проблем с кодировкой
const themes = [
{ _id: "1", Emoticon: "\uD83C\uDFE0" }, // Home
{ _id: "2", Emoticon: "\u2764\uFE0F" }, // Love
{ _id: "3", Emoticon: "\uD83C\uDF89" }, // Party
{ _id: "4", Emoticon: "\uD83C\uDF0A" }, // Ocean
{ _id: "5", Emoticon: "\uD83C\uDF38" }, // Flower
{ _id: "6", Emoticon: "\uD83C\uDF19" }, // Night
{ _id: "7", Emoticon: "\uD83D\uDD25" }, // Fire
{ _id: "8", Emoticon: "\uD83D\uDC9A" } // Green
];
db.chat_themes.insertMany(themes);
print(`Добавлено тем оформления чата: ${themes.length}`);
print('Проверяем темы:');
db.chat_themes.find().forEach(function(theme) {
print(` ID: ${theme._id}, Emoticon: ${theme.Emoticon}`);
});
+302
View File
@@ -0,0 +1,302 @@
// Скрипт MongoDB: добавляет стандартные темы оформления чата вместе с обоями
// Запуск: docker exec -i compose-mongodb-1 mongosh tg
db = db.getSiblingDB('tg');
// Проверяем, не созданы ли темы оформления ранее
const existingCount = db.chat_themes.countDocuments();
if (existingCount > 0) {
print(`Chat themes already exist (${existingCount}), skipping...`);
} else {
print('Добавляем стандартные темы оформления чата...');
const themes = [
{
_id: "1",
Id: NumberLong("1"),
AccessHash: NumberLong("1234567890123456"),
Slug: "blue",
Title: "Blue",
Default: true,
ForChat: true,
Emoticon: "💙",
DocumentId: null,
Settings: [
{
MessageColorsAnimated: false,
BaseTheme: NumberLong("2"), // Дневная
AccentColor: 0x0f4c81,
OutboxAccentColor: null,
MessageColors: [0x0f4c81, 0x1a7fc1],
WallPaper: {
Id: NumberLong("1001"),
AccessHash: NumberLong("1001"),
Default: true,
Pattern: false,
Dark: false,
Slug: "blue-gradient",
DocumentId: null,
Settings: {
Blur: false,
Motion: true,
BackgroundColor: 0x0f4c81,
SecondBackgroundColor: 0x1a7fc1,
ThirdBackgroundColor: null,
FourthBackgroundColor: null,
Intensity: 50,
Rotation: 45,
Emoticon: "💙"
}
}
}
]
},
{
_id: "2",
Id: NumberLong("2"),
AccessHash: NumberLong("1234567890123457"),
Slug: "pink",
Title: "Pink",
Default: true,
ForChat: true,
Emoticon: "💗",
DocumentId: null,
Settings: [
{
MessageColorsAnimated: false,
BaseTheme: NumberLong("2"),
AccentColor: 0xff6b9d,
OutboxAccentColor: null,
MessageColors: [0xff6b9d, 0xffa8c5],
WallPaper: {
Id: NumberLong("1002"),
AccessHash: NumberLong("1002"),
Default: true,
Pattern: false,
Dark: false,
Slug: "pink-gradient",
DocumentId: null,
Settings: {
Blur: false,
Motion: true,
BackgroundColor: 0xff6b9d,
SecondBackgroundColor: 0xffa8c5,
ThirdBackgroundColor: null,
FourthBackgroundColor: null,
Intensity: 50,
Rotation: 135,
Emoticon: "💗"
}
}
}
]
},
{
_id: "3",
Id: NumberLong("3"),
AccessHash: NumberLong("1234567890123458"),
Slug: "green",
Title: "Green",
Default: true,
ForChat: true,
Emoticon: "💚",
DocumentId: null,
Settings: [
{
MessageColorsAnimated: false,
BaseTheme: NumberLong("2"),
AccentColor: 0x0a8754,
OutboxAccentColor: null,
MessageColors: [0x0a8754, 0x2ecc71],
WallPaper: {
Id: NumberLong("1003"),
AccessHash: NumberLong("1003"),
Default: true,
Pattern: false,
Dark: false,
Slug: "green-gradient",
DocumentId: null,
Settings: {
Blur: false,
Motion: true,
BackgroundColor: 0x0a8754,
SecondBackgroundColor: 0x2ecc71,
ThirdBackgroundColor: null,
FourthBackgroundColor: null,
Intensity: 50,
Rotation: 90,
Emoticon: "💚"
}
}
}
]
},
{
_id: "4",
Id: NumberLong("4"),
AccessHash: NumberLong("1234567890123459"),
Slug: "orange",
Title: "Orange",
Default: true,
ForChat: true,
Emoticon: "🧡",
DocumentId: null,
Settings: [
{
MessageColorsAnimated: false,
BaseTheme: NumberLong("2"),
AccentColor: 0xff6b35,
OutboxAccentColor: null,
MessageColors: [0xff6b35, 0xffa500],
WallPaper: {
Id: NumberLong("1004"),
AccessHash: NumberLong("1004"),
Default: true,
Pattern: false,
Dark: false,
Slug: "orange-gradient",
DocumentId: null,
Settings: {
Blur: false,
Motion: true,
BackgroundColor: 0xff6b35,
SecondBackgroundColor: 0xffa500,
ThirdBackgroundColor: null,
FourthBackgroundColor: null,
Intensity: 50,
Rotation: 180,
Emoticon: "🧡"
}
}
}
]
},
{
_id: "5",
Id: NumberLong("5"),
AccessHash: NumberLong("1234567890123460"),
Slug: "purple",
Title: "Purple",
Default: true,
ForChat: true,
Emoticon: "💜",
DocumentId: null,
Settings: [
{
MessageColorsAnimated: false,
BaseTheme: NumberLong("2"),
AccentColor: 0x8e44ad,
OutboxAccentColor: null,
MessageColors: [0x8e44ad, 0xc39bd3],
WallPaper: {
Id: NumberLong("1005"),
AccessHash: NumberLong("1005"),
Default: true,
Pattern: false,
Dark: false,
Slug: "purple-gradient",
DocumentId: null,
Settings: {
Blur: false,
Motion: true,
BackgroundColor: 0x8e44ad,
SecondBackgroundColor: 0xc39bd3,
ThirdBackgroundColor: null,
FourthBackgroundColor: null,
Intensity: 50,
Rotation: 225,
Emoticon: "💜"
}
}
}
]
},
{
_id: "6",
Id: NumberLong("6"),
AccessHash: NumberLong("1234567890123461"),
Slug: "sunset",
Title: "Sunset",
Default: true,
ForChat: true,
Emoticon: "🌅",
DocumentId: null,
Settings: [
{
MessageColorsAnimated: true,
BaseTheme: NumberLong("2"),
AccentColor: 0xff6b35,
OutboxAccentColor: null,
MessageColors: [0xff6b35, 0xff8c42, 0xffa94d, 0xffc65c],
WallPaper: {
Id: NumberLong("1006"),
AccessHash: NumberLong("1006"),
Default: true,
Pattern: false,
Dark: false,
Slug: "sunset-gradient",
DocumentId: null,
Settings: {
Blur: false,
Motion: true,
BackgroundColor: 0xff6b35,
SecondBackgroundColor: 0xff8c42,
ThirdBackgroundColor: 0xffa94d,
FourthBackgroundColor: 0xffc65c,
Intensity: 60,
Rotation: 315,
Emoticon: "🌅"
}
}
}
]
},
{
_id: "7",
Id: NumberLong("7"),
AccessHash: NumberLong("1234567890123462"),
Slug: "ocean",
Title: "Ocean",
Default: true,
ForChat: true,
Emoticon: "🌊",
DocumentId: null,
Settings: [
{
MessageColorsAnimated: true,
BaseTheme: NumberLong("2"),
AccentColor: 0x006994,
OutboxAccentColor: null,
MessageColors: [0x006994, 0x0099cc, 0x33b5e5, 0x66ccff],
WallPaper: {
Id: NumberLong("1007"),
AccessHash: NumberLong("1007"),
Default: true,
Pattern: false,
Dark: false,
Slug: "ocean-gradient",
DocumentId: null,
Settings: {
Blur: false,
Motion: true,
BackgroundColor: 0x006994,
SecondBackgroundColor: 0x0099cc,
ThirdBackgroundColor: 0x33b5e5,
FourthBackgroundColor: 0x66ccff,
Intensity: 60,
Rotation: 0,
Emoticon: "🌊"
}
}
}
]
}
];
db.chat_themes.insertMany(themes);
print(`Добавлено тем оформления чата: ${themes.length}`);
}
// Выводим итоговое количество
print(`Total chat themes in database: ${db.chat_themes.countDocuments()}`);
+81
View File
@@ -0,0 +1,81 @@
# Coturn TURN/STUN Server Configuration for MyTelegram VoIP
# Based on RFC 5766 (TURN) and RFC 5389 (STUN)
# Listen on all interfaces (Docker compatibility)
listening-ip=0.0.0.0
# Listening port for TURN/STUN
listening-port=3478
# TLS listening port (disabled for local testing)
# tls-listening-port=5349
# Relay ports range for media
min-port=49152
max-port=65535
# Server realm (domain)
realm=mytelegram.local
# Authentication
# Use long-term credentials
lt-cred-mech
# User credentials (username:password)
# Format: user=username:password
# In production, use strong passwords and rotate regularly
user=user:CHANGE_ME
# Logging
verbose
log-file=/var/log/coturn/turnserver.log
# Performance tuning
# Total quota (bytes per allocation)
total-quota=100
# Bandwidth per session (bytes/sec)
bps-capacity=0
# No multicast
no-multicast-peers
# Disable software attributes
no-software-attribute
# Mobility
mobility
# WebRTC compatibility
fingerprint
# Secure communication (TLS disabled for local testing)
# no-tls
# no-dtls
# TLS/DTLS certificate paths (optional, for TLS)
# cert=/etc/coturn/cert.pem
# pkey=/etc/coturn/key.pem
# CLI
cli-password=CHANGE_CLI_PASSWORD
# Allow TURN for specific IP ranges (optional)
# allowed-peer-ip=10.0.0.0-10.255.255.255
# denied-peer-ip=192.168.0.0-192.168.255.255
# Database for persistent storage (optional)
# psql-userdb="host=postgresql dbname=coturn user=coturn password=password"
# Redis for session management (optional)
# redis-userdb="ip=redis dbname=0 password=turn connect_timeout=30"
# Prometheus metrics (optional)
# prometheus
# Disable UDP relay endpoints
# no-udp-relay
# Use external IP (important for NAT)
# Must match App__WebRtcConnections__0__Ip in .env
# For local testing use 127.0.0.1, for production use your public IP
external-ip=127.0.0.1
+29
View File
@@ -0,0 +1,29 @@
// Update custom emoji documents to add MinIO file path
const db = db.getSiblingDB('tg');
const collection = db.getCollection('ReadModel-DocumentReadModel');
// Find all custom emoji documents
const customEmojiDocs = collection.find({
'Attributes2._t': 'TDocumentAttributeCustomEmoji'
});
let updated = 0;
customEmojiDocs.forEach(doc => {
const documentId = doc.DocumentId;
const subDir = (documentId % 1000).toString().padStart(3, '0');
const filePath = `custom_emoji/${subDir}/${documentId}.tgs`;
// Update document with file path
collection.updateOne(
{ _id: doc._id },
{
$set: {
MinIOPath: filePath,
MinIOBucket: 'tg-files'
}
}
);
updated++;
});
print(`Updated ${updated} custom emoji documents with MinIO paths`);
+19
View File
@@ -0,0 +1,19 @@
@echo off
echo Stopping session-server container...
docker-compose stop session-server
echo Removing session-server container...
docker-compose rm -f session-server
echo Removing old session server image (0.36.220.1231)...
docker rmi mytelegram/mytelegram-session-server:0.36.220.1231
echo Pulling session server image v0.32.206.802...
docker pull mytelegram/mytelegram-session-server:0.32.206.802
echo Starting session-server with version 0.32.206.802...
set MyTelegramVersion=0.32.206.802
docker-compose up -d session-server
echo Done.
pause
+12
View File
@@ -0,0 +1,12 @@
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY server.js ./
EXPOSE 5000
CMD ["node", "server.js"]
+13
View File
@@ -0,0 +1,13 @@
{
"name": "file-merge-proxy",
"version": "1.0.0",
"description": "HTTP proxy for merging file parts on file-server",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2",
"json-bigint": "^1.0.0"
}
}
+192
View File
@@ -0,0 +1,192 @@
const express = require('express');
const fs = require('fs').promises;
const path = require('path');
const JSONbig = require('json-bigint')({ storeAsString: true });
const app = express();
// Используем json-bigint, чтобы не терять точность больших чисел
app.use(express.text({ type: 'application/json' }));
app.use((req, res, next) => {
if (req.body && typeof req.body === 'string') {
try {
req.body = JSONbig.parse(req.body);
} catch (e) {
return res.status(400).json({ error: 'Invalid JSON' });
}
}
next();
});
const UPLOAD_BASE = process.env.UPLOAD_PATH || '/app/uploads';
app.post('/api/files/merge', async (req, res) => {
try {
const { userId, fileId, parts, fileName } = req.body;
// Защита от выхода за пределы каталога (path traversal)
if (!fileName || typeof fileName !== 'string') {
return res.status(400).json({ error: 'Invalid fileName parameter' });
}
// Блокируем попытки обхода пути
if (fileName.includes('..') || fileName.includes('/') || fileName.includes('\\')) {
console.error(`Заблокирована попытка обхода пути - fileName: ${fileName}, IP: ${req.ip}`);
return res.status(400).json({ error: 'Invalid fileName: path traversal not allowed' });
}
// Очищаем имя файла: оставляем только буквы, цифры, дефис, подчёркивание и точки
const sanitizedFileName = fileName.replace(/[^a-zA-Z0-9._-]/g, '_');
if (sanitizedFileName !== fileName) {
console.warn(`Имя файла очищено: "${fileName}" -> "${sanitizedFileName}"`);
}
console.log(`Merging file from local filesystem: userId=${userId}, fileId=${fileId}, parts=${parts}, fileName=${fileName}`);
// Части файла хранятся в формате: {userId}-{actualFileId}-{partNumber}
// fileId из command-server может отличаться от того, что использует file-server,
// поэтому реальный fileId находим, перебирая файлы этого пользователя
console.log(`Searching for files with userId: ${userId}`);
// Получаем список всех файлов этого пользователя
const allFiles = await fs.readdir(UPLOAD_BASE);
const userFiles = allFiles.filter(f => f.startsWith(`${userId}-`));
console.log(`Found ${userFiles.length} files for user ${userId}`);
if (userFiles.length === 0) {
return res.status(404).json({ error: 'No files found for user' });
}
// Группируем файлы по fileId (берём его из имени: userId-fileId-partNumber)
const fileGroups = {};
for (const file of userFiles) {
const match = file.match(/^(\d+)-(-?\d+)-(\d+)$/);
if (match) {
const [, uid, fid, partNum] = match;
if (!fileGroups[fid]) {
fileGroups[fid] = [];
}
fileGroups[fid].push({ file, partNum: parseInt(partNum) });
}
}
console.log(`Found ${Object.keys(fileGroups).length} file groups:`, Object.keys(fileGroups));
// Ищем группу с нужным количеством частей
let actualFileId = null;
let partFiles = null;
for (const [fid, files] of Object.entries(fileGroups)) {
if (files.length === parts) {
actualFileId = fid;
partFiles = files.sort((a, b) => a.partNum - b.partNum);
console.log(`Found matching file group: fileId=${fid}, parts=${files.length}`);
break;
}
}
if (!actualFileId || !partFiles) {
console.error(`No file group with ${parts} parts found`);
console.log('Available groups:', Object.entries(fileGroups).map(([fid, files]) => `${fid} (${files.length} parts)`));
return res.status(404).json({ error: `No file group with ${parts} parts found` });
}
// Проверяем, что все части на месте
const partPaths = [];
for (let i = 0; i < parts; i++) {
const partFile = partFiles.find(f => f.partNum === i);
if (!partFile) {
console.error(`Missing part ${i}`);
return res.status(404).json({ error: `Missing file part ${i}` });
}
partPaths.push(path.join(UPLOAD_BASE, partFile.file));
}
console.log(`All ${parts} parts found, merging...`);
// Склеиваем части
const mergedDir = path.join(UPLOAD_BASE, 'merged');
await fs.mkdir(mergedDir, { recursive: true });
// Используем очищенное имя файла
const extension = sanitizedFileName.split('.').pop() || '';
const mergedFileName = `${userId}-${actualFileId}${extension ? '.' + extension : ''}`;
// Дополнительно убеждаемся, что итоговый путь остаётся внутри разрешённого каталога
const mergedFilePath = path.join(mergedDir, mergedFileName);
const resolvedPath = path.resolve(mergedFilePath);
const resolvedMergedDir = path.resolve(mergedDir);
if (!resolvedPath.startsWith(resolvedMergedDir)) {
console.error(`Обнаружен выход за пределы каталога. Запрошенный путь: ${resolvedPath}`);
return res.status(400).json({ error: 'Invalid file path' });
}
const writeStream = require('fs').createWriteStream(mergedFilePath);
let totalSize = 0;
// Ограничение на размер файла (максимум 2 ГБ)
const MAX_FILE_SIZE = 2 * 1024 * 1024 * 1024; // 2 ГБ
for (const partPath of partPaths) {
const partData = await fs.readFile(partPath);
// Проверяем размер перед записью
if (totalSize + partData.length > MAX_FILE_SIZE) {
console.error(`Превышен лимит размера файла - запрошенный размер: ${totalSize + partData.length}, лимит: ${MAX_FILE_SIZE}`);
// Удаляем недописанный файл
try {
writeStream.close();
await fs.unlink(mergedFilePath);
} catch (e) {}
return res.status(413).json({ error: 'File too large: Maximum size is 2GB' });
}
writeStream.write(partData);
totalSize += partData.length;
}
console.log(`Проверка размера пройдена: ${totalSize} bytes (limit: ${MAX_FILE_SIZE})`);
writeStream.end();
await new Promise((resolve, reject) => {
writeStream.on('finish', resolve);
writeStream.on('error', reject);
});
console.log(`Merged ${parts} parts into ${mergedFileName}, total size: ${totalSize} bytes`);
// Удаляем части после склейки
for (const partPath of partPaths) {
try {
await fs.unlink(partPath);
} catch (e) {
console.error(`Failed to remove part ${partPath}:`, e.message);
}
}
res.json({
filePath: path.join('merged', mergedFileName),
totalSize: totalSize
});
} catch (error) {
console.error('Merge error:', error);
res.status(500).json({ error: error.message });
}
});
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`File merge proxy listening on port ${PORT}`);
console.log(`Upload base path: ${UPLOAD_BASE}`);
});
+7
View File
@@ -0,0 +1,7 @@
# Mediasoup SFU Configuration
PORT=3200
MEDIASOUP_LISTEN_IP=0.0.0.0
MEDIASOUP_ANNOUNCED_IP=127.0.0.1
RTC_MIN_PORT=10000
RTC_MAX_PORT=10100
LOG_LEVEL=debug
+4
View File
@@ -0,0 +1,4 @@
node_modules/
*.log
.env.local
.DS_Store
+27
View File
@@ -0,0 +1,27 @@
FROM node:18-alpine
# Install dependencies for Mediasoup
RUN apk add --no-cache \
python3 \
make \
g++ \
gcc \
linux-headers
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install npm dependencies
RUN npm ci --only=production
# Copy application files
COPY . .
# Expose ports
EXPOSE 3200
EXPOSE 10000-10100/udp
# Start server
CMD ["node", "server.js"]
+2013
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
{
"name": "mytelegram-mediasoup-sfu",
"version": "1.0.0",
"description": "Mediasoup SFU server for MyTelegram group calls",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"keywords": ["mediasoup", "webrtc", "sfu", "telegram"],
"author": "MyTelegram",
"license": "MIT",
"dependencies": {
"mediasoup": "^3.13.0",
"express": "^4.18.2",
"socket.io": "^4.6.1",
"cors": "^2.8.5",
"winston": "^3.11.0",
"dotenv": "^16.3.1"
},
"devDependencies": {
"nodemon": "^3.0.2"
},
"engines": {
"node": ">=16.0.0"
}
}
+703
View File
@@ -0,0 +1,703 @@
/**
* MyTelegram Mediasoup SFU Server
* Production-ready WebRTC SFU for group calls
*/
const mediasoup = require('mediasoup');
const express = require('express');
const http = require('http');
const socketIO = require('socket.io');
const cors = require('cors');
const winston = require('winston');
require('dotenv').config();
// ==================== Configuration ====================
const config = {
listenIp: process.env.MEDIASOUP_LISTEN_IP || '0.0.0.0',
announcedIp: process.env.MEDIASOUP_ANNOUNCED_IP || '127.0.0.1',
httpPort: parseInt(process.env.PORT) || 3200,
rtcMinPort: parseInt(process.env.RTC_MIN_PORT) || 10000,
rtcMaxPort: parseInt(process.env.RTC_MAX_PORT) || 10100,
logLevel: process.env.LOG_LEVEL || 'debug',
};
// ==================== Logger Setup ====================
const logger = winston.createLogger({
level: config.logLevel,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(({ timestamp, level, message, ...meta }) => {
return `[${timestamp}] [${level.toUpperCase()}] ${message} ${Object.keys(meta).length ? JSON.stringify(meta) : ''}`;
})
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'mediasoup-error.log', level: 'error' }),
new winston.transports.File({ filename: 'mediasoup-combined.log' })
]
});
// ==================== Express App Setup ====================
const app = express();
app.use(cors());
app.use(express.json());
const server = http.createServer(app);
const io = socketIO(server, {
cors: {
origin: '*',
methods: ['GET', 'POST']
}
});
// ==================== Global State ====================
let worker;
let router;
const rooms = new Map(); // roomId -> Room
const peers = new Map(); // socketId -> Peer
class Room {
constructor(roomId) {
this.id = roomId;
this.peers = new Map(); // peerId -> Peer
this.router = null;
logger.info(`Room created: ${roomId}`);
}
addPeer(peer) {
this.peers.set(peer.id, peer);
logger.info(`Peer ${peer.id} added to room ${this.id}`);
}
removePeer(peerId) {
const peer = this.peers.get(peerId);
if (peer) {
peer.close();
this.peers.delete(peerId);
logger.info(`Peer ${peerId} removed from room ${this.id}`);
}
if (this.peers.size === 0) {
logger.info(`Room ${this.id} is empty, closing...`);
return true; // Room can be deleted
}
return false;
}
getPeer(peerId) {
return this.peers.get(peerId);
}
getAllPeers() {
return Array.from(this.peers.values());
}
close() {
for (const peer of this.peers.values()) {
peer.close();
}
this.peers.clear();
logger.info(`Room ${this.id} closed`);
}
}
class Peer {
constructor(id, socket, roomId) {
this.id = id;
this.socket = socket;
this.roomId = roomId;
this.transports = new Map(); // transportId -> Transport
this.producers = new Map(); // producerId -> Producer
this.consumers = new Map(); // consumerId -> Consumer
this.rtpCapabilities = null;
logger.info(`Peer created: ${id} in room ${roomId}`);
}
addTransport(transport) {
this.transports.set(transport.id, transport);
logger.debug(`Transport ${transport.id} added to peer ${this.id}`);
}
addProducer(producer) {
this.producers.set(producer.id, producer);
logger.info(`Producer ${producer.id} (${producer.kind}) added to peer ${this.id}`);
}
addConsumer(consumer) {
this.consumers.set(consumer.id, consumer);
logger.info(`Consumer ${consumer.id} (${consumer.kind}) added to peer ${this.id}`);
}
close() {
logger.info(`Closing peer ${this.id}`);
// Close all consumers
for (const consumer of this.consumers.values()) {
consumer.close();
}
this.consumers.clear();
// Close all producers
for (const producer of this.producers.values()) {
producer.close();
}
this.producers.clear();
// Close all transports
for (const transport of this.transports.values()) {
transport.close();
}
this.transports.clear();
}
}
// ==================== Mediasoup Worker & Router ====================
async function createWorker() {
logger.info('Creating Mediasoup worker...');
worker = await mediasoup.createWorker({
logLevel: 'warn',
rtcMinPort: config.rtcMinPort,
rtcMaxPort: config.rtcMaxPort,
});
worker.on('died', () => {
logger.error('Mediasoup worker died, exiting in 2 seconds...');
setTimeout(() => process.exit(1), 2000);
});
logger.info(`Mediasoup worker created [PID: ${worker.pid}]`);
return worker;
}
async function createRouter() {
logger.info('Creating Mediasoup router...');
const mediaCodecs = [
{
kind: 'audio',
mimeType: 'audio/opus',
clockRate: 48000,
channels: 2
},
{
kind: 'video',
mimeType: 'video/VP8',
clockRate: 90000,
parameters: {
'x-google-start-bitrate': 1000
}
},
{
kind: 'video',
mimeType: 'video/H264',
clockRate: 90000,
parameters: {
'packetization-mode': 1,
'profile-level-id': '42e01f',
'level-asymmetry-allowed': 1
}
}
];
router = await worker.createRouter({ mediaCodecs });
logger.info(`Mediasoup router created [ID: ${router.id}]`);
return router;
}
// ==================== HTTP API Endpoints ====================
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
worker: {
pid: worker?.pid,
dead: worker?.closed
},
router: {
id: router?.id,
closed: router?.closed
},
rooms: rooms.size,
peers: peers.size
});
});
// Get DTLS fingerprint (for MyTelegram backend)
app.get('/api/dtls-fingerprint', (req, res) => {
if (!router) {
return res.status(503).json({ error: 'Router not initialized' });
}
const fingerprint = router.rtpCapabilities.headerExtensions.length > 0
? router.rtpCapabilities
: null;
// Get DTLS parameters from a temporary transport
router.createWebRtcTransport({
listenIps: [{ ip: config.listenIp, announcedIp: config.announcedIp }],
enableUdp: true,
enableTcp: false,
preferUdp: true
}).then(transport => {
const dtls = transport.dtlsParameters;
res.json({
algorithm: dtls.fingerprints[0].algorithm,
value: dtls.fingerprints[0].value
});
transport.close();
}).catch(err => {
logger.error('Failed to get DTLS fingerprint:', err);
res.status(500).json({ error: err.message });
});
});
// Get router RTP capabilities
app.get('/api/rtp-capabilities', (req, res) => {
if (!router) {
return res.status(503).json({ error: 'Router not initialized' });
}
res.json(router.rtpCapabilities);
});
// Get room info
app.get('/api/rooms/:roomId', (req, res) => {
const room = rooms.get(req.params.roomId);
if (!room) {
return res.status(404).json({ error: 'Room not found' });
}
res.json({
id: room.id,
peers: room.getAllPeers().map(p => ({
id: p.id,
producers: p.producers.size,
consumers: p.consumers.size
}))
});
});
// Create WebRTC transport for Telegram MTProto integration
app.post('/api/transports/create', async (req, res) => {
try {
const { roomId, peerId, direction } = req.body;
logger.info(`Creating ${direction} transport for peer ${peerId} in room ${roomId}`);
const transport = await router.createWebRtcTransport({
listenIps: [
{
ip: config.listenIp,
announcedIp: config.announcedIp
}
],
enableUdp: true,
enableTcp: false,
preferUdp: true,
initialAvailableOutgoingBitrate: 1000000,
minimumAvailableOutgoingBitrate: 600000,
maxSctpMessageSize: 262144,
maxIncomingBitrate: 1500000
});
// Store transport info
const key = `${roomId}:${peerId}:${direction}`;
if (!global.transports) global.transports = new Map();
global.transports.set(key, transport);
global.transports.set(transport.id, transport);
logger.info(`Transport ${transport.id} created on port ${transport.tuple?.localPort || 'dynamic'}`);
res.json({
id: transport.id,
iceParameters: transport.iceParameters,
iceCandidates: transport.iceCandidates,
dtlsParameters: transport.dtlsParameters
});
} catch (error) {
logger.error('Error creating transport:', error);
res.status(500).json({ error: error.message });
}
});
// Connect transport with DTLS parameters from client
app.post('/api/transports/connect', async (req, res) => {
try {
const { transportId, dtlsParameters } = req.body;
logger.info(`Connecting transport ${transportId}`);
const transport = global.transports?.get(transportId);
if (!transport) {
return res.status(404).json({ error: 'Transport not found' });
}
await transport.connect({ dtlsParameters });
logger.info(`Transport ${transportId} connected`);
res.json({ success: true });
} catch (error) {
logger.error('Error connecting transport:', error);
res.status(500).json({ error: error.message });
}
});
// Create producer (for sending media)
app.post('/api/produce', async (req, res) => {
try {
const { transportId, kind, rtpParameters } = req.body;
logger.info(`Creating producer on transport ${transportId}, kind: ${kind}`);
const transport = global.transports?.get(transportId);
if (!transport) {
return res.status(404).json({ error: 'Transport not found' });
}
const producer = await transport.produce({
kind,
rtpParameters
});
logger.info(`Producer ${producer.id} created`);
res.json({ producerId: producer.id });
} catch (error) {
logger.error('Error creating producer:', error);
res.status(500).json({ error: error.message });
}
});
// Create consumer (for receiving media)
app.post('/api/consume', async (req, res) => {
try {
const { transportId, producerId, rtpCapabilities } = req.body;
logger.info(`Creating consumer on transport ${transportId} for producer ${producerId}`);
const transport = global.transports?.get(transportId);
if (!transport) {
return res.status(404).json({ error: 'Transport not found' });
}
// Check if can consume
if (!router.canConsume({ producerId, rtpCapabilities })) {
return res.status(400).json({ error: 'Cannot consume' });
}
const consumer = await transport.consume({
producerId,
rtpCapabilities,
paused: false
});
logger.info(`Consumer ${consumer.id} created`);
res.json({
id: consumer.id,
producerId: producerId,
kind: consumer.kind,
rtpParameters: consumer.rtpParameters
});
} catch (error) {
logger.error('Error creating consumer:', error);
res.status(500).json({ error: error.message });
}
});
// ==================== Socket.IO Signaling ====================
io.on('connection', (socket) => {
logger.info(`Socket connected: ${socket.id}`);
socket.on('join-room', async ({ roomId, peerId }, callback) => {
try {
logger.info(`Peer ${peerId} joining room ${roomId}`);
// Get or create room
let room = rooms.get(roomId);
if (!room) {
room = new Room(roomId);
room.router = router;
rooms.set(roomId, room);
}
// Create peer
const peer = new Peer(peerId, socket, roomId);
room.addPeer(peer);
peers.set(socket.id, peer);
socket.join(roomId);
callback({
success: true,
rtpCapabilities: router.rtpCapabilities
});
} catch (error) {
logger.error('Error joining room:', error);
callback({ success: false, error: error.message });
}
});
socket.on('create-transport', async ({ roomId, direction }, callback) => {
try {
const peer = peers.get(socket.id);
if (!peer) {
throw new Error('Peer not found');
}
const transport = await router.createWebRtcTransport({
listenIps: [
{
ip: config.listenIp,
announcedIp: config.announcedIp
}
],
enableUdp: true,
enableTcp: false,
preferUdp: true,
initialAvailableOutgoingBitrate: 1000000,
minimumAvailableOutgoingBitrate: 600000,
maxSctpMessageSize: 262144,
maxIncomingBitrate: 1500000
});
peer.addTransport(transport);
transport.on('dtlsstatechange', (dtlsState) => {
logger.debug(`Transport ${transport.id} DTLS state: ${dtlsState}`);
if (dtlsState === 'closed') {
transport.close();
}
});
transport.on('close', () => {
logger.info(`Transport ${transport.id} closed`);
});
callback({
success: true,
transport: {
id: transport.id,
iceParameters: transport.iceParameters,
iceCandidates: transport.iceCandidates,
dtlsParameters: transport.dtlsParameters
}
});
} catch (error) {
logger.error('Error creating transport:', error);
callback({ success: false, error: error.message });
}
});
socket.on('connect-transport', async ({ transportId, dtlsParameters }, callback) => {
try {
const peer = peers.get(socket.id);
if (!peer) {
throw new Error('Peer not found');
}
const transport = peer.transports.get(transportId);
if (!transport) {
throw new Error('Transport not found');
}
await transport.connect({ dtlsParameters });
logger.info(`Transport ${transportId} connected for peer ${peer.id}`);
callback({ success: true });
} catch (error) {
logger.error('Error connecting transport:', error);
callback({ success: false, error: error.message });
}
});
socket.on('produce', async ({ transportId, kind, rtpParameters }, callback) => {
try {
const peer = peers.get(socket.id);
if (!peer) {
throw new Error('Peer not found');
}
const transport = peer.transports.get(transportId);
if (!transport) {
throw new Error('Transport not found');
}
const producer = await transport.produce({
kind,
rtpParameters
});
peer.addProducer(producer);
producer.on('transportclose', () => {
logger.info(`Producer ${producer.id} transport closed`);
producer.close();
peer.producers.delete(producer.id);
});
// Notify other peers in room about new producer
const room = rooms.get(peer.roomId);
if (room) {
socket.to(peer.roomId).emit('new-producer', {
peerId: peer.id,
producerId: producer.id,
kind: producer.kind
});
}
callback({
success: true,
producerId: producer.id
});
} catch (error) {
logger.error('Error producing:', error);
callback({ success: false, error: error.message });
}
});
socket.on('consume', async ({ transportId, producerId, rtpCapabilities }, callback) => {
try {
const peer = peers.get(socket.id);
if (!peer) {
throw new Error('Peer not found');
}
const transport = peer.transports.get(transportId);
if (!transport) {
throw new Error('Transport not found');
}
// Find the producer peer
const room = rooms.get(peer.roomId);
let producer = null;
for (const otherPeer of room.getAllPeers()) {
producer = otherPeer.producers.get(producerId);
if (producer) break;
}
if (!producer) {
throw new Error('Producer not found');
}
// Check if router can consume
if (!router.canConsume({ producerId, rtpCapabilities })) {
throw new Error('Cannot consume');
}
const consumer = await transport.consume({
producerId,
rtpCapabilities,
paused: false
});
peer.addConsumer(consumer);
consumer.on('transportclose', () => {
logger.info(`Consumer ${consumer.id} transport closed`);
consumer.close();
peer.consumers.delete(consumer.id);
});
consumer.on('producerclose', () => {
logger.info(`Consumer ${consumer.id} producer closed`);
socket.emit('consumer-closed', { consumerId: consumer.id });
consumer.close();
peer.consumers.delete(consumer.id);
});
callback({
success: true,
consumer: {
id: consumer.id,
producerId: producerId,
kind: consumer.kind,
rtpParameters: consumer.rtpParameters
}
});
} catch (error) {
logger.error('Error consuming:', error);
callback({ success: false, error: error.message });
}
});
socket.on('disconnect', () => {
logger.info(`Socket disconnected: ${socket.id}`);
const peer = peers.get(socket.id);
if (peer) {
const room = rooms.get(peer.roomId);
if (room) {
const isEmpty = room.removePeer(peer.id);
if (isEmpty) {
rooms.delete(room.id);
}
// Notify other peers
socket.to(peer.roomId).emit('peer-left', { peerId: peer.id });
}
peers.delete(socket.id);
}
});
});
// ==================== Server Startup ====================
async function startServer() {
try {
await createWorker();
await createRouter();
server.listen(config.httpPort, () => {
logger.info(`==============================================`);
logger.info(`MyTelegram Mediasoup SFU Server Started`);
logger.info(`==============================================`);
logger.info(`HTTP API: http://localhost:${config.httpPort}`);
logger.info(`WebRTC RTC Ports: ${config.rtcMinPort}-${config.rtcMaxPort}`);
logger.info(`Announced IP: ${config.announcedIp}`);
logger.info(`==============================================`);
});
} catch (error) {
logger.error('Failed to start server:', error);
process.exit(1);
}
}
// Graceful shutdown
process.on('SIGINT', async () => {
logger.info('Shutting down gracefully...');
// Close all rooms
for (const room of rooms.values()) {
room.close();
}
rooms.clear();
peers.clear();
if (worker) {
worker.close();
}
server.close(() => {
logger.info('Server closed');
process.exit(0);
});
});
startServer();
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
find . -iname "Logs" | xargs rm -rf
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
find . -iname "bin" | xargs rm -rf
find . -iname "obj" | xargs rm -rf
+240
View File
@@ -0,0 +1,240 @@
:: For more information,please visit https://github.com/loyldg/mytelegram
set App__QueryServerEventStoreDatabaseName=tg-1
set App__QueryServerReadModelDatabaseName=tg-1
set App__DatabaseName=tg
set App__BotDatabaseName=tg
set App__FixedVerifyCode=22222
set Serilog__MinimumLevel__Default=Information
set Serilog__MinimumLevel__Override__Microsoft=Information
:: Use separate media server
set App__Servers__2__Enabled=True
set App__Servers__3__Enabled=True
:: RabbitMQ
:: set RabbitMQ__Connections__Default__HostName=localhost
:: set RabbitMQ__Connections__Default__Password=test
:: set RabbitMQ__Connections__Default__Port=5672
:: set RabbitMQ__Connections__Default__UserName=test
:: set RabbitMQ__EventBus__ExchangeName=MyTelegramExchange
:: Redis
:: set Redis__Configuration=localhost:6379
:: MongoDB
:: set ConnectionStrings__Default=mongodb://localhost:27017
:: Minio
:: set Minio__AccessKey=test
:: set Minio__BucketName=tg-files
:: set Minio__CreateBucketIfNotExists=True
:: set Minio__Endpoint=localhost:9000
:: set Minio__SecretKey=12345678
:: TwilioSms
:: set TwilioSms__AccountSId=
:: set TwilioSms__AuthToken=
:: set TwilioSms__Enabled=False
:: set TwilioSms__FromNumber=
:: VonageSms
:: set VonageSms__ApiKey=
:: set VonageSms__ApiSecret=
:: set VonageSms__BrandName=
:: set VonageSms__Enabled=False
:: App
:: set App=
:: set App__AllowedUserIds__0=2000001
:: set App__AutoCreateSuperGroup=True
:: set App__BotDatabaseName=tg
:: set App__ChannelGetDifferenceIntervalSeconds=60
:: set App__CommandServerObjectIds=
:: set App__DatabaseName=tg
:: set App__DcOptions__0__Cdn=False
:: set App__DcOptions__0__Enabled=True
:: set App__DcOptions__0__Id=1
:: set App__DcOptions__0__IpAddress=192.168.1.100
:: set App__DcOptions__0__Ipv6=False
:: set App__DcOptions__0__MediaOnly=False
:: set App__DcOptions__0__Port=20443
:: set App__DcOptions__0__Static=False
:: set App__DcOptions__0__TcpoOnly=True
:: set App__DcOptions__0__ThisPortOnly=True
:: set App__DcOptions__1__Cdn=False
:: set App__DcOptions__1__Enabled=True
:: set App__DcOptions__1__Id=2
:: set App__DcOptions__1__IpAddress=192.168.1.100
:: set App__DcOptions__1__Ipv6=False
:: set App__DcOptions__1__MediaOnly=False
:: set App__DcOptions__1__Port=20543
:: set App__DcOptions__1__Static=False
:: set App__DcOptions__1__TcpoOnly=True
:: set App__DcOptions__1__ThisPortOnly=True
:: set App__DcOptions__2__Cdn=False
:: set App__DcOptions__2__Enabled=True
:: set App__DcOptions__2__Id=2
:: set App__DcOptions__2__IpAddress=192.168.1.100
:: set App__DcOptions__2__Ipv6=False
:: set App__DcOptions__2__MediaOnly=True
:: set App__DcOptions__2__Port=20643
:: set App__DcOptions__2__Static=False
:: set App__DcOptions__2__TcpoOnly=True
:: set App__DcOptions__2__ThisPortOnly=True
:: set App__DcOptions__3__Cdn=False
:: set App__DcOptions__3__Enabled=True
:: set App__DcOptions__3__Id=2
:: set App__DcOptions__3__IpAddress=192.168.1.100
:: set App__DcOptions__3__Ipv6=False
:: set App__DcOptions__3__MediaOnly=True
:: set App__DcOptions__3__Port=20644
:: set App__DcOptions__3__Static=False
:: set App__DcOptions__3__TcpoOnly=True
:: set App__DcOptions__3__ThisPortOnly=True
:: set App__DispatchUnknownObjectToMessengerServer=True
:: set App__EnableFutureAuthToken=True
:: set App__FileServerGrpcServiceUrl=http://localhost:10001
:: set App__FixedVerifyCode=
:: set App__IdGeneratorGrpcServiceUrl=http://localhost:10002
:: set App__IsMediaDc=False
:: set App__JoinChatDomain=https://t.me
:: set App__LoadAllStickersIntoMemory=True
:: set App__MediaDcId=2
:: set App__MediaOnly=False
:: set App__MessengerServerGrpcServiceUrl=http://localhost:10003
:: set App__OfficialTelegramBotOptions__ProxyUrl=socks5://localhost:10808
:: set App__OfficialTelegramBotOptions__Token=
:: set App__OfficialTelegramBotOptions__UseProxy=True
:: set App__PrivateKeyFilePath=private.pkcs8.key
:: set App__QueryServerEventStoreDatabaseName=tg-1
:: set App__QueryServerReadModelDatabaseName=tg-1
:: set App__SendWelcomeMessageAfterUserSignIn=False
:: set App__Servers__0__CertPemFilePath=
:: set App__Servers__0__Enabled=True
:: set App__Servers__0__EnableProxyProtocolV2=False
:: set App__Servers__0__Ip=
:: set App__Servers__0__Ipv6=True
:: set App__Servers__0__KeyPemFilePath=
:: set App__Servers__0__MediaOnly=False
:: set App__Servers__0__Port=20443
:: set App__Servers__0__ServerType=0
:: set App__Servers__0__Ssl=False
:: set App__Servers__1__CertPemFilePath=
:: set App__Servers__1__Enabled=True
:: set App__Servers__1__EnableProxyProtocolV2=False
:: set App__Servers__1__Ip=
:: set App__Servers__1__Ipv6=True
:: set App__Servers__1__KeyPemFilePath=
:: set App__Servers__1__MediaOnly=False
:: set App__Servers__1__Port=20543
:: set App__Servers__1__ServerType=0
:: set App__Servers__1__Ssl=False
:: set App__Servers__2__CertPemFilePath=
:: set App__Servers__2__Enabled=False
:: set App__Servers__2__EnableProxyProtocolV2=False
:: set App__Servers__2__Ip=
:: set App__Servers__2__Ipv6=True
:: set App__Servers__2__KeyPemFilePath=
:: set App__Servers__2__MediaOnly=True
:: set App__Servers__2__Port=20643
:: set App__Servers__2__ServerType=0
:: set App__Servers__2__Ssl=False
:: set App__Servers__3__CertPemFilePath=
:: set App__Servers__3__Enabled=False
:: set App__Servers__3__EnableProxyProtocolV2=False
:: set App__Servers__3__Ip=
:: set App__Servers__3__Ipv6=True
:: set App__Servers__3__KeyPemFilePath=
:: set App__Servers__3__MediaOnly=True
:: set App__Servers__3__Port=20644
:: set App__Servers__3__ServerType=0
:: set App__Servers__3__Ssl=False
:: set App__Servers__4__CertPemFilePath=_wildcard.telegram2.com.pem
:: set App__Servers__4__Enabled=True
:: set App__Servers__4__EnableProxyProtocolV2=False
:: set App__Servers__4__Ip=
:: set App__Servers__4__Ipv6=True
:: set App__Servers__4__KeyPemFilePath=_wildcard.telegram2.com-key.pem
:: set App__Servers__4__MediaOnly=False
:: set App__Servers__4__Port=30443
:: set App__Servers__4__ServerType=1
:: set App__Servers__4__Ssl=True
:: set App__Servers__5__CertPemFilePath=
:: set App__Servers__5__Enabled=True
:: set App__Servers__5__EnableProxyProtocolV2=False
:: set App__Servers__5__Ip=
:: set App__Servers__5__Ipv6=True
:: set App__Servers__5__KeyPemFilePath=
:: set App__Servers__5__MediaOnly=False
:: set App__Servers__5__Port=30444
:: set App__Servers__5__ServerType=1
:: set App__Servers__5__Ssl=False
:: set App__SetPremiumToTrueAfterUserCreated=True
:: set App__SubscribeLocalRequest=True
:: set App__SubscribeRemoteRequest=True
:: set App__TempAuthKeyExpirationMinutes=720
:: set App__ThisDcId=1
:: set App__UploadRootPath=uploads2
:: set App__UseExternalWebHookSender=False
:: set App__UseInMemoryFilters=True
:: set App__VerificationCodeExpirationSeconds=300
:: set App__VerificationCodeLength=5
:: set App__WebHookSenderUrl=
:: set App__WebRtcConnections__0__Ip=192.168.1.100
:: set App__WebRtcConnections__0__Ipv6=
:: set App__WebRtcConnections__0__Password=b
:: set App__WebRtcConnections__0__Port=20444
:: set App__WebRtcConnections__0__Stun=True
:: set App__WebRtcConnections__0__Turn=True
:: set App__WebRtcConnections__0__UserName=a
cd ./command-server
start MyTelegram.Messenger.CommandServer.exe
cd ../query-server
start MyTelegram.Messenger.QueryServer.exe
cd ../gateway
start MyTelegram.GatewayServer.exe
:: start MyTelegram.GatewayServer.exe ^
:: RabbitMQ:EventBus:ExchangeName=MyTelegramExchange.MediaOnly ^
:: RabbitMQ:EventBus:ClientName=MyTelegramGatewayServer.MediaOnly ^
:: RabbitMQ:EventBus:TopicExchangeName=MyTelegramTopicExchange.MediaOnly ^
:: App:Servers:0:Enabled=false ^
:: App:Servers:1:Enabled=false ^
:: App:Servers:2:Enabled=true ^
:: App:Servers:3:Enabled=true ^
:: App:Servers:4:Enabled=false ^
:: App:Servers:5:Enabled=false
cd ../auth
start MyTelegram.AuthServer.exe
:: start MyTelegram.AuthServer.exe ^
:: RabbitMQ:EventBus:ExchangeName=MyTelegramExchange.MediaOnly ^
:: RabbitMQ:EventBus:ClientName=MyTelegramAuthServer.MediaOnly ^
:: RabbitMQ:EventBus:TopicExchangeName=MyTelegramTopicExchange.MediaOnly
cd ../session
start MyTelegram.SessionServer.exe
:: start MyTelegram.SessionServer.exe ^
:: RabbitMQ:EventBus:ExchangeName=MyTelegramExchange.MediaOnly ^
:: App:MediaOnly=true ^
:: RabbitMQ:EventBus:ClientName=MyTelegramSessionServer.MediaOnly ^
:: RabbitMQ:EventBus:TopicExchangeName=MyTelegramTopicExchange.MediaOnly
cd ../file
start MyTelegram.FileServer.exe urls=http://.+:10001
:: start MyTelegram.FileServer.exe urls=http://.+:10007 ^
:: RabbitMQ:EventBus:ClientName=MyTelegramFileServer.MediaOnly ^
:: RabbitMQ:EventBus:TopicExchangeName=MyTelegramTopicExchange.MediaOnly
cd ../messenger-grpc-service
start MyTelegram.MessengerServer.GrpcService.exe urls=http://.+:10003
cd ../sms
start MyTelegram.SmsSender.exe
+253
View File
@@ -0,0 +1,253 @@
# For more information,please visit https://github.com/loyldg/mytelegram
$env:App__QueryServerEventStoreDatabaseName = "tg-1"
$env:App__QueryServerReadModelDatabaseName = "tg-1"
$env:App__DatabaseName = "tg"
$env:App__BotDatabaseName = "tg"
$env:App__FixedVerifyCode = "22222"
$env:Serilog__MinimumLevel__Default = "Information"
$env:Serilog__MinimumLevel__Override__Microsoft = "Information"
# Use separate media server
$env:App__Servers__2__Enabled = "True"
$env:App__Servers__3__Enabled = "True"
# RabbitMQ
# $env:RabbitMQ__Connections__Default__HostName=localhost
# $env:RabbitMQ__Connections__Default__Password=test
# $env:RabbitMQ__Connections__Default__Port=5672
# $env:RabbitMQ__Connections__Default__UserName=test
# $env:RabbitMQ__EventBus__ExchangeName=MyTelegramExchange
# Redis
# $env:Redis__Configuration=localhost:6379
# MongoDB
# $env:ConnectionStrings__Default=mongodb://localhost:27017
# Minio
# $env:Minio__AccessKey=test
# $env:Minio__BucketName=tg-files
# $env:Minio__CreateBucketIfNotExists=True
# $env:Minio__Endpoint=localhost:9000
# $env:Minio__SecretKey=12345678
# TwilioSms
# $env:TwilioSms__AccountSId=
# $env:TwilioSms__AuthToken=
# $env:TwilioSms__Enabled=False
# $env:TwilioSms__FromNumber=
# VonageSms
# $env:VonageSms__ApiKey=
# $env:VonageSms__ApiSecret=
# $env:VonageSms__BrandName=
# $env:VonageSms__Enabled=False
# App
# $env:App=
# $env:App__AllowedUserIds__0=2000001
# $env:App__AutoCreateSuperGroup=True
# $env:App__BotDatabaseName=tg
# $env:App__ChannelGetDifferenceIntervalSeconds=60
# $env:App__CommandServerObjectIds=
# $env:App__DatabaseName=tg
# $env:App__DcOptions__0__Cdn=False
# $env:App__DcOptions__0__Enabled=True
# $env:App__DcOptions__0__Id=1
# $env:App__DcOptions__0__IpAddress=192.168.1.100
# $env:App__DcOptions__0__Ipv6=False
# $env:App__DcOptions__0__MediaOnly=False
# $env:App__DcOptions__0__Port=20443
# $env:App__DcOptions__0__Static=False
# $env:App__DcOptions__0__TcpoOnly=True
# $env:App__DcOptions__0__ThisPortOnly=True
# $env:App__DcOptions__1__Cdn=False
# $env:App__DcOptions__1__Enabled=True
# $env:App__DcOptions__1__Id=2
# $env:App__DcOptions__1__IpAddress=192.168.1.100
# $env:App__DcOptions__1__Ipv6=False
# $env:App__DcOptions__1__MediaOnly=False
# $env:App__DcOptions__1__Port=20543
# $env:App__DcOptions__1__Static=False
# $env:App__DcOptions__1__TcpoOnly=True
# $env:App__DcOptions__1__ThisPortOnly=True
# $env:App__DcOptions__2__Cdn=False
# $env:App__DcOptions__2__Enabled=True
# $env:App__DcOptions__2__Id=2
# $env:App__DcOptions__2__IpAddress=192.168.1.100
# $env:App__DcOptions__2__Ipv6=False
# $env:App__DcOptions__2__MediaOnly=True
# $env:App__DcOptions__2__Port=20643
# $env:App__DcOptions__2__Static=False
# $env:App__DcOptions__2__TcpoOnly=True
# $env:App__DcOptions__2__ThisPortOnly=True
# $env:App__DcOptions__3__Cdn=False
# $env:App__DcOptions__3__Enabled=True
# $env:App__DcOptions__3__Id=2
# $env:App__DcOptions__3__IpAddress=192.168.1.100
# $env:App__DcOptions__3__Ipv6=False
# $env:App__DcOptions__3__MediaOnly=True
# $env:App__DcOptions__3__Port=20644
# $env:App__DcOptions__3__Static=False
# $env:App__DcOptions__3__TcpoOnly=True
# $env:App__DcOptions__3__ThisPortOnly=True
# $env:App__DispatchUnknownObjectToMessengerServer=True
# $env:App__EnableFutureAuthToken=True
# $env:App__FileServerGrpcServiceUrl=http://localhost:10001
# $env:App__FixedVerifyCode=
# $env:App__IdGeneratorGrpcServiceUrl=http://localhost:10002
# $env:App__IsMediaDc=False
# $env:App__JoinChatDomain=https://t.me
# $env:App__LoadAllStickersIntoMemory=True
# $env:App__MediaDcId=2
# $env:App__MediaOnly=False
# $env:App__MessengerServerGrpcServiceUrl=http://localhost:10003
# $env:App__OfficialTelegramBotOptions__ProxyUrl=socks5://localhost:10808
# $env:App__OfficialTelegramBotOptions__Token=
# $env:App__OfficialTelegramBotOptions__UseProxy=True
# $env:App__PrivateKeyFilePath=private.pkcs8.key
# $env:App__QueryServerEventStoreDatabaseName=tg-1
# $env:App__QueryServerReadModelDatabaseName=tg-1
# $env:App__SendWelcomeMessageAfterUserSignIn=False
# $env:App__Servers__0__CertPemFilePath=
# $env:App__Servers__0__Enabled=True
# $env:App__Servers__0__EnableProxyProtocolV2=False
# $env:App__Servers__0__Ip=
# $env:App__Servers__0__Ipv6=True
# $env:App__Servers__0__KeyPemFilePath=
# $env:App__Servers__0__MediaOnly=False
# $env:App__Servers__0__Port=20443
# $env:App__Servers__0__ServerType=0
# $env:App__Servers__0__Ssl=False
# $env:App__Servers__1__CertPemFilePath=
# $env:App__Servers__1__Enabled=True
# $env:App__Servers__1__EnableProxyProtocolV2=False
# $env:App__Servers__1__Ip=
# $env:App__Servers__1__Ipv6=True
# $env:App__Servers__1__KeyPemFilePath=
# $env:App__Servers__1__MediaOnly=False
# $env:App__Servers__1__Port=20543
# $env:App__Servers__1__ServerType=0
# $env:App__Servers__1__Ssl=False
# $env:App__Servers__2__CertPemFilePath=
# $env:App__Servers__2__Enabled=False
# $env:App__Servers__2__EnableProxyProtocolV2=False
# $env:App__Servers__2__Ip=
# $env:App__Servers__2__Ipv6=True
# $env:App__Servers__2__KeyPemFilePath=
# $env:App__Servers__2__MediaOnly=True
# $env:App__Servers__2__Port=20643
# $env:App__Servers__2__ServerType=0
# $env:App__Servers__2__Ssl=False
# $env:App__Servers__3__CertPemFilePath=
# $env:App__Servers__3__Enabled=False
# $env:App__Servers__3__EnableProxyProtocolV2=False
# $env:App__Servers__3__Ip=
# $env:App__Servers__3__Ipv6=True
# $env:App__Servers__3__KeyPemFilePath=
# $env:App__Servers__3__MediaOnly=True
# $env:App__Servers__3__Port=20644
# $env:App__Servers__3__ServerType=0
# $env:App__Servers__3__Ssl=False
# $env:App__Servers__4__CertPemFilePath=_wildcard.telegram2.com.pem
# $env:App__Servers__4__Enabled=True
# $env:App__Servers__4__EnableProxyProtocolV2=False
# $env:App__Servers__4__Ip=
# $env:App__Servers__4__Ipv6=True
# $env:App__Servers__4__KeyPemFilePath=_wildcard.telegram2.com-key.pem
# $env:App__Servers__4__MediaOnly=False
# $env:App__Servers__4__Port=30443
# $env:App__Servers__4__ServerType=1
# $env:App__Servers__4__Ssl=True
# $env:App__Servers__5__CertPemFilePath=
# $env:App__Servers__5__Enabled=True
# $env:App__Servers__5__EnableProxyProtocolV2=False
# $env:App__Servers__5__Ip=
# $env:App__Servers__5__Ipv6=True
# $env:App__Servers__5__KeyPemFilePath=
# $env:App__Servers__5__MediaOnly=False
# $env:App__Servers__5__Port=30444
# $env:App__Servers__5__ServerType=1
# $env:App__Servers__5__Ssl=False
# $env:App__SetPremiumToTrueAfterUserCreated=True
# $env:App__SubscribeLocalRequest=True
# $env:App__SubscribeRemoteRequest=True
# $env:App__TempAuthKeyExpirationMinutes=720
# $env:App__ThisDcId=1
# $env:App__UploadRootPath=uploads2
# $env:App__UseExternalWebHookSender=False
# $env:App__UseInMemoryFilters=True
# $env:App__VerificationCodeExpirationSeconds=300
# $env:App__VerificationCodeLength=5
# $env:App__WebHookSenderUrl=
# $env:App__WebRtcConnections__0__Ip=192.168.1.100
# $env:App__WebRtcConnections__0__Ipv6=
# $env:App__WebRtcConnections__0__Password=b
# $env:App__WebRtcConnections__0__Port=20444
# $env:App__WebRtcConnections__0__Stun=True
# $env:App__WebRtcConnections__0__Turn=True
# $env:App__WebRtcConnections__0__UserName=a
Set-Location -Path ".\command-server"
Start-Process -FilePath "MyTelegram.Messenger.CommandServer.exe"
Set-Location -Path "..\query-server"
Start-Process -FilePath "MyTelegram.Messenger.QueryServer.exe"
Set-Location -Path "..\gateway"
Start-Process -FilePath "MyTelegram.GatewayServer.exe"
<#
Start-Process -FilePath "MyTelegram.GatewayServer.exe" -ArgumentList `
"RabbitMQ:EventBus:ExchangeName=MyTelegramExchange.MediaOnly", `
"RabbitMQ:EventBus:ClientName=MyTelegramGatewayServer.MediaOnly", `
"RabbitMQ:EventBus:TopicExchangeName=MyTelegramTopicExchange.MediaOnly", `
"App:Servers:0:Enabled=false", `
"App:Servers:1:Enabled=false", `
"App:Servers:2:Enabled=true", `
"App:Servers:3:Enabled=true", `
"App:Servers:4:Enabled=false", `
"App:Servers:5:Enabled=false"
#>
Set-Location -Path "..\auth"
Start-Process -FilePath "MyTelegram.AuthServer.exe"
<#
Start-Process -FilePath "MyTelegram.AuthServer.exe" -ArgumentList `
"RabbitMQ:EventBus:ExchangeName=MyTelegramExchange.MediaOnly", `
"RabbitMQ:EventBus:ClientName=MyTelegramAuthServer.MediaOnly", `
"RabbitMQ:EventBus:TopicExchangeName=MyTelegramTopicExchange.MediaOnly"
#>
Set-Location -Path "..\session"
Start-Process -FilePath "MyTelegram.SessionServer.exe"
<#
Start-Process -FilePath "MyTelegram.SessionServer.exe" -ArgumentList `
"RabbitMQ:EventBus:ExchangeName=MyTelegramExchange.MediaOnly", `
"App:MediaOnly=true", `
"RabbitMQ:EventBus:ClientName=MyTelegramSessionServer.MediaOnly", `
"RabbitMQ:EventBus:TopicExchangeName=MyTelegramTopicExchange.MediaOnly"
#>
Set-Location -Path "..\file"
Start-Process -FilePath "MyTelegram.FileServer.exe" -ArgumentList "urls=http://.+:10001"
<#
Start-Process -FilePath "MyTelegram.FileServer.exe" -ArgumentList `
"urls=http://.+:10007", `
"RabbitMQ:EventBus:ClientName=MyTelegramFileServer.MediaOnly", `
"RabbitMQ:EventBus:TopicExchangeName=MyTelegramTopicExchange.MediaOnly"
#>
Set-Location -Path "..\messenger-grpc-service"
Start-Process -FilePath "MyTelegram.MessengerServer.GrpcService.exe" -ArgumentList "urls=http://.+:10003"
Set-Location -Path "..\sms"
Start-Process -FilePath "MyTelegram.SmsSender.exe"
Set-Location ../
+240
View File
@@ -0,0 +1,240 @@
#!/bin/sh
# For more information,please visit https://github.com/loyldg/mytelegram
export App__QueryServerEventStoreDatabaseName=tg-1
export App__QueryServerReadModelDatabaseName=tg-1
export App__DatabaseName=tg
export App__BotDatabaseName=tg
export App__FixedVerifyCode=22222
export Serilog__MinimumLevel__Default=Information
export Serilog__MinimumLevel__Override__Microsoft=Information
# Use separate media server
export App__Servers__2__Enabled=True
export App__Servers__3__Enabled=True
# RabbitMQ
# export RabbitMQ__Connections__Default__HostName=localhost
# export RabbitMQ__Connections__Default__Password=test
# export RabbitMQ__Connections__Default__Port=5672
# export RabbitMQ__Connections__Default__UserName=test
# export RabbitMQ__EventBus__ExchangeName=MyTelegramExchange
# Redis
# export Redis__Configuration=localhost:6379
# MongoDB
# export ConnectionStrings__Default=mongodb://localhost:27017
# Minio
# export Minio__AccessKey=test
# export Minio__BucketName=tg-files
# export Minio__CreateBucketIfNotExists=True
# export Minio__Endpoint=localhost:9000
# export Minio__SecretKey=12345678
# TwilioSms
# export TwilioSms__AccountSId=
# export TwilioSms__AuthToken=
# export TwilioSms__Enabled=False
# export TwilioSms__FromNumber=
# VonageSms
# export VonageSms__ApiKey=
# export VonageSms__ApiSecret=
# export VonageSms__BrandName=
# export VonageSms__Enabled=False
# App
# export App__AllowedUserIds__0=2000001
# export App__AutoCreateSuperGroup=True
# export App__BotDatabaseName=tg
# export App__ChannelGetDifferenceIntervalSeconds=60
# export App__CommandServerObjectIds=
# export App__DatabaseName=tg
# export App__DcOptions__0__Cdn=False
# export App__DcOptions__0__Enabled=True
# export App__DcOptions__0__Id=1
# export App__DcOptions__0__IpAddress=192.168.1.100
# export App__DcOptions__0__Ipv6=False
# export App__DcOptions__0__MediaOnly=False
# export App__DcOptions__0__Port=20443
# export App__DcOptions__0__Static=False
# export App__DcOptions__0__TcpoOnly=True
# export App__DcOptions__0__ThisPortOnly=True
# export App__DcOptions__1__Cdn=False
# export App__DcOptions__1__Enabled=True
# export App__DcOptions__1__Id=2
# export App__DcOptions__1__IpAddress=192.168.1.100
# export App__DcOptions__1__Ipv6=False
# export App__DcOptions__1__MediaOnly=False
# export App__DcOptions__1__Port=20543
# export App__DcOptions__1__Static=False
# export App__DcOptions__1__TcpoOnly=True
# export App__DcOptions__1__ThisPortOnly=True
# export App__DcOptions__2__Cdn=False
# export App__DcOptions__2__Enabled=True
# export App__DcOptions__2__Id=2
# export App__DcOptions__2__IpAddress=192.168.1.100
# export App__DcOptions__2__Ipv6=False
# export App__DcOptions__2__MediaOnly=True
# export App__DcOptions__2__Port=20643
# export App__DcOptions__2__Static=False
# export App__DcOptions__2__TcpoOnly=True
# export App__DcOptions__2__ThisPortOnly=True
# export App__DcOptions__3__Cdn=False
# export App__DcOptions__3__Enabled=True
# export App__DcOptions__3__Id=2
# export App__DcOptions__3__IpAddress=192.168.1.100
# export App__DcOptions__3__Ipv6=False
# export App__DcOptions__3__MediaOnly=True
# export App__DcOptions__3__Port=20644
# export App__DcOptions__3__Static=False
# export App__DcOptions__3__TcpoOnly=True
# export App__DcOptions__3__ThisPortOnly=True
# export App__DispatchUnknownObjectToMessengerServer=True
# export App__EnableFutureAuthToken=True
# export App__FileServerGrpcServiceUrl=http://localhost:10001
# export App__FixedVerifyCode=
# export App__IdGeneratorGrpcServiceUrl=http://localhost:10002
# export App__IsMediaDc=False
# export App__JoinChatDomain=https://t.me
# export App__LoadAllStickersIntoMemory=True
# export App__MediaDcId=2
# export App__MediaOnly=False
# export App__MessengerServerGrpcServiceUrl=http://localhost:10003
# export App__OfficialTelegramBotOptions__ProxyUrl=socks5://localhost:10808
# export App__OfficialTelegramBotOptions__Token=
# export App__OfficialTelegramBotOptions__UseProxy=True
# export App__PrivateKeyFilePath=private.pkcs8.key
# export App__QueryServerEventStoreDatabaseName=tg-1
# export App__QueryServerReadModelDatabaseName=tg-1
# export App__SendWelcomeMessageAfterUserSignIn=False
# export App__Servers__0__CertPemFilePath=
# export App__Servers__0__Enabled=True
# export App__Servers__0__EnableProxyProtocolV2=False
# export App__Servers__0__Ip=
# export App__Servers__0__Ipv6=True
# export App__Servers__0__KeyPemFilePath=
# export App__Servers__0__MediaOnly=False
# export App__Servers__0__Port=20443
# export App__Servers__0__ServerType=0
# export App__Servers__0__Ssl=False
# export App__Servers__1__CertPemFilePath=
# export App__Servers__1__Enabled=True
# export App__Servers__1__EnableProxyProtocolV2=False
# export App__Servers__1__Ip=
# export App__Servers__1__Ipv6=True
# export App__Servers__1__KeyPemFilePath=
# export App__Servers__1__MediaOnly=False
# export App__Servers__1__Port=20543
# export App__Servers__1__ServerType=0
# export App__Servers__1__Ssl=False
# export App__Servers__2__CertPemFilePath=
# export App__Servers__2__Enabled=False
# export App__Servers__2__EnableProxyProtocolV2=False
# export App__Servers__2__Ip=
# export App__Servers__2__Ipv6=True
# export App__Servers__2__KeyPemFilePath=
# export App__Servers__2__MediaOnly=True
# export App__Servers__2__Port=20643
# export App__Servers__2__ServerType=0
# export App__Servers__2__Ssl=False
# export App__Servers__3__CertPemFilePath=
# export App__Servers__3__Enabled=False
# export App__Servers__3__EnableProxyProtocolV2=False
# export App__Servers__3__Ip=
# export App__Servers__3__Ipv6=True
# export App__Servers__3__KeyPemFilePath=
# export App__Servers__3__MediaOnly=True
# export App__Servers__3__Port=20644
# export App__Servers__3__ServerType=0
# export App__Servers__3__Ssl=False
# export App__Servers__4__CertPemFilePath=_wildcard.telegram2.com.pem
# export App__Servers__4__Enabled=True
# export App__Servers__4__EnableProxyProtocolV2=False
# export App__Servers__4__Ip=
# export App__Servers__4__Ipv6=True
# export App__Servers__4__KeyPemFilePath=_wildcard.telegram2.com-key.pem
# export App__Servers__4__MediaOnly=False
# export App__Servers__4__Port=30443
# export App__Servers__4__ServerType=1
# export App__Servers__4__Ssl=True
# export App__Servers__5__CertPemFilePath=
# export App__Servers__5__Enabled=True
# export App__Servers__5__EnableProxyProtocolV2=False
# export App__Servers__5__Ip=
# export App__Servers__5__Ipv6=True
# export App__Servers__5__KeyPemFilePath=
# export App__Servers__5__MediaOnly=False
# export App__Servers__5__Port=30444
# export App__Servers__5__ServerType=1
# export App__Servers__5__Ssl=False
# export App__SetPremiumToTrueAfterUserCreated=True
# export App__SubscribeLocalRequest=True
# export App__SubscribeRemoteRequest=True
# export App__TempAuthKeyExpirationMinutes=720
# export App__ThisDcId=1
# export App__UploadRootPath=uploads2
# export App__UseExternalWebHookSender=False
# export App__UseInMemoryFilters=True
# export App__VerificationCodeExpirationSeconds=300
# export App__VerificationCodeLength=5
# export App__WebHookSenderUrl=
# export App__WebRtcConnections__0__Ip=192.168.1.100
# export App__WebRtcConnections__0__Ipv6=
# export App__WebRtcConnections__0__Password=b
# export App__WebRtcConnections__0__Port=20444
# export App__WebRtcConnections__0__Stun=True
# export App__WebRtcConnections__0__Turn=True
# export App__WebRtcConnections__0__UserName=a
cd ./command-server
./MyTelegram.Messenger.CommandServer &
cd ../query-server
./MyTelegram.Messenger.QueryServer &
cd ../gateway
./MyTelegram.GatewayServer &
# ./MyTelegram.GatewayServer \
# RabbitMQ:EventBus:ExchangeName=MyTelegramExchange.MediaOnly \
# RabbitMQ:EventBus:ClientName=MyTelegramGatewayServer.MediaOnly \
# RabbitMQ:EventBus:TopicExchangeName=MyTelegramTopicExchange.MediaOnly \
# App:Servers:0:Enabled=false \
# App:Servers:1:Enabled=false \
# App:Servers:2:Enabled=true \
# App:Servers:3:Enabled=true \
# App:Servers:4:Enabled=false \
# App:Servers:5:Enabled=false &
cd ../auth
./MyTelegram.AuthServer &
# ./MyTelegram.AuthServer \
# RabbitMQ:EventBus:ExchangeName=MyTelegramExchange.MediaOnly \
# RabbitMQ:EventBus:ClientName=MyTelegramAuthServer.MediaOnly \
# RabbitMQ:EventBus:TopicExchangeName=MyTelegramTopicExchange.MediaOnly &
cd ../session
./MyTelegram.SessionServer &
# ./MyTelegram.SessionServer \
# RabbitMQ:EventBus:ExchangeName=MyTelegramExchange.MediaOnly \
# App:MediaOnly=true \
# RabbitMQ:EventBus:ClientName=MyTelegramSessionServer.MediaOnly \
# RabbitMQ:EventBus:TopicExchangeName=MyTelegramTopicExchange.MediaOnly &
cd ../file
./MyTelegram.FileServer urls=http://.+:10001 &
# ./MyTelegram.FileServer.exe urls=http://.+:10007 \
# RabbitMQ:EventBus:ClientName=MyTelegramFileServer.MediaOnly \
# RabbitMQ:EventBus:TopicExchangeName=MyTelegramTopicExchange.MediaOnly &
cd ../messenger-grpc
./MyTelegram.MessengerServer.GrpcService urls=http://.+:10003 &
cd ../sms
./MyTelegram.SmsSender &
+10
View File
@@ -0,0 +1,10 @@
taskkill /im MyTelegram.Messenger.CommandServer.exe /f
taskkill /im MyTelegram.Messenger.QueryServer.exe /f
taskkill /im MyTelegram.GatewayServer.exe /f
taskkill /im MyTelegram.AuthServer.exe /f
taskkill /im MyTelegram.SessionServer.exe /f
taskkill /im MyTelegram.FileServer.exe /f
taskkill /im MyTelegram.MessengerServer.GrpcService.exe /f
taskkill /im MyTelegram.MessengerServer.exe /f
taskkill /im MyTelegram.SmsSender.exe /f
+30
View File
@@ -0,0 +1,30 @@
# Скрипт PowerShell для проверки Bot API
$TOKEN = "999999999:ABCdefGHIjklMNOpqrsTUVwxyz123456"
$BASE_URL = "http://localhost:8081/bot$TOKEN"
Write-Host "Testing MyTelegram Bot API" -ForegroundColor Cyan
Write-Host "================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "1. Testing getMe..." -ForegroundColor Yellow
Invoke-RestMethod -Uri "$BASE_URL/getMe" | ConvertTo-Json
Write-Host ""
Write-Host "2. Testing getUpdates..." -ForegroundColor Yellow
Invoke-RestMethod -Uri "$BASE_URL/getUpdates" | ConvertTo-Json
Write-Host ""
Write-Host "3. Testing getWebhookInfo..." -ForegroundColor Yellow
Invoke-RestMethod -Uri "$BASE_URL/getWebhookInfo" | ConvertTo-Json
Write-Host ""
Write-Host "4. Testing sendMessage..." -ForegroundColor Yellow
$body = @{
chat_id = 2010001
text = "Hello from test bot!"
} | ConvertTo-Json
Invoke-RestMethod -Uri "$BASE_URL/sendMessage" -Method Post -Body $body -ContentType "application/json" | ConvertTo-Json
Write-Host ""
Write-Host "Tests completed" -ForegroundColor Green
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
# Скрипт для проверки Bot API
TOKEN="999999999:ABCdefGHIjklMNOpqrsTUVwxyz123456"
BASE_URL="http://localhost:8081/bot$TOKEN"
echo "Testing MyTelegram Bot API"
echo "================================"
echo ""
echo "1. Testing getMe..."
curl -s "$BASE_URL/getMe" | jq .
echo ""
echo "2. Testing getUpdates..."
curl -s "$BASE_URL/getUpdates" | jq .
echo ""
echo "3. Testing getWebhookInfo..."
curl -s "$BASE_URL/getWebhookInfo" | jq .
echo ""
echo "4. Testing sendMessage..."
curl -s -X POST "$BASE_URL/sendMessage" \
-H "Content-Type: application/json" \
-d '{"chat_id": 2010001, "text": "Hello from test bot!"}' | jq .
echo ""
echo "Tests completed"
+25
View File
@@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
+60
View File
@@ -0,0 +1,60 @@
<Project>
<PropertyGroup>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<PropertyGroup>
<!-- <VersionSuffix>0.6.$([System.DateTime]::Now.ToString(MMdd)).$([System.DateTime]::Now.ToString("HHmm"))</VersionSuffix> -->
<VersionSuffix>0.32.206.$([System.DateTime]::Now.ToString("Mdd"))</VersionSuffix>
<AssemblyVersion Condition="'$(VersionSuffix)' =='' ">0.0.0.1</AssemblyVersion>
<AssemblyVersion Condition="'$(VersionSuffix)' !='' ">$(VersionSuffix)</AssemblyVersion>
<Version Condition="'$(VersionSuffix)' =='' ">0.0.1.0</Version>
<Version Condition="'$(VersionSuffix)' !='' ">$(VersionSuffix)</Version>
<NoWarn>$(NoWarn);CS1591;CS0436;NU1803;</NoWarn>
<Authors>MyTelegram</Authors>
<RepositoryUrl>https://github.com/loyldg/mytelegram</RepositoryUrl>
<Company>MyTelegram</Company>
<Copyright>Copyright (c) MyTelegram 2021 - $([System.DateTime]::Now.ToString("yyyy"))</Copyright>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<!--
<PropertyGroup>
<OutputPath>$(MSBuildThisFileDirectory)out\local\$(AssemblyVersion)\$(Configuration)\$(MSBuildProjectName.Replace("MyTelegram.", ""))</OutputPath>
</PropertyGroup> -->
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>Windows</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>Linux</DefineConstants>
</PropertyGroup>
<!--
<PropertyGroup>
<DebuggerSupport>false</DebuggerSupport>
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
<EventSourceSupport>false</EventSourceSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<InvariantGlobalization>true</InvariantGlobalization>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>link</TrimMode>
<TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<IlcOptimizationPreference>Size</IlcOptimizationPreference>
</PropertyGroup>
-->
<ItemGroup>
<PackageReference Include="ConfigureAwait.Fody" PrivateAssets="All" />
<PackageReference Include="Fody" >
<PrivateAssets>All</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
+64
View File
@@ -0,0 +1,64 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
<PackageVersion Include="BloomFilter.NetCore" Version="2.1.1" />
<PackageVersion Include="CommunityToolkit.HighPerformance" Version="8.4.0" />
<PackageVersion Include="ConfigureAwait.Fody" PrivateAssets="All" Version="3.3.2" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Cube.Timer" Version="1.0.5" />
<PackageVersion Include="EventFlow" Version="1.2.1" />
<PackageVersion Include="EventFlow.MongoDB" Version="1.2.1" />
<PackageVersion Include="Fody" Version="6.9.2" />
<PackageVersion Include="Google.Protobuf" Version="3.31.1" />
<PackageVersion Include="Grpc.AspNetCore" Version="2.66.0" />
<PackageVersion Include="Grpc.Net.Client" Version="2.71.0" />
<PackageVersion Include="Grpc.Tools" Version="2.72.0" />
<PackageVersion Include="HashedWheelTimer" Version="1.0.3" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.7" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.7" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1-Preview.1" />
<PackageVersion Include="MessagePack" Version="2.5.192" />
<PackageVersion Include="Minio" Version="6.0.3" />
<PackageVersion Include="MongoDB.Driver" Version="3.0.0" />
<PackageVersion Include="Polly" Version="8.6.2" />
<PackageVersion Include="RabbitMQ.Client" Version="7.1.2" />
<PackageVersion Include="Rebus" Version="8.8.0" />
<PackageVersion Include="Rebus.Kafka" Version="2.0.0" />
<PackageVersion Include="Rebus.MsgPack" Version="7.0.0" />
<PackageVersion Include="Rebus.RabbitMq" Version="10.0.1" />
<PackageVersion Include="Rebus.ServiceProvider" Version="10.3.0" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
<PackageVersion Include="Sigil" Version="5.0.0" />
<PackageVersion Include="Standart.Hash.xxHash" Version="4.0.5" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.2.0" />
<PackageVersion Include="System.ComponentModel.TypeConverter" Version="4.3.0" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Text.Json" Version="9.0.7" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="Twilio" Version="7.12.0" />
<PackageVersion Include="Vonage" Version="8.2.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.3" />
</ItemGroup>
</Project>
+289
View File
@@ -0,0 +1,289 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32210.308
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2DA560C9-CF55-4AE8-9876-035A24D70098}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{91ACB8C4-58D1-42AC-A233-248BC18925B7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Schema", "src\MyTelegram.Schema\MyTelegram.Schema.csproj", "{635E30FB-0B64-41D9-ACCC-BD927FD1A2EB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Schema.Tests", "test\MyTelegram.Schema.Tests\MyTelegram.Schema.Tests.csproj", "{F86A6EBD-7FCC-4349-865B-BADF2AC331C2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{EB17BE1D-A92C-49B5-BFF1-DD75BC7DC94B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{DB4DF262-E785-497F-B465-440175026F97}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Core", "src\MyTelegram.Core\MyTelegram.Core.csproj", "{77D2BB76-B7AF-4200-B799-F19F879F7CB4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Domain", "src\MyTelegram.Domain\MyTelegram.Domain.csproj", "{88DF870D-E5E7-4CC0-8FF0-60EF746C28AF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Domain.Shared", "src\MyTelegram.Domain.Shared\MyTelegram.Domain.Shared.csproj", "{117B0121-B9E5-4481-8054-F33D1DA86AEC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.GrpcService", "src\MyTelegram.GrpcService\MyTelegram.GrpcService.csproj", "{B9DC33D2-34E6-4B6B-A22E-D7EFA1380004}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Queries", "src\MyTelegram.Queries\MyTelegram.Queries.csproj", "{6BCC94F3-717C-42EB-B051-DCFD15C9A068}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.ReadModel", "src\MyTelegram.ReadModel\MyTelegram.ReadModel.csproj", "{7602FA14-87A9-490E-91E2-BBC0633FE194}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.ReadModel.MongoDB", "src\MyTelegram.ReadModel.MongoDB\MyTelegram.ReadModel.MongoDB.csproj", "{1B3840FD-9C14-489F-8E03-DFEDA01207CF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReadModel", "ReadModel", "{3BF8F0CE-05C3-4386-B960-C56F6719D294}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Domain.Tests", "test\MyTelegram.Domain.Tests\MyTelegram.Domain.Tests.csproj", "{1076A82B-A813-44A5-93F6-4CCA1C15090E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{5F3FB626-5C24-4663-9B74-C9257417305E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Cuckoo", "src\Microsoft.Cuckoo\Microsoft.Cuckoo.csproj", "{9B208770-781B-42B0-8F2A-E87B0D5341AD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.ReadModel.InMemory", "src\MyTelegram.ReadModel.InMemory\MyTelegram.ReadModel.InMemory.csproj", "{11F1970C-D03D-4153-B27E-C4774AB4509A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Domain.IntegrationTests", "test\MyTelegram.Domain.IntegrationTests\MyTelegram.Domain.IntegrationTests.csproj", "{BF85677F-198E-4CAE-A13D-38CA555C9F76}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.TestBase", "src\MyTelegram.TestBase\MyTelegram.TestBase.csproj", "{D6842BB0-435B-408A-8040-11FAD9479E79}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.QueryHandlers.InMemory", "src\MyTelegram.QueryHandlers.InMemory\MyTelegram.QueryHandlers.InMemory.csproj", "{65D57042-EE58-4EBC-A5EE-B618BB99641D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.SmsSender", "src\MyTelegram.SmsSender\MyTelegram.SmsSender.csproj", "{165F6836-4B8F-48E0-AFC4-8D2A0286F3F5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.EventBus", "src\MyTelegram.EventBus\MyTelegram.EventBus.csproj", "{F23531FF-582D-48BA-988A-C484B86B44B2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.EventFlow.MongoDB", "src\MyTelegram.EventFlow.MongoDB\MyTelegram.EventFlow.MongoDB.csproj", "{175B77B0-25DB-47C4-881B-DEC3FAADF78A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Caching.Redis", "src\MyTelegram.Caching.Redis\MyTelegram.Caching.Redis.csproj", "{7F5612B7-188E-42D1-9F41-3CC32062519B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.MTProto", "src\MyTelegram.MTProto\MyTelegram.MTProto.csproj", "{44C09B7D-9917-4AFE-809C-2DA8E93048DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.MTProto.Tests", "test\MyTelegram.MTProto.Tests\MyTelegram.MTProto.Tests.csproj", "{816A9714-67FD-42BA-8411-3AD9B0A90701}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.GatewayServer", "src\MyTelegram.GatewayServer\MyTelegram.GatewayServer.csproj", "{5BFC0B2E-0DD2-48D4-AD4F-7245687C34F7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Messenger", "src\MyTelegram.Messenger\MyTelegram.Messenger.csproj", "{9967162A-3ABF-44A9-B796-D1925B257805}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Messenger.CommandServer", "src\MyTelegram.Messenger.CommandServer\MyTelegram.Messenger.CommandServer.csproj", "{18483359-47B7-4FB7-9BAE-F598105B15F5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Messenger.QueryServer", "src\MyTelegram.Messenger.QueryServer\MyTelegram.Messenger.QueryServer.csproj", "{ACCB7A7F-19E2-4593-B6A0-97989D6B78E6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.Services", "src\MyTelegram.Services\MyTelegram.Services.csproj", "{0A9D2AE0-F943-408E-B740-2101DDF865CE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.EventFlow", "src\MyTelegram.EventFlow\MyTelegram.EventFlow.csproj", "{D4E1CD1A-EA33-48B8-A965-D55F82CCFBC5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.TestApp", "src\MyTelegram.TestApp\MyTelegram.TestApp.csproj", "{8D070E04-ABCE-439F-A7A9-864D73225D34}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyTelegram.AuthServer", "src\MyTelegram.AuthServer\MyTelegram.AuthServer.csproj", "{A45E8362-191B-4CF4-BC0C-89F81C5A17FE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyTelegram.ReadModel.Interfaces", "src\MyTelegram.ReadModel.Interfaces\MyTelegram.ReadModel.Interfaces.csproj", "{A5AD515A-B43E-C625-1B44-589AAA14686C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyTelegram.Services.Tests", "test\MyTelegram.Services.Tests\MyTelegram.Services.Tests.csproj", "{8CD9759A-0026-33FC-DE4B-6BE3229B5109}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyTelegram.QueryHandlers.MongoDB", "src\MyTelegram.QueryHandlers.MongoDB\MyTelegram.QueryHandlers.MongoDB.csproj", "{710834A8-C271-4853-7B3D-1661A2535D9C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyTelegram.Converters", "src\MyTelegram.Converters\MyTelegram.Converters.csproj", "{2970172B-D8E3-F5AC-ABE1-F96E39F15A80}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyTelegram.DataSeeder", "src\MyTelegram.DataSeeder\MyTelegram.DataSeeder.csproj", "{AE915013-50A2-6610-F705-53059D40139B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyTelegram.Abstractions", "src\MyTelegram.Abstractions\MyTelegram.Abstractions.csproj", "{81E53953-EFD5-F7BE-C844-FEC29CA7B3F7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyTelegram.EventBus.RabbitMQ", "src\MyTelegram.EventBus.RabbitMQ\MyTelegram.EventBus.RabbitMQ.csproj", "{87FBEAB7-2F9A-EAAC-084E-F9408151E00B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{635E30FB-0B64-41D9-ACCC-BD927FD1A2EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{635E30FB-0B64-41D9-ACCC-BD927FD1A2EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{635E30FB-0B64-41D9-ACCC-BD927FD1A2EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{635E30FB-0B64-41D9-ACCC-BD927FD1A2EB}.Release|Any CPU.Build.0 = Release|Any CPU
{F86A6EBD-7FCC-4349-865B-BADF2AC331C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F86A6EBD-7FCC-4349-865B-BADF2AC331C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F86A6EBD-7FCC-4349-865B-BADF2AC331C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F86A6EBD-7FCC-4349-865B-BADF2AC331C2}.Release|Any CPU.Build.0 = Release|Any CPU
{77D2BB76-B7AF-4200-B799-F19F879F7CB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{77D2BB76-B7AF-4200-B799-F19F879F7CB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77D2BB76-B7AF-4200-B799-F19F879F7CB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77D2BB76-B7AF-4200-B799-F19F879F7CB4}.Release|Any CPU.Build.0 = Release|Any CPU
{88DF870D-E5E7-4CC0-8FF0-60EF746C28AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{88DF870D-E5E7-4CC0-8FF0-60EF746C28AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{88DF870D-E5E7-4CC0-8FF0-60EF746C28AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{88DF870D-E5E7-4CC0-8FF0-60EF746C28AF}.Release|Any CPU.Build.0 = Release|Any CPU
{117B0121-B9E5-4481-8054-F33D1DA86AEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{117B0121-B9E5-4481-8054-F33D1DA86AEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{117B0121-B9E5-4481-8054-F33D1DA86AEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{117B0121-B9E5-4481-8054-F33D1DA86AEC}.Release|Any CPU.Build.0 = Release|Any CPU
{B9DC33D2-34E6-4B6B-A22E-D7EFA1380004}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B9DC33D2-34E6-4B6B-A22E-D7EFA1380004}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B9DC33D2-34E6-4B6B-A22E-D7EFA1380004}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B9DC33D2-34E6-4B6B-A22E-D7EFA1380004}.Release|Any CPU.Build.0 = Release|Any CPU
{6BCC94F3-717C-42EB-B051-DCFD15C9A068}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BCC94F3-717C-42EB-B051-DCFD15C9A068}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6BCC94F3-717C-42EB-B051-DCFD15C9A068}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BCC94F3-717C-42EB-B051-DCFD15C9A068}.Release|Any CPU.Build.0 = Release|Any CPU
{7602FA14-87A9-490E-91E2-BBC0633FE194}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7602FA14-87A9-490E-91E2-BBC0633FE194}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7602FA14-87A9-490E-91E2-BBC0633FE194}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7602FA14-87A9-490E-91E2-BBC0633FE194}.Release|Any CPU.Build.0 = Release|Any CPU
{1B3840FD-9C14-489F-8E03-DFEDA01207CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B3840FD-9C14-489F-8E03-DFEDA01207CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B3840FD-9C14-489F-8E03-DFEDA01207CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B3840FD-9C14-489F-8E03-DFEDA01207CF}.Release|Any CPU.Build.0 = Release|Any CPU
{1076A82B-A813-44A5-93F6-4CCA1C15090E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1076A82B-A813-44A5-93F6-4CCA1C15090E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1076A82B-A813-44A5-93F6-4CCA1C15090E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1076A82B-A813-44A5-93F6-4CCA1C15090E}.Release|Any CPU.Build.0 = Release|Any CPU
{9B208770-781B-42B0-8F2A-E87B0D5341AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B208770-781B-42B0-8F2A-E87B0D5341AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B208770-781B-42B0-8F2A-E87B0D5341AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B208770-781B-42B0-8F2A-E87B0D5341AD}.Release|Any CPU.Build.0 = Release|Any CPU
{11F1970C-D03D-4153-B27E-C4774AB4509A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11F1970C-D03D-4153-B27E-C4774AB4509A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11F1970C-D03D-4153-B27E-C4774AB4509A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11F1970C-D03D-4153-B27E-C4774AB4509A}.Release|Any CPU.Build.0 = Release|Any CPU
{BF85677F-198E-4CAE-A13D-38CA555C9F76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BF85677F-198E-4CAE-A13D-38CA555C9F76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BF85677F-198E-4CAE-A13D-38CA555C9F76}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BF85677F-198E-4CAE-A13D-38CA555C9F76}.Release|Any CPU.Build.0 = Release|Any CPU
{D6842BB0-435B-408A-8040-11FAD9479E79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6842BB0-435B-408A-8040-11FAD9479E79}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6842BB0-435B-408A-8040-11FAD9479E79}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6842BB0-435B-408A-8040-11FAD9479E79}.Release|Any CPU.Build.0 = Release|Any CPU
{65D57042-EE58-4EBC-A5EE-B618BB99641D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65D57042-EE58-4EBC-A5EE-B618BB99641D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65D57042-EE58-4EBC-A5EE-B618BB99641D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65D57042-EE58-4EBC-A5EE-B618BB99641D}.Release|Any CPU.Build.0 = Release|Any CPU
{165F6836-4B8F-48E0-AFC4-8D2A0286F3F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{165F6836-4B8F-48E0-AFC4-8D2A0286F3F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{165F6836-4B8F-48E0-AFC4-8D2A0286F3F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{165F6836-4B8F-48E0-AFC4-8D2A0286F3F5}.Release|Any CPU.Build.0 = Release|Any CPU
{F23531FF-582D-48BA-988A-C484B86B44B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F23531FF-582D-48BA-988A-C484B86B44B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F23531FF-582D-48BA-988A-C484B86B44B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F23531FF-582D-48BA-988A-C484B86B44B2}.Release|Any CPU.Build.0 = Release|Any CPU
{175B77B0-25DB-47C4-881B-DEC3FAADF78A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{175B77B0-25DB-47C4-881B-DEC3FAADF78A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{175B77B0-25DB-47C4-881B-DEC3FAADF78A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{175B77B0-25DB-47C4-881B-DEC3FAADF78A}.Release|Any CPU.Build.0 = Release|Any CPU
{7F5612B7-188E-42D1-9F41-3CC32062519B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F5612B7-188E-42D1-9F41-3CC32062519B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F5612B7-188E-42D1-9F41-3CC32062519B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F5612B7-188E-42D1-9F41-3CC32062519B}.Release|Any CPU.Build.0 = Release|Any CPU
{44C09B7D-9917-4AFE-809C-2DA8E93048DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44C09B7D-9917-4AFE-809C-2DA8E93048DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44C09B7D-9917-4AFE-809C-2DA8E93048DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44C09B7D-9917-4AFE-809C-2DA8E93048DD}.Release|Any CPU.Build.0 = Release|Any CPU
{816A9714-67FD-42BA-8411-3AD9B0A90701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{816A9714-67FD-42BA-8411-3AD9B0A90701}.Debug|Any CPU.Build.0 = Debug|Any CPU
{816A9714-67FD-42BA-8411-3AD9B0A90701}.Release|Any CPU.ActiveCfg = Release|Any CPU
{816A9714-67FD-42BA-8411-3AD9B0A90701}.Release|Any CPU.Build.0 = Release|Any CPU
{5BFC0B2E-0DD2-48D4-AD4F-7245687C34F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5BFC0B2E-0DD2-48D4-AD4F-7245687C34F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5BFC0B2E-0DD2-48D4-AD4F-7245687C34F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5BFC0B2E-0DD2-48D4-AD4F-7245687C34F7}.Release|Any CPU.Build.0 = Release|Any CPU
{9967162A-3ABF-44A9-B796-D1925B257805}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9967162A-3ABF-44A9-B796-D1925B257805}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9967162A-3ABF-44A9-B796-D1925B257805}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9967162A-3ABF-44A9-B796-D1925B257805}.Release|Any CPU.Build.0 = Release|Any CPU
{18483359-47B7-4FB7-9BAE-F598105B15F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{18483359-47B7-4FB7-9BAE-F598105B15F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18483359-47B7-4FB7-9BAE-F598105B15F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18483359-47B7-4FB7-9BAE-F598105B15F5}.Release|Any CPU.Build.0 = Release|Any CPU
{ACCB7A7F-19E2-4593-B6A0-97989D6B78E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ACCB7A7F-19E2-4593-B6A0-97989D6B78E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ACCB7A7F-19E2-4593-B6A0-97989D6B78E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ACCB7A7F-19E2-4593-B6A0-97989D6B78E6}.Release|Any CPU.Build.0 = Release|Any CPU
{0A9D2AE0-F943-408E-B740-2101DDF865CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A9D2AE0-F943-408E-B740-2101DDF865CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A9D2AE0-F943-408E-B740-2101DDF865CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A9D2AE0-F943-408E-B740-2101DDF865CE}.Release|Any CPU.Build.0 = Release|Any CPU
{D4E1CD1A-EA33-48B8-A965-D55F82CCFBC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4E1CD1A-EA33-48B8-A965-D55F82CCFBC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4E1CD1A-EA33-48B8-A965-D55F82CCFBC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4E1CD1A-EA33-48B8-A965-D55F82CCFBC5}.Release|Any CPU.Build.0 = Release|Any CPU
{8D070E04-ABCE-439F-A7A9-864D73225D34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D070E04-ABCE-439F-A7A9-864D73225D34}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D070E04-ABCE-439F-A7A9-864D73225D34}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8D070E04-ABCE-439F-A7A9-864D73225D34}.Release|Any CPU.Build.0 = Release|Any CPU
{A45E8362-191B-4CF4-BC0C-89F81C5A17FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A45E8362-191B-4CF4-BC0C-89F81C5A17FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A45E8362-191B-4CF4-BC0C-89F81C5A17FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A45E8362-191B-4CF4-BC0C-89F81C5A17FE}.Release|Any CPU.Build.0 = Release|Any CPU
{A5AD515A-B43E-C625-1B44-589AAA14686C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5AD515A-B43E-C625-1B44-589AAA14686C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5AD515A-B43E-C625-1B44-589AAA14686C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5AD515A-B43E-C625-1B44-589AAA14686C}.Release|Any CPU.Build.0 = Release|Any CPU
{8CD9759A-0026-33FC-DE4B-6BE3229B5109}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8CD9759A-0026-33FC-DE4B-6BE3229B5109}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CD9759A-0026-33FC-DE4B-6BE3229B5109}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CD9759A-0026-33FC-DE4B-6BE3229B5109}.Release|Any CPU.Build.0 = Release|Any CPU
{710834A8-C271-4853-7B3D-1661A2535D9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{710834A8-C271-4853-7B3D-1661A2535D9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{710834A8-C271-4853-7B3D-1661A2535D9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{710834A8-C271-4853-7B3D-1661A2535D9C}.Release|Any CPU.Build.0 = Release|Any CPU
{2970172B-D8E3-F5AC-ABE1-F96E39F15A80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2970172B-D8E3-F5AC-ABE1-F96E39F15A80}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2970172B-D8E3-F5AC-ABE1-F96E39F15A80}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2970172B-D8E3-F5AC-ABE1-F96E39F15A80}.Release|Any CPU.Build.0 = Release|Any CPU
{AE915013-50A2-6610-F705-53059D40139B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE915013-50A2-6610-F705-53059D40139B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE915013-50A2-6610-F705-53059D40139B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE915013-50A2-6610-F705-53059D40139B}.Release|Any CPU.Build.0 = Release|Any CPU
{81E53953-EFD5-F7BE-C844-FEC29CA7B3F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81E53953-EFD5-F7BE-C844-FEC29CA7B3F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81E53953-EFD5-F7BE-C844-FEC29CA7B3F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81E53953-EFD5-F7BE-C844-FEC29CA7B3F7}.Release|Any CPU.Build.0 = Release|Any CPU
{87FBEAB7-2F9A-EAAC-084E-F9408151E00B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{87FBEAB7-2F9A-EAAC-084E-F9408151E00B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{87FBEAB7-2F9A-EAAC-084E-F9408151E00B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{87FBEAB7-2F9A-EAAC-084E-F9408151E00B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{635E30FB-0B64-41D9-ACCC-BD927FD1A2EB} = {5F3FB626-5C24-4663-9B74-C9257417305E}
{F86A6EBD-7FCC-4349-865B-BADF2AC331C2} = {91ACB8C4-58D1-42AC-A233-248BC18925B7}
{EB17BE1D-A92C-49B5-BFF1-DD75BC7DC94B} = {2DA560C9-CF55-4AE8-9876-035A24D70098}
{DB4DF262-E785-497F-B465-440175026F97} = {2DA560C9-CF55-4AE8-9876-035A24D70098}
{77D2BB76-B7AF-4200-B799-F19F879F7CB4} = {5F3FB626-5C24-4663-9B74-C9257417305E}
{88DF870D-E5E7-4CC0-8FF0-60EF746C28AF} = {EB17BE1D-A92C-49B5-BFF1-DD75BC7DC94B}
{117B0121-B9E5-4481-8054-F33D1DA86AEC} = {EB17BE1D-A92C-49B5-BFF1-DD75BC7DC94B}
{B9DC33D2-34E6-4B6B-A22E-D7EFA1380004} = {DB4DF262-E785-497F-B465-440175026F97}
{6BCC94F3-717C-42EB-B051-DCFD15C9A068} = {3BF8F0CE-05C3-4386-B960-C56F6719D294}
{7602FA14-87A9-490E-91E2-BBC0633FE194} = {3BF8F0CE-05C3-4386-B960-C56F6719D294}
{1B3840FD-9C14-489F-8E03-DFEDA01207CF} = {3BF8F0CE-05C3-4386-B960-C56F6719D294}
{3BF8F0CE-05C3-4386-B960-C56F6719D294} = {2DA560C9-CF55-4AE8-9876-035A24D70098}
{1076A82B-A813-44A5-93F6-4CCA1C15090E} = {91ACB8C4-58D1-42AC-A233-248BC18925B7}
{5F3FB626-5C24-4663-9B74-C9257417305E} = {2DA560C9-CF55-4AE8-9876-035A24D70098}
{9B208770-781B-42B0-8F2A-E87B0D5341AD} = {5F3FB626-5C24-4663-9B74-C9257417305E}
{11F1970C-D03D-4153-B27E-C4774AB4509A} = {3BF8F0CE-05C3-4386-B960-C56F6719D294}
{BF85677F-198E-4CAE-A13D-38CA555C9F76} = {91ACB8C4-58D1-42AC-A233-248BC18925B7}
{D6842BB0-435B-408A-8040-11FAD9479E79} = {5F3FB626-5C24-4663-9B74-C9257417305E}
{65D57042-EE58-4EBC-A5EE-B618BB99641D} = {3BF8F0CE-05C3-4386-B960-C56F6719D294}
{165F6836-4B8F-48E0-AFC4-8D2A0286F3F5} = {DB4DF262-E785-497F-B465-440175026F97}
{F23531FF-582D-48BA-988A-C484B86B44B2} = {5F3FB626-5C24-4663-9B74-C9257417305E}
{175B77B0-25DB-47C4-881B-DEC3FAADF78A} = {5F3FB626-5C24-4663-9B74-C9257417305E}
{7F5612B7-188E-42D1-9F41-3CC32062519B} = {5F3FB626-5C24-4663-9B74-C9257417305E}
{44C09B7D-9917-4AFE-809C-2DA8E93048DD} = {5F3FB626-5C24-4663-9B74-C9257417305E}
{816A9714-67FD-42BA-8411-3AD9B0A90701} = {91ACB8C4-58D1-42AC-A233-248BC18925B7}
{5BFC0B2E-0DD2-48D4-AD4F-7245687C34F7} = {DB4DF262-E785-497F-B465-440175026F97}
{9967162A-3ABF-44A9-B796-D1925B257805} = {DB4DF262-E785-497F-B465-440175026F97}
{18483359-47B7-4FB7-9BAE-F598105B15F5} = {DB4DF262-E785-497F-B465-440175026F97}
{ACCB7A7F-19E2-4593-B6A0-97989D6B78E6} = {DB4DF262-E785-497F-B465-440175026F97}
{0A9D2AE0-F943-408E-B740-2101DDF865CE} = {DB4DF262-E785-497F-B465-440175026F97}
{D4E1CD1A-EA33-48B8-A965-D55F82CCFBC5} = {EB17BE1D-A92C-49B5-BFF1-DD75BC7DC94B}
{8D070E04-ABCE-439F-A7A9-864D73225D34} = {DB4DF262-E785-497F-B465-440175026F97}
{A45E8362-191B-4CF4-BC0C-89F81C5A17FE} = {DB4DF262-E785-497F-B465-440175026F97}
{A5AD515A-B43E-C625-1B44-589AAA14686C} = {3BF8F0CE-05C3-4386-B960-C56F6719D294}
{8CD9759A-0026-33FC-DE4B-6BE3229B5109} = {91ACB8C4-58D1-42AC-A233-248BC18925B7}
{710834A8-C271-4853-7B3D-1661A2535D9C} = {3BF8F0CE-05C3-4386-B960-C56F6719D294}
{2970172B-D8E3-F5AC-ABE1-F96E39F15A80} = {DB4DF262-E785-497F-B465-440175026F97}
{AE915013-50A2-6610-F705-53059D40139B} = {DB4DF262-E785-497F-B465-440175026F97}
{81E53953-EFD5-F7BE-C844-FEC29CA7B3F7} = {5F3FB626-5C24-4663-9B74-C9257417305E}
{87FBEAB7-2F9A-EAAC-084E-F9408151E00B} = {5F3FB626-5C24-4663-9B74-C9257417305E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A8E7A112-1B80-4DCC-9884-5069D0E3B651}
EndGlobalSection
EndGlobal
+30
View File
@@ -0,0 +1,30 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<!-- <VersionSuffix>0.6.$([System.DateTime]::Now.ToString(MMdd)).$([System.DateTime]::Now.ToString("HHmm"))</VersionSuffix> -->
<VersionSuffix>0.8.821.932</VersionSuffix>
<AssemblyVersion Condition="'$(VersionSuffix)' =='' ">0.0.0.1</AssemblyVersion>
<AssemblyVersion Condition="'$(VersionSuffix)' !='' ">$(VersionSuffix)</AssemblyVersion>
<Version Condition="'$(VersionSuffix)' =='' ">0.0.1.0</Version>
<Version Condition="'$(VersionSuffix)' !='' ">$(VersionSuffix)</Version>
<NoWarn>$(NoWarn);CS1591;CS0436;NU1803;</NoWarn>
<Authors>Loyldg</Authors>
<RepositoryUrl>https://github.com/loyldg/mytelegram</RepositoryUrl>
<Company>Loyldg</Company>
<Copyright>Copyright (c) loyldg 2021 - 2023</Copyright>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsOSX Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">true</IsOSX>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>Windows</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)'=='true'">
<DefineConstants>OSX</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>Linux</DefineConstants>
</PropertyGroup>
</Project>
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<!-- `key` can be any identifier for your source. -->
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<!-- Define mappings by adding package patterns beneath the target source. -->
<packageSourceMapping>
<!-- key value for <packageSource> should match key values from <packageSources> element -->
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
@@ -0,0 +1,335 @@
// -----------------------------------------------------------------------
// <copyright file="CodegenHelpers.cs" company="Microsoft">
// Copyright (c) Microsoft. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Cuckoo.Benchmark")]
[assembly: InternalsVisibleTo("Microsoft.Cuckoo.Test")]
namespace Microsoft.Cuckoo;
/// <summary>
/// Collection helper methods for generating code and comparators.
/// </summary>
internal static class CodegenHelpers
{
public static bool BytesEquals(byte[] a, byte[] b)
{
return BytesEquals(a, 0, b, 0, a.Length);
}
/// <summary>
/// Returns whether the two arrays are equal. It assumes that both
/// are at least the "length" long.
/// </summary>
/// <param name="a">First array</param>
/// <param name="offsetA">Offset in first array to look from</param>
/// <param name="b">Second array</param>
/// <param name="offsetB">Offset in second array to look from</param>
/// <param name="length">Number of bytes in each to compare</param>
/// <returns>True if all bytes are equal, false otherwise</returns>
public static bool BytesEquals(byte[] a, int offsetA, byte[] b, int offsetB, int length)
{
var baseOffset = 0;
for (; baseOffset + Vector<byte>.Count <= length; baseOffset += Vector<byte>.Count)
{
var aVec = new Vector<byte>(a, baseOffset + offsetA);
var bVec = new Vector<byte>(b, baseOffset + offsetB);
if (aVec != bVec)
{
return false;
}
}
var remaining = length - baseOffset;
for (var i = 0; i < remaining; i++)
{
if (a[baseOffset + offsetA + i] != b[baseOffset + offsetB + i])
{
return false;
}
}
return true;
}
/// <summary>
/// Returns whether the array is all zeroes from the given offset
/// and length.
/// </summary>
/// <param name="array">Array to check</param>
/// <param name="offset">Offset to look at</param>
/// <param name="length">Number of bytes to check</param>
/// <returns>The number of bytes that are zero</returns>
public static bool IsZero(byte[] array, int offset, int length)
{
var zeroVector = new Vector<byte>(0);
var baseOffset = 0;
for (; baseOffset + Vector<byte>.Count <= length; baseOffset += Vector<byte>.Count)
{
if (new Vector<byte>(array, baseOffset + offset) != zeroVector)
{
return false;
}
}
var remaining = length - baseOffset;
for (var i = 0; i < remaining; i++)
{
if (array[offset + baseOffset + i] != 0)
{
return false;
}
}
return true;
}
/// <summary>
/// Creates a function that checks if the fingerprint at the given
/// offset (arg 2) in the first argument (arg 1) is equal to the
/// fingerprint in arg 3.
/// </summary>
/// <param name="fingerprintSize">Number of bytes in the fingerprint</param>
/// <param name="entriesPerBucket">Number of entries in each bucket</param>
/// <returns>The created delegate</returns>
public static Func<byte[], int, byte[], int> CreateFingerprintComparator(uint fingerprintSize,
uint entriesPerBucket)
{
int GetFingerprintIndex(byte[] originalFingerprintBytes,
int originalFingerprintOffset,
byte[] newFingerprintBytes)
{
var size = (int)fingerprintSize;
var span = originalFingerprintBytes.AsSpan()[originalFingerprintOffset..];
for (var entryIndex = 0; entryIndex < entriesPerBucket; entryIndex++)
{
var fingerprintBytes = span.Slice(size * entryIndex, size);
if (fingerprintBytes.SequenceEqual(newFingerprintBytes))
{
return entryIndex;
}
}
return -1;
}
return GetFingerprintIndex;
//var e1 = Emit<Func<byte[], int, byte[], int>>.NewDynamicMethod();
//Label nextStatement = null;
//for (var entryIndex = 0; entryIndex < entriesPerBucket; entryIndex++)
//{
// if (nextStatement != null)
// {
// e1.MarkLabel(nextStatement);
// }
// nextStatement = e1.DefineLabel();
// for (var byteIndex = 0; byteIndex < fingerprintSize; byteIndex++)
// {
// // Retrieve a[offset + i]
// e1.LoadArgument(0);
// e1.LoadArgument(1);
// var offset = (int)(fingerprintSize * entryIndex + byteIndex);
// if (offset > 0)
// {
// e1.LoadConstant(offset);
// e1.Add();
// }
// e1.LoadElement<byte>();
// // Retrieve b[i]
// e1.LoadArgument(2);
// e1.LoadConstant(byteIndex);
// e1.LoadElement<byte>();
// // Go to the false return of not equal
// e1.UnsignedBranchIfNotEqual(nextStatement);
// }
// e1.LoadConstant(entryIndex);
// e1.Return();
//}
//e1.MarkLabel(nextStatement);
//e1.LoadConstant(-1);
//e1.Return();
//return e1.CreateDelegate();
}
/// <summary>
/// Creates a function that checks if the fingerprint at the given
/// offset (arg 2) in the first argument (arg 1) is zero.
/// </summary>
/// <param name="fingerprintSize">Number of bytes in the fingerprint</param>
/// <returns>The created delegate</returns>
public static Func<byte[], int, bool> CreateZeroChecker(uint fingerprintSize)
{
Func<byte[], int, bool> func = (fingerprintBytes,
offset) =>
{
var bytes = fingerprintBytes.AsSpan().Slice(offset, (int)fingerprintSize);
foreach (var b in bytes)
{
if (b != 0)
{
return false;
}
}
return true;
};
return func;
//var e1 = Emit<Func<byte[], int, bool>>.NewDynamicMethod();
//var returnFalse = e1.DefineLabel();
//for (var byteIndex = 0; byteIndex < fingerprintSize; byteIndex++)
//{
// // Load the contents array:
// e1.LoadArgument(0);
// // Set the index we want to offset + i:
// e1.LoadArgument(1);
// if (byteIndex > 0)
// {
// e1.LoadConstant(byteIndex);
// e1.Add();
// }
// e1.LoadElement<byte>();
// // Go to the next statement (the next entry check) if it's not 0.
// e1.LoadConstant(0);
// e1.UnsignedBranchIfNotEqual(returnFalse);
//}
//// Got down here? We're good, true true.
//e1.LoadConstant(true);
//e1.Return();
//// False branch:
//e1.MarkLabel(returnFalse);
//e1.LoadConstant(false);
//e1.Return();
//return e1.CreateDelegate();
}
/// <summary>
/// Creates a function that checks inserts a fingerprint (arg 3) into
/// the index (arg 2) in the contents (arg 1) if there's any available
/// unassigned fingerprint slot. Returns true if the insertion was
/// made successfully.
/// </summary>
/// <param name="fingerprintSize">Number of bytes in the fingerprint</param>
/// <param name="entriesPerBucket">Number of entries in each bucket</param>
/// <returns>The created delegate</returns>
public static Func<byte[], int, byte[], bool> CreateInsertIntoBucket(uint fingerprintSize, uint entriesPerBucket)
{
Func<byte[], int, byte[], bool> func = (fingerprintBytes,
fingerprintIndex,
newFingerprintBytes) =>
{
var zeroBytes = new byte[fingerprintSize];
var bytes = fingerprintBytes.AsSpan().Slice(fingerprintIndex);
var size = (int)fingerprintSize;
for (var i = 0; i < bytes.Length; i += size)
{
var span = bytes.Slice(i, size);
if (span.SequenceEqual(zeroBytes))
{
newFingerprintBytes.CopyTo(span);
return true;
}
}
return false;
};
return func;
//var e = Emit<Func<byte[], int, byte[], bool>>.NewDynamicMethod();
//Label nextStatement = null;
//for (var entryIndex = 0; entryIndex < entriesPerBucket; entryIndex++)
//{
// if (nextStatement != null)
// {
// e.MarkLabel(nextStatement);
// }
// nextStatement = e.DefineLabel();
// // First, check that all bytes in the target spot are 0.
// for (var byteIndex = 0; byteIndex < fingerprintSize; byteIndex++)
// {
// // Load the contents array:
// e.LoadArgument(0);
// // Set the index we want to offset + i:
// e.LoadArgument(1);
// var offset = (int)(fingerprintSize * entryIndex + byteIndex);
// if (offset > 0)
// {
// e.LoadConstant(offset);
// e.Add();
// }
// // Load the element at contents[offset]:
// e.LoadElement<byte>();
// // Go to the next statement (the next entry check) if it's not 0.
// e.LoadConstant(0);
// e.UnsignedBranchIfNotEqual(nextStatement);
// }
// // If we're here, we found a free space that we can put our
// // fingerprint in.
// for (var byteIndex = 0; byteIndex < fingerprintSize; byteIndex++)
// {
// // Element setting takes three values: the array to load
// // into, the offset, and the data to load.
// // Array to load into:
// e.LoadArgument(0);
// // Target offset in the array:
// e.LoadArgument(1);
// var offset = (int)(fingerprintSize * entryIndex + byteIndex);
// if (offset > 0)
// {
// e.LoadConstant(offset);
// e.Add();
// }
// // Load the value to be loaded from the fingerprint:
// e.LoadArgument(2);
// e.LoadConstant(byteIndex);
// e.LoadElement<byte>();
// // Store it!
// e.StoreElement<byte>();
// }
// // Now we're good, retur:
// e.LoadConstant(true);
// e.Return();
//}
//e.MarkLabel(nextStatement);
//e.LoadConstant(false);
//e.Return();
//return e.CreateDelegate();
}
}
+549
View File
@@ -0,0 +1,549 @@
// -----------------------------------------------------------------------
// <copyright file="CuckooFilter.cs" company="Microsoft">
// Copyright (c) Microsoft. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
[assembly: InternalsVisibleTo("Cuckoo.UnitTests")]
namespace Microsoft.Cuckoo;
/// <summary>
/// Implementation of the Cuckoo filter.
/// </summary>
public class CuckooFilter : ICuckooFilter, IEquatable<CuckooFilter>
{
/// <summary>
/// Number of bytes in a <see cref="uint" />
/// </summary>
private const int Uint32Bytes = sizeof(uint);
/// <summary>
/// Comparator function, see <see cref="CodegenHelpers.CreateFingerprintComparator" />
/// </summary>
private readonly Func<byte[], int, byte[], int> _comparator;
/// <summary>
/// Hashing algorithm.
/// </summary>
private readonly IHashAlgorithm _hashAlgorithm;
/// <summary>
/// Comparator function, see <see cref="CodegenHelpers.CreateInsertIntoBucket" />
/// </summary>
private readonly Func<byte[], int, byte[], bool> _insertIntoBucket;
/// <summary>
/// Pre-allocated buffer we hash value indices into.
/// </summary>
private readonly byte[] _valuesBuffer = new byte[Uint32Bytes];
/// <summary>
/// Comparator function, see <see cref="CodegenHelpers.CreateZeroChecker" />
/// </summary>
private readonly Func<byte[], int, bool> _zeroCheck;
/// <summary>
/// Random instance.
/// </summary>
internal readonly Random Random;
/// <summary>
/// Pre-allocated buffer we hash the fingerprint into.
/// </summary>
private byte[] _fingerprintBuffer;
/// <summary>
/// Pre-allocated buffer we use for swapping fingerprints into.
/// </summary>
private byte[] _fingerprintSwapBuffer;
/// <summary>
/// Creates a new CuckooFilter.
/// </summary>
/// <param name="buckets">Number of Buckets to store</param>
/// <param name="entriesPerBucket">
/// Number of fingerprints stored
/// in each bucket.
/// </param>
/// <param name="fingerprintLength">Length of the fingerprint to use</param>
/// <param name="maxKicks">
/// Maximum number of times to relocate a value
/// on a collision.
/// </param>
/// <param name="hashAlgorithm">Hashing algorithm to use</param>
/// <param name="randomSeed">Random seed value</param>
public CuckooFilter(
uint buckets,
uint entriesPerBucket,
uint fingerprintLength,
uint? maxKicks = null,
IHashAlgorithm hashAlgorithm = null,
int? randomSeed = null)
{
Buckets = buckets;
EntriesPerBucket = entriesPerBucket;
_hashAlgorithm = hashAlgorithm ?? XxHashAlgorithm.Instance;
MaxKicks = maxKicks ?? buckets;
if (UpperPower2(buckets) != buckets)
{
throw new ArgumentException("Buckets must be a power of 2", nameof(buckets));
}
_fingerprintBuffer = new byte[fingerprintLength];
Contents = CreateEmptyBucketData(buckets, entriesPerBucket, fingerprintLength);
Random = randomSeed == null
? new Random()
: new Random(randomSeed.Value);
BytesPerBucket = EntriesPerBucket * fingerprintLength;
_fingerprintSwapBuffer = new byte[_fingerprintBuffer.Length];
_comparator = CodegenHelpers.CreateFingerprintComparator(fingerprintLength, entriesPerBucket);
_zeroCheck = CodegenHelpers.CreateZeroChecker(fingerprintLength);
_insertIntoBucket = CodegenHelpers.CreateInsertIntoBucket(fingerprintLength, entriesPerBucket);
}
/// <summary>
/// Creates a new optimally-sized CuckooFilter with a target
/// false-positive-at-capacity.
/// </summary>
/// <param name="capacity">Filter capacity</param>
/// <param name="falsePositiveRate">Desired false positive rate.</param>
/// <param name="hashAlgorithm">Hashing algorithm to use</param>
/// <param name="randomSeed">Random seed value</param>
public CuckooFilter(
uint capacity,
double falsePositiveRate,
IHashAlgorithm hashAlgorithm = null,
int? randomSeed = null)
{
_hashAlgorithm = hashAlgorithm ?? XxHashAlgorithm.Instance;
// "In summary, we choose (2, 4)-cuckoo filter (i.e., each item has
// two candidate Buckets and each bucket has up to four fingerprints)
// as the default configuration, because it achieves the best or
// close - to - best space efficiency for the false positive
// rates that most practical applications""
EntriesPerBucket = 4;
// Equation here from page 8, step 6, of the paper:
// ceil(log_2 (2b / \epsilon)
var desiredLength = Math.Log(2 * (float)EntriesPerBucket / falsePositiveRate, 2);
var fingerprintLength = (uint)Math.Ceiling(desiredLength / 8);
_fingerprintBuffer = new byte[fingerprintLength];
// Not explicitly defined in the paper, however this is the
// algorithm used in the author's implementation:
// https://github.com/efficient/cuckoofilter/blob/master/src/cuckoofilter.h#L89
Buckets = UpperPower2(capacity / EntriesPerBucket);
if ((double)capacity / Buckets / EntriesPerBucket > 0.96)
{
Buckets <<= 1;
}
MaxKicks = Buckets;
Random = randomSeed == null
? new Random()
: new Random(randomSeed.Value);
Contents = CreateEmptyBucketData(Buckets, EntriesPerBucket, fingerprintLength);
BytesPerBucket = EntriesPerBucket * fingerprintLength;
_fingerprintSwapBuffer = new byte[_fingerprintBuffer.Length];
_zeroCheck = CodegenHelpers.CreateZeroChecker(fingerprintLength);
_comparator = CodegenHelpers.CreateFingerprintComparator(fingerprintLength, 4);
_insertIntoBucket = CodegenHelpers.CreateInsertIntoBucket(fingerprintLength, EntriesPerBucket);
}
/// <summary>
/// Creates a new CuckooFilter.
/// </summary>
/// <param name="contents">Contents of th efilter</param>
/// <param name="entriesPerBucket">
/// Number of fingerprints stored
/// in each bucket.
/// </param>
/// <param name="fingerprintLength">Length of the fingerprint to use</param>
/// <param name="maxKicks">
/// Maximum number of times to relocate a value
/// on a collision.
/// </param>
/// <param name="hashAlgorithm">Hashing algorithm to use</param>
internal CuckooFilter(
byte[] contents,
uint entriesPerBucket,
uint fingerprintLength,
uint maxKicks,
IHashAlgorithm hashAlgorithm = null)
{
Contents = contents;
MaxKicks = maxKicks;
EntriesPerBucket = entriesPerBucket;
BytesPerBucket = entriesPerBucket * fingerprintLength;
Buckets = (uint)contents.Length / BytesPerBucket;
_fingerprintBuffer = new byte[fingerprintLength];
_fingerprintSwapBuffer = new byte[_fingerprintBuffer.Length];
_hashAlgorithm = hashAlgorithm ?? XxHashAlgorithm.Instance;
_zeroCheck = CodegenHelpers.CreateZeroChecker(fingerprintLength);
_comparator = CodegenHelpers.CreateFingerprintComparator(fingerprintLength, 4);
_insertIntoBucket = CodegenHelpers.CreateInsertIntoBucket(fingerprintLength, entriesPerBucket);
}
/// <summary>
/// Gets the number of Buckets the filter contains.
/// </summary>
public uint Buckets { get; }
/// <summary>
/// Number of bytes each bucket takes.
/// </summary>
public uint BytesPerBucket { get; }
/// <summary>
/// Gets the number of fingerprints to store per bucket.
/// </summary>
public uint EntriesPerBucket { get; }
/// <summary>
/// Gets the length of the fingerprint.
/// </summary>
public uint FingerprintLength => (uint)_fingerprintBuffer.Length;
/// <summary>
/// Gets the max number of times we'll try to kick and item from a
/// bucket when we insert before giving it.
/// </summary>
public uint MaxKicks { get; }
/// <summary>
/// Contents of the cuckoo filter.
/// </summary>
internal byte[] Contents { get; }
/// <summary>
/// Gets the total size, in memory, of the filter.
/// </summary>
public uint Size => (uint)Contents.Length;
/// <summary>
/// Returns a value indicating whether the filter probably contains
/// the given item.
/// </summary>
/// <param name="value">Value to check</param>
/// <returns>True if the filter contains the value, false otherwise.</returns>
public bool Contains(byte[] value)
{
var fingerprint = GetFingerprint(value);
_hashAlgorithm.Hash(_valuesBuffer, value, Uint32Bytes);
var index1 = ToInt32(_valuesBuffer) & (Buckets - 1);
if (_comparator(Contents, IndexToOffset(index1), fingerprint) != -1)
{
return true;
}
var index2 = DeriveIndex2(fingerprint, index1);
if (_comparator(Contents, IndexToOffset(index2), fingerprint) != -1)
{
return true;
}
return false;
}
/// <summary>
/// Inserts the value into the filter. Whereas <see cref="TryInsert" />
/// returns false if the value cannot be inserted, this throws.
/// </summary>
/// <param name="value">Value to insert</param>
/// <exception cref="FilterFullException">
/// Thrown if the filter is
/// too full to accept the value.
/// </exception>
public void Insert(byte[] value)
{
if (!TryInsert(value))
{
throw new FilterFullException();
}
}
/// <summary>
/// Removes a value from the filter.
/// </summary>
/// <param name="value">Value to remove</param>
/// <returns>True if the filter contained the value, false otherwise.</returns>
public bool Remove(byte[] value)
{
var fingerprint = GetFingerprint(value);
_hashAlgorithm.Hash(_valuesBuffer, value, Uint32Bytes);
var index1 = ToInt32(_valuesBuffer) & (Buckets - 1);
var offset = IndexToOffset(index1);
var removal = _comparator(Contents, offset, fingerprint);
if (removal != -1)
{
Array.Clear(Contents, offset + _fingerprintBuffer.Length * removal, fingerprint.Length);
return true;
}
var index2 = DeriveIndex2(fingerprint, index1);
offset = IndexToOffset(index2);
removal = _comparator(Contents, offset, fingerprint);
if (removal != -1)
{
Array.Clear(Contents, offset + _fingerprintBuffer.Length * removal, fingerprint.Length);
return true;
}
return false;
}
/// <summary>
/// Attempts to insert the value into the filter.
/// </summary>
/// <param name="value">Value to insert</param>
/// <returns>
/// True if it was inserted successfully, false if the
/// filter was too full to do so.
/// </returns>
public bool TryInsert(byte[] value)
{
return TryInsertInner(value);
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// true if the current object is equal to the <paramref name="other">other</paramref> parameter; otherwise,
/// false.
/// </returns>
public bool Equals(CuckooFilter other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return Buckets == other.Buckets
&& BytesPerBucket == other.BytesPerBucket
&& CodegenHelpers.BytesEquals(Contents, other.Contents)
&& EntriesPerBucket == other.EntriesPerBucket
&& MaxKicks == other.MaxKicks;
}
/// <summary>Determines whether the specified object is equal to the current object.</summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns>true if the specified object is equal to the current object; otherwise, false.</returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((CuckooFilter)obj);
}
/// <summary>Serves as the default hash function.</summary>
/// <returns>A hash code for the current object.</returns>
public override int GetHashCode()
{
unchecked
{
var contentHash = new byte[4];
_hashAlgorithm.Hash(contentHash, Contents, 4);
var hashCode = (int)Buckets;
hashCode = (hashCode * 397) ^ (int)BytesPerBucket;
hashCode = (hashCode * 397) ^ (contentHash[0] | (contentHash[1] << 8)
| (contentHash[2] << 16)
| (contentHash[3] << 24));
hashCode = (hashCode * 397) ^ (int)EntriesPerBucket;
hashCode = (hashCode * 397) ^ (int)MaxKicks;
return hashCode;
}
}
/// <summary>
/// Attempts to insert the value into the filter.
/// </summary>
/// <param name="value">Value to insert</param>
/// <returns>
/// True if it was inserted successfully, false if the
/// filter was too full to do so.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryInsertInner(byte[] value)
{
var fingerprint = GetFingerprint(value);
_hashAlgorithm.Hash(_valuesBuffer, value, Uint32Bytes);
var index1 = BoundToBucketCount(ToInt32(_valuesBuffer));
if (_insertIntoBucket(Contents, IndexToOffset(index1), fingerprint))
{
return true;
}
var index2 = DeriveIndex2(fingerprint, index1);
if (_insertIntoBucket(Contents, IndexToOffset(index2), fingerprint))
{
return true;
}
var targetIndex = Random.Next(1) == 0
? index1
: index2;
for (var i = 0; i < MaxKicks; i++)
{
fingerprint = SwapIntoBucket(fingerprint, targetIndex);
targetIndex = DeriveIndex2(fingerprint, targetIndex);
if (_insertIntoBucket(Contents, IndexToOffset(targetIndex), fingerprint))
{
return true;
}
}
return false;
}
/// <summary>
/// Returns a nicely formatted version of the filter. Used for
/// examining internal state while testing.
/// </summary>
/// <returns></returns>
internal IList<IList<string>> DumpDebug()
{
var list = new List<IList<string>>();
for (var offset = 0L; offset < Contents.Length; offset += BytesPerBucket)
{
var items = new List<string>();
for (var i = 0; i < EntriesPerBucket; i++)
{
var target = (int)offset + i * _fingerprintBuffer.Length;
if (!_zeroCheck(Contents, target))
{
items.Add(Encoding.ASCII.GetString(Contents, target, _fingerprintBuffer.Length));
}
}
list.Add(items);
}
return list;
}
private static byte[] CreateEmptyBucketData(long buckets, uint itemsPerBucket, uint bytesPerItem)
{
return new byte[buckets * itemsPerBucket * bytesPerItem];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ToInt32(byte[] data)
{
var x = data[0] << 24;
x |= data[1] << 16;
x |= data[2] << 8;
x |= data[3];
return x;
}
private static uint UpperPower2(uint x)
{
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
x++;
return x;
}
/// <summary>
/// Ensures the value is less than or equal to the number of Buckets.
/// </summary>
/// <param name="value">Value to bound</param>
/// <returns>Truncated value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private long BoundToBucketCount(long value)
{
// this.Buckets is always a power of 2, so to ensure index1 is <=1
// the number of Buckets, we mask it against Buckets - 1. So if
// Buckets is 16 (0b10000), we mask it against (0b01111).
return value & (Buckets - 1);
}
/// <summary>
/// Get the alternative index for an item, given its primary
/// index and fingerprint.
/// </summary>
/// <param name="fingerprint">Fingerprint value</param>
/// <param name="index1">Primary index</param>
/// <returns>The secondary index</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private long DeriveIndex2(byte[] fingerprint, long index1)
{
_hashAlgorithm.Hash(_valuesBuffer, fingerprint, Uint32Bytes);
return index1 ^ BoundToBucketCount(ToInt32(_valuesBuffer));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte[] GetFingerprint(byte[] input)
{
_hashAlgorithm.Hash(_fingerprintBuffer, input, _fingerprintBuffer.Length);
if (_zeroCheck(_fingerprintBuffer, 0))
{
for (var i = 0; i < _fingerprintBuffer.Length; i++)
{
_fingerprintBuffer[i] = 0xff;
}
}
return _fingerprintBuffer;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int IndexToOffset(long index)
{
return (int)(index * BytesPerBucket);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte[] SwapIntoBucket(byte[] fingerprint, long bucket)
{
var subIndex = Random.Next((int)EntriesPerBucket);
var offset = IndexToOffset(bucket) + subIndex * fingerprint.Length;
var newFingerprint = _fingerprintSwapBuffer;
Array.Copy(Contents, offset, _fingerprintSwapBuffer, 0, fingerprint.Length);
Array.Copy(fingerprint, 0, Contents, offset, fingerprint.Length);
_fingerprintSwapBuffer = fingerprint;
_fingerprintBuffer = newFingerprint;
return newFingerprint;
}
}
@@ -0,0 +1,56 @@
// -----------------------------------------------------------------------
// <copyright file="CuckooFilterExtensions.cs" company="Microsoft">
// Copyright (c) Microsoft. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
using System.Text;
namespace Microsoft.Cuckoo;
/// <summary>
/// Extension methods for the Cuckoo filter.
/// </summary>
public static class CuckooFilterExtensions
{
/// <summary>
/// Returns a value indicating whether the filter probably contains
/// the given item.
/// </summary>
/// <param name="filter">Cuckoo filter instance.</param>
/// <param name="value">Value to check</param>
/// <returns>True if the filter contains the value, false otherwise.</returns>
public static bool ContainsString(this CuckooFilter filter, string value)
{
return filter.Contains(Encoding.ASCII.GetBytes(value));
}
/// <summary>
/// Inserts the value into the filter. Whereas <see cref="TryInsertString" />
/// returns false if the value cannot be inserted, this throws.
/// </summary>
/// <param name="filter">Cuckoo filter instance.</param>
/// <param name="value">Value to insert</param>
/// <exception cref="FilterFullException">
/// Thrown if the filter is
/// too full to accept the value.
/// </exception>
public static bool InsertString(this CuckooFilter filter, string value)
{
return filter.TryInsert(Encoding.ASCII.GetBytes(value));
}
/// <summary>
/// Attempts to insert the value into the filter.
/// </summary>
/// <param name="filter">Cuckoo filter instance.</param>
/// <param name="value">Value to insert</param>
/// <returns>
/// True if it was inserted successfully, false if the
/// filter was too full to do so.
/// </returns>
public static bool TryInsertString(this CuckooFilter filter, string value)
{
return filter.TryInsert(Encoding.ASCII.GetBytes(value));
}
}
@@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="FilterFullException.cs" company="Microsoft">
// Copyright (c) Microsoft. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
using System;
namespace Microsoft.Cuckoo;
/// <summary>
/// Exception thrown in <see cref="CuckooFilter.Insert" /> if the filter is
/// too full to accept more items.
/// </summary>
[Serializable]
public class FilterFullException : Exception
{
/// <summary>
/// Creates a new instance of the exception.
/// </summary>
public FilterFullException()
: base("The cuckoo filter is too full to accept more items")
{
}
}
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
@@ -0,0 +1,50 @@
// -----------------------------------------------------------------------
// <copyright file="GzipStreamSerializer.cs" company="Microsoft">
// Copyright (c) Microsoft. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
using System.IO;
using System.IO.Compression;
namespace Microsoft.Cuckoo;
/// <summary>
/// Serializer that creates a gzipped stream.
/// </summary>
public class GzipStreamSerializer : SimpleStreamSerializer
{
private readonly CompressionLevel _compressionLevel;
/// <summary>
/// Creates a new serialize that serializes into a gzipped string.
/// </summary>
/// <param name="compressionLevel">Gzip compression level</param>
public GzipStreamSerializer(CompressionLevel compressionLevel = CompressionLevel.Optimal)
{
_compressionLevel = compressionLevel;
}
/// <summary>
/// Deserializes the CuckooFilter to from a stream.
/// </summary>
/// <param name="source">Source stream to read</param>
/// <param name="hashAlgorithm">Hash algorithm</param>
public override CuckooFilter Deserialize(Stream source, IHashAlgorithm hashAlgorithm = null)
{
var compressionStream = new GZipStream(source, CompressionMode.Decompress);
return base.Deserialize(compressionStream, hashAlgorithm);
}
/// <summary>
/// Serializes the CuckooFilter to a stream.
/// </summary>
/// <param name="target">Target stream to write to</param>
/// <param name="filter">Filter to serialize</param>
public override void Serialize(Stream target, CuckooFilter filter)
{
var compressionStream = new GZipStream(target, _compressionLevel);
base.Serialize(compressionStream, filter);
compressionStream.Flush();
}
}
@@ -0,0 +1,54 @@
// -----------------------------------------------------------------------
// <copyright file="ICuckooFilter.cs" company="Microsoft">
// Copyright (c) Microsoft. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
namespace Microsoft.Cuckoo;
/// <summary>
/// Cuckoo filter interface.
/// </summary>
public interface ICuckooFilter
{
/// <summary>
/// Gets the total size, in memory, of the filter.
/// </summary>
uint Size { get; }
/// <summary>
/// Returns a value indicating whether the filter probably contains
/// the given item.
/// </summary>
/// <param name="value">Value to check</param>
/// <returns>True if the filter contains the value, false otherwise.</returns>
bool Contains(byte[] value);
/// <summary>
/// Inserts the value into the filter. Whereas <see cref="CuckooFilter.TryInsert" />
/// returns false if the value cannot be inserted, this throws.
/// </summary>
/// <param name="value">Value to insert</param>
/// <exception cref="FilterFullException">
/// Thrown if the filter is
/// too full to accept the value.
/// </exception>
void Insert(byte[] value);
/// <summary>
/// Removes a value from the filter.
/// </summary>
/// <param name="value">Value to remove</param>
/// <returns>True if the filter contained the value, false otherwise.</returns>
bool Remove(byte[] value);
/// <summary>
/// Attempts to insert the value into the filter.
/// </summary>
/// <param name="value">Value to insert</param>
/// <returns>
/// True if it was inserted successfully, false if the
/// filter was too full to do so.
/// </returns>
bool TryInsert(byte[] value);
}
@@ -0,0 +1,26 @@
// -----------------------------------------------------------------------
// <copyright file="ICuckooSerializer.cs" company="Microsoft">
// Copyright (c) Microsoft. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
using System.IO;
namespace Microsoft.Cuckoo;
public interface ICuckooSerializer
{
/// <summary>
/// Deserializes the CuckooFilter to from a stream.
/// </summary>
/// <param name="source">Source stream to read</param>
/// <param name="hashAlgorithm">Hash algorithm</param>
CuckooFilter Deserialize(Stream source, IHashAlgorithm hashAlgorithm = null);
/// <summary>
/// Serializes the CuckooFilter to a stream.
/// </summary>
/// <param name="target">Target stream to write to</param>
/// <param name="filter">Filter to serialize</param>
void Serialize(Stream target, CuckooFilter filter);
}
@@ -0,0 +1,21 @@
// -----------------------------------------------------------------------
// <copyright file="IHashAlgorithm.cs" company="Microsoft">
// Copyright (c) Microsoft. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
namespace Microsoft.Cuckoo;
/// <summary>
/// Describes a type used for hashing a value for the cuckoo filter.
/// </summary>
public interface IHashAlgorithm
{
/// <summary>
/// Returns a hash of the value.
/// </summary>
/// <param name="target">Target array to hash to</param>
/// <param name="value">Value to hash</param>
/// <param name="hashLength">Number of bytes to write into the target.</param>
void Hash(byte[] target, byte[] value, int hashLength);
}
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PackageId>Microsoft.Cuckoo</PackageId>
<Version>1.0.0</Version>
<Authors>Microsoft</Authors>
<Company>Microsoft Corporation</Company>
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
<RepositoryType>GitHub</RepositoryType>
<RepositoryUrl>https://github.com/microsoft/mixer-cuckoo</RepositoryUrl>
<ProjectUrl>https://github.com/microsoft/mixer-cuckoo</ProjectUrl>
<Description>A cuckoo filter implementation for C#</Description>
<License>MIT</License>
<RequireLicenseAcceptance>true</RequireLicenseAcceptance>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Sigil" />
<PackageReference Include="Standart.Hash.xxHash" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="Fody">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
@@ -0,0 +1,80 @@
// -----------------------------------------------------------------------
// <copyright file="SimpleStreamSerializer.cs" company="Microsoft">
// Copyright (c) Microsoft. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
using System.IO;
namespace Microsoft.Cuckoo;
/// <summary>
/// Simple stream serializer that writes the cuckoo filter directly out
/// to a stream. Investigate <see cref="GzipStreamSerializer" /> for a more
/// storage-friendly version.
/// </summary>
public class SimpleStreamSerializer : ICuckooSerializer
{
/// <summary>
/// Deserializes the CuckooFilter to from a stream.
/// </summary>
/// <param name="source">Source stream to read</param>
/// <param name="hashAlgorithm">Hash algorithm</param>
public virtual CuckooFilter Deserialize(Stream source, IHashAlgorithm hashAlgorithm = null)
{
var paramsReadBuffer = new byte[16];
//source.Read(paramsReadBuffer, 0, 16);
source.ReadExactly(paramsReadBuffer);
var entriesPerBucket = ReadBigEndianUint(paramsReadBuffer, 0);
var fingerprintLength = ReadBigEndianUint(paramsReadBuffer, 4);
var buckets = ReadBigEndianUint(paramsReadBuffer, 8);
var maxKicks = ReadBigEndianUint(paramsReadBuffer, 12);
var contents = new byte[buckets * entriesPerBucket * fingerprintLength];
//source.Read(contents, 0, contents.Length);
source.ReadExactly(contents);
return new CuckooFilter(
contents,
entriesPerBucket,
fingerprintLength,
maxKicks,
hashAlgorithm);
}
/// <summary>
/// Serializes the CuckooFilter to a stream.
/// </summary>
/// <param name="target">Target stream to write to</param>
/// <param name="filter">Filter to serialize</param>
public virtual void Serialize(Stream target, CuckooFilter filter)
{
var writeBuffer = new byte[16];
WriteBigEndianUint(writeBuffer, filter.EntriesPerBucket, 0);
WriteBigEndianUint(writeBuffer, filter.FingerprintLength, 4);
WriteBigEndianUint(writeBuffer, filter.Buckets, 8);
WriteBigEndianUint(writeBuffer, filter.MaxKicks, 12);
target.Write(writeBuffer, 0, 16);
target.Write(filter.Contents, 0, filter.Contents.Length);
}
private static uint ReadBigEndianUint(byte[] target, int offset)
{
uint x = 0;
x |= (uint)(target[offset] << 24);
x |= (uint)(target[offset + 1] << 16);
x |= (uint)(target[offset + 2] << 8);
x |= target[offset + 3];
return x;
}
private static void WriteBigEndianUint(byte[] target, uint number, int offset)
{
target[offset] = (byte)(number >> 24);
target[offset + 1] = (byte)(number >> 16);
target[offset + 2] = (byte)(number >> 8);
target[offset + 3] = (byte)number;
}
}
@@ -0,0 +1,44 @@
// -----------------------------------------------------------------------
// <copyright file="XxHashAlgorithm.cs" company="Microsoft">
// Copyright (c) Microsoft. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
using Standart.Hash.xxHash;
namespace Microsoft.Cuckoo;
/// <summary>
/// An IHashAlgorithm using XxHash.
/// </summary>
public class XxHashAlgorithm : IHashAlgorithm
{
internal static readonly IHashAlgorithm Instance = new XxHashAlgorithm();
private readonly ulong _seed;
/// <summary>
/// Creates a new xxhash algorithm instance.
/// </summary>
/// <param name="seed">Seed value for the hash</param>
public XxHashAlgorithm(ulong seed = 0)
{
_seed = seed;
}
/// <summary>
/// Returns a hash of the value.
/// </summary>
/// <param name="target">Target array to hash to</param>
/// <param name="value">Value to hash</param>
/// <param name="hashLength">Desired length of the fingerprint.</param>
public void Hash(byte[] target, byte[] value, int hashLength)
{
var hash = xxHash64.ComputeHash(value, value.Length, _seed);
for (var i = 0; i < hashLength; i++)
{
target[i] = (byte)(hash & 0xFF);
hash >>= 8;
}
}
}
@@ -0,0 +1,9 @@
namespace MyTelegram.Abstractions;
public enum ConnectionType
{
UnKnown = 0,
Generic = 1,
Media = 2,
Push = 4
}
@@ -0,0 +1,28 @@
namespace MyTelegram.Abstractions;
/// <summary>
///
/// </summary>
/// <param name="AuthKeyId"></param>
/// <param name="MsgKey">int128,length=16</param>
/// <param name="EncryptedData"></param>
/// <param name="ConnectionId"></param>
/// <param name="ConnectionType"></param>
/// <param name="ClientIp"></param>
/// <param name="RequestId"></param>
/// <param name="Date"></param>
public record EncryptedMessage(long AuthKeyId,
ReadOnlyMemory<byte> MsgKey,
ReadOnlyMemory<byte> EncryptedData,
string ConnectionId,
ConnectionType ConnectionType,
string ClientIp,
Guid RequestId,
long Date
) : IMtpMessage
{
public string ConnectionId { get; set; } = ConnectionId;
public ConnectionType ConnectionType { get; set; } = ConnectionType;
public string ClientIp { get; set; } = ClientIp;
[JsonIgnore]public IMemoryOwner<byte>? MemoryOwner { get; set; }
}
@@ -0,0 +1,10 @@
namespace MyTelegram.Abstractions;
public record EncryptedMessageResponse(long AuthKeyId,
ReadOnlyMemory<byte> Data,
string ConnectionId,
long SeqNumber
) : IMayHaveMemoryOwner
{
public IMemoryOwner<byte>? MemoryOwner { get; set; }
}
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
@@ -0,0 +1,2 @@
global using System.Buffers;
global using System.Text.Json.Serialization;
@@ -0,0 +1,6 @@
namespace MyTelegram.Abstractions;
public interface IDataProcessor<in TData>
{
Task ProcessAsync(TData data);
}
@@ -0,0 +1,13 @@
namespace MyTelegram.Abstractions;
public interface IEventDataSerializer<TEventData> : IEventDataSerializer
{
void Serialize(IBufferWriter<byte> writer, TEventData data);
TEventData Deserialize(ReadOnlyMemory<byte> buffer);
}
public interface IEventDataSerializer
{
//object? Deserialize(Type type, ReadOnlyMemory<byte> buffer);
object Deserialize(Type type, ReadOnlyMemory<byte> buffer);
}
@@ -0,0 +1,6 @@
namespace MyTelegram.Abstractions;
public interface IMayHaveMemoryOwner
{
IMemoryOwner<byte>? MemoryOwner { get; set; }
}
@@ -0,0 +1,8 @@
namespace MyTelegram.Abstractions;
public interface IMessageIdHelper
{
long GenerateMessageId();
long GenerateUniqueId();
}
@@ -0,0 +1,9 @@
namespace MyTelegram.Abstractions;
public interface IMessageQueueProcessor<in TData>
{
void Enqueue(TData message,
long key);
Task ProcessAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,8 @@
namespace MyTelegram.Abstractions;
public interface IMtpMessage : IMayHaveMemoryOwner
{
string ClientIp { get; set; }
string ConnectionId { get; set; }
ConnectionType ConnectionType { get; set; }
}
@@ -0,0 +1,3 @@
namespace MyTelegram.Abstractions;
public interface ISingletonDependency { }
@@ -0,0 +1,3 @@
namespace MyTelegram.Abstractions;
public interface ITransientDependency { }
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
</ItemGroup>
</Project>
@@ -0,0 +1,43 @@
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
namespace MyTelegram.Abstractions;
public static class MyTelegramAbstractionsExtensions
{
public static void RegisterServices(this IServiceCollection services, Assembly? assembly = null)
{
assembly ??= Assembly.GetCallingAssembly();
var singletonBaseInterface = typeof(ISingletonDependency);
var transientBaseInterface = typeof(ITransientDependency);
var types = assembly.GetTypes()
.Where(p => p != singletonBaseInterface && p != transientBaseInterface && !p.IsAbstract)
.ToList()
;
var singletonTypes = types.Where(singletonBaseInterface.IsAssignableFrom);
var transientTypes = types.Where(transientBaseInterface.IsAssignableFrom);
foreach (var type in singletonTypes)
{
var baseInterfaces = type.GetInterfaces();
foreach (var baseInterface in baseInterfaces)
{
services.AddSingleton(baseInterface, type);
}
services.AddSingleton(type);
}
foreach (var type in transientTypes)
{
var baseInterfaces = type.GetInterfaces();
foreach (var baseInterface in baseInterfaces)
{
services.AddTransient(baseInterface, type);
}
services.AddTransient(type);
}
}
}
@@ -0,0 +1,19 @@
namespace MyTelegram.Abstractions;
public record UnencryptedMessage(
long AuthKeyId,
string ClientIp,
string ConnectionId,
ConnectionType ConnectionType,
ReadOnlyMemory<byte> MessageData,
int MessageDataLength,
long MessageId,
uint ObjectId,
Guid RequestId,
long Date
) : IMtpMessage
{
public string ConnectionId { get; set; } = ConnectionId;
public ConnectionType ConnectionType { get; set; } = ConnectionType;
public string ClientIp { get; set; } = ClientIp;
public IMemoryOwner<byte>? MemoryOwner { get; set; }
}
@@ -0,0 +1,8 @@
namespace MyTelegram.Abstractions;
public record UnencryptedMessageResponse(long AuthKeyId,
ReadOnlyMemory<byte> Data,
string ConnectionId,
long ReqMsgId) : IMayHaveMemoryOwner
{
public IMemoryOwner<byte>? MemoryOwner { get; set; }
}
@@ -0,0 +1,59 @@
using Microsoft.AspNetCore.Mvc;
using MyTelegram.Messenger.Services.Interfaces;
namespace MyTelegram.Admin.Api.Controllers;
[ApiController]
[Route("api/service-notifications")]
public class ServiceNotificationsController : ControllerBase
{
private readonly IServiceNotificationAppService _serviceNotificationAppService;
private readonly ILogger<ServiceNotificationsController> _logger;
public ServiceNotificationsController(
IServiceNotificationAppService serviceNotificationAppService,
ILogger<ServiceNotificationsController> logger)
{
_serviceNotificationAppService = serviceNotificationAppService;
_logger = logger;
}
[HttpPost("send")]
public async Task<IActionResult> SendNotification([FromBody] SendNotificationRequest request)
{
_logger.LogInformation("Received notification request for user {UserId}: Type={Type}, Popup={Popup}",
request.UserId, request.Type, request.Popup);
try
{
await _serviceNotificationAppService.SendServiceNotificationAsync(
request.UserId,
request.Type,
request.Message,
request.Popup,
media: null, // TODO: добавить поддержку медиа
entities: null,
invertMedia: false
);
_logger.LogInformation("Notification sent successfully to user {UserId}", request.UserId);
return Ok(new { success = true, message = "Notification sent" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to send notification to user {UserId}", request.UserId);
return StatusCode(500, new { success = false, error = ex.Message });
}
}
}
public class SendNotificationRequest
{
public long UserId { get; set; }
public string Type { get; set; } = string.Empty;
public string Message { get; set; } = string.Empty;
public bool Popup { get; set; }
public string? MediaUrl { get; set; }
public string? MediaType { get; set; }
}
@@ -0,0 +1,38 @@
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 5000
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Directory.Build.props", "."]
COPY ["Directory.Packages.props", "."]
COPY ["src/MyTelegram.Admin.Api/MyTelegram.Admin.Api.csproj", "src/MyTelegram.Admin.Api/"]
COPY ["src/MyTelegram.Domain/MyTelegram.Domain.csproj", "src/MyTelegram.Domain/"]
COPY ["src/MyTelegram.Domain.Shared/MyTelegram.Domain.Shared.csproj", "src/MyTelegram.Domain.Shared/"]
COPY ["src/MyTelegram.Services/MyTelegram.Services.csproj", "src/MyTelegram.Services/"]
COPY ["src/MyTelegram.Queries/MyTelegram.Queries.csproj", "src/MyTelegram.Queries/"]
COPY ["src/MyTelegram.ReadModel.Interfaces/MyTelegram.ReadModel.Interfaces.csproj", "src/MyTelegram.ReadModel.Interfaces/"]
COPY ["src/MyTelegram.ReadModel/MyTelegram.ReadModel.csproj", "src/MyTelegram.ReadModel/"]
COPY ["src/MyTelegram.Schema/MyTelegram.Schema.csproj", "src/MyTelegram.Schema/"]
COPY ["src/MyTelegram.Core/MyTelegram.Core.csproj", "src/MyTelegram.Core/"]
COPY ["src/MyTelegram.EventFlow/MyTelegram.EventFlow.csproj", "src/MyTelegram.EventFlow/"]
COPY ["src/MyTelegram.EventFlow.MongoDB/MyTelegram.EventFlow.MongoDB.csproj", "src/MyTelegram.EventFlow.MongoDB/"]
COPY ["src/MyTelegram.EventBus/MyTelegram.EventBus.csproj", "src/MyTelegram.EventBus/"]
COPY ["src/MyTelegram.EventBus.RabbitMQ/MyTelegram.EventBus.RabbitMQ.csproj", "src/MyTelegram.EventBus.RabbitMQ/"]
COPY ["src/MyTelegram.Abstractions/MyTelegram.Abstractions.csproj", "src/MyTelegram.Abstractions/"]
RUN dotnet restore "./src/MyTelegram.Admin.Api/MyTelegram.Admin.Api.csproj"
COPY . .
WORKDIR "/src/src/MyTelegram.Admin.Api"
RUN dotnet build "./MyTelegram.Admin.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./MyTelegram.Admin.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyTelegram.Admin.Api.dll"]
@@ -0,0 +1,71 @@
using System.Security.Claims;
namespace MyTelegram.Admin.Api.Middleware;
/// <summary>
/// Middleware аутентификации для Admin API.
/// Для всех админских эндпоинтов требует API-ключ или токен.
/// </summary>
public class AdminAuthMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<AdminAuthMiddleware> _logger;
private readonly string _adminApiKey;
public AdminAuthMiddleware(RequestDelegate next, ILogger<AdminAuthMiddleware> logger, IConfiguration configuration)
{
_next = next;
_logger = logger;
// Берём ключ из конфигурации или переменной окружения,
// значение по умолчанию пригодно только для разработки
_adminApiKey = configuration["ADMIN_API_KEY"]
?? Environment.GetEnvironmentVariable("ADMIN_API_KEY")
?? "CHANGE_ME_IN_PRODUCTION";
if (_adminApiKey == "CHANGE_ME_IN_PRODUCTION")
{
_logger.LogWarning("Using default Admin API key! Set ADMIN_API_KEY environment variable.");
}
else
{
_logger.LogInformation("Admin API authentication enabled with custom key");
}
}
public async Task InvokeAsync(HttpContext context)
{
var path = context.Request.Path.Value;
// Проверяем аутентификацию для всех эндпоинтов /api/*
if (path != null && path.StartsWith("/api/"))
{
// Сначала ищем ключ в заголовке X-Admin-API-Key
if (!context.Request.Headers.TryGetValue("X-Admin-API-Key", out var apiKey) ||
apiKey != _adminApiKey)
{
// Затем пробуем заголовок Authorization с Bearer-токеном
if (!context.Request.Headers.TryGetValue("Authorization", out var authHeader) ||
!authHeader.ToString().StartsWith("Bearer ") ||
authHeader.ToString().Substring(7) != _adminApiKey)
{
_logger.LogWarning("Unauthorized Admin API access attempt - IP: {IP}, Path: {Path}",
context.Connection.RemoteIpAddress, path);
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(new
{
success = false,
error = "Unauthorized: Invalid or missing API key"
});
return;
}
}
_logger.LogDebug("Admin API access authorized - Path: {Path}", path);
}
await _next(context);
}
}
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="MessagePack" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MyTelegram.Domain\MyTelegram.Domain.csproj" />
<ProjectReference Include="..\MyTelegram.Domain.Shared\MyTelegram.Domain.Shared.csproj" />
<ProjectReference Include="..\MyTelegram.Services\MyTelegram.Services.csproj" />
<ProjectReference Include="..\MyTelegram.Queries\MyTelegram.Queries.csproj" />
<ProjectReference Include="..\MyTelegram.EventFlow.MongoDB\MyTelegram.EventFlow.MongoDB.csproj" />
<ProjectReference Include="..\MyTelegram.EventBus.RabbitMQ\MyTelegram.EventBus.RabbitMQ.csproj" />
<ProjectReference Include="..\MyTelegram.Messenger\MyTelegram.Messenger.csproj" />
</ItemGroup>
</Project>
+231
View File
@@ -0,0 +1,231 @@
using EventFlow;
using EventFlow.Extensions;
using EventFlow.MongoDB.Extensions;
using MyTelegram;
using MyTelegram.Domain.Commands.User;
using MyTelegram.Domain.Aggregates.User;
using MyTelegram.Domain.Shared.Events;
using MyTelegram.EventBus;
using MyTelegram.Abstractions;
using MyTelegram.Admin.Api.Serializers;
using MyTelegram.EventBus.RabbitMQ.Extensions;
using MyTelegram.EventBus.RabbitMQ;
using MyTelegram.Messenger.Extensions;
using MyTelegram.ReadModel.MongoDB;
using MyTelegram.ReadModel.Impl;
using MyTelegram.Schema;
using MongoDB.Driver;
var builder = WebApplication.CreateBuilder(args);
// Слушаем порт 5000
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(5000);
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Настройки RabbitMQ и MongoDB
builder.Services.Configure<EventBusRabbitMqOptions>(builder.Configuration.GetSection("RabbitMQ:EventBus"));
builder.Services.Configure<RabbitMqOptions>(builder.Configuration.GetSection("RabbitMQ:Connections:Default"));
var connectionString = builder.Configuration.GetConnectionString("Default") ?? "mongodb://mongodb:27017";
var databaseName = builder.Configuration.GetValue<string>("App:DatabaseName") ?? "eventflow";
var mongoClient = new MongoClient(connectionString);
builder.Services.AddSingleton<IMongoClient>(mongoClient);
builder.Services.AddSingleton<IMongoDatabase>(_ => mongoClient.GetDatabase(databaseName));
// Подключаем EventFlow с хранилищем событий в MongoDB
builder.Services.AddEventFlow(options =>
{
options.AddDefaults(typeof(UserAggregate).Assembly);
options.UseMongoDbEventStore();
options.ConfigureMongoDb(connectionString, databaseName);
});
// Регистрируем только нужные сериализаторы, не подключая весь набор сервисов
// мессенджера, иначе сериализаторы Mongo конфликтуют между собой
try
{
builder.Services.RegisterMongoDbSerializer();
}
catch (MongoDB.Bson.BsonSerializationException)
{
// Сериализатор мог быть уже зарегистрирован, повторную регистрацию пропускаем
}
// Шина событий поверх RabbitMQ
builder.Services.AddTransient<IEventDataSerializer<BotGiftEvent>, BotGiftEventSerializer>();
builder.Services.AddMyTelegramRabbitMqEventBus();
// Регистрируем сервис отправки служебных уведомлений вручную
builder.Services.AddScoped<MyTelegram.Messenger.Services.Interfaces.IServiceNotificationAppService,
MyTelegram.Messenger.Services.Impl.ServiceNotificationAppService>();
// CORS для админ-панели
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAdmin", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseCors("AllowAdmin");
// Ограничиваем размер тела запроса, чтобы не переполнять память
app.Use(async (context, next) =>
{
const long maxSize = 10 * 1024 * 1024; // 10MB
var feature = context.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature>();
if (feature != null && !feature.IsReadOnly)
{
feature.MaxRequestBodySize = maxSize;
}
await next();
});
app.UseMiddleware<MyTelegram.Admin.Api.Middleware.AdminAuthMiddleware>();
app.MapControllers();
app.MapPost("/api/admin/star-gifts/send", async (
AdminSendStarGiftRequest request,
IMongoDatabase mongoDatabase,
IEventBus eventBus) =>
{
try
{
if (request.Count <= 0)
{
return Results.BadRequest(new { success = false, error = "Count must be positive" });
}
var giftId = request.GiftId;
var giftCollection = mongoDatabase.GetCollection<AvailableStarGiftReadModel>("AvailableStarGiftReadModel");
var gift = await giftCollection.Find(x => x.GiftId == giftId).FirstOrDefaultAsync();
if (gift == null)
{
return Results.NotFound(new { success = false, error = "Gift not found" });
}
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var sent = 0;
var canUpgrade = request.CanUpgrade ?? (gift.UpgradeStars.HasValue);
for (var i = 0; i < request.Count; i++)
{
var evt = new BotGiftEvent
{
BotUserId = request.FromUserId,
ToUserId = request.ToUserId,
GiftId = gift.GiftId,
Count = 1,
Message = request.Message,
HideName = request.NameHidden,
IncludeUpgrade = canUpgrade,
Timestamp = now,
RandomId = Random.Shared.NextInt64()
};
await eventBus.PublishAsync(evt);
sent++;
}
return Results.Ok(new { success = true, sent });
}
catch (Exception ex)
{
return Results.BadRequest(new { success = false, error = ex.Message });
}
});
// Установка верификации пользователя
app.MapPost("/api/admin/verification/users/{userId}", async (
long userId,
SetUserVerificationRequest request,
ICommandBus commandBus) =>
{
try
{
var command = new SetCustomVerificationCommand(
UserId.Create(userId),
RequestInfo.Empty with { Date = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() },
true,
request.BotUserId,
userId,
request.IconEmojiId,
request.Description,
request.CustomDescription
);
await commandBus.PublishAsync(command, CancellationToken.None);
return Results.Ok(new { success = true, message = "Verification set successfully" });
}
catch (Exception ex)
{
return Results.BadRequest(new { success = false, error = ex.Message });
}
});
// Снятие верификации пользователя
app.MapDelete("/api/admin/verification/users/{userId}", async (
long userId,
ICommandBus commandBus) =>
{
try
{
var command = new SetCustomVerificationCommand(
UserId.Create(userId),
RequestInfo.Empty with { Date = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() },
false,
null,
userId,
0,
string.Empty,
null
);
await commandBus.PublishAsync(command, CancellationToken.None);
return Results.Ok(new { success = true, message = "Verification removed successfully" });
}
catch (Exception ex)
{
return Results.BadRequest(new { success = false, error = ex.Message });
}
});
app.Run();
public record AdminSendStarGiftRequest(
long FromUserId,
long ToUserId,
long GiftId,
int Count,
bool NameHidden,
bool? CanUpgrade,
string? Message
);
public record SetUserVerificationRequest(
long BotUserId,
long IconEmojiId,
string Description,
string? CustomDescription
);
@@ -0,0 +1,27 @@
using System.Buffers;
using MessagePack;
using MyTelegram.Abstractions;
using MyTelegram.Domain.Shared.Events;
namespace MyTelegram.Admin.Api.Serializers;
public class BotGiftEventSerializer : IEventDataSerializer<BotGiftEvent>
{
private static readonly MessagePackSerializerOptions Options = MessagePackSerializerOptions.Standard
.WithResolver(MessagePack.Resolvers.ContractlessStandardResolver.Instance);
public object Deserialize(Type type, ReadOnlyMemory<byte> buffer)
{
return MessagePackSerializer.Deserialize<BotGiftEvent>(buffer, Options)!;
}
public BotGiftEvent Deserialize(ReadOnlyMemory<byte> buffer)
{
return MessagePackSerializer.Deserialize<BotGiftEvent>(buffer, Options);
}
public void Serialize(IBufferWriter<byte> writer, BotGiftEvent value)
{
MessagePackSerializer.Serialize(writer, value, Options);
}
}
@@ -0,0 +1,30 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"Default": "mongodb://user:CHANGE_ME@mongodb:27017"
},
"App": {
"DatabaseName": "eventflow",
"ReadModelDatabaseName": "readmodel"
},
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "rabbitmq",
"Port": 5672,
"UserName": "guest",
"Password": "guest"
}
},
"EventBus": {
"ClientName": "MyTelegram.Admin.Api",
"ExchangeName": "MyTelegram"
}
}
}
@@ -0,0 +1,22 @@
namespace MyTelegram.AuthServer.BackgroundServices;
public class MyTelegramAuthServerBackgroundService(
ILogger<MyTelegramAuthServerBackgroundService> logger,
IHandlerHelper handlerHelper,
IFingerprintHelper fingerprintHelper
) : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
handlerHelper.InitAllHandlers();
logger.LogInformation("MyTelegram auth server started");
const long defaultFingerprint = -3591632762792723036;
var fingerprint = fingerprintHelper.GetFingerprint();
if (fingerprint == defaultFingerprint)
{
logger.LogWarning("You are currently using the default private key, which anyone can obtain from the mytelegram open source project. For security reasons, please use your own private key and replace the client's public key.");
}
return Task.CompletedTask;
}
}
@@ -0,0 +1,40 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/runtime:9.0 AS base
USER $APP_UID
WORKDIR /app
# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["Directory.Packages.props", "."]
COPY ["Directory.Build.props", "."]
COPY ["nuget.config", "."]
COPY ["src/MyTelegram.AuthServer/MyTelegram.AuthServer.csproj", "src/MyTelegram.AuthServer/"]
COPY ["src/MyTelegram.Caching.Redis/MyTelegram.Caching.Redis.csproj", "src/MyTelegram.Caching.Redis/"]
COPY ["src/MyTelegram.Core/MyTelegram.Core.csproj", "src/MyTelegram.Core/"]
COPY ["src/MyTelegram.Abstractions/MyTelegram.Abstractions.csproj", "src/MyTelegram.Abstractions/"]
COPY ["src/MyTelegram.Domain.Shared/MyTelegram.Domain.Shared.csproj", "src/MyTelegram.Domain.Shared/"]
COPY ["src/MyTelegram.Schema/MyTelegram.Schema.csproj", "src/MyTelegram.Schema/"]
COPY ["src/MyTelegram.EventBus.Rebus/MyTelegram.EventBus.Rebus.csproj", "src/MyTelegram.EventBus.Rebus/"]
COPY ["src/MyTelegram.EventBus/MyTelegram.EventBus.csproj", "src/MyTelegram.EventBus/"]
COPY ["src/MyTelegram.Services/MyTelegram.Services.csproj", "src/MyTelegram.Services/"]
COPY ["src/MyTelegram.EventFlow/MyTelegram.EventFlow.csproj", "src/MyTelegram.EventFlow/"]
RUN dotnet restore "./src/MyTelegram.AuthServer/MyTelegram.AuthServer.csproj"
COPY . .
WORKDIR "/src/src/MyTelegram.AuthServer"
RUN dotnet build "./MyTelegram.AuthServer.csproj" -c $BUILD_CONFIGURATION -o /app/build
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./MyTelegram.AuthServer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyTelegram.AuthServer.dll"]
@@ -0,0 +1,92 @@
namespace MyTelegram.AuthServer.EventHandlers;
public class UnencryptedMessageHandler(
ILogger<UnencryptedMessageHandler> logger,
IHandlerHelper handlerHelper,
IEventBus eventBus
) : IEventHandler<UnencryptedMessage>, ITransientDependency
{
public async Task HandleEventAsync(UnencryptedMessage eventData)
{
try
{
if (!handlerHelper.TryGetHandler(eventData.ObjectId, out var handler))
{
logger.LogWarning(
"Cannot find a handler with objectId {ObjectId:x2}, connectionId: {ConnectionId}",
eventData.ObjectId,
eventData.ConnectionId
);
return;
}
if (logger.IsEnabled(LogLevel.Trace))
{
logger.LogTrace(
"Processing unencrypted message, connectionId: {ConnectionId}, [objectId]: {ObjectId:x2}, handler: {Handler}, reqMsgId: {ReqMsgId}",
eventData.ConnectionId,
eventData.ObjectId,
handler.GetType().Name,
eventData.MessageId
);
}
var obj = eventData.MessageData.ToTObject<IObject>();
var r = await handler.HandleAsync(
new RequestInput(
eventData.ConnectionId,
eventData.RequestId,
eventData.ObjectId,
eventData.MessageId,
0,
0,
eventData.AuthKeyId,
eventData.AuthKeyId,
0,
eventData.Date,
DeviceType.Unknown,
eventData.ClientIp,
0,
0
),
obj
);
if (r != null!)
{
using var writer = new ArrayPoolBufferWriter<byte>();
r.Serialize(writer);
var unencryptedResponse = new UnencryptedMessageResponse(
eventData.AuthKeyId,
writer.WrittenMemory,
eventData.ConnectionId,
eventData.MessageId
);
await eventBus.PublishAsync(unencryptedResponse);
}
}
catch (Exception ex)
{
logger.LogError(
"Process request failed, connectionId: {ConnectionId}, reqMsgId: {ReqMsgId}, error: {Error}",
eventData.ConnectionId,
eventData.MessageId,
ex
);
}
finally
{
eventData.MemoryOwner?.Dispose();
}
if (logger.IsEnabled(LogLevel.Trace))
{
logger.LogTrace(
"Process unencrypted message completed, reqMsgId: {ReqMsgId}",
eventData.MessageId
);
}
}
}

Some files were not shown because too many files have changed in this diff Show More