添加简单的群组活跃状态记录器,添加程序安全结束钩子

- ./data/tracker/<chatid>/<userid>/<unixdaystamp>.txt
  - 每行一个发言时的timestamp
  - 0/0/currentTime 用于记录tracker的在线状态
This commit is contained in:
A.C.Sukazyo Eyre 2021-11-09 17:27:11 +08:00
parent 976d106de9
commit 66866572c0
Signed by: Eyre_S
GPG Key ID: EFB47D98FE082FAD
6 changed files with 165 additions and 4 deletions

View File

@ -3,7 +3,7 @@ plugins {
} }
group 'cc.sukazyo' group 'cc.sukazyo'
version '0.1.2' version '0.2.0'
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -2,7 +2,7 @@ package cc.sukazyo.cono.morny;
import cc.sukazyo.cono.morny.bot.api.OnUpdate; import cc.sukazyo.cono.morny.bot.api.OnUpdate;
import cc.sukazyo.cono.morny.bot.event.EventListeners; import cc.sukazyo.cono.morny.bot.event.EventListeners;
import cc.sukazyo.cono.morny.data.MornyHello; import cc.sukazyo.cono.morny.data.tracker.TrackerDataManager;
import com.pengrad.telegrambot.TelegramBot; import com.pengrad.telegrambot.TelegramBot;
import com.pengrad.telegrambot.request.GetMe; import com.pengrad.telegrambot.request.GetMe;
@ -18,6 +18,8 @@ public class MornyCoeur {
logger.info(MornyHello.MORNY_PREVIEW_IMAGE_ASCII); logger.info(MornyHello.MORNY_PREVIEW_IMAGE_ASCII);
logger.info("System Starting"); logger.info("System Starting");
configureSafeExit();
logger.info("args key:\n " + args[0]); logger.info("args key:\n " + args[0]);
try { account = login(args[0]); } try { account = login(args[0]); }
@ -25,6 +27,7 @@ public class MornyCoeur {
logger.info("Bot login succeed."); logger.info("Bot login succeed.");
TrackerDataManager.init();
EventListeners.registerAllListeners(); EventListeners.registerAllListeners();
account.setUpdatesListener(OnUpdate::onNormalUpdate); account.setUpdatesListener(OnUpdate::onNormalUpdate);
@ -32,6 +35,15 @@ public class MornyCoeur {
} }
private static void exitCleanup () {
TrackerDataManager.DAEMON.interrupt();
TrackerDataManager.trackingLock.lock();
}
private static void configureSafeExit () {
Runtime.getRuntime().addShutdownHook(new Thread(MornyCoeur::exitCleanup));
}
public static TelegramBot login (String key) { public static TelegramBot login (String key) {
TelegramBot account = new TelegramBot(key); TelegramBot account = new TelegramBot(key);
logger.info("Trying to login..."); logger.info("Trying to login...");

View File

@ -25,9 +25,13 @@ public class EventListenerManager {
@Override @Override
public void run () { public void run () {
for (EventListener x : listeners) { for (EventListener x : listeners) {
try {
if (exec.apply(x)) return; if (exec.apply(x)) return;
} catch (Exception e) {
logger.error("Event Error!");
e.printStackTrace(System.out);
}
} }
logger.info("event exited undone");
} }
} }

View File

@ -5,9 +5,11 @@ import cc.sukazyo.cono.morny.bot.api.EventListenerManager;
public class EventListeners { public class EventListeners {
public static final OnCommandExecute COMMANDS_LISTENER = new OnCommandExecute(); public static final OnCommandExecute COMMANDS_LISTENER = new OnCommandExecute();
public static final OnActivityRecord ACTIVITY_RECORDER = new OnActivityRecord();
public static void registerAllListeners () { public static void registerAllListeners () {
EventListenerManager.addListener( EventListenerManager.addListener(
ACTIVITY_RECORDER,
COMMANDS_LISTENER COMMANDS_LISTENER
); );
} }

View File

@ -0,0 +1,25 @@
package cc.sukazyo.cono.morny.bot.event;
import cc.sukazyo.cono.morny.bot.api.EventListener;
import cc.sukazyo.cono.morny.data.tracker.TrackerDataManager;
import com.pengrad.telegrambot.model.Chat;
import com.pengrad.telegrambot.model.Update;
public class OnActivityRecord extends EventListener {
@Override
public boolean onMessage (Update update) {
if (
update.message().chat().type() == Chat.Type.supergroup ||
update.message().chat().type() == Chat.Type.group
) {
TrackerDataManager.record(
update.message().chat().id(),
update.message().from().id(),
(long)update.message().date() * 1000
);
}
return super.onMessage(update);
}
}

View File

@ -0,0 +1,118 @@
package cc.sukazyo.cono.morny.data.tracker;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantLock;
import static cc.sukazyo.cono.morny.Logger.logger;
public class TrackerDataManager {
public static final ReentrantLock trackingLock = new ReentrantLock();
private static final ReentrantLock recordLock = new ReentrantLock();
private static HashMap<Long, HashMap<Long, TreeSet<Long>>> record = new HashMap<>();
public static final TrackerDaemon DAEMON = new TrackerDaemon();
public static class TrackerDaemon extends Thread {
public TrackerDaemon () { this.setName("TRACKER"); }
@Override
public void run () {
trackingLock.lock();
long lastWaitTimestamp = System.currentTimeMillis();
boolean postProcess = false;
do {
long sleeping = lastWaitTimestamp - System.currentTimeMillis();
if (sleeping > 0) {
try { Thread.sleep(sleeping); } catch (InterruptedException e) { interrupt(); }
} else {
logger.warn("Tracker may be too busy to process data!!");
lastWaitTimestamp = System.currentTimeMillis();
}
if (interrupted()) {
postProcess = true;
logger.warn("last tracker write in this processor!");
}
lastWaitTimestamp += 10 * 60 * 1000;
if (record.size() != 0) {
logger.info("start writing tracker data.");
save(reset());
logger.info("done writing tracker data.");
}
else logger.info("nothing to do yet");
} while (!postProcess);
trackingLock.unlock();
}
}
public static void record (long chat, long user, long timestamp) {
recordLock.lock();
if (!record.containsKey(chat)) record.put(chat, new HashMap<>());
HashMap<Long, TreeSet<Long>> chatUsers = record.get(chat);
if (!chatUsers.containsKey(user)) chatUsers.put(user, new TreeSet<>());
TreeSet<Long> userRecords = chatUsers.get(user);
userRecords.add(timestamp);
recordLock.unlock();
}
public static void init () {
DAEMON.start();
}
private static HashMap<Long, HashMap<Long, TreeSet<Long>>> reset () {
recordLock.lock();
HashMap<Long, HashMap<Long, TreeSet<Long>>> recordOld = record;
record = new HashMap<>();
recordLock.unlock();
return recordOld;
}
private static void save (HashMap<Long, HashMap<Long, TreeSet<Long>>> record) {
record.forEach((chat, chatUsers) -> chatUsers.forEach((user, userRecords) -> {
long dayCurrent = -1;
FileChannel channelCurrent = null;
for (long timestamp : userRecords) {
try {
long day = timestamp / (24 * 60 * 60 * 1000);
if (dayCurrent != day) {
if (channelCurrent != null) channelCurrent.close();
channelCurrent = openFile(chat, user, day);
dayCurrent = day;
}
assert channelCurrent != null;
channelCurrent.write(ByteBuffer.wrap(
String.format("%d\n", timestamp).getBytes(StandardCharsets.UTF_8)
));
} catch (Exception e) {
logger.error(String.format("exception in write tracker data: %d/%d/%d", chat, user, timestamp));
e.printStackTrace(System.out);
}
}
}));
}
private static FileChannel openFile (long chat, long user, long day) throws IOException {
File data = new File(String.format("./data/tracker/%d/%d", chat, user));
if (!data.isDirectory()) if (!data.mkdirs()) throw new IOException("Cannot create file directory " + data.getPath());
File file = new File(data, String.valueOf(day));
if (!file.isFile()) if (!file.createNewFile()) throw new IOException("Cannot create file " + file.getPath());
return new FileOutputStream(file, true).getChannel();
}
}