From f3240bef36005ba75598e6625d7c5f38be8229ae Mon Sep 17 00:00:00 2001 From: Brian Fopiano Date: Thu, 3 Feb 2022 10:26:09 -0800 Subject: [PATCH] Shitty discord api makes webhooks nolonger have ID's but are also member --- .DS_Store | Bin 0 -> 6148 bytes .../abyssalith/commands/general/Log.java | 3 + .../commands/listeners/MessageListener.java | 300 +++++++++--------- .../listeners/MessageReactionListener.java | 28 +- .../listeners/SelectionMenuListener.java | 38 +-- 5 files changed, 189 insertions(+), 180 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..fd05bb214774437e7436fa779e916c229bc2b2ba GIT binary patch literal 6148 zcmeHK%Sr=55Ue&)0$y_TIKSW@EFpe@KcLBq2nzv|-1p{hTm6V6EJ98mqzbxgwx@fB zt;6;<09zgVM_>tHL375JyD5C%ZGxMM%*YmJ^!t}6p#Y{Q~{f>pVuqCT=mw;YkIG3v=`b5%#C!q lXvO4c#eDEq{BV}ne9ir?cubCR#-p699|6}zkOF_9z&A)E9&P{t literal 0 HcmV?d00001 diff --git a/src/main/java/com/volmit/abyssalith/commands/general/Log.java b/src/main/java/com/volmit/abyssalith/commands/general/Log.java index 8cd4a30..f4ac2b6 100644 --- a/src/main/java/com/volmit/abyssalith/commands/general/Log.java +++ b/src/main/java/com/volmit/abyssalith/commands/general/Log.java @@ -61,6 +61,9 @@ public void handle(List args, MessageReceivedEvent e) { **-** Go to And paste that file there. **-** Alternatively and paste it there. **-** Or just send the file""", false); + if(e.getMessage().getMentionedMembers().get(0) != null){ + embed.setTitle("This was for: " +e.getMessage().getMentionedMembers().get(0).getAsMention() +""); + } //Commands //embed.addField("Name Here", "" + "Value here", false); diff --git a/src/main/java/com/volmit/abyssalith/commands/listeners/MessageListener.java b/src/main/java/com/volmit/abyssalith/commands/listeners/MessageListener.java index 0e9b3ee..57c0d45 100644 --- a/src/main/java/com/volmit/abyssalith/commands/listeners/MessageListener.java +++ b/src/main/java/com/volmit/abyssalith/commands/listeners/MessageListener.java @@ -39,183 +39,185 @@ public class MessageListener extends ListenerAdapter { public void onMessageReceived(MessageReceivedEvent e) { + //nullchecks for bots now ig or webhooks to be more specific (ie: discord/Git bit causing a no-id Error) + if (e.getMember() != null) { - // XP APPLICATION FOR THE USER --- - if (!e.getMessage().getAuthor().isBot()) { + // XP APPLICATION FOR THE USER --- + if (!e.getMessage().getAuthor().isBot()) { // Abyss.debug("XP Check"); - User u = Abyss.getLoader().getUser(e.getMessage().getAuthor().getIdLong()); // USER LOADER - u.experience(u.experience() + Kit.get().xpPerMessage.rand()); //XP - u.messagesSent(u.messagesSent() + 1); - double uxp = u.experience(); - int validator = XP.getLevelForXp(uxp); - if (validator < Kit.get().xpMaxLevels) { - if (Kit.get().useRoleSystem) { - roleValidator(e, Kit.get().levelName + XP.getLevelForXp(uxp)); - roleManager(e, Kit.get().levelName + XP.getLevelForXp(uxp), validator); + User u = Abyss.getLoader().getUser(e.getMessage().getAuthor().getIdLong()); // USER LOADER + u.experience(u.experience() + Kit.get().xpPerMessage.rand()); //XP + u.messagesSent(u.messagesSent() + 1); + double uxp = u.experience(); + int validator = XP.getLevelForXp(uxp); + if (validator < Kit.get().xpMaxLevels) { + if (Kit.get().useRoleSystem) { + roleValidator(e, Kit.get().levelName + XP.getLevelForXp(uxp)); + roleManager(e, Kit.get().levelName + XP.getLevelForXp(uxp), validator); + } } } - } - //ANTI-GHOST MENTION PORTION --- - if (e.getMessage().getMentionedMembers().size() > 0) { - Abyss.debug("User Mention Noticed, and Logged"); - for (Member m : e.getMessage().getMentionedMembers()) { - User u = Abyss.getLoader().getUser(m.getIdLong()); - if (u.recentMentions().size() > 2) { - u.recentMentions().remove(u.recentMentions().size() - 1); + //ANTI-GHOST MENTION PORTION --- + if (e.getMessage().getMentionedMembers().size() > 0) { + Abyss.debug("User Mention Noticed, and Logged"); + for (Member m : e.getMessage().getMentionedMembers()) { + User u = Abyss.getLoader().getUser(m.getIdLong()); + if (u.recentMentions().size() > 2) { + u.recentMentions().remove(u.recentMentions().size() - 1); + } + u.recentMentions().put(u.recentMentions().size(), "[**USER**]" + Objects.requireNonNull(e.getMember()).getEffectiveName() + " [**SAID**]: " + e.getMessage().getContentRaw()); } - u.recentMentions().put(u.recentMentions().size(), "[**USER**]" + Objects.requireNonNull(e.getMember()).getEffectiveName() + " [**SAID**]: " + e.getMessage().getContentRaw()); } - } - // REACTIONS FOR BOT TO DELETE ITSELF --- - if (e.getMessage().getAuthor().getIdLong() == e.getJDA().getSelfUser().getIdLong() - && !e.getMessage().getEmbeds().isEmpty() - && e.getMessage().getActionRows().size() == 0 // Are their no clickable actions - ) { - Abyss.debug("Adding Delete Button"); - e.getMessage().addReaction("U+274C").queue(); - } - - //REACTION-ROLE VISTA - if (!e.getMessage().getAuthor().isBot() // not a bot - && e.getMessage().getMentionedRoles().size() > 0 // mentioning roles - && PermHandler.hasAdmin(Objects.requireNonNull(e.getMember())) // Has admin permissions - && e.getMessage().getContentRaw().contains(Kit.get().reactionRoleString)) { // Contains reaction role string - Abyss.debug("ReactionRoles Instanced"); - if (e.getMessage().getMentionedRoles().size() > 1) { - MenuHandler.RoleListMenu("rolepage", - "Choose your Role(s)!", - e.getMessage().getMentionedRoles(), - e.getChannel()); - } else { - e.getChannel().sendMessage("CAn you mention more than 1 role please...").queue(); + // REACTIONS FOR BOT TO DELETE ITSELF --- + if (e.getMessage().getAuthor().getIdLong() == e.getJDA().getSelfUser().getIdLong() + && !e.getMessage().getEmbeds().isEmpty() + && e.getMessage().getActionRows().size() == 0 // Are their no clickable actions + ) { + Abyss.debug("Adding Delete Button"); + e.getMessage().addReaction("U+274C").queue(); } - e.getMessage().delete().queue(); - } - //LINGUA PARTITIONER --- - if (!e.getMessage().getAuthor().isBot() && e.getMessage().getContentRaw().length() > 5 && !e.getMessage().getContentRaw().contains("https:") && Kit.get().useLingua) { - Abyss.debug("Lingua Check"); - final LanguageDetector detector = LanguageDetectorBuilder.fromLanguages( - Language.ENGLISH, // Default language - Language.FRENCH, Language.GERMAN, Language.SPANISH, // Only what i can read or write is here - Language.TURKISH, Language.PORTUGUESE, Language.POLISH, - Language.KOREAN, Language.DUTCH, Language.CZECH).build(); - if (!detector.detectLanguageOf(e.getMessage().getContentRaw().toLowerCase()).equals(Language.ENGLISH)) { - //todo: Figure out why this is necessary, and implement a better way to detect/use languages - Abyss.info("Someone is speaking: " + "" + detector.detectLanguageOf(e.getMessage().getContentRaw().toLowerCase())); - if (detector.detectLanguageOf(e.getMessage().getContentRaw().toLowerCase()) != Language.UNKNOWN) { - Role langRole = e.getGuild().getRolesByName("" + detector.detectLanguageOf(e.getMessage().getContentRaw().toLowerCase()), false).get(0); - e.getChannel().sendMessage("Debugging the langRole System! " + langRole.getAsMention()).queue(); - //todo Actual system here should be something useful - Abyss.debug("Language Debug: " + detector.computeLanguageConfidenceValues(e.getMessage().getContentRaw().toLowerCase())); + //REACTION-ROLE VISTA + if (!e.getMessage().getAuthor().isBot() // not a bot + && e.getMessage().getMentionedRoles().size() > 0 // mentioning roles + && PermHandler.hasAdmin(Objects.requireNonNull(e.getMember())) // Has admin permissions + && e.getMessage().getContentRaw().contains(Kit.get().reactionRoleString)) { // Contains reaction role string + Abyss.debug("ReactionRoles Instanced"); + if (e.getMessage().getMentionedRoles().size() > 1) { + MenuHandler.RoleListMenu("rolepage", + "Choose your Role(s)!", + e.getMessage().getMentionedRoles(), + e.getChannel()); + } else { + e.getChannel().sendMessage("CAn you mention more than 1 role please...").queue(); } - } - } - - //PHISHING DILEMMA - for (String p : Kit.get().phishing) { - if (e.getMessage().getContentRaw().toLowerCase().contains(p)) { e.getMessage().delete().queue(); - Abyss.debug("Phishing Check"); - Objects.requireNonNull(e.getMember()).getUser().openPrivateChannel().complete().sendMessage("Hello, You have been banned from volmit for sending Phishing links: `\n" + - e.getMessage() + - "`\nIf you feel this is a mistake, please send a friend request to: `⋈-NestorPsycho-⋈#0001` and explain the problem, otherwise your account was compromised, or you were unaware of what you were sending \n" + - "regardless, you have been banned, until you either fix your account, or handle the situation").queue(); - try { - WarningHandler.phishBan(e.getMember(), e.getGuild(), e.getMessage()); // ez ban - } catch (Exception ignored) { - } // Don't care about exceptions here, the only possible one is a permission one, and the bot needs to be able to handle it elsewhere } - } - //PASTEBIN REGISTRAR --- - if (Kit.get().usePasteService) { - if (!e.getMessage().getAuthor().isBot() && e.getMessage().getContentRaw().contains("https://pastebin.com/")) { - Abyss.info("Initializing Paste Service"); - String str = e.getMessage().getContentRaw(); - String[] pbArr = str.split(" "); - for (String s : pbArr) { - if (s.contains("https://pastebin.com/")) { - e.getChannel().sendMessage("Would you like me to scan this for you?: <" + s + ">").queue(f -> { - f.editMessageComponents().setActionRow( - Button.success("pastbinlinknew", "Yes please!"), - Button.danger("no", "No, go away!") - ).queue(); - Abyss.info("Sent Paste Service Buttons"); - }); + //LINGUA PARTITIONER --- + if (!e.getMessage().getAuthor().isBot() && e.getMessage().getContentRaw().length() > 5 && !e.getMessage().getContentRaw().contains("https:") && Kit.get().useLingua) { + Abyss.debug("Lingua Check"); + final LanguageDetector detector = LanguageDetectorBuilder.fromLanguages( + Language.ENGLISH, // Default language + Language.FRENCH, Language.GERMAN, Language.SPANISH, // Only what i can read or write is here + Language.TURKISH, Language.PORTUGUESE, Language.POLISH, + Language.KOREAN, Language.DUTCH, Language.CZECH).build(); + if (!detector.detectLanguageOf(e.getMessage().getContentRaw().toLowerCase()).equals(Language.ENGLISH)) { + //todo: Figure out why this is necessary, and implement a better way to detect/use languages + Abyss.info("Someone is speaking: " + "" + detector.detectLanguageOf(e.getMessage().getContentRaw().toLowerCase())); + if (detector.detectLanguageOf(e.getMessage().getContentRaw().toLowerCase()) != Language.UNKNOWN) { + Role langRole = e.getGuild().getRolesByName("" + detector.detectLanguageOf(e.getMessage().getContentRaw().toLowerCase()), false).get(0); + e.getChannel().sendMessage("Debugging the langRole System! " + langRole.getAsMention()).queue(); + //todo Actual system here should be something useful + Abyss.debug("Language Debug: " + detector.computeLanguageConfidenceValues(e.getMessage().getContentRaw().toLowerCase())); } } } - if (!e.getMessage().getAuthor().isBot() && e.getMessage().getContentRaw().contains("https://mclo.gs/")) { - Abyss.info("Initializing McLogs Service"); - String str = e.getMessage().getContentRaw(); - String[] pbArr = str.split(" "); - for (String s : pbArr) { - if (s.contains("https://mclo.gs/")) { - e.getChannel().sendMessage("Would you like me to scan this for you?: <" + s + ">").queue(f -> { - f.editMessageComponents().setActionRow( - Button.success("mcloglinknew", "Yes please!"), - Button.danger("no", "No, go away!") - ).queue(); - Abyss.info("Sent McLog Service Buttons"); - }); - } + + //PHISHING DILEMMA + for (String p : Kit.get().phishing) { + if (e.getMessage().getContentRaw().toLowerCase().contains(p)) { + e.getMessage().delete().queue(); + Abyss.debug("Phishing Check"); + Objects.requireNonNull(e.getMember()).getUser().openPrivateChannel().complete().sendMessage("Hello, You have been banned from volmit for sending Phishing links: `\n" + + e.getMessage() + + "`\nIf you feel this is a mistake, please send a friend request to: `⋈-NestorPsycho-⋈#0001` and explain the problem, otherwise your account was compromised, or you were unaware of what you were sending \n" + + "regardless, you have been banned, until you either fix your account, or handle the situation").queue(); + try { + WarningHandler.phishBan(e.getMember(), e.getGuild(), e.getMessage()); // ez ban + } catch (Exception ignored) { + } // Don't care about exceptions here, the only possible one is a permission one, and the bot needs to be able to handle it elsewhere } } - if (!e.getMessage().getAuthor().isBot() && e.getMessage().getContentRaw().contains("https://hastebin.com/")) { - Abyss.info("Started the Hastebin Service"); - String str = e.getMessage().getContentRaw(); - String[] pbArr = str.split(" "); - for (String s : pbArr) { - if (s.contains("https://hastebin.com/")) { - e.getChannel().sendMessage("Would you like me to scan this for you?: <" + s + ">").queue(f -> { - f.editMessageComponents().setActionRow( - Button.success("hastebinlinknew", "Yes please!"), - Button.danger("no", "No, go away!") - ).queue(); - Abyss.info("Sent HasteBin Service Buttons"); - }); + + //PASTEBIN REGISTRAR --- + if (Kit.get().usePasteService) { + if (!e.getMessage().getAuthor().isBot() && e.getMessage().getContentRaw().contains("https://pastebin.com/")) { + Abyss.info("Initializing Paste Service"); + String str = e.getMessage().getContentRaw(); + String[] pbArr = str.split(" "); + for (String s : pbArr) { + if (s.contains("https://pastebin.com/")) { + e.getChannel().sendMessage("Would you like me to scan this for you?: <" + s + ">").queue(f -> { + f.editMessageComponents().setActionRow( + Button.success("pastbinlinknew", "Yes please!"), + Button.danger("no", "No, go away!") + ).queue(); + Abyss.info("Sent Paste Service Buttons"); + }); + } } } - } - } - - //ROLE MANAGEMENT FINAL RESULT - User u = Abyss.getLoader().getUser(e.getMember().getIdLong()); // Load the user object - Set fRoles = u.roleIds(); // Load the Roles from the user file - Set sRoles = new HashSet<>(); // Load the Roles from the server - for (Role r : e.getMember().getRoles()){ - sRoles.add(r.getId()); - } - if (fRoles.containsAll(sRoles) && sRoles.containsAll(fRoles)){ - //same server, same roles, all is well. Do nothing. but ill add something here later - } else { - if(!fRoles.equals(sRoles) && sRoles.size() != 0){ // They have roles, that dont match what they have on file - u.roleIds(sRoles); - Abyss.info("Roles on user, dont match file. Rebinding file to match: " + e.getMember().getEffectiveName() + "' Server roles"); - } else if(!fRoles.equals(sRoles) && sRoles.size() == 0 && Kit.get().usePersistentRoles ) { //If they have Roles on file, and zero roles on the server - boolean shr = true; - for (String fr : fRoles){ - if (e.getGuild().getRoleById(fr) == null){ - shr = false; + if (!e.getMessage().getAuthor().isBot() && e.getMessage().getContentRaw().contains("https://mclo.gs/")) { + Abyss.info("Initializing McLogs Service"); + String str = e.getMessage().getContentRaw(); + String[] pbArr = str.split(" "); + for (String s : pbArr) { + if (s.contains("https://mclo.gs/")) { + e.getChannel().sendMessage("Would you like me to scan this for you?: <" + s + ">").queue(f -> { + f.editMessageComponents().setActionRow( + Button.success("mcloglinknew", "Yes please!"), + Button.danger("no", "No, go away!") + ).queue(); + Abyss.info("Sent McLog Service Buttons"); + }); + } } } - if (shr){ - Abyss.info("Reapplying PersistentRoles to: " + e.getMember().getEffectiveName()); - for (String f : fRoles) { - if (e.getMessage().getGuild().getRoleById(f) != null) { - e.getMessage().getGuild().addRoleToMember(e.getMember(), e.getMessage().getGuild().getRoleById(f)).complete(); + if (!e.getMessage().getAuthor().isBot() && e.getMessage().getContentRaw().contains("https://hastebin.com/")) { + Abyss.info("Started the Hastebin Service"); + String str = e.getMessage().getContentRaw(); + String[] pbArr = str.split(" "); + for (String s : pbArr) { + if (s.contains("https://hastebin.com/")) { + e.getChannel().sendMessage("Would you like me to scan this for you?: <" + s + ">").queue(f -> { + f.editMessageComponents().setActionRow( + Button.success("hastebinlinknew", "Yes please!"), + Button.danger("no", "No, go away!") + ).queue(); + Abyss.info("Sent HasteBin Service Buttons"); + }); } } - } else { - u.roleIds(sRoles); - Abyss.info("Server Role ReSync: " + e.getMember().getEffectiveName()); } + } + //ROLE MANAGEMENT FINAL RESULT + User u = Abyss.getLoader().getUser(e.getMember().getIdLong()); // Load the user object + Set fRoles = u.roleIds(); // Load the Roles from the user file + Set sRoles = new HashSet<>(); // Load the Roles from the server + for (Role r : e.getMember().getRoles()) { + sRoles.add(r.getId()); + } + if (fRoles.containsAll(sRoles) && sRoles.containsAll(fRoles)) { + //same server, same roles, all is well. Do nothing. but ill add something here later + } else { + if (!fRoles.equals(sRoles) && sRoles.size() != 0) { // They have roles, that don't match what they have on file + u.roleIds(sRoles); + Abyss.info("Roles on user, dont match file. Rebinding file to match: " + e.getMember().getEffectiveName() + "' Server roles"); + } else if (!fRoles.equals(sRoles) && sRoles.size() == 0 && Kit.get().usePersistentRoles) { //If they have Roles on file, and zero roles on the server + boolean shr = true; + for (String fr : fRoles) { + if (e.getGuild().getRoleById(fr) == null) { + shr = false; + } + } + if (shr) { + Abyss.info("Reapplying PersistentRoles to: " + e.getMember().getEffectiveName()); + for (String f : fRoles) { + if (e.getMessage().getGuild().getRoleById(f) != null) { + e.getMessage().getGuild().addRoleToMember(e.getMember(), Objects.requireNonNull(e.getMessage().getGuild().getRoleById(f))).complete(); + } + } + } else { + u.roleIds(sRoles); + Abyss.info("Server Role ReSync: " + e.getMember().getEffectiveName()); + } + } } } @@ -228,7 +230,7 @@ private void roleManager(MessageReceivedEvent e, String role, int v) { try { if (e.getGuild().getRolesByName(role, false).size() == 1 && e.getGuild().getRolesByName(role, false).contains(e.getGuild().getRolesByName(role, true).get(0))) { r = e.getGuild().getRolesByName(role, true).get(0); - if (r != null) { + if (r != null && e.getMember() != null) { e.getGuild().addRoleToMember(e.getMember().getIdLong(), r).queue(); if (v > 0) { for (Role rol : e.getMember().getRoles()) { diff --git a/src/main/java/com/volmit/abyssalith/commands/listeners/MessageReactionListener.java b/src/main/java/com/volmit/abyssalith/commands/listeners/MessageReactionListener.java index 3c2ed50..4da6a76 100644 --- a/src/main/java/com/volmit/abyssalith/commands/listeners/MessageReactionListener.java +++ b/src/main/java/com/volmit/abyssalith/commands/listeners/MessageReactionListener.java @@ -31,20 +31,22 @@ public class MessageReactionListener extends ListenerAdapter { public void onMessageReactionAdd(MessageReactionAddEvent e) { Abyss.debug("Reaction Added"); Message m = e.getChannel().retrieveMessageById(e.getMessageId()).complete(); - if (!e.getUser().isBot()) { - User u = Abyss.getLoader().getUser(e.getUser().getIdLong()); - u.experience((u.experience() + Kit.get().xpPerMessage.rand())); - u.reactions(u.reactions() + 1); - } + if (e.getUser() != null) { + if (!e.getUser().isBot()) { + User u = Abyss.getLoader().getUser(e.getUser().getIdLong()); + u.experience((u.experience() + Kit.get().xpPerMessage.rand())); + u.reactions(u.reactions() + 1); + } - if (!e.getUser().isBot() // is the reactor a user - && m.getAuthor().getIdLong() == e.getJDA().getSelfUser().getIdLong() - && e.getReaction().toString().contains("U+274c") // is the X reaction there - && e.getChannel().retrieveMessageById(e.getMessageId()).complete().getActionRows().size() == 0 /* Are their no clickable actions*/) { - J.a(() -> { - Abyss.info(" Cleaning bot response as requested"); - e.getChannel().retrieveMessageById(e.getMessageId()).complete().delete().queue(); - }); + if (!e.getUser().isBot() // is the reactor a user + && m.getAuthor().getIdLong() == e.getJDA().getSelfUser().getIdLong() + && e.getReaction().toString().contains("U+274c") // is the X reaction there + && e.getChannel().retrieveMessageById(e.getMessageId()).complete().getActionRows().size() == 0 /* Are their no clickable actions*/) { + J.a(() -> { + Abyss.info(" Cleaning bot response as requested"); + e.getChannel().retrieveMessageById(e.getMessageId()).complete().delete().queue(); + }); + } } } diff --git a/src/main/java/com/volmit/abyssalith/commands/listeners/SelectionMenuListener.java b/src/main/java/com/volmit/abyssalith/commands/listeners/SelectionMenuListener.java index fabd6c0..827c703 100644 --- a/src/main/java/com/volmit/abyssalith/commands/listeners/SelectionMenuListener.java +++ b/src/main/java/com/volmit/abyssalith/commands/listeners/SelectionMenuListener.java @@ -31,25 +31,27 @@ public class SelectionMenuListener extends ListenerAdapter { public void onSelectionMenu(SelectionMenuEvent e) { Abyss.debug("Menu Selection Recorded"); - if (e.getComponentId().equalsIgnoreCase("menu:rolepage")) { - Abyss.info("Subjugating selection Menu"); - - Set roles = new HashSet<>(); // Null Set of Roles - List sel = e.getSelectedOptions(); // Selected Options - List other = e.getComponent().getOptions(); // All Options - - for (SelectOption S : other) { - RoleHandler.removeRole(e.getMember(), S.getLabel()); + if (e.getMember() != null) { + if (e.getComponentId().equalsIgnoreCase("menu:rolepage")) { + Abyss.info("Subjugating selection Menu"); + + Set roles = new HashSet<>(); // Null Set of Roles + List sel = e.getSelectedOptions(); // Selected Options + List other = e.getComponent().getOptions(); // All Options + + for (SelectOption S : other) { + RoleHandler.removeRole(e.getMember(), S.getLabel()); + } + + for (SelectOption S : sel) { + RoleHandler.addRole(e.getMember(), S.getLabel()); + roles.add(S.getLabel()); + } + + e.reply("Your new roles are:" + roles).setEphemeral(true).queue(); + } else { + e.reply("Please contact an administrator, i cant see any roles!").setEphemeral(true).queue(); } - - for (SelectOption S : sel) { - RoleHandler.addRole(e.getMember(), S.getLabel()); - roles.add(S.getLabel()); - } - - e.reply("Your new roles are:" + roles).setEphemeral(true).queue(); - } else { - e.reply("Please contact an administrator, i cant see any roles!").setEphemeral(true).queue(); } } } \ No newline at end of file