@Override
  public void sendFileAsync(File file, Message message, Consumer<Message> callback) {
    checkVerification();
    if (!checkPermission(getJDA().getSelfInfo(), Permission.MESSAGE_WRITE))
      throw new PermissionException(Permission.MESSAGE_WRITE);
    if (!checkPermission(getJDA().getSelfInfo(), Permission.MESSAGE_ATTACH_FILES))
      throw new PermissionException(Permission.MESSAGE_ATTACH_FILES);

    Thread thread =
        new Thread(
            () -> {
              Message messageReturn;
              try {
                messageReturn = sendFile(file, message);
              } catch (RateLimitedException e) {
                JDAImpl.LOG.warn(
                    "Got ratelimited when trying to upload file. Providing null to callback.");
                messageReturn = null;
              }

              if (callback != null) callback.accept(messageReturn);
            });
    thread.setName("TextChannelImpl sendFileAsync Channel: " + id);
    thread.setDaemon(true);
    thread.start();
  }
  @Override
  public Message sendMessage(Message msg) {
    checkVerification();
    SelfInfo self = getJDA().getSelfInfo();
    if (!checkPermission(self, Permission.MESSAGE_WRITE))
      throw new PermissionException(Permission.MESSAGE_WRITE);

    JDAImpl api = (JDAImpl) getJDA();
    if (api.getMessageLimit(guild.getId()) != null) {
      throw new RateLimitedException(
          api.getMessageLimit(guild.getId()) - System.currentTimeMillis());
    }
    try {
      Requester.Response response =
          api.getRequester()
              .post(
                  Requester.DISCORD_API_PREFIX + "channels/" + getId() + "/messages",
                  new JSONObject().put("content", msg.getRawContent()).put("tts", msg.isTTS()));
      if (response.isRateLimit()) {
        long retry_after = response.getObject().getLong("retry_after");
        api.setMessageTimeout(guild.getId(), retry_after);
        throw new RateLimitedException(retry_after);
      }
      if (!response.isOk()) // sending failed (Verification-level?)
      return null;
      return new EntityBuilder(api).createMessage(response.getObject());
    } catch (JSONException ex) {
      JDAImpl.LOG.log(ex);
      // sending failed
      return null;
    }
  }
 @Override
 public void run() {
   sending: // Label so that, if needed, we can completely kill the while loop from inside the
            // nested loop.
   while (sender.alive) {
     Queue<Task> queue = sender.getQueue();
     while (sender.alive && !queue.isEmpty()) {
       Long messageLimit = sender.api.getMessageLimit(sender.ratelimitIdentifier);
       if (messageLimit != null) {
         try {
           Thread.sleep(messageLimit - System.currentTimeMillis());
         } catch (InterruptedException e) {
           JDAImpl.LOG.log(e);
         }
       }
       Task task = queue.peek();
       Message msg = task.message;
       Requester.Response response;
       if (sender.api.getTextChannelById(msg.getChannelId()) == null
           && sender.api.getPrivateChannelById(msg.getChannelId()) == null) {
         // We no longer have access to the MessageChannel that this message is queued to
         // send to. This is most likely because it was deleted.
         AsyncMessageSender.stop(sender.api, sender.ratelimitIdentifier);
         break sending;
       }
       if (task.isEdit) {
         response =
             sender
                 .api
                 .getRequester()
                 .patch(
                     Requester.DISCORD_API_PREFIX
                         + "channels/"
                         + msg.getChannelId()
                         + "/messages/"
                         + msg.getId(),
                     new JSONObject().put("content", msg.getRawContent()));
       } else {
         response =
             sender
                 .api
                 .getRequester()
                 .post(
                     Requester.DISCORD_API_PREFIX
                         + "channels/"
                         + msg.getChannelId()
                         + "/messages",
                     new JSONObject()
                         .put("content", msg.getRawContent())
                         .put("tts", msg.isTTS()));
       }
       if (response.responseText == null) {
         JDAImpl.LOG.debug(
             "Error sending async-message (returned null-text)... Retrying after 1s");
         sender.api.setMessageTimeout(sender.ratelimitIdentifier, 1000);
       } else if (!response.isRateLimit()) // success/unrecoverable error
       {
         queue.poll(); // remove from queue
         try {
           if (response.isOk()) {
             if (task.callback != null)
               task.callback.accept(
                   new EntityBuilder(sender.api).createMessage(response.getObject()));
           } else {
             // if response didn't have id, sending failed (due to permission/blocked pm,...
             JDAImpl.LOG.fatal(
                 "Could not send/update async message to channel: "
                     + msg.getChannelId()
                     + ". Discord-response: "
                     + response.toString());
             if (task.callback != null) task.callback.accept(null);
           }
         } catch (JSONException ex) {
           // could not generate message from json
           JDAImpl.LOG.log(ex);
         } catch (IllegalArgumentException ex) {
           JDAImpl.LOG.log(ex);
         }
       } else {
         sender.api.setMessageTimeout(
             sender.ratelimitIdentifier, response.getObject().getLong("retry_after"));
       }
       if (queue.isEmpty()) {
         queue = sender.getQueue();
       }
     }
     sender.waitNew();
   }
 }