diff --git a/src/main/java/com/annimon/ffmpegbot/MainBotHandler.java b/src/main/java/com/annimon/ffmpegbot/MainBotHandler.java
index fa427a3..baa9283 100644
--- a/src/main/java/com/annimon/ffmpegbot/MainBotHandler.java
+++ b/src/main/java/com/annimon/ffmpegbot/MainBotHandler.java
@@ -40,7 +40,7 @@ public class MainBotHandler extends BotHandler {
commands.registerBundle(mediaProcessingBundle);
commands.registerBundle(new InputParametersBundle(sessions));
commands.registerBundle(new YtDlpCommandBundle(sessions));
- commands.registerBundle(new AdminCommandBundle());
+ commands.registerBundle(new AdminCommandBundle(sessions));
commands.register(new HelpCommand());
}
diff --git a/src/main/java/com/annimon/ffmpegbot/commands/HelpCommand.java b/src/main/java/com/annimon/ffmpegbot/commands/HelpCommand.java
index 156c2ef..9b53410 100644
--- a/src/main/java/com/annimon/ffmpegbot/commands/HelpCommand.java
+++ b/src/main/java/com/annimon/ffmpegbot/commands/HelpCommand.java
@@ -40,7 +40,7 @@ public class HelpCommand implements TextCommand {
yt-dlp
/dl link [format] — download a media using yt-dlp
link
— a link to download (it must be supported by yt-dlp)
- format
— (optional) a download format. Can be "audio", "240", "360", "480", "720" or "1080"
+ format
— (optional) a download format. Can be "best", "audio", "240", "360", "480", "720" or "1080"
""".stripIndent()).enableHtml().callAsync(ctx.sender);
}
}
diff --git a/src/main/java/com/annimon/ffmpegbot/commands/admin/AdminCommandBundle.java b/src/main/java/com/annimon/ffmpegbot/commands/admin/AdminCommandBundle.java
index a7a38c6..0e336ac 100644
--- a/src/main/java/com/annimon/ffmpegbot/commands/admin/AdminCommandBundle.java
+++ b/src/main/java/com/annimon/ffmpegbot/commands/admin/AdminCommandBundle.java
@@ -1,5 +1,6 @@
package com.annimon.ffmpegbot.commands.admin;
+import com.annimon.ffmpegbot.session.Sessions;
import com.annimon.tgbotsmodule.commands.CommandBundle;
import com.annimon.tgbotsmodule.commands.CommandRegistry;
import com.annimon.tgbotsmodule.commands.authority.For;
@@ -7,9 +8,17 @@ import org.jetbrains.annotations.NotNull;
public class AdminCommandBundle implements CommandBundle {
+ private final Sessions sessions;
+
+ public AdminCommandBundle(Sessions sessions) {
+ this.sessions = sessions;
+ }
+
@Override
public void register(@NotNull CommandRegistry commands) {
commands.register(new RunCommand());
commands.register(new ClearCommand());
+ commands.register(new SessionsCommand(sessions));
+ commands.register(new SessionsClearCommand(sessions));
}
}
diff --git a/src/main/java/com/annimon/ffmpegbot/commands/admin/SessionsClearCommand.java b/src/main/java/com/annimon/ffmpegbot/commands/admin/SessionsClearCommand.java
new file mode 100644
index 0000000..3bdb6dc
--- /dev/null
+++ b/src/main/java/com/annimon/ffmpegbot/commands/admin/SessionsClearCommand.java
@@ -0,0 +1,38 @@
+package com.annimon.ffmpegbot.commands.admin;
+
+import com.annimon.ffmpegbot.Permissions;
+import com.annimon.ffmpegbot.session.Sessions;
+import com.annimon.tgbotsmodule.commands.CallbackQueryCommand;
+import com.annimon.tgbotsmodule.commands.authority.For;
+import com.annimon.tgbotsmodule.commands.context.CallbackQueryContext;
+import org.jetbrains.annotations.NotNull;
+import java.util.EnumSet;
+
+public class SessionsClearCommand implements CallbackQueryCommand {
+ static final String CALLBACK_NAME = "sessions_clear";
+ private final Sessions sessions;
+
+ public SessionsClearCommand(Sessions sessions) {
+ this.sessions = sessions;
+ }
+
+ @Override
+ public String command() {
+ return CALLBACK_NAME;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public EnumSet authority() {
+ return Permissions.SUPERUSERS;
+ }
+
+ @Override
+ public void accept(@NotNull CallbackQueryContext ctx) {
+ final int size = sessions.getSize();
+ sessions.clear();
+ ctx.editMessage("%d sessions cleared".formatted(size))
+ .setReplyMarkup(null)
+ .callAsync(ctx.sender);
+ }
+}
diff --git a/src/main/java/com/annimon/ffmpegbot/commands/admin/SessionsCommand.java b/src/main/java/com/annimon/ffmpegbot/commands/admin/SessionsCommand.java
new file mode 100644
index 0000000..265b540
--- /dev/null
+++ b/src/main/java/com/annimon/ffmpegbot/commands/admin/SessionsCommand.java
@@ -0,0 +1,84 @@
+package com.annimon.ffmpegbot.commands.admin;
+
+import com.annimon.ffmpegbot.Permissions;
+import com.annimon.ffmpegbot.TextUtils;
+import com.annimon.ffmpegbot.session.MediaSession;
+import com.annimon.ffmpegbot.session.Session;
+import com.annimon.ffmpegbot.session.Sessions;
+import com.annimon.ffmpegbot.session.YtDlpSession;
+import com.annimon.tgbotsmodule.commands.TextCommand;
+import com.annimon.tgbotsmodule.commands.authority.For;
+import com.annimon.tgbotsmodule.commands.context.MessageContext;
+import org.jetbrains.annotations.NotNull;
+import org.telegram.telegrambots.meta.api.objects.replykeyboard.buttons.InlineKeyboardButton;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class SessionsCommand implements TextCommand {
+ private final Sessions sessions;
+
+ public SessionsCommand(Sessions sessions) {
+ this.sessions = sessions;
+ }
+
+ @Override
+ public String command() {
+ return "/sessions";
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public EnumSet authority() {
+ return Permissions.SUPERUSERS;
+ }
+
+ @Override
+ public void accept(@NotNull MessageContext ctx) {
+ final int size = sessions.getSize();
+ if (size == 0) {
+ ctx.replyToMessage("No sessions found").callAsync(ctx.sender);
+ return;
+ }
+
+
+ if (ctx.argument(1).equals("clear")) {
+ sessions.clear();
+ ctx.replyToMessage("%d sessions cleared".formatted(size)).callAsync(ctx.sender);
+ } else {
+ final var sessionsInfo = sessions.sessions()
+ .sorted(Comparator.comparing(Session::getInstant).reversed())
+ .map(this::formatSession)
+ .limit(30)
+ .collect(Collectors.joining("\n"));
+ ctx.replyToMessage(("%d active sessions:\n%s").formatted(size, sessionsInfo))
+ .disableWebPagePreview()
+ .setSingleRowInlineKeyboard(InlineKeyboardButton.builder()
+ .text("Clear").callbackData(SessionsClearCommand.CALLBACK_NAME)
+ .build())
+ .callAsync(ctx.sender);
+ }
+ }
+
+ private String formatSession(Session session) {
+ long chatId = session.getChatId();
+ if (session instanceof MediaSession ms) {
+ return String.join(" ", str(chatId), str(ms.getOriginalFilename()),
+ str(ms.getFileSize(), TextUtils::readableFileSize),
+ str(ms.getDuration(), TextUtils::readableDuration));
+ } else if (session instanceof YtDlpSession ys) {
+ return String.join(" ", str(chatId), str(ys.getUrl()));
+ }
+ return str(chatId);
+ }
+
+ private String str(Object obj) {
+ return obj == null ? "" : Objects.toString(obj);
+ }
+
+ private String str(T obj, Function super T, String> func) {
+ return obj == null ? "" : str(func.apply(obj));
+ }
+}
diff --git a/src/main/java/com/annimon/ffmpegbot/session/MediaSession.java b/src/main/java/com/annimon/ffmpegbot/session/MediaSession.java
index e8b9134..055fffa 100644
--- a/src/main/java/com/annimon/ffmpegbot/session/MediaSession.java
+++ b/src/main/java/com/annimon/ffmpegbot/session/MediaSession.java
@@ -54,14 +54,26 @@ public final class MediaSession extends Session {
this.originalFilename = originalFilename;
}
+ public String getOriginalFilename() {
+ return originalFilename;
+ }
+
public void setFileSize(Long fileSize) {
this.fileSize = fileSize;
}
+ public Long getFileSize() {
+ return fileSize;
+ }
+
public void setDuration(Integer duration) {
this.duration = duration;
}
+ public Integer getDuration() {
+ return duration;
+ }
+
public void setResolution(Integer width, Integer height) {
if (width == null && height == null) {
this.resolution = null;
diff --git a/src/main/java/com/annimon/ffmpegbot/session/Session.java b/src/main/java/com/annimon/ffmpegbot/session/Session.java
index 2468897..9ad952e 100644
--- a/src/main/java/com/annimon/ffmpegbot/session/Session.java
+++ b/src/main/java/com/annimon/ffmpegbot/session/Session.java
@@ -2,6 +2,7 @@ package com.annimon.ffmpegbot.session;
import com.annimon.ffmpegbot.parameters.InputParameters;
+import java.time.Instant;
import java.util.StringJoiner;
public abstract sealed class Session permits MediaSession, YtDlpSession {
@@ -10,6 +11,8 @@ public abstract sealed class Session permits MediaSession, YtDlpSession {
protected int messageId;
// Parameters
protected final InputParameters inputParams = new InputParameters();
+ // Meta
+ private final Instant instant = Instant.now();
public long getChatId() {
return chatId;
@@ -31,5 +34,9 @@ public abstract sealed class Session permits MediaSession, YtDlpSession {
return inputParams;
}
+ public Instant getInstant() {
+ return instant;
+ }
+
abstract StringJoiner describe();
}
diff --git a/src/main/java/com/annimon/ffmpegbot/session/Sessions.java b/src/main/java/com/annimon/ffmpegbot/session/Sessions.java
index 4d172d1..214d87a 100644
--- a/src/main/java/com/annimon/ffmpegbot/session/Sessions.java
+++ b/src/main/java/com/annimon/ffmpegbot/session/Sessions.java
@@ -2,6 +2,7 @@ package com.annimon.ffmpegbot.session;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Stream;
public class Sessions {
private final Map sessions;
@@ -10,6 +11,18 @@ public class Sessions {
sessions = new ConcurrentHashMap<>();
}
+ public void clear() {
+ sessions.clear();
+ }
+
+ public int getSize() {
+ return sessions.size();
+ }
+
+ public Stream sessions() {
+ return sessions.values().stream();
+ }
+
public Session get(long chatId, long messageId) {
return sessions.get(mapKey(chatId, messageId));
}