diff --git a/build.gradle b/build.gradle index 8eb1d4e..140c3c5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,18 @@ +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.github.jengelman.gradle.plugins:shadow:5.0.0' + } +} + plugins { id 'java' } group 'cc.sukazyo' -version '0.1.2' +version '0.2.0' repositories { mavenCentral() @@ -21,3 +30,26 @@ dependencies { test { useJUnitPlatform() } + +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} + +tasks.withType(Javadoc) { + options.encoding = 'UTF-8' + options.docEncoding = 'UTF-8' + options.charSet = 'UTF-8' +} + +tasks.test { + useJUnitPlatform() +} + +apply plugin: 'com.github.johnrengelman.shadow' +apply plugin: 'application' + +shadowJar { + mainClassName = 'cc.sukazyo.cono.morny.MornyCoeur' + archiveBaseName.set("Morny_Coeur") + archiveVersion.set(version) +} diff --git a/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java b/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java index 1b339ff..03ca922 100644 --- a/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java +++ b/src/main/java/cc/sukazyo/cono/morny/MornyCoeur.java @@ -2,7 +2,7 @@ package cc.sukazyo.cono.morny; import cc.sukazyo.cono.morny.bot.api.OnUpdate; 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.request.GetMe; @@ -11,19 +11,23 @@ import static cc.sukazyo.cono.morny.Logger.logger; public class MornyCoeur { private static TelegramBot account; + public static final String USERNAME = "morny_cono_annie_bot"; public static void main (String[] args) { logger.info(MornyHello.MORNY_PREVIEW_IMAGE_ASCII); logger.info("System Starting"); + configureSafeExit(); + logger.info("args key:\n " + args[0]); try { account = login(args[0]); } - catch (Exception e) { logger.error("Cannot login to bot/api."); System.exit(-1); } + catch (Exception e) { logger.error("Cannot login to bot/api. :\n " + e.getMessage()); System.exit(-1); } logger.info("Bot login succeed."); + TrackerDataManager.init(); EventListeners.registerAllListeners(); account.setUpdatesListener(OnUpdate::onNormalUpdate); @@ -31,13 +35,25 @@ 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) { TelegramBot account = new TelegramBot(key); logger.info("Trying to login..."); for (int i = 1; i < 4; i++) { if (i != 1) logger.info("retrying..."); try { - logger.info("Succeed login to @" + account.execute(new GetMe()).user().username()); + String username = account.execute(new GetMe()).user().username(); + if (!USERNAME.equals(username)) + throw new RuntimeException("Required the bot @"+USERNAME + " but @"+username + "logged in!"); + logger.info("Succeed login to @" + username); return account; } catch (Exception e) { e.printStackTrace(System.out); diff --git a/src/main/java/cc/sukazyo/cono/morny/data/MornyHello.java b/src/main/java/cc/sukazyo/cono/morny/MornyHello.java similarity index 99% rename from src/main/java/cc/sukazyo/cono/morny/data/MornyHello.java rename to src/main/java/cc/sukazyo/cono/morny/MornyHello.java index f8be6e3..4004403 100644 --- a/src/main/java/cc/sukazyo/cono/morny/data/MornyHello.java +++ b/src/main/java/cc/sukazyo/cono/morny/MornyHello.java @@ -1,4 +1,4 @@ -package cc.sukazyo.cono.morny.data; +package cc.sukazyo.cono.morny; @SuppressWarnings("all") public class MornyHello { diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/api/EventListenerManager.java b/src/main/java/cc/sukazyo/cono/morny/bot/api/EventListenerManager.java index 5b1eeba..9b59bb1 100644 --- a/src/main/java/cc/sukazyo/cono/morny/bot/api/EventListenerManager.java +++ b/src/main/java/cc/sukazyo/cono/morny/bot/api/EventListenerManager.java @@ -25,9 +25,13 @@ public class EventListenerManager { @Override public void run () { for (EventListener x : listeners) { - if (exec.apply(x)) return; + try { + if (exec.apply(x)) return; + } catch (Exception e) { + logger.error("Event Error!"); + e.printStackTrace(System.out); + } } - logger.info("event exited undone"); } } diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/event/EventListeners.java b/src/main/java/cc/sukazyo/cono/morny/bot/event/EventListeners.java index aaeefb4..bd26c6b 100644 --- a/src/main/java/cc/sukazyo/cono/morny/bot/event/EventListeners.java +++ b/src/main/java/cc/sukazyo/cono/morny/bot/event/EventListeners.java @@ -5,9 +5,11 @@ import cc.sukazyo.cono.morny.bot.api.EventListenerManager; public class EventListeners { public static final OnCommandExecute COMMANDS_LISTENER = new OnCommandExecute(); + public static final OnActivityRecord ACTIVITY_RECORDER = new OnActivityRecord(); public static void registerAllListeners () { EventListenerManager.addListener( + ACTIVITY_RECORDER, COMMANDS_LISTENER ); } diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/event/OnActivityRecord.java b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnActivityRecord.java new file mode 100644 index 0000000..adbd3b3 --- /dev/null +++ b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnActivityRecord.java @@ -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); + } + +} diff --git a/src/main/java/cc/sukazyo/cono/morny/bot/event/OnCommandExecute.java b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnCommandExecute.java index 4f2b9ea..a6653d2 100644 --- a/src/main/java/cc/sukazyo/cono/morny/bot/event/OnCommandExecute.java +++ b/src/main/java/cc/sukazyo/cono/morny/bot/event/OnCommandExecute.java @@ -22,13 +22,17 @@ public class OnCommandExecute extends EventListener { } switch (event.message().text()) { case "/o": + case "/o@" + MornyCoeur.USERNAME: onCommandOnExec(event); break; case "/hi": + case "/hi@" + MornyCoeur.USERNAME: case "/hello": + case "/hello@" + MornyCoeur.USERNAME: onCommandHelloExec(event); break; case "/exit": + case "/exit@" + MornyCoeur.USERNAME: onCommandExitExec(event); break; default: diff --git a/src/main/java/cc/sukazyo/cono/morny/data/tracker/TrackerDataManager.java b/src/main/java/cc/sukazyo/cono/morny/data/tracker/TrackerDataManager.java new file mode 100644 index 0000000..1695a24 --- /dev/null +++ b/src/main/java/cc/sukazyo/cono/morny/data/tracker/TrackerDataManager.java @@ -0,0 +1,127 @@ +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>> 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 { + lastWaitTimestamp += 10 * 60 * 1000; + 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!"); + } + 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> chatUsers = record.get(chat); + if (!chatUsers.containsKey(user)) chatUsers.put(user, new TreeSet<>()); + TreeSet userRecords = chatUsers.get(user); + userRecords.add(timestamp); + recordLock.unlock(); + } + + public static void init () { + DAEMON.start(); + } + + private static HashMap>> reset () { + recordLock.lock(); + HashMap>> recordOld = record; + record = new HashMap<>(); + recordLock.unlock(); + return recordOld; + } + + private static void save (HashMap>> record) { + + { + if (!record.containsKey(0L)) record.put(0L, new HashMap<>()); + HashMap> chatUsers = record.get(0L); + if (!chatUsers.containsKey(0L)) chatUsers.put(0L, new TreeSet<>()); + TreeSet userRecords = chatUsers.get(0L); + userRecords.add(System.currentTimeMillis()); + } + + 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(); + } + +}