mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-18 20:04:17 +01:00
Merge b1d7415dad into 3529849bca
This commit is contained in:
commit
7785cb4e88
@ -60,6 +60,7 @@ public class ApplicationProperties {
|
||||
private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated();
|
||||
|
||||
private Mail mail = new Mail();
|
||||
private Telegram telegram = new Telegram();
|
||||
|
||||
private Premium premium = new Premium();
|
||||
|
||||
@ -335,8 +336,8 @@ public class ApplicationProperties {
|
||||
throw new UnsupportedProviderException(
|
||||
"Logout from the provider "
|
||||
+ registrationId
|
||||
+ " is not supported. "
|
||||
+ "Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues");
|
||||
+ " is not supported. Report it at"
|
||||
+ " https://github.com/Stirling-Tools/Stirling-PDF/issues");
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -536,10 +537,10 @@ public class ApplicationProperties {
|
||||
@Override
|
||||
public String toString() {
|
||||
return """
|
||||
Driver {
|
||||
driverName='%s'
|
||||
}
|
||||
"""
|
||||
Driver {
|
||||
driverName='%s'
|
||||
}
|
||||
"""
|
||||
.formatted(driverName);
|
||||
}
|
||||
}
|
||||
@ -592,6 +593,7 @@ public class ApplicationProperties {
|
||||
private boolean ssoAutoLogin;
|
||||
private CustomMetadata customMetadata = new CustomMetadata();
|
||||
|
||||
@Deprecated
|
||||
@Data
|
||||
public static class CustomMetadata {
|
||||
private boolean autoUpdateMetadata;
|
||||
@ -631,6 +633,59 @@ public class ApplicationProperties {
|
||||
private Boolean sslCheckServerIdentity;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Telegram {
|
||||
private Boolean enabled = false;
|
||||
private String botToken;
|
||||
private String botUsername;
|
||||
private String pipelineInboxFolder = "telegram";
|
||||
private Boolean customFolderSuffix = false;
|
||||
private Boolean enableAllowUserIDs = false;
|
||||
private List<Long> allowUserIDs = new ArrayList<>();
|
||||
private Boolean enableAllowChannelIDs = false;
|
||||
private List<Long> allowChannelIDs = new ArrayList<>();
|
||||
private long processingTimeoutSeconds = 180;
|
||||
private long pollingIntervalMillis = 2000;
|
||||
private Feedback feedback = new Feedback();
|
||||
|
||||
@Data
|
||||
public static class Feedback {
|
||||
private General general = new General();
|
||||
private Channel channel = new Channel();
|
||||
private User user = new User();
|
||||
|
||||
@Data
|
||||
public static class General {
|
||||
private Boolean enabled = true; // set to 'true' to enable feedback messages
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Channel {
|
||||
private Boolean noValidDocument =
|
||||
false; // set to 'true' to deny feedback messages in channels (to avoid
|
||||
// spam)
|
||||
private Boolean processingError =
|
||||
false; // set to 'true' to deny feedback messages in channels (to avoid
|
||||
// spam)
|
||||
private Boolean errorMessage =
|
||||
false; // set to 'true' to deny error feedback messages in channels (to
|
||||
// avoid spam)
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class User {
|
||||
private Boolean noValidDocument =
|
||||
false; // set to 'true' to deny feedback messages to users (to avoid
|
||||
// spam)
|
||||
private Boolean processingError =
|
||||
false; // set to 'true' to deny feedback messages to users (to avoid
|
||||
// spam)
|
||||
private Boolean errorMessage =
|
||||
false; // set to 'true' to deny error feedback messages to users (to avoid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Premium {
|
||||
private boolean enabled;
|
||||
|
||||
@ -60,6 +60,7 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-jetty'
|
||||
implementation 'com.posthog.java:posthog:1.2.0'
|
||||
implementation 'commons-io:commons-io:2.20.0'
|
||||
implementation 'org.telegram:telegrambots:6.9.7.1'
|
||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
||||
implementation 'io.micrometer:micrometer-core:1.15.4'
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
package stirling.software.SPDF.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.telegram.telegrambots.meta.TelegramBotsApi;
|
||||
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
|
||||
import org.telegram.telegrambots.updatesreceivers.DefaultBotSession;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "telegram", name = "enabled", havingValue = "true")
|
||||
public class TelegramBotConfig {
|
||||
|
||||
@Bean
|
||||
public TelegramBotsApi telegramBotsApi() throws TelegramApiException {
|
||||
return new TelegramBotsApi(DefaultBotSession.class);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,446 @@
|
||||
package stirling.software.SPDF.service.telegram;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
|
||||
import org.telegram.telegrambots.meta.TelegramBotsApi;
|
||||
import org.telegram.telegrambots.meta.api.methods.GetFile;
|
||||
import org.telegram.telegrambots.meta.api.methods.send.SendDocument;
|
||||
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
|
||||
import org.telegram.telegrambots.meta.api.objects.Chat;
|
||||
import org.telegram.telegrambots.meta.api.objects.Document;
|
||||
import org.telegram.telegrambots.meta.api.objects.File;
|
||||
import org.telegram.telegrambots.meta.api.objects.InputFile;
|
||||
import org.telegram.telegrambots.meta.api.objects.Message;
|
||||
import org.telegram.telegrambots.meta.api.objects.Update;
|
||||
import org.telegram.telegrambots.meta.api.objects.User;
|
||||
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import stirling.software.common.configuration.RuntimePathConfig;
|
||||
import stirling.software.common.model.ApplicationProperties;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConditionalOnProperty(prefix = "telegram", name = "enabled", havingValue = "true")
|
||||
public class TelegramPipelineBot extends TelegramLongPollingBot {
|
||||
|
||||
private static final String CHAT_PRIVATE = "private";
|
||||
private static final String CHAT_GROUP = "group";
|
||||
private static final String CHAT_SUPERGROUP = "supergroup";
|
||||
private static final String CHAT_CHANNEL = "channel";
|
||||
|
||||
private static final List<String> ALLOWED_MIME_TYPES = List.of("application/pdf");
|
||||
|
||||
private final Object pipelinePollMonitor = new Object();
|
||||
|
||||
private final ApplicationProperties.Telegram telegramProperties;
|
||||
private final RuntimePathConfig runtimePathConfig;
|
||||
private final TelegramBotsApi telegramBotsApi;
|
||||
|
||||
public TelegramPipelineBot(
|
||||
ApplicationProperties applicationProperties,
|
||||
RuntimePathConfig runtimePathConfig,
|
||||
TelegramBotsApi telegramBotsApi) {
|
||||
|
||||
super(applicationProperties.getTelegram().getBotToken());
|
||||
this.telegramProperties = applicationProperties.getTelegram();
|
||||
this.runtimePathConfig = runtimePathConfig;
|
||||
this.telegramBotsApi = telegramBotsApi;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void register() {
|
||||
if (StringUtils.isAnyBlank(getBotUsername(), getBotToken())) {
|
||||
log.warn("Telegram bot disabled because botToken or botUsername is not configured");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
telegramBotsApi.registerBot(this);
|
||||
log.info("Telegram pipeline bot registered as {}", getBotUsername());
|
||||
} catch (TelegramApiException e) {
|
||||
log.error("Failed to register Telegram bot", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdateReceived(Update update) {
|
||||
Message message = extractMessage(update);
|
||||
if (message == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Chat chat = message.getChat();
|
||||
if (chat == null || !isSupportedChatType(chat.getType())) {
|
||||
log.debug(
|
||||
"Ignoring message {}, unsupported chat type {}",
|
||||
message.getMessageId(),
|
||||
chat != null ? chat.getType() : "null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAuthorized(message, chat)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (update.hasMessage() && update.getMessage().hasText()) {
|
||||
String message_text = update.getMessage().getText();
|
||||
long chat_id = update.getMessage().getChatId();
|
||||
if ("/start".equals(message_text)) {
|
||||
sendMessage(
|
||||
chat_id,
|
||||
"""
|
||||
Welcome to the SPDF Telegram Bot!
|
||||
|
||||
To get started, please send me a PDF document that you would like to process.
|
||||
Make sure the document is in PDF format.
|
||||
|
||||
Once I receive your document, I'll begin processing it through the pipeline.
|
||||
""");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (message.hasDocument()) {
|
||||
handleIncomingFile(message);
|
||||
return;
|
||||
}
|
||||
if (telegramProperties.getFeedback().getGeneral().getEnabled()) {
|
||||
sendMessage(
|
||||
chat.getId(),
|
||||
"No valid file found in the message. Please send a document to process.");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// Message Extraction / Chat Type
|
||||
// ---------------------------
|
||||
|
||||
private Message extractMessage(Update update) {
|
||||
if (update.hasMessage()) return update.getMessage();
|
||||
if (update.hasChannelPost()) return update.getChannelPost();
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isSupportedChatType(String type) {
|
||||
return CHAT_PRIVATE.equals(type)
|
||||
|| CHAT_GROUP.equals(type)
|
||||
|| CHAT_SUPERGROUP.equals(type)
|
||||
|| CHAT_CHANNEL.equals(type);
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// Authorization
|
||||
// ---------------------------
|
||||
|
||||
private boolean isAuthorized(Message message, Chat chat) {
|
||||
if (!(telegramProperties.getEnableAllowUserIDs()
|
||||
|| telegramProperties.getEnableAllowChannelIDs())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return switch (chat.getType()) {
|
||||
case CHAT_CHANNEL -> checkChannelAccess(message, chat);
|
||||
case CHAT_PRIVATE -> checkUserAccess(message, chat);
|
||||
case CHAT_GROUP, CHAT_SUPERGROUP -> true; // groups allowed by default
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean checkUserAccess(Message message, Chat chat) {
|
||||
if (!telegramProperties.getEnableAllowUserIDs()) return true;
|
||||
|
||||
User from = message.getFrom();
|
||||
List<Long> allow = telegramProperties.getAllowUserIDs();
|
||||
|
||||
if (allow.isEmpty()) {
|
||||
log.warn("No allowed user IDs configured - allowing all users.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (from == null || !allow.contains(from.getId())) {
|
||||
log.info(
|
||||
"Rejecting user {} in private chat {}",
|
||||
from != null ? from.getId() : "unknown",
|
||||
chat.getId());
|
||||
sendMessage(chat.getId(), "You are not authorized to use this bot.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean checkChannelAccess(Message message, Chat chat) {
|
||||
if (!telegramProperties.getEnableAllowChannelIDs()) return true;
|
||||
|
||||
Chat senderChat = message.getSenderChat();
|
||||
List<Long> allow = telegramProperties.getAllowChannelIDs();
|
||||
|
||||
if (allow.isEmpty()) {
|
||||
log.warn("No allowed channel IDs configured - allowing all channels.");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (senderChat == null || !allow.contains(senderChat.getId())) {
|
||||
log.info(
|
||||
"Rejecting channel {} in chat {}",
|
||||
senderChat != null ? senderChat.getId() : "unknown",
|
||||
chat.getId());
|
||||
|
||||
sendMessage(chat.getId(), "This channel is not authorized to use this bot.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// File Handling
|
||||
// ---------------------------
|
||||
|
||||
private void handleIncomingFile(Message message) {
|
||||
Long chatId = message.getChatId();
|
||||
Document doc = message.getDocument();
|
||||
|
||||
if (doc == null) {
|
||||
sendMessage(chatId, "No document found.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (doc.getMimeType() != null
|
||||
&& !ALLOWED_MIME_TYPES.contains(doc.getMimeType().toLowerCase())) {
|
||||
sendMessage(
|
||||
chatId,
|
||||
"Unsupported MIME type: "
|
||||
+ doc.getMimeType()
|
||||
+ "\nAllowed: "
|
||||
+ String.join(", ", ALLOWED_MIME_TYPES));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasJsonConfig(chatId)) {
|
||||
sendMessage(
|
||||
chatId,
|
||||
"No JSON configuration file found in the pipeline inbox folder. Please contact"
|
||||
+ " the administrator.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!CHAT_CHANNEL.equals(message.getChat().getType())) {
|
||||
sendMessage(chatId, "File received. Starting processing...");
|
||||
}
|
||||
|
||||
PipelineFileInfo info = downloadMessageFile(message);
|
||||
List<Path> outputs = waitForPipelineOutputs(info);
|
||||
|
||||
if (outputs.isEmpty()) {
|
||||
sendMessage(
|
||||
chatId,
|
||||
"No results were found in the pipeline output folder. Check"
|
||||
+ " configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (Path file : outputs) {
|
||||
SendDocument out = new SendDocument();
|
||||
out.setChatId(chatId);
|
||||
out.setDocument(new InputFile(file.toFile(), file.getFileName().toString()));
|
||||
execute(out);
|
||||
}
|
||||
|
||||
} catch (TelegramApiException e) {
|
||||
log.error("Telegram API error", e);
|
||||
sendMessage(chatId, "Telegram API error occurred.");
|
||||
} catch (IOException e) {
|
||||
log.error("IO error", e);
|
||||
sendMessage(chatId, "An IO error occurred.");
|
||||
} catch (Exception e) {
|
||||
log.error("Unexpected error", e);
|
||||
sendMessage(chatId, "Unexpected error occurred.");
|
||||
}
|
||||
}
|
||||
|
||||
private PipelineFileInfo downloadMessageFile(Message message)
|
||||
throws TelegramApiException, IOException {
|
||||
Document document = message.getDocument();
|
||||
String filename = document.getFileName();
|
||||
String name =
|
||||
StringUtils.isNotBlank(filename) ? filename : document.getFileUniqueId() + ".bin";
|
||||
|
||||
return downloadFile(document.getFileId(), name, message);
|
||||
}
|
||||
|
||||
private PipelineFileInfo downloadFile(String fileId, String originalName, Message message)
|
||||
throws TelegramApiException, IOException {
|
||||
|
||||
Long chatId = message.getChatId();
|
||||
|
||||
Path inboxFolder = getInboxFolder(chatId);
|
||||
|
||||
GetFile getFile = new GetFile(fileId);
|
||||
File tgFile = execute(getFile);
|
||||
|
||||
if (tgFile == null || StringUtils.isBlank(tgFile.getFilePath())) {
|
||||
throw new IOException("Telegram did not return a file path.");
|
||||
}
|
||||
|
||||
URL url = buildDownloadUrl(tgFile.getFilePath());
|
||||
|
||||
String base = FilenameUtils.getBaseName(originalName) + "-" + UUID.randomUUID();
|
||||
String ext = FilenameUtils.getExtension(originalName);
|
||||
String outFile = ext.isBlank() ? base : base + "." + ext;
|
||||
|
||||
Path targetFile = inboxFolder.resolve(outFile);
|
||||
|
||||
try (InputStream in = url.openStream()) {
|
||||
Files.copy(in, targetFile);
|
||||
}
|
||||
|
||||
log.info("Saved Telegram file {} to {}", originalName, targetFile);
|
||||
return new PipelineFileInfo(targetFile, base, Instant.now());
|
||||
}
|
||||
|
||||
private URL buildDownloadUrl(String filePath) throws MalformedURLException {
|
||||
return URI.create("https://api.telegram.org/file/bot" + getBotToken() + "/" + filePath)
|
||||
.toURL();
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// Inbox-Ordner & JSON-Check
|
||||
// ---------------------------
|
||||
|
||||
private Path getInboxFolder(Long chatId) throws IOException {
|
||||
Path baseInbox =
|
||||
Paths.get(
|
||||
runtimePathConfig.getPipelineWatchedFoldersPath(),
|
||||
telegramProperties.getPipelineInboxFolder());
|
||||
|
||||
Files.createDirectories(baseInbox);
|
||||
|
||||
Path inboxFolder =
|
||||
telegramProperties.getCustomFolderSuffix()
|
||||
? baseInbox.resolve(chatId.toString())
|
||||
: baseInbox;
|
||||
|
||||
Files.createDirectories(inboxFolder);
|
||||
|
||||
return inboxFolder;
|
||||
}
|
||||
|
||||
private boolean hasJsonConfig(Long chatId) {
|
||||
try {
|
||||
Path inboxFolder = getInboxFolder(chatId);
|
||||
try (Stream<Path> s = Files.list(inboxFolder)) {
|
||||
return s.anyMatch(p -> p.toString().endsWith(".json"));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to check JSON config for chat {}", chatId, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// Pipeline polling
|
||||
// ---------------------------
|
||||
|
||||
private List<Path> waitForPipelineOutputs(PipelineFileInfo info) throws IOException {
|
||||
|
||||
Path finishedDir = Paths.get(runtimePathConfig.getPipelineFinishedFoldersPath());
|
||||
Files.createDirectories(finishedDir);
|
||||
|
||||
Instant start = info.savedAt();
|
||||
Duration timeout = Duration.ofSeconds(telegramProperties.getProcessingTimeoutSeconds());
|
||||
Duration poll = Duration.ofMillis(telegramProperties.getPollingIntervalMillis());
|
||||
List<Path> results = new ArrayList<>();
|
||||
|
||||
while (Duration.between(start, Instant.now()).compareTo(timeout) <= 0) {
|
||||
try (Stream<Path> s = Files.walk(finishedDir, 1)) {
|
||||
results =
|
||||
s.filter(Files::isRegularFile)
|
||||
.filter(path -> matchesBaseName(info.uniqueBaseName(), path))
|
||||
.filter(path -> isNewerThan(path, start))
|
||||
.sorted(Comparator.comparing(Path::toString))
|
||||
.toList();
|
||||
}
|
||||
|
||||
if (!results.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
synchronized (pipelinePollMonitor) {
|
||||
try {
|
||||
pipelinePollMonitor.wait(poll.toMillis());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private boolean matchesBaseName(String base, Path file) {
|
||||
return file.getFileName().toString().contains(base);
|
||||
}
|
||||
|
||||
private boolean isNewerThan(Path path, Instant since) {
|
||||
try {
|
||||
return Files.getLastModifiedTime(path).toInstant().isAfter(since);
|
||||
} catch (IOException e) {
|
||||
log.debug("Could not read modification time for {}", path);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// Messaging
|
||||
// ---------------------------
|
||||
|
||||
private void sendMessage(Long chatId, String text) {
|
||||
if (chatId == null) return;
|
||||
|
||||
SendMessage msg = new SendMessage();
|
||||
msg.setChatId(chatId);
|
||||
msg.setText(text);
|
||||
try {
|
||||
execute(msg);
|
||||
} catch (TelegramApiException e) {
|
||||
log.warn("Failed to send message to {}", chatId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private record PipelineFileInfo(Path originalFile, String uniqueBaseName, Instant savedAt) {}
|
||||
|
||||
@Override
|
||||
public String getBotUsername() {
|
||||
return telegramProperties.getBotUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBotToken() {
|
||||
return telegramProperties.getBotToken();
|
||||
}
|
||||
}
|
||||
@ -112,6 +112,30 @@ mail:
|
||||
sslTrust: '' # optional trusted host override, e.g. "smtp.example.com" or "*"; defaults to "*" (trust all) when empty
|
||||
sslCheckServerIdentity: false # enable hostname verification when using SSL/TLS
|
||||
|
||||
telegram:
|
||||
enabled: false # set to 'true' to enable Telegram bot integration
|
||||
botToken: '' # Telegram bot token obtained from BotFather
|
||||
botUsername: '' # Telegram bot username (without @)
|
||||
pipelineInboxFolder: telegram # Name of the pipeline inbox folder for Telegram uploads
|
||||
customFolderSuffix: true # set to 'true' to allow users to specify custom target folders via UserID
|
||||
enableAllowUserIDs: true # set to 'true' to restrict access to specific Telegram user IDs
|
||||
allowUserIDs: [] # List of allowed Telegram user IDs (e.g. [123456789, 987654321]). Leave empty to allow all users.
|
||||
enableAllowChannelIDs: true # set to 'true' to restrict access to specific Telegram channel IDs
|
||||
allowChannelIDs: [] # List of allowed Telegram channel IDs (e.g. [-1001234567890, -1009876543210]). Leave empty to allow all channels.
|
||||
processingTimeoutSeconds: 180 # Maximum time in seconds to wait for processing a Telegram request
|
||||
pollingIntervalMillis: 2000 # Interval in milliseconds between polling for new messages
|
||||
feedback:
|
||||
general:
|
||||
enabled: true # set to 'true' to enable feedback messages
|
||||
channel:
|
||||
noValidDocument: false # set to 'true' to deny feedback messages in channels (to avoid spam)
|
||||
processingError: false # set to 'true' to deny feedback messages in channels (to avoid spam)
|
||||
errorMessage: false # set to 'true' to deny error messages in channels (to avoid spam)
|
||||
user:
|
||||
noValidDocument: false # set to 'true' to deny feedback messages to users (to avoid spam)
|
||||
processingError: false # set to 'true' to deny feedback messages to users (to avoid spam)
|
||||
errorMessage: false # set to 'true' to deny error messages to users (to avoid spam)
|
||||
|
||||
legal:
|
||||
termsAndConditions: https://www.stirling.com/legal/terms-of-service # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder
|
||||
privacyPolicy: https://www.stirling.com/legal/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy). Empty string to disable or filename to load from local file in static folder
|
||||
|
||||
Loading…
Reference in New Issue
Block a user