Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an event inverter utility class #113

Open
wants to merge 29 commits into
base: master
Choose a base branch
from

Conversation

HydroPage
Copy link

@HydroPage HydroPage commented Aug 24, 2020

Pull Request

Pull Request Checklist

Pull Request Information

  • My PR fixes a bug, error, or other issue with the library's codebase.
  • My PR is for the commons module of the JDA-Utilities library.
  • My PR creates a new module for the JDA-Utilities library: ______.

Description

I have begun implementing a utility class I have, as of now, named InverseAction. I have explained the granular details in the class' javadoc, but I can summarize here. This util class' contract promises to return a RestAction<?> that will effectively wrap the "undoing" of a given input event type. This is to clean the instances where you want to detect an event, and revert its effects if the context around the event meets certain causes, etc.

Again, this will only be implemented for events in which it makes sense to fully be able to "undo" an action that was done.
The removal of a voice channel can be undone effectively; however, the removal of a text channel cannot, and will not be included.

I have sifted carefully through every event in JDA (There are some I'm still not sure of, I believe) to include only the events that can be logically reverted by the bot.

I have included the implementation of a couple of the first methods in the class so you can get a full understanding of what I mean. I hope you will accept my feature proposal

@HydroPage HydroPage marked this pull request as ready for review August 24, 2020 22:44
Copy link
Contributor

@JohnnyJayJay JohnnyJayJay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few notes. But also a general question: What use cases for this do you have in mind?

Comment on lines 321 to 324
public static RoleAction of(RoleDeleteEvent event)
{
return event.getRole().createCopy();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be made more clear which events are only "partially" reversible or if those should even be included. That is, you can create a copy of that role, but everything that still uses the old id is still broken and the members that used to have it don't have it anymore.
Same for e.g. voice channel delete.

I see it being mentioned in the summary javadoc, but it's still somewhat confusing.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't include inverse actions for deleting/removing events since you can't invert it fully.

Copy link
Author

@HydroPage HydroPage Aug 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it could be part of the documentation. I agree, my contract promises full reversion, but since that's not a promise I can keep while still wanting to implement the inverses to deletions, I can simply include it in the contract that most inversions regarding the removal of an entity will only be partial, and all immutable state from the original entity cannot possibly be copied

@jagrosh
Copy link
Member

jagrosh commented Aug 25, 2020

This seems like an interesting - but niche - concept. I've taken a preliminary glance at what you have so far, and I not a huge fan of having a bunch of static methods, all with different return types. Ideally, something like this would be possible:

List<Event> someEventsToRevert; // accumulates somehow
List<RestAction> restActionsToCall;
for(Event e: someEventsToRevert)
    restActionsToCall.add(someInvertMethodOrEnclosingClass(e));

which would also mean that giving this system any generic event would return either a RestAction, or possibly throw an exception if there's no suitable inversion to the event.

@HydroPage
Copy link
Author

@jagrosh Thank you for the view, and I understand your concern in terms of the differing return types thing, however, the documentation states that my class guarantees a return type of RestAction<?>. All of the methods in this class, do, in fact, return an instance of this, so if you would want to dynamically pass methods to this class, you'd depend on a RestAction<?> return value, instead of the more specific type. I just did this so you wouldn't have to cast if you needed to obtain a ChannelManager instance from inverting a TextChannelUpdateNameEvent for example, do I make myself clear?
Also, regarding your "error on no method found", if you're dynamically passing events to this, and no overload is found, you'd simply catch that exception, and it would have the same effect, would it not?

@HydroPage
Copy link
Author

@JohnnyJayJay Thanks for the view, and about your question with use cases, I believed it would be nice to wrap the instances where you want to "undo" certain actions. Like jagrosh pointed out, it's niche, I agree, but in the instances where it can come in handy, it's nice and neat. For example, say you want to prevent a moderator from being able to create a TextChannel under certain circumstances: you'd check the context, and InverseAction.of(event).queue();

@Sanduhr32
Copy link

Just a question @HydroPage90 but isn't the inverse of a guild ban being unbanned instead of banning with a 0 day prune again?

@HydroPage
Copy link
Author

@Sanduhr32 Dang it

@Sanduhr32
Copy link

Sanduhr32 commented Aug 25, 2020

I also have a concern about having a different overload for each specific event that could be simplified to an XYZGenericXYZEvent, where you internally check for the specific type of event then handle it in an according way, because they are very likely to have the same return value.

Edit: English

@HydroPage
Copy link
Author

I agree, it could have been done this way, but I believe separating into the overloads allows me to document the subtle logical differences between the inversions. Their return type may be the same, but the logic behind returning the objects is not

@jagrosh
Copy link
Member

jagrosh commented Aug 25, 2020

if you're dynamically passing events to this, and no overload is found, you'd simply catch that exception, and it would have the same effect, would it not?

The way you have it set up, you can't actually pass a GenericEvent into the system at all, nor different kinds of events in the same loop; there's no exception because it wouldn't even compile.

@HydroPage
Copy link
Author

HydroPage commented Aug 25, 2020

Understood. So you would like for me to wrap the API I've made by only making generic event methods publicly accessible, and using my InversionException to tell when it goes wrong

@jagrosh
Copy link
Member

jagrosh commented Aug 25, 2020

For clarification, a bot that attempts to invert every event should be no more complicated than this:

class MyListener implements EventListener {
    @Override
    onEvent(GenericEvent event) {
        try {
            RestAction action = someInversionMethod(event);
            action.queue();
        } catch (PermissionException e) {
            System.out.println("Insufficient perms to invert " + event);
        } catch (InversionException e) {
            System.out.println("Cannot invert " + event);
        }
    }
}

@jagrosh
Copy link
Member

jagrosh commented Aug 25, 2020

It's fine if the methods provide more type information than necessary, but they should respect inheritance when possible

@HydroPage
Copy link
Author

@jagrosh Got it, I will get to work on generifying. What do you think we should do about methods that violate my contract slightly? (Like a Role deletion being unable to be undone due to copying it giving it a new ID, and the people who had it losing the Role)

@HydroPage
Copy link
Author

I'm not sure how I could go about making this work. I don't want to make an instanceof chain, so I thought reflection could help

public static RestAction<?> of(GenericEvent event)
{
    try
    {
        Class<? extends GenericEvent> eventClass = event.getClass();
            
        if (invertableEvents.contains(eventClass))
        {
            return (RestAction<?>) InverseAction.class.getMethod("inverse", eventClass)
                               .invoke(InverseAction.class, event);
        }
    }
    catch (ReflectiveOperationException e)
    {
        throw new InversionException("No available inversion for event " + event, e);
    }
        
    return null;
}

Is this a good start? Or should I stick with making a tower of instanceofs?

@HydroPage
Copy link
Author

I think I'll stick with the instanceof chain

@HydroPage
Copy link
Author

Alright. Made a singular public delegator method, renamed ofs to inverses for clarity, only made the public method be named of
Removed all those docs, since they're unreachable now

@HydroPage
Copy link
Author

Wrong tag, smh. Sorry, whoever that is, lmao

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants