Java-приложение для анализа Social Dating Rank в Telegram. Будем использовать Telegram API (Telegram Bot API) и библиотеку JGraphT для реализации PageRank.
Полный проект: Telegram Social Dating Rank Analyzer
1. Структура проекта
telegram-social-dating/
├── pom.xml
├── src/main/java/com/socialdating/
│ ├── Main.java
│ ├── bot/
│ │ ├── TelegramBot.java
│ │ └── BotCommands.java
│ ├── analyzer/
│ │ ├── SocialGraphBuilder.java
│ │ ├── DatingRankCalculator.java
│ │ └── InteractionWeights.java
│ ├── model/
│ │ ├── User.java
│ │ ├── Interaction.java
│ │ └── InteractionType.java
│ ├── storage/
│ │ ├── GraphStorage.java
│ │ └── UserRepository.java
│ └── api/
│ └── TelegramClient.java
└── config/
└── application.properties
2. Файл зависимостей (pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.socialdating</groupId>
<artifactId>telegram-social-dating</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Telegram Bot API -->
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>6.8.0</version>
</dependency>
<!-- Graph Algorithms -->
<dependency>
<groupId>org.jgrapht</groupId>
<artifactId>jgrapht-core</artifactId>
<version>1.5.2</version>
</dependency>
<!-- Database -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.42.0.0</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Configuration -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.0</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.7</version>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.socialdating.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
3. Модели данных
User.java:
package com.socialdating.model;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
public class User {
private Long id;
private String username;
private String firstName;
private String lastName;
private Integer followerCount;
private LocalDateTime joinedDate;
private Double datingRank;
private Set<Interaction> interactions = new HashSet<>();
// Конструкторы, геттеры, сеттеры
public User() {}
public User(Long id, String username, String firstName) {
this.id = id;
this.username = username;
this.firstName = firstName;
this.datingRank = 1.0; // Базовый рейтинг
this.joinedDate = LocalDateTime.now();
}
public void addInteraction(Interaction interaction) {
this.interactions.add(interaction);
}
public double getTotalOutgoingWeight() {
return interactions.stream()
.filter(i -> i.getFromUser().equals(this))
.mapToDouble(Interaction::getWeight)
.sum();
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public Integer getFollowerCount() { return followerCount; }
public void setFollowerCount(Integer followerCount) { this.followerCount = followerCount; }
public LocalDateTime getJoinedDate() { return joinedDate; }
public void setJoinedDate(LocalDateTime joinedDate) { this.joinedDate = joinedDate; }
public Double getDatingRank() { return datingRank; }
public void setDatingRank(Double datingRank) { this.datingRank = datingRank; }
public Set<Interaction> getInteractions() { return interactions; }
public void setInteractions(Set<Interaction> interactions) { this.interactions = interactions; }
}
Interaction.java:
package com.socialdating.model;
import java.time.LocalDateTime;
public class Interaction {
private Long id;
private User fromUser;
private User toUser;
private InteractionType type;
private Double weight;
private LocalDateTime timestamp;
private String contentHash; // Для уникальности
public Interaction() {}
public Interaction(User fromUser, User toUser, InteractionType type) {
this.fromUser = fromUser;
this.toUser = toUser;
this.type = type;
this.weight = type.getBaseWeight();
this.timestamp = LocalDateTime.now();
this.contentHash = generateHash();
}
private String generateHash() {
return fromUser.getId() + "_" + toUser.getId() + "_" +
type.name() + "_" + timestamp.toEpochSecond();
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public User getFromUser() { return fromUser; }
public void setFromUser(User fromUser) { this.fromUser = fromUser; }
public User getToUser() { return toUser; }
public void setToUser(User toUser) { this.toUser = toUser; }
public InteractionType getType() { return type; }
public void setType(InteractionType type) {
this.type = type;
this.weight = type.getBaseWeight();
}
public Double getWeight() { return weight; }
public void setWeight(Double weight) { this.weight = weight; }
public LocalDateTime getTimestamp() { return timestamp; }
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
public String getContentHash() { return contentHash; }
}
InteractionType.java:
package com.socialdating.model;
public enum InteractionType {
// Веса для разных типов взаимодействий
MENTION(0.8, "Упоминание @username"),
REPLY(0.7, "Ответ на сообщение"),
FORWARD(0.9, "Пересылка сообщения"),
REACTION(0.3, "Реакция (лайк, дизлайк)"),
REPLY_TO_STORY(0.6, "Ответ на историю"),
QUOTE(0.85, "Цитирование сообщения"),
PIN(0.5, "Закрепление сообщения"),
SAVE_MESSAGE(0.4, "Сохранение сообщения"),
JOIN_VIA_LINK(0.2, "Вступление по ссылке пользователя"),
DONATION(1.5, "Финансовый донат");
private final double baseWeight;
private final String description;
InteractionType(double baseWeight, String description) {
this.baseWeight = baseWeight;
this.description = description;
}
public double getBaseWeight() {
return baseWeight;
}
public String getDescription() {
return description;
}
public static InteractionType fromString(String text) {
for (InteractionType type : InteractionType.values()) {
if (type.name().equalsIgnoreCase(text)) {
return type;
}
}
return REACTION; // По умолчанию
}
}
4. Построение социального графа
SocialGraphBuilder.java:
package com.socialdating.analyzer;
import com.socialdating.model.User;
import com.socialdating.model.Interaction;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultDirectedWeightedGraph;
import org.jgrapht.graph.DefaultWeightedEdge;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
public class SocialGraphBuilder {
private final Map<Long, User> users = new HashMap<>();
private final List<Interaction> interactions = new ArrayList<>();
private final double timeDecayFactor; // Фактор временного затухания
public SocialGraphBuilder(double timeDecayFactor) {
this.timeDecayFactor = timeDecayFactor;
}
public void addUser(User user) {
users.put(user.getId(), user);
}
public void addInteraction(Interaction interaction) {
interactions.add(interaction);
// Добавляем пользователей, если их еще нет
if (!users.containsKey(interaction.getFromUser().getId())) {
users.put(interaction.getFromUser().getId(), interaction.getFromUser());
}
if (!users.containsKey(interaction.getToUser().getId())) {
users.put(interaction.getToUser().getId(), interaction.getToUser());
}
}
public Graph<User, DefaultWeightedEdge> buildGraph(LocalDateTime analysisDate) {
Graph<User, DefaultWeightedEdge> graph =
new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class);
// Добавляем вершины (пользователей)
for (User user : users.values()) {
graph.addVertex(user);
}
// Добавляем рёбра (взаимодействия) с учётом времени
for (Interaction interaction : interactions) {
User from = users.get(interaction.getFromUser().getId());
User to = users.get(interaction.getToUser().getId());
if (from != null && to != null && !from.equals(to)) {
double timeWeight = calculateTimeDecay(interaction.getTimestamp(), analysisDate);
double finalWeight = interaction.getWeight() * timeWeight;
if (finalWeight > 0.01) { // Игнорируем слишком старые взаимодействия
DefaultWeightedEdge edge = graph.getEdge(from, to);
if (edge == null) {
edge = graph.addEdge(from, to);
graph.setEdgeWeight(edge, finalWeight);
} else {
// Агрегируем веса для повторных взаимодействий
double currentWeight = graph.getEdgeWeight(edge);
graph.setEdgeWeight(edge, currentWeight + finalWeight);
}
}
}
}
return graph;
}
private double calculateTimeDecay(LocalDateTime eventTime, LocalDateTime currentTime) {
long daysBetween = ChronoUnit.DAYS.between(eventTime, currentTime);
// Экспоненциальное затухание: вес = e^(-λ * days)
// где λ = timeDecayFactor (например, 0.01 = 1% затухания в день)
return Math.exp(-timeDecayFactor * daysBetween);
}
public Map<Long, User> getUsers() {
return Collections.unmodifiableMap(users);
}
public List<Interaction> getInteractions() {
return Collections.unmodifiableList(interactions);
}
}
5. Калькулятор Social Dating Rank
DatingRankCalculator.java:
package com.socialdating.analyzer;
import com.socialdating.model.User;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultWeightedEdge;
import java.util.HashMap;
import java.util.Map;
public class DatingRankCalculator {
private static final double DAMPING_FACTOR = 0.85;
private static final double CONVERGENCE_THRESHOLD = 0.0001;
private static final int MAX_ITERATIONS = 100;
public Map<User, Double> calculatePageRank(Graph<User, DefaultWeightedEdge> graph) {
int vertexCount = graph.vertexSet().size();
if (vertexCount == 0) {
return new HashMap<>();
}
// Инициализация
Map<User, Double> currentRank = new HashMap<>();
double initialRank = 1.0 / vertexCount;
for (User user : graph.vertexSet()) {
currentRank.put(user, initialRank);
}
// Итеративный расчёт PageRank
for (int iteration = 0; iteration < MAX_ITERATIONS; iteration++) {
Map<User, Double> nextRank = new HashMap<>();
// Базовая часть (телепортация)
double teleportation = (1.0 - DAMPING_FACTOR) / vertexCount;
for (User user : graph.vertexSet()) {
nextRank.put(user, teleportation);
}
// Распространение ранга по рёбрам
for (User source : graph.vertexSet()) {
double sourceRank = currentRank.get(source);
double totalOutgoingWeight = 0.0;
// Считаем общий вес исходящих рёбер
for (DefaultWeightedEdge edge : graph.outgoingEdgesOf(source)) {
totalOutgoingWeight += graph.getEdgeWeight(edge);
}
if (totalOutgoingWeight > 0) {
// Распределяем ранг источника по исходящим рёбрам
for (DefaultWeightedEdge edge : graph.outgoingEdgesOf(source)) {
User target = graph.getEdgeTarget(edge);
double edgeWeight = graph.getEdgeWeight(edge);
double contribution = DAMPING_FACTOR * sourceRank *
(edgeWeight / totalOutgoingWeight);
nextRank.put(target, nextRank.get(target) + contribution);
}
} else {
// Если нет исходящих рёбер - распределяем ранг равномерно
double distributedRank = DAMPING_FACTOR * sourceRank / vertexCount;
for (User target : graph.vertexSet()) {
nextRank.put(target, nextRank.get(target) + distributedRank);
}
}
}
// Проверка сходимости
if (hasConverged(currentRank, nextRank)) {
currentRank = nextRank;
break;
}
currentRank = nextRank;
// Нормализация
normalizeRanks(currentRank);
}
return currentRank;
}
private boolean hasConverged(Map<User, Double> oldRank, Map<User, Double> newRank) {
double totalDiff = 0.0;
for (User user : oldRank.keySet()) {
double diff = Math.abs(oldRank.get(user) - newRank.get(user));
totalDiff += diff;
}
return totalDiff < CONVERGENCE_THRESHOLD;
}
private void normalizeRanks(Map<User, Double> ranks) {
double sum = ranks.values().stream().mapToDouble(Double::doubleValue).sum();
if (sum > 0) {
for (User user : ranks.keySet()) {
ranks.put(user, ranks.get(user) / sum);
}
}
}
public Map<User, Double> calculateWithPersonalization(
Graph<User, DefaultWeightedEdge> graph,
Map<User, Double> personalization) {
Map<User, Double> baseRanks = calculatePageRank(graph);
// Применяем персонализацию (например, учитываем количество подписчиков)
for (User user : baseRanks.keySet()) {
double personalizationFactor = 1.0;
if (personalization.containsKey(user)) {
personalizationFactor = personalization.get(user);
} else if (user.getFollowerCount() != null) {
// Логарифмическая шкала для количества подписчиков
personalizationFactor = Math.log1p(user.getFollowerCount()) / 10.0 + 1.0;
}
baseRanks.put(user, baseRanks.get(user) * personalizationFactor);
}
normalizeRanks(baseRanks);
return baseRanks;
}
}
6. Telegram бот для управления
TelegramBot.java:
package com.socialdating.bot;
import com.socialdating.analyzer.DatingRankCalculator;
import com.socialdating.analyzer.SocialGraphBuilder;
import com.socialdating.model.User;
import com.socialdating.model.Interaction;
import com.socialdating.model.InteractionType;
import com.socialdating.storage.UserRepository;
import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultWeightedEdge;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.User as TelegramUser;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class TelegramBot extends TelegramLongPollingBot {
private final String botToken;
private final String botUsername;
private final UserRepository userRepository;
private final SocialGraphBuilder graphBuilder;
private final DatingRankCalculator rankCalculator;
private final Map<Long, LocalDateTime> lastUserActivity = new ConcurrentHashMap<>();
private final Map<String, List<Interaction>> pendingInteractions = new ConcurrentHashMap<>();
public TelegramBot(String botToken, String botUsername, UserRepository userRepository) {
this.botToken = botToken;
this.botUsername = botUsername;
this.userRepository = userRepository;
this.graphBuilder = new SocialGraphBuilder(0.01); // 1% затухание в день
this.rankCalculator = new DatingRankCalculator();
}
@Override
public void onUpdateReceived(Update update) {
if (update.hasMessage()) {
handleMessage(update.getMessage());
} else if (update.hasCallbackQuery()) {
handleCallbackQuery(update.getCallbackQuery());
}
}
private void handleMessage(Message message) {
TelegramUser tgUser = message.getFrom();
Long chatId = message.getChatId();
// Сохраняем/обновляем пользователя
User user = userRepository.findOrCreate(
tgUser.getId(),
tgUser.getUserName(),
tgUser.getFirstName(),
tgUser.getLastName()
);
lastUserActivity.put(user.getId(), LocalDateTime.now());
// Анализируем сообщение на взаимодействия
analyzeMessageForInteractions(message, user);
// Обработка команд
if (message.hasText()) {
String text = message.getText().toLowerCase();
if (text.startsWith("/start")) {
sendWelcomeMessage(chatId, user);
} else if (text.startsWith("/myrank")) {
sendUserRank(chatId, user);
} else if (text.startsWith("/top")) {
sendTopUsers(chatId, extractTopCount(text));
} else if (text.startsWith("/analyze")) {
analyzeAndSendResults(chatId);
} else if (text.startsWith("/help")) {
sendHelpMessage(chatId);
}
}
}
private void analyzeMessageForInteractions(Message message, User fromUser) {
String text = message.getText();
if (text == null) return;
// Поиск упоминаний (@username)
String[] words = text.split("\\s+");
for (String word : words) {
if (word.startsWith("@") && word.length() > 1) {
String username = word.substring(1);
User mentionedUser = userRepository.findByUsername(username);
if (mentionedUser != null && !mentionedUser.equals(fromUser)) {
Interaction interaction = new Interaction(
fromUser,
mentionedUser,
InteractionType.MENTION
);
graphBuilder.addInteraction(interaction);
}
}
}
// Если это ответ на сообщение
if (message.getReplyToMessage() != null) {
Message repliedTo = message.getReplyToMessage();
if (repliedTo.getFrom() != null) {
TelegramUser repliedUser = repliedTo.getFrom();
User toUser = userRepository.findOrCreate(
repliedUser.getId(),
repliedUser.getUserName(),
repliedUser.getFirstName(),
repliedUser.getLastName()
);
if (!toUser.equals(fromUser)) {
Interaction interaction = new Interaction(
fromUser,
toUser,
InteractionType.REPLY
);
graphBuilder.addInteraction(interaction);
}
}
}
}
private void analyzeAndSendResults(Long chatId) {
try {
// Строим граф
Graph<User, DefaultWeightedEdge> graph =
graphBuilder.buildGraph(LocalDateTime.now());
// Вычисляем ранги
Map<User, Double> ranks = rankCalculator.calculatePageRank(graph);
// Обновляем ранги пользователей
for (Map.Entry<User, Double> entry : ranks.entrySet()) {
User user = entry.getKey();
user.setDatingRank(entry.getValue());
userRepository.save(user);
}
// Формируем результат
StringBuilder response = new StringBuilder();
response.append("📊 *Результаты анализа Social Dating Rank*\n\n");
response.append("Проанализировано пользователей: ").append(ranks.size()).append("\n");
response.append("Взаимодействий: ").append(graphBuilder.getInteractions().size()).append("\n\n");
response.append("*Топ-5 пользователей:*\n");
ranks.entrySet().stream()
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.limit(5)
.forEach(entry -> {
User user = entry.getKey();
response.append("👤 ")
.append(user.getUsername() != null ? "@" + user.getUsername() : user.getFirstName())
.append(": ")
.append(String.format("%.6f", entry.getValue()))
.append("\n");
});
sendMessage(chatId, response.toString(), true);
} catch (Exception e) {
sendMessage(chatId, "❌ Ошибка при анализе: " + e.getMessage(), false);
}
}
private void sendUserRank(Long chatId, User user) {
double rank = user.getDatingRank() != null ? user.getDatingRank() : 1.0;
String response = String.format(
"👤 *Ваш Social Dating Rank*\n\n" +
"Имя: %s\n" +
"Username: %s\n" +
"Ранг: %.6f\n" +
"Место в рейтинге: #%d\n\n" +
"Ранг показывает ваше влияние в сети на основе взаимодействий с другими пользователями.",
user.getFirstName(),
user.getUsername() != null ? "@" + user.getUsername() : "не указан",
rank,
calculateUserPosition(user.getId())
);
sendMessage(chatId, response, true);
}
private int calculateUserPosition(Long userId) {
// Здесь должна быть логика получения позиции из БД
return 1; // Заглушка
}
private void sendTopUsers(Long chatId, int count) {
List<User> topUsers = userRepository.findTopUsers(count);
if (topUsers.isEmpty()) {
sendMessage(chatId, "📊 Рейтинг пока пуст. Используйте /analyze для анализа.", false);
return;
}
StringBuilder response = new StringBuilder();
response.append("🏆 *Топ-").append(count).append(" пользователей*\n\n");
for (int i = 0; i < topUsers.size(); i++) {
User user = topUsers.get(i);
response.append(i + 1).append(". ")
.append(user.getUsername() != null ? "@" + user.getUsername() : user.getFirstName())
.append(" - ")
.append(String.format("%.6f", user.getDatingRank()))
.append("\n");
}
sendMessage(chatId, response.toString(), true);
}
private int extractTopCount(String text) {
try {
String[] parts = text.split(" ");
if (parts.length > 1) {
return Integer.parseInt(parts[1]);
}
} catch (NumberFormatException e) {
// Игнорируем
}
return 10; // По умолчанию
}
private void sendWelcomeMessage(Long chatId, User user) {
String welcome = String.format(
"👋 Привет, %s!\n\n" +
"Я бот для анализа *Social Dating Rank* в Telegram.\n\n" +
"📊 *Социальный рейтинг* показывает ваше влияние в сети на основе:\n" +
"• Упоминаний (@username)\n" +
"• Ответов на сообщения\n" +
"• Репостов и реакций\n" +
"• Других взаимодействий\n\n" +
"📋 *Доступные команды:*\n" +
"/myrank - ваш текущий рейтинг\n" +
"/top [N] - топ-N пользователей\n" +
"/analyze - запустить анализ\n" +
"/help - помощь\n\n" +
"Бот автоматически анализирует ваши взаимодействия в чатах!",
user.getFirstName()
);
sendMessage(chatId, welcome, true);
}
private void sendHelpMessage(Long chatId) {
String help = """
📚 *Помощь по боту Social Dating Rank*
*Как работает рейтинг:*
Алгоритм анализирует ваши взаимодействия с другими пользователями:
• Упоминания (@username) = 0.8
• Ответы на сообщения = 0.7
• Репосты = 0.9
• Реакции = 0.3
• и т.д.
Ранг рассчитывается по модифицированному алгоритму PageRank, где:
• Каждое взаимодействие имеет вес
• Более свежие взаимодействия значимее
• Взаимодействия с влиятельными пользователями ценятся выше
*Команды:*
/start - начать работу
/myrank - ваш рейтинг
/top 10 - топ-10 пользователей
/analyze - запустить анализ (только для админов)
/help - эта справка
Бот работает в фоновом режиме и собирает данные из чатов, где он добавлен.
""";
sendMessage(chatId, help, true);
}
private void sendMessage(Long chatId, String text, boolean markdown) {
SendMessage message = new SendMessage();
message.setChatId(chatId.toString());
message.setText(text);
message.setParseMode(markdown ? "Markdown" : null);
try {
execute(message);
} catch (TelegramApiException e) {
System.err.println("Ошибка отправки сообщения: " + e.getMessage());
}
}
@Override
public String getBotUsername() {
return botUsername;
}
@Override
public String getBotToken() {
return botToken;
}
}
7. Хранилище данных (SQLite)
UserRepository.java:
package com.socialdating.storage;
import com.socialdating.model.User;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class UserRepository {
private final Connection connection;
public UserRepository(String dbPath) throws SQLException {
String url = "jdbc:sqlite:" + dbPath;
connection = DriverManager.getConnection(url);
createTables();
}
private void createTables() throws SQLException {
String createUsersTable = """
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT,
first_name TEXT NOT NULL,
last_name TEXT,
follower_count INTEGER DEFAULT 0,
joined_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
dating_rank REAL DEFAULT 1.0,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""";
String createInteractionsTable = """
CREATE TABLE IF NOT EXISTS interactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
from_user_id INTEGER NOT NULL,
to_user_id INTEGER NOT NULL,
interaction_type TEXT NOT NULL,
weight REAL NOT NULL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
content_hash TEXT UNIQUE,
FOREIGN KEY (from_user_id) REFERENCES users(id),
FOREIGN KEY (to_user_id) REFERENCES users(id)
)
""";
String createIndex = """
CREATE INDEX IF NOT EXISTS idx_interactions_users
ON interactions(from_user_id, to_user_id, timestamp)
""";
try (Statement stmt = connection.createStatement()) {
stmt.execute(createUsersTable);
stmt.execute(createInteractionsTable);
stmt.execute(createIndex);
}
}
public User findOrCreate(Long id, String username, String firstName, String lastName) {
String selectSql = "SELECT * FROM users WHERE id = ?";
String insertSql = """
INSERT OR IGNORE INTO users
(id, username, first_name, last_name)
VALUES (?, ?, ?, ?)
""";
try (PreparedStatement selectStmt = connection.prepareStatement(selectSql)) {
selectStmt.setLong(1, id);
ResultSet rs = selectStmt.executeQuery();
if (rs.next()) {
return mapUserFromResultSet(rs);
} else {
try (PreparedStatement insertStmt = connection.prepareStatement(insertSql)) {
insertStmt.setLong(1, id);
insertStmt.setString(2, username);
insertStmt.setString(3, firstName);
insertStmt.setString(4, lastName);
insertStmt.executeUpdate();
return new User(id, username, firstName);
}
}
} catch (SQLException e) {
System.err.println("Ошибка поиска/создания пользователя: " + e.getMessage());
return new User(id, username, firstName);
}
}
public void save(User user) {
String sql = """
UPDATE users SET
username = ?,
first_name = ?,
last_name = ?,
follower_count = ?,
dating_rank = ?,
last_updated = CURRENT_TIMESTAMP
WHERE id = ?
""";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, user.getUsername());
stmt.setString(2, user.getFirstName());
stmt.setString(3, user.getLastName());
stmt.setInt(4, user.getFollowerCount() != null ? user.getFollowerCount() : 0);
stmt.setDouble(5, user.getDatingRank() != null ? user.getDatingRank() : 1.0);
stmt.setLong(6, user.getId());
stmt.executeUpdate();
} catch (SQLException e) {
System.err.println("Ошибка сохранения пользователя: " + e.getMessage());
}
}
public List<User> findTopUsers(int limit) {
List<User> users = new ArrayList<>();
String sql = "SELECT * FROM users WHERE dating_rank > 0 ORDER BY dating_rank DESC LIMIT ?";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setInt(1, limit);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
users.add(mapUserFromResultSet(rs));
}
} catch (SQLException e) {
System.err.println("Ошибка получения топ-пользователей: " + e.getMessage());
}
return users;
}
public User findByUsername(String username) {
String sql = "SELECT * FROM users WHERE username = ?";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
return mapUserFromResultSet(rs);
}
} catch (SQLException e) {
System.err.println("Ошибка поиска по username: " + e.getMessage());
}
return null;
}
private User mapUserFromResultSet(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getLong("id"));
user.setUsername(rs.getString("username"));
user.setFirstName(rs.getString("first_name"));
user.setLastName(rs.getString("last_name"));
user.setFollowerCount(rs.getInt("follower_count"));
user.setJoinedDate(rs.getTimestamp("joined_date").toLocalDateTime());
user.setDatingRank(rs.getDouble("dating_rank"));
return user;
}
public void close() {
try {
if (connection != null && !connection.isClosed()) {
connection.close();
}
} catch (SQLException e) {
System.err.println("Ошибка закрытия соединения: " + e.getMessage());
}
}
}
8. Главный класс приложения
Main.java:
package com.socialdating;
import com.socialdating.bot.TelegramBot;
import com.socialdating.storage.UserRepository;
import org.telegram.telegrambots.meta.TelegramBotsApi;
import org.telegram.telegrambots.updatesreceivers.DefaultBotSession;
import java.util.Scanner;
public class Main {
private static final String CONFIG_FILE = "config.properties";
public static void main(String[] args) {
try {
System.out.println("🚀 Запуск Social Dating Rank Analyzer для Telegram");
System.out.println("===================================================\n");
// Загрузка конфигурации
Config config = loadConfig();
// Инициализация хранилища
UserRepository userRepository = new UserRepository(config.getDbPath());
System.out.println("✅ База данных инициали