Java-приложение для анализа Social Dating Rank в Telegram.
Автор: Сергей Владимирович
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 <?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: `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: `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: `java package com.socialdating.model; public enum InteractionType { // Веса для разных типов взаимодействий MENTION(0.8, "Упоминание @username"), REPLY(0.7, "Ответ на сообщение"), FORWARD(0.9, "Пересылка сообщения"), REACTION(0.3, "Реакция (лайк, дизлайк)"), REPLYTOSTORY(0.6, "Ответ на историю"), QUOTE(0.85, "Цитирование сообщения"), PIN(0.5, "Закрепление сообщения"), SAVE_MESSAGE(0.4, "Сохранение сообщения"), JOINVIALINK(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: `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: `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: `java package com.socialdating.bot; import com.socialdating.analyzer.DatingRankCalculator; import com.socialdating.analyzer.SocialGraphBuilder; import com.socialdating.model.User