All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.dv8tion.jda.api.utils.FileUpload Maven / Gradle / Ivy

Go to download

Java wrapper for the popular chat & VOIP service: Discord https://discord.com

The newest version!
/*
 * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.dv8tion.jda.api.utils;

import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.requests.Requester;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.EntityString;
import net.dv8tion.jda.internal.utils.IOUtil;
import net.dv8tion.jda.internal.utils.requestbody.DataSupplierBody;
import net.dv8tion.jda.internal.utils.requestbody.TypedBody;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import okio.Okio;
import okio.Source;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Base64;
import java.util.function.Supplier;

/**
 * Represents a file that is intended to be uploaded to Discord for arbitrary requests.
 * 
This is used to upload data to discord for various purposes. * *

The {@link InputStream} will be closed on consumption by the request. * You can use {@link #close()} to close the stream manually. */ public class FileUpload implements Closeable, AttachedFile { private final InputStream resource; private final Supplier resourceSupplier; private String name; private TypedBody body; private String description; private MediaType mediaType = Requester.MEDIA_TYPE_OCTET; private byte[] waveform; private double durationSeconds; protected FileUpload(InputStream resource, String name) { this.resource = resource; this.resourceSupplier = null; this.name = name; } protected FileUpload(Supplier resourceSupplier, String name) { this.resourceSupplier = resourceSupplier; this.resource = null; this.name = name; } /** * Creates a FileUpload that sources its data from the supplier. *
The supplier must return a new stream on every call. * *

The streams are expected to always be at the beginning, when they are taken from the supplier. * If the supplier returned the same stream instance, the reader would start at the wrong position when re-attempting a request. * *

When this supplier factory is used, {@link #getData()} will return a new instance on each call. * It is the responsibility of the caller to close that stream. * * @param name * The file name * @param supplier * The resource supplier, which returns a new stream on each call * * @throws IllegalArgumentException * If null is provided or the name is blank * * @return {@link FileUpload} */ @Nonnull public static FileUpload fromStreamSupplier(@Nonnull String name, @Nonnull Supplier supplier) { Checks.notNull(supplier, "Supplier"); return fromSourceSupplier(name, () -> Okio.source(supplier.get())); } /** * Creates a FileUpload that sources its data from the supplier. *
The supplier must return a new stream on every call. * *

The streams are expected to always be at the beginning, when they are taken from the supplier. * If the supplier returned the same stream instance, the reader would start at the wrong position when re-attempting a request. * *

When this supplier factory is used, {@link #getData()} will return a new instance on each call. * It is the responsibility of the caller to close that stream. * * @param name * The file name * @param supplier * The resource supplier, which returns a new {@link Source} on each call * * @throws IllegalArgumentException * If null is provided or the name is blank * * @return {@link FileUpload} */ @Nonnull public static FileUpload fromSourceSupplier(@Nonnull String name, @Nonnull Supplier supplier) { Checks.notNull(supplier, "Supplier"); Checks.notBlank(name, "Name"); return new FileUpload(supplier, name); } /** * Create a new {@link FileUpload} for an input stream. *
This is used to upload data to discord for various purposes. * *

The {@link InputStream} will be closed on consumption by the request. * You can use {@link FileUpload#close()} to close the stream manually. * * @param data * The {@link InputStream} to upload * @param name * The representative name to use for the file * * @throws IllegalArgumentException * If null is provided or the name is empty * * @return {@link FileUpload} * * @see java.io.FileInputStream FileInputStream */ @Nonnull public static FileUpload fromData(@Nonnull InputStream data, @Nonnull String name) { Checks.notNull(data, "Data"); Checks.notBlank(name, "Name"); return new FileUpload(data, name); } /** * Create a new {@link FileUpload} for a byte array. *
This is used to upload data to discord for various purposes. * * @param data * The {@code byte[]} to upload * @param name * The representative name to use for the file * * @throws IllegalArgumentException * If null is provided or the name is empty * * @return {@link FileUpload} */ @Nonnull public static FileUpload fromData(@Nonnull byte[] data, @Nonnull String name) { Checks.notNull(data, "Data"); Checks.notNull(name, "Name"); return fromData(new ByteArrayInputStream(data), name); } /** * Create a new {@link FileUpload} for a local file. *
This is used to upload data to discord for various purposes. * *

This opens a {@link FileInputStream}, which will be closed on consumption by the request. * You can use {@link FileUpload#close()} to close the stream manually. * * @param file * The {@link File} to upload * @param name * The representative name to use for the file * * @throws IllegalArgumentException * If null is provided or the name is empty * @throws UncheckedIOException * If an IOException is thrown while opening the file * * @return {@link FileUpload} * * @see java.io.FileInputStream FileInputStream */ @Nonnull public static FileUpload fromData(@Nonnull File file, @Nonnull String name) { Checks.notNull(file, "File"); try { return fromData(new FileInputStream(file), name); } catch (FileNotFoundException e) { throw new UncheckedIOException(e); } } /** * Create a new {@link FileUpload} for a local file. *
This is used to upload data to discord for various purposes. * *

This opens a {@link FileInputStream}, which will be closed on consumption by the request. * You can use {@link FileUpload#close()} to close the stream manually. * * @param file * The {@link File} to upload * * @throws IllegalArgumentException * If null is provided * @throws UncheckedIOException * If an IOException is thrown while opening the file * * @return {@link FileUpload} * * @see java.io.FileInputStream FileInputStream * @see #fromData(File, String) */ @Nonnull public static FileUpload fromData(@Nonnull File file) { Checks.notNull(file, "File"); try { return fromData(new FileInputStream(file), file.getName()); } catch (FileNotFoundException e) { throw new UncheckedIOException(e); } } /** * Create a new {@link FileUpload} for a local file. *
This is used to upload data to discord for various purposes. * *

This opens the path using {@link Files#newInputStream(Path, OpenOption...)}, which will be closed on consumption by the request. * You can use {@link FileUpload#close()} to close the stream manually. * * @param path * The {@link Path} of the file to upload * @param name * The representative name to use for the file * @param options * The {@link OpenOption OpenOptions} specifying how the file is opened * * @throws IllegalArgumentException * If null is provided or the name is empty * @throws UncheckedIOException * If an IOException is thrown while opening the file * * @return {@link FileUpload} */ @Nonnull public static FileUpload fromData(@Nonnull Path path, @Nonnull String name, @Nonnull OpenOption... options) { Checks.notNull(path, "Path"); Checks.noneNull(options, "Options"); Checks.check(Files.isReadable(path), "File for specified path cannot be read. Path: %s", path); try { return fromData(Files.newInputStream(path, options), name); } catch (IOException e) { throw new UncheckedIOException("Could not open file for specified path. Path: " + path, e); } } /** * Create a new {@link FileUpload} for a local file. *
This is used to upload data to discord for various purposes. * Uses {@link Path#getFileName()} to specify the name of the file, to customize the filename use {@link #fromData(Path, String, OpenOption...)}. * *

This opens the path using {@link Files#newInputStream(Path, OpenOption...)}, which will be closed on consumption by the request. * You can use {@link FileUpload#close()} to close the stream manually. * * @param path * The {@link Path} of the file to upload * @param options * The {@link OpenOption OpenOptions} specifying how the file is opened * * @throws IllegalArgumentException * If null is provided * @throws UncheckedIOException * If an IOException is thrown while opening the file * * @return {@link FileUpload} */ @Nonnull public static FileUpload fromData(@Nonnull Path path, @Nonnull OpenOption... options) { Checks.notNull(path, "Path"); Path fileName = path.getFileName(); Checks.check(fileName != null, "Path does not have a file name. Path: %s", path); return fromData(path, fileName.toString(), options); } /** * Changes the name of this file, to be prefixed as {@code SPOILER_}. *
This will cause the file to be rendered as a spoiler attachment in the client. * * @return The updated FileUpload instance */ @Nonnull public FileUpload asSpoiler() { if (name.startsWith("SPOILER_")) return this; return setName("SPOILER_" + name); } /** * Changes the name of this file. * * @param name * The new filename * * @throws IllegalArgumentException * If the name is null, blank, or empty * * @return The updated FileUpload instance */ @Nonnull public FileUpload setName(@Nonnull String name) { Checks.notBlank(name, "Name"); this.name = name; return this; } /** * Set the file description used as ALT text for screenreaders. * * @param description * The alt text describing this file attachment (up to {@value MAX_DESCRIPTION_LENGTH} characters) * * @throws IllegalArgumentException * If the description is longer than {@value MAX_DESCRIPTION_LENGTH} characters * * @return The same FileUpload instance with the new description */ @Nonnull public FileUpload setDescription(@Nullable String description) { if (description != null) Checks.notLonger(description = description.trim(), MAX_DESCRIPTION_LENGTH, "Description"); this.description = description; return this; } /** * Turns this attachment into a voice message with the provided waveform. * * @param mediaType * The audio type for the attached audio file. Should be {@code audio/ogg} or similar. * @param waveform * The waveform of the audio, which is a low frequency sampling up to 256 bytes. * @param duration * The actual duration of the audio data. * * @throws IllegalArgumentException * If null is provided or the waveform is not between 1 and 256 bytes long. * * @return The same FileUpload instance configured as a voice message attachment */ @Nonnull public FileUpload asVoiceMessage(@Nonnull MediaType mediaType, @Nonnull byte[] waveform, @Nonnull Duration duration) { Checks.notNull(duration, "Duration"); return this.asVoiceMessage(mediaType, waveform, duration.toNanos() / 1_000_000_000.0); } /** * Turns this attachment into a voice message with the provided waveform. * * @param mediaType * The audio type for the attached audio file. Should be {@code audio/ogg} or similar. * @param waveform * The waveform of the audio, which is a low frequency sampling up to 256 bytes. * @param durationSeconds * The actual duration of the audio data in seconds. * * @throws IllegalArgumentException * If null is provided or the waveform is not between 1 and 256 bytes long. * * @return The same FileUpload instance configured as a voice message attachment */ @Nonnull public FileUpload asVoiceMessage(@Nonnull MediaType mediaType, @Nonnull byte[] waveform, double durationSeconds) { Checks.notNull(mediaType, "Media type"); Checks.notNull(waveform, "Waveform"); Checks.check(waveform.length > 0 && waveform.length <= 256, "Waveform must be between 1 and 256 bytes long"); Checks.check(Double.isFinite(durationSeconds), "Duration must be a finite number"); Checks.check(durationSeconds > 0, "Duration must be positive"); this.waveform = waveform; this.durationSeconds = durationSeconds; this.mediaType = mediaType; return this; } /** * Whether this attachment is a valid voice message attachment. * * @return True, if this is a voice message attachment. */ public boolean isVoiceMessage() { return this.mediaType.type().equals("audio") && this.durationSeconds > 0.0 && this.waveform != null && this.waveform.length > 0; } /** * The filename for the file. * * @return The filename */ @Nonnull public String getName() { return name; } /** * The description for the file. * * @return The description */ @Nullable public String getDescription() { return description; } /** * The {@link InputStream} representing the data to upload as a file. * * @return The {@link InputStream} */ @Nonnull public InputStream getData() { if (resource != null) return resource; else return Okio.buffer(resourceSupplier.get()).inputStream(); } /** * Creates a re-usable instance of {@link RequestBody} with the specified content-type. * *

This body will automatically close the {@link #getData() resource} when the request is done. * However, since the body buffers the data, it can be used multiple times regardless. * * @param type * The content-type to use for the body (e.g. {@code "application/octet-stream"}) * * @throws IllegalArgumentException * If the content-type is null * * @return {@link RequestBody} */ @Nonnull public synchronized RequestBody getRequestBody(@Nonnull MediaType type) { Checks.notNull(type, "Type"); if (body != null) // This allows FileUpload to be used more than once! return body.withType(type); if (resource == null) return body = new DataSupplierBody(type, resourceSupplier); else return body = IOUtil.createRequestBody(type, resource); } @Override @SuppressWarnings("ConstantConditions") public synchronized void addPart(@Nonnull MultipartBody.Builder builder, int index) { builder.addFormDataPart("files[" + index + "]", name, getRequestBody(mediaType)); } @Nonnull @Override public DataObject toAttachmentData(int index) { DataObject attachment = DataObject.empty() .put("id", index) .put("description", description == null ? "" : description) .put("content_type", mediaType.toString()) .put("filename", name); if (waveform != null && durationSeconds > 0) { attachment.put("waveform", new String(Base64.getEncoder().encode(waveform), StandardCharsets.UTF_8)); attachment.put("duration_secs", durationSeconds); } return attachment; } @Override public synchronized void close() throws IOException { if (body == null) forceClose(); } @Override public void forceClose() throws IOException { if (resource != null) resource.close(); } @Override @SuppressWarnings("deprecation") protected void finalize() { if (body == null && resource != null) // Only close if the resource was never used IOUtil.silentClose(resource); } @Override public String toString() { return new EntityString("AttachedFile") .setType("Data") .setName(name) .toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy