commit
a56c2f8ba6
18
patch.py
18
patch.py
@ -8,8 +8,8 @@ from hashlib import sha256
|
|||||||
# - update version in a few places
|
# - update version in a few places
|
||||||
# - insert new line in ripme.json with message
|
# - insert new line in ripme.json with message
|
||||||
# - build ripme
|
# - build ripme
|
||||||
# - add the hash of the lastest binary to ripme.json
|
# - add the hash of the latest binary to ripme.json
|
||||||
|
# - commit all changes
|
||||||
message = input('message: ')
|
message = input('message: ')
|
||||||
|
|
||||||
|
|
||||||
@ -30,8 +30,7 @@ def update_hash(current_hash):
|
|||||||
def update_change_list(message):
|
def update_change_list(message):
|
||||||
ripmeJson = get_ripme_json()
|
ripmeJson = get_ripme_json()
|
||||||
with open('ripme.json', 'w') as dataFile:
|
with open('ripme.json', 'w') as dataFile:
|
||||||
ripmeJson["changeList"] = ripmeJson["changeList"].insert(0, message)
|
ripmeJson["changeList"].insert(0, message)
|
||||||
print(ripmeJson["currentHash"])
|
|
||||||
json.dump(ripmeJson, dataFile, indent=4)
|
json.dump(ripmeJson, dataFile, indent=4)
|
||||||
|
|
||||||
|
|
||||||
@ -63,17 +62,9 @@ subprocess.call(['sed', '-i', '-e', substrExpr, 'pom.xml'])
|
|||||||
subprocess.call(['git', 'grep', '<version>' + nextVersion + '</version>', 'pom.xml'])
|
subprocess.call(['git', 'grep', '<version>' + nextVersion + '</version>', 'pom.xml'])
|
||||||
|
|
||||||
commitMessage = nextVersion + ': ' + message
|
commitMessage = nextVersion + ': ' + message
|
||||||
changeLogLine = ' \"' + commitMessage + '\",\n'
|
|
||||||
|
|
||||||
dataFile = open("ripme.json", "r")
|
update_change_list(commitMessage)
|
||||||
ripmeJsonLines = dataFile.readlines()
|
|
||||||
ripmeJsonLines.insert(3, changeLogLine)
|
|
||||||
outputContent = ''.join(ripmeJsonLines)
|
|
||||||
dataFile.close()
|
|
||||||
|
|
||||||
dataFile = open("ripme.json", "w")
|
|
||||||
dataFile.write(outputContent)
|
|
||||||
dataFile.close()
|
|
||||||
|
|
||||||
print("Building ripme")
|
print("Building ripme")
|
||||||
subprocess.call(["mvn", "clean", "compile", "assembly:single"])
|
subprocess.call(["mvn", "clean", "compile", "assembly:single"])
|
||||||
@ -89,3 +80,4 @@ update_hash(file_hash)
|
|||||||
subprocess.call(['git', 'add', '-u'])
|
subprocess.call(['git', 'add', '-u'])
|
||||||
subprocess.call(['git', 'commit', '-m', commitMessage])
|
subprocess.call(['git', 'commit', '-m', commitMessage])
|
||||||
subprocess.call(['git', 'tag', nextVersion])
|
subprocess.call(['git', 'tag', nextVersion])
|
||||||
|
print("Remember to run `git push origin master` before release.py")
|
||||||
|
2
pom.xml
2
pom.xml
@ -4,7 +4,7 @@
|
|||||||
<groupId>com.rarchives.ripme</groupId>
|
<groupId>com.rarchives.ripme</groupId>
|
||||||
<artifactId>ripme</artifactId>
|
<artifactId>ripme</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<version>1.7.51</version>
|
<version>1.7.60</version>
|
||||||
<name>ripme</name>
|
<name>ripme</name>
|
||||||
<url>http://rip.rarchives.com</url>
|
<url>http://rip.rarchives.com</url>
|
||||||
<properties>
|
<properties>
|
||||||
|
67
release.py
Normal file → Executable file
67
release.py
Normal file → Executable file
@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -15,9 +15,12 @@ parser.add_argument("-f", "--file", help="Path to the version of ripme to releas
|
|||||||
parser.add_argument("-t", "--token", help="Your github personal access token")
|
parser.add_argument("-t", "--token", help="Your github personal access token")
|
||||||
parser.add_argument("-d", "--debug", help="Run in debug mode", action="store_true")
|
parser.add_argument("-d", "--debug", help="Run in debug mode", action="store_true")
|
||||||
parser.add_argument("-n", "--non-interactive", help="Do not ask for any input from the user", action="store_true")
|
parser.add_argument("-n", "--non-interactive", help="Do not ask for any input from the user", action="store_true")
|
||||||
|
parser.add_argument("--test", help="Perform a dry run (Do everything but upload new release)", action="store_true")
|
||||||
|
parser.add_argument("--skip-hash-check", help="Skip hash check (This should only be used for testing)", action="store_true")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# This binds input to raw_input on python2, we do this because input acts like eval on python2
|
||||||
input = raw_input
|
input = raw_input
|
||||||
except NameError:
|
except NameError:
|
||||||
pass
|
pass
|
||||||
@ -34,13 +37,24 @@ def isJar(filename):
|
|||||||
# false if not
|
# false if not
|
||||||
def isValidCommitMessage(message):
|
def isValidCommitMessage(message):
|
||||||
if debug:
|
if debug:
|
||||||
print("Checking if {} matchs pattern ^\d+\.\d+\.\d+:".format(message))
|
print(r"Checking if {} matches pattern ^\d+\.\d+\.\d+:".format(message))
|
||||||
pattern = re.compile("^\d+\.\d+\.\d+:")
|
pattern = re.compile(r"^\d+\.\d+\.\d+:")
|
||||||
return re.match(pattern, message)
|
return re.match(pattern, message)
|
||||||
|
|
||||||
|
|
||||||
|
# Checks if the update has the name ripme.jar, if not it renames the file
|
||||||
|
def checkAndRenameFile(path):
|
||||||
|
"""Check if path (a string) points to a ripme.jar. Returns the possibly renamed file path"""
|
||||||
|
if not path.endswith("ripme.jar"):
|
||||||
|
print("Specified file is not named ripme.jar, renaming")
|
||||||
|
new_path = os.path.join(os.path.dirname(path), "ripme.jar")
|
||||||
|
os.rename(path, new_path)
|
||||||
|
return new_path
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
ripmeJson = json.loads(open("ripme.json").read())
|
ripmeJson = json.loads(open("ripme.json").read())
|
||||||
fileToUploadPath = args.file
|
fileToUploadPath = checkAndRenameFile(args.file)
|
||||||
InNoninteractiveMode = args.non_interactive
|
InNoninteractiveMode = args.non_interactive
|
||||||
commitMessage = ripmeJson.get("changeList")[0]
|
commitMessage = ripmeJson.get("changeList")[0]
|
||||||
releaseVersion = ripmeJson.get("latestVersion")
|
releaseVersion = ripmeJson.get("latestVersion")
|
||||||
@ -61,22 +75,27 @@ if not isValidCommitMessage(commitMessage):
|
|||||||
print("[!] Error: {} is not a valid commit message as it does not start with a version".format(fileToUploadPath))
|
print("[!] Error: {} is not a valid commit message as it does not start with a version".format(fileToUploadPath))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
ripmeUpdate = open(fileToUploadPath, mode='rb').read()
|
|
||||||
|
|
||||||
# The hash that we expect the update to have
|
if not args.skip_hash_check:
|
||||||
expectedHash = ripmeJson.get("currentHash")
|
if debug:
|
||||||
|
print("Reading file {}".format(fileToUploadPath))
|
||||||
|
ripmeUpdate = open(fileToUploadPath, mode='rb').read()
|
||||||
|
|
||||||
# The actual hash of the file on disk
|
# The actual hash of the file on disk
|
||||||
actualHash = sha256(ripmeUpdate).hexdigest()
|
actualHash = sha256(ripmeUpdate).hexdigest()
|
||||||
|
|
||||||
# Make sure that the hash of the file we're uploading matches the hash in ripme.json. These hashes not matching will
|
# The hash that we expect the update to have
|
||||||
# cause ripme to refuse to install the update for all users who haven't disabled update hash checking
|
expectedHash = ripmeJson.get("currentHash")
|
||||||
if expectedHash != actualHash:
|
|
||||||
print("[!] Error: expected hash of file and actual hash differ")
|
|
||||||
print("[!] Expected hash is {}".format(expectedHash))
|
|
||||||
print("[!] Actual hash is {}".format(actualHash))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
# Make sure that the hash of the file we're uploading matches the hash in ripme.json. These hashes not matching will
|
||||||
|
# cause ripme to refuse to install the update for all users who haven't disabled update hash checking
|
||||||
|
if expectedHash != actualHash:
|
||||||
|
print("[!] Error: expected hash of file and actual hash differ")
|
||||||
|
print("[!] Expected hash is {}".format(expectedHash))
|
||||||
|
print("[!] Actual hash is {}".format(actualHash))
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print("[*] WARNING: SKIPPING HASH CHECK")
|
||||||
# Ask the user to review the information before we precede
|
# Ask the user to review the information before we precede
|
||||||
# This only runs in we're in interactive mode
|
# This only runs in we're in interactive mode
|
||||||
if not InNoninteractiveMode:
|
if not InNoninteractiveMode:
|
||||||
@ -85,12 +104,14 @@ if not InNoninteractiveMode:
|
|||||||
print("Repo: {}/{}".format(repoOwner, repoName))
|
print("Repo: {}/{}".format(repoOwner, repoName))
|
||||||
input("\nPlease review the information above and ensure it is correct and then press enter")
|
input("\nPlease review the information above and ensure it is correct and then press enter")
|
||||||
|
|
||||||
print("Accessing github using token")
|
if not args.test:
|
||||||
g = Github(accessToken)
|
print("Accessing github using token")
|
||||||
|
g = Github(accessToken)
|
||||||
|
|
||||||
|
print("Creating release")
|
||||||
|
release = g.get_user(repoOwner).get_repo(repoName).create_git_release(releaseVersion, commitMessage, "")
|
||||||
|
|
||||||
print("Creating release")
|
print("Uploading file")
|
||||||
release = g.get_user(repoOwner).get_repo(repoName).create_git_release(releaseVersion, commitMessage, "")
|
release.upload_asset(fileToUploadPath, "ripme.jar")
|
||||||
|
else:
|
||||||
print("Uploading file")
|
print("Not uploading release being script was run with --test flag")
|
||||||
release.upload_asset(fileToUploadPath, "ripme.jar")
|
|
||||||
|
13
ripme.json
13
ripme.json
@ -1,6 +1,14 @@
|
|||||||
{
|
{
|
||||||
"currentHash": "aadb71bf5cdf46fe92e270b50a55c8d8d7200a6dd304a4c2ac9f68cddc687d7e",
|
|
||||||
"changeList": [
|
"changeList": [
|
||||||
|
"1.7.60: Fixed EightmusesRipper; added Jab Archives ripper; loveroms ripper now properly names files; Fixed ArtStationRipper",
|
||||||
|
"1.7.59: Added Loverom ripper; added Imagearn ripper; Added support for Desuarchive.org; Fixed erome ripper",
|
||||||
|
"1.7.58: Fixed Deviantart ripper; fixed HitomiRipper; Fixed ManganeloRipper; Fixed update box formating",
|
||||||
|
"1.7.57: Got DeviantartRipper working again; Imagefap ripper now downloads full sized images; Twitter ripper can now rip extended tweets; Added nl_NL translation",
|
||||||
|
"1.7.56: Fixed DeviantartRipper ripper; Added support for resuming file downloads; Fixed erome ripper; Fixed ModelmayhemRipper NSFW image downloading",
|
||||||
|
"1.7.55: Fixed instagram ripper; Reddit ripper now respects history.end_rip_after_already_seen; Improvements to patch.py and release.py",
|
||||||
|
"1.7.54: Fixed twitter ripper video downloading; fixed instagram ripper",
|
||||||
|
"1.7.53: Added Picstatio ripper; Fixed instagram ripper; Reddit ripper now gets videos from v.redd.it; Fixed ZikiRipper getAlbumTitle; fixed twitter ripper",
|
||||||
|
"1.7.52: Added warning about using furaffinty shared account; Refactoring in Utils class; XhamsterRipper now accepts all countries subdomains; E621 ripper now accepts urls with order:Score at the end; release.py imrpovements; DeviantartRipper now logs in using cookies; patch.py imrpovements",
|
||||||
"1.7.51: Fixed instagram ripper; Added the ability to rip from vsco profiles; Fixed TheyiffgalleryRipper; Can now update ripme using the -j flag; added script to automate releases; Code style fixes",
|
"1.7.51: Fixed instagram ripper; Added the ability to rip from vsco profiles; Fixed TheyiffgalleryRipper; Can now update ripme using the -j flag; added script to automate releases; Code style fixes",
|
||||||
"1.7.50: Ripme now checks file hash before running update; fixed update bug which cased ripme to report every update as new",
|
"1.7.50: Ripme now checks file hash before running update; fixed update bug which cased ripme to report every update as new",
|
||||||
"1.7.49: Fixed -n flag; Added ability to change locale at runtime and from gui; Update kr_KR translation; Removed support for tnbtu.com; No longer writes url to url_history file is save urls only is checked",
|
"1.7.49: Fixed -n flag; Added ability to change locale at runtime and from gui; Update kr_KR translation; Removed support for tnbtu.com; No longer writes url to url_history file is save urls only is checked",
|
||||||
@ -223,5 +231,6 @@
|
|||||||
"1.0.3: Added VK.com ripper",
|
"1.0.3: Added VK.com ripper",
|
||||||
"1.0.1: Added auto-update functionality"
|
"1.0.1: Added auto-update functionality"
|
||||||
],
|
],
|
||||||
"latestVersion": "1.7.51"
|
"latestVersion": "1.7.60",
|
||||||
|
"currentHash": "f206e478822134328763fc41676f438ee5cc795f31984613619952dab8402301"
|
||||||
}
|
}
|
@ -97,7 +97,7 @@ public abstract class AbstractHTMLRipper extends AlbumRipper {
|
|||||||
|
|
||||||
while (doc != null) {
|
while (doc != null) {
|
||||||
if (alreadyDownloadedUrls >= Utils.getConfigInteger("history.end_rip_after_already_seen", 1000000000) && !isThisATest()) {
|
if (alreadyDownloadedUrls >= Utils.getConfigInteger("history.end_rip_after_already_seen", 1000000000) && !isThisATest()) {
|
||||||
sendUpdate(STATUS.DOWNLOAD_COMPLETE, "Already seen the last " + alreadyDownloadedUrls + " images ending rip");
|
sendUpdate(STATUS.DOWNLOAD_COMPLETE_HISTORY, "Already seen the last " + alreadyDownloadedUrls + " images ending rip");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
List<String> imageURLs = getURLsFromPage(doc);
|
List<String> imageURLs = getURLsFromPage(doc);
|
||||||
@ -224,7 +224,6 @@ public abstract class AbstractHTMLRipper extends AlbumRipper {
|
|||||||
if (!subdirectory.equals("")) { // Not sure about this part
|
if (!subdirectory.equals("")) { // Not sure about this part
|
||||||
subdirectory = File.separator + subdirectory;
|
subdirectory = File.separator + subdirectory;
|
||||||
}
|
}
|
||||||
// TODO Get prefix working again, probably requires reworking a lot of stuff! (Might be fixed now)
|
|
||||||
saveFileAs = new File(
|
saveFileAs = new File(
|
||||||
workingDir.getCanonicalPath()
|
workingDir.getCanonicalPath()
|
||||||
+ subdirectory
|
+ subdirectory
|
||||||
|
@ -69,7 +69,7 @@ public abstract class AbstractJSONRipper extends AlbumRipper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageURLs.isEmpty()) {
|
if (imageURLs.isEmpty() && !hasASAPRipping()) {
|
||||||
throw new IOException("No images found at " + this.url);
|
throw new IOException("No images found at " + this.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -613,4 +613,9 @@ public abstract class AbstractRipper
|
|||||||
protected boolean isThisATest() {
|
protected boolean isThisATest() {
|
||||||
return thisIsATest;
|
return thisIsATest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If true ripme uses a byte progress bar
|
||||||
|
protected boolean useByteProgessBar() { return false;}
|
||||||
|
// If true ripme will try to resume a broken download for this ripper
|
||||||
|
protected boolean tryResumeDownload() { return false;}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.rarchives.ripme.ripper;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.utils.Utils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is just an extension of AbstractHTMLRipper that auto overrides a few things
|
||||||
|
* to help cut down on copy pasted code
|
||||||
|
*/
|
||||||
|
public abstract class AbstractSingleFileRipper extends AbstractHTMLRipper {
|
||||||
|
private int bytesTotal = 1;
|
||||||
|
private int bytesCompleted = 1;
|
||||||
|
|
||||||
|
protected AbstractSingleFileRipper(URL url) throws IOException {
|
||||||
|
super(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStatusText() {
|
||||||
|
return Utils.getByteStatusText(getCompletionPercentage(), bytesCompleted, bytesTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCompletionPercentage() {
|
||||||
|
return (int) (100 * (bytesCompleted / (float) bytesTotal));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytesTotal(int bytes) {
|
||||||
|
this.bytesTotal = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytesCompleted(int bytes) {
|
||||||
|
this.bytesCompleted = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useByteProgessBar() {return true;}
|
||||||
|
}
|
@ -71,10 +71,7 @@ public abstract class AlbumRipper extends AbstractRipper {
|
|||||||
try (FileWriter fw = new FileWriter(urlFile, true)) {
|
try (FileWriter fw = new FileWriter(urlFile, true)) {
|
||||||
fw.write(url.toExternalForm());
|
fw.write(url.toExternalForm());
|
||||||
fw.write("\n");
|
fw.write("\n");
|
||||||
|
|
||||||
RipStatusMessage msg = new RipStatusMessage(STATUS.DOWNLOAD_COMPLETE, urlFile);
|
|
||||||
itemsCompleted.put(url, new File(urlFile));
|
itemsCompleted.put(url, new File(urlFile));
|
||||||
observer.update(this, msg);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOGGER.error("Error while writing to " + urlFile, e);
|
LOGGER.error("Error while writing to " + urlFile, e);
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,11 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -59,18 +61,26 @@ class DownloadFileThread extends Thread {
|
|||||||
this.cookies = cookies;
|
this.cookies = cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to download the file. Retries as needed.
|
* Attempts to download the file. Retries as needed.
|
||||||
* Notifies observers upon completion/error/warn.
|
* Notifies observers upon completion/error/warn.
|
||||||
*/
|
*/
|
||||||
public void run() {
|
public void run() {
|
||||||
|
long fileSize = 0;
|
||||||
|
int bytesTotal = 0;
|
||||||
|
int bytesDownloaded = 0;
|
||||||
|
if (saveAs.exists() && observer.tryResumeDownload()) {
|
||||||
|
fileSize = saveAs.length();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
observer.stopCheck();
|
observer.stopCheck();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
observer.downloadErrored(url, "Download interrupted");
|
observer.downloadErrored(url, "Download interrupted");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (saveAs.exists()) {
|
if (saveAs.exists() && !observer.tryResumeDownload() && !getFileExtFromMIME ||
|
||||||
|
Utils.fuzzyExists(new File(saveAs.getParent()), saveAs.getName()) && getFileExtFromMIME && !observer.tryResumeDownload()) {
|
||||||
if (Utils.getConfigBoolean("file.overwrite", false)) {
|
if (Utils.getConfigBoolean("file.overwrite", false)) {
|
||||||
logger.info("[!] Deleting existing file" + prettySaveAs);
|
logger.info("[!] Deleting existing file" + prettySaveAs);
|
||||||
saveAs.delete();
|
saveAs.delete();
|
||||||
@ -80,7 +90,6 @@ class DownloadFileThread extends Thread {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
URL urlToDownload = this.url;
|
URL urlToDownload = this.url;
|
||||||
boolean redirected = false;
|
boolean redirected = false;
|
||||||
int tries = 0; // Number of attempts to download
|
int tries = 0; // Number of attempts to download
|
||||||
@ -114,11 +123,20 @@ class DownloadFileThread extends Thread {
|
|||||||
cookie += key + "=" + cookies.get(key);
|
cookie += key + "=" + cookies.get(key);
|
||||||
}
|
}
|
||||||
huc.setRequestProperty("Cookie", cookie);
|
huc.setRequestProperty("Cookie", cookie);
|
||||||
|
if (observer.tryResumeDownload()) {
|
||||||
|
if (fileSize != 0) {
|
||||||
|
huc.setRequestProperty("Range", "bytes=" + fileSize + "-");
|
||||||
|
}
|
||||||
|
}
|
||||||
logger.debug("Request properties: " + huc.getRequestProperties());
|
logger.debug("Request properties: " + huc.getRequestProperties());
|
||||||
huc.connect();
|
huc.connect();
|
||||||
|
|
||||||
int statusCode = huc.getResponseCode();
|
int statusCode = huc.getResponseCode();
|
||||||
logger.debug("Status code: " + statusCode);
|
logger.debug("Status code: " + statusCode);
|
||||||
|
if (statusCode != 206 && observer.tryResumeDownload() && saveAs.exists()) {
|
||||||
|
// TODO find a better way to handle servers that don't support resuming downloads then just erroring out
|
||||||
|
throw new IOException("Server doesn't support resuming downloads");
|
||||||
|
}
|
||||||
if (statusCode / 100 == 3) { // 3xx Redirect
|
if (statusCode / 100 == 3) { // 3xx Redirect
|
||||||
if (!redirected) {
|
if (!redirected) {
|
||||||
// Don't increment retries on the first redirect
|
// Don't increment retries on the first redirect
|
||||||
@ -146,17 +164,63 @@ class DownloadFileThread extends Thread {
|
|||||||
observer.downloadErrored(url, "Imgur image is 404: " + url.toExternalForm());
|
observer.downloadErrored(url, "Imgur image is 404: " + url.toExternalForm());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the ripper is using the bytes progress bar set bytesTotal to huc.getContentLength()
|
||||||
|
if (observer.useByteProgessBar()) {
|
||||||
|
bytesTotal = huc.getContentLength();
|
||||||
|
observer.setBytesTotal(bytesTotal);
|
||||||
|
observer.sendUpdate(STATUS.TOTAL_BYTES, bytesTotal);
|
||||||
|
logger.debug("Size of file at " + this.url + " = " + bytesTotal + "b");
|
||||||
|
}
|
||||||
|
|
||||||
// Save file
|
// Save file
|
||||||
bis = new BufferedInputStream(huc.getInputStream());
|
bis = new BufferedInputStream(huc.getInputStream());
|
||||||
|
|
||||||
// Check if we should get the file ext from the MIME type
|
// Check if we should get the file ext from the MIME type
|
||||||
if (getFileExtFromMIME) {
|
if (getFileExtFromMIME) {
|
||||||
String fileExt = URLConnection.guessContentTypeFromStream(bis).replaceAll("image/", "");
|
String fileExt = URLConnection.guessContentTypeFromStream(bis);
|
||||||
saveAs = new File(saveAs.toString() + "." + fileExt);
|
if (fileExt != null) {
|
||||||
|
fileExt = fileExt.replaceAll("image/", "");
|
||||||
|
saveAs = new File(saveAs.toString() + "." + fileExt);
|
||||||
|
} else {
|
||||||
|
logger.error("Was unable to get content type from stream");
|
||||||
|
// Try to get the file type from the magic number
|
||||||
|
byte[] magicBytes = new byte[8];
|
||||||
|
bis.read(magicBytes,0, 5);
|
||||||
|
bis.reset();
|
||||||
|
fileExt = Utils.getEXTFromMagic(magicBytes);
|
||||||
|
if (fileExt != null) {
|
||||||
|
saveAs = new File(saveAs.toString() + "." + fileExt);
|
||||||
|
} else {
|
||||||
|
logger.error("Was unable to get content type using magic number");
|
||||||
|
logger.error("Magic number was: " + Arrays.toString(magicBytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// If we're resuming a download we append data to the existing file
|
||||||
fos = new FileOutputStream(saveAs);
|
if (statusCode == 206) {
|
||||||
IOUtils.copy(bis, fos);
|
fos = new FileOutputStream(saveAs, true);
|
||||||
|
} else {
|
||||||
|
fos = new FileOutputStream(saveAs);
|
||||||
|
}
|
||||||
|
byte[] data = new byte[1024 * 256];
|
||||||
|
int bytesRead;
|
||||||
|
while ( (bytesRead = bis.read(data)) != -1) {
|
||||||
|
try {
|
||||||
|
observer.stopCheck();
|
||||||
|
} catch (IOException e) {
|
||||||
|
observer.downloadErrored(url, "Download interrupted");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fos.write(data, 0, bytesRead);
|
||||||
|
if (observer.useByteProgessBar()) {
|
||||||
|
bytesDownloaded += bytesRead;
|
||||||
|
observer.setBytesCompleted(bytesDownloaded);
|
||||||
|
observer.sendUpdate(STATUS.COMPLETED_BYTES, bytesDownloaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bis.close();
|
||||||
|
fos.close();
|
||||||
break; // Download successful: break out of infinite loop
|
break; // Download successful: break out of infinite loop
|
||||||
} catch (HttpStatusException hse) {
|
} catch (HttpStatusException hse) {
|
||||||
logger.debug("HTTP status exception", hse);
|
logger.debug("HTTP status exception", hse);
|
||||||
|
@ -76,7 +76,8 @@ class DownloadVideoThread extends Thread {
|
|||||||
int tries = 0; // Number of attempts to download
|
int tries = 0; // Number of attempts to download
|
||||||
do {
|
do {
|
||||||
InputStream bis = null; OutputStream fos = null;
|
InputStream bis = null; OutputStream fos = null;
|
||||||
byte[] data = new byte[1024 * 256]; int bytesRead;
|
byte[] data = new byte[1024 * 256];
|
||||||
|
int bytesRead;
|
||||||
try {
|
try {
|
||||||
logger.info(" Downloading file: " + url + (tries > 0 ? " Retry #" + tries : ""));
|
logger.info(" Downloading file: " + url + (tries > 0 ? " Retry #" + tries : ""));
|
||||||
observer.sendUpdate(STATUS.DOWNLOAD_STARTED, url.toExternalForm());
|
observer.sendUpdate(STATUS.DOWNLOAD_STARTED, url.toExternalForm());
|
||||||
|
@ -0,0 +1,270 @@
|
|||||||
|
package com.rarchives.ripme.ripper.rippers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.AbstractJSONRipper;
|
||||||
|
import com.rarchives.ripme.utils.Http;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class ArtStationRipper extends AbstractJSONRipper {
|
||||||
|
enum URL_TYPE {
|
||||||
|
SINGLE_PROJECT, USER_PORTFOLIO, UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParsedURL albumURL;
|
||||||
|
private String projectName;
|
||||||
|
private Integer projectIndex;
|
||||||
|
private Integer projectPageNumber;
|
||||||
|
|
||||||
|
public ArtStationRipper(URL url) throws IOException {
|
||||||
|
super(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDomain() {
|
||||||
|
return "artstation.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHost() {
|
||||||
|
return "ArtStation";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getGID(URL url) throws MalformedURLException {
|
||||||
|
JSONObject groupData;
|
||||||
|
|
||||||
|
// Parse URL and store for later use
|
||||||
|
albumURL = parseURL(url);
|
||||||
|
|
||||||
|
if (albumURL.getType() == URL_TYPE.SINGLE_PROJECT) {
|
||||||
|
// URL points to single project, use project title as GID
|
||||||
|
try {
|
||||||
|
groupData = Http.url(albumURL.getLocation()).getJSON();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MalformedURLException("Couldn't load JSON from " + albumURL.getLocation());
|
||||||
|
}
|
||||||
|
return groupData.getString("title");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (albumURL.getType() == URL_TYPE.USER_PORTFOLIO) {
|
||||||
|
// URL points to user portfolio, use user's full name as GID
|
||||||
|
String userInfoURL = "https://www.artstation.com/users/" + albumURL.getID() + "/quick.json";
|
||||||
|
try {
|
||||||
|
groupData = Http.url(userInfoURL).getJSON();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new MalformedURLException("Couldn't load JSON from " + userInfoURL);
|
||||||
|
}
|
||||||
|
return groupData.getString("full_name");
|
||||||
|
}
|
||||||
|
|
||||||
|
// No JSON found in the URL entered, can't rip
|
||||||
|
throw new MalformedURLException(
|
||||||
|
"Expected URL to an ArtStation project or user profile - got " + url + " instead");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JSONObject getFirstPage() throws IOException {
|
||||||
|
if (albumURL.getType() == URL_TYPE.SINGLE_PROJECT) {
|
||||||
|
// URL points to JSON of a single project, just return it
|
||||||
|
return Http.url(albumURL.getLocation()).getJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (albumURL.getType() == URL_TYPE.USER_PORTFOLIO) {
|
||||||
|
// URL points to JSON of a list of projects, load it to parse individual
|
||||||
|
// projects
|
||||||
|
JSONObject albumContent = Http.url(albumURL.getLocation()).getJSON();
|
||||||
|
|
||||||
|
if (albumContent.getInt("total_count") > 0) {
|
||||||
|
// Get JSON of the first project and return it
|
||||||
|
JSONObject projectInfo = albumContent.getJSONArray("data").getJSONObject(0);
|
||||||
|
ParsedURL projectURL = parseURL(new URL(projectInfo.getString("permalink")));
|
||||||
|
return Http.url(projectURL.getLocation()).getJSON();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IOException("URL specified points to an user with empty portfolio");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JSONObject getNextPage(JSONObject doc) throws IOException {
|
||||||
|
if (albumURL.getType() == URL_TYPE.USER_PORTFOLIO) {
|
||||||
|
// Initialize the page number if it hasn't been initialized already
|
||||||
|
if (projectPageNumber == null) {
|
||||||
|
projectPageNumber = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each page holds a maximum of 50 projects. Initialize the index if it hasn't
|
||||||
|
// been initialized already or increment page number and reset the index if all
|
||||||
|
// projects of the current page were already processed
|
||||||
|
if (projectIndex == null) {
|
||||||
|
projectIndex = 0;
|
||||||
|
} else if (projectIndex > 49) {
|
||||||
|
projectPageNumber++;
|
||||||
|
projectIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer currentProject = ((projectPageNumber - 1) * 50) + (projectIndex + 1);
|
||||||
|
JSONObject albumContent = Http.url(albumURL.getLocation() + "?page=" + projectPageNumber).getJSON();
|
||||||
|
|
||||||
|
if (albumContent.getInt("total_count") > currentProject) {
|
||||||
|
// Get JSON of the next project and return it
|
||||||
|
JSONObject projectInfo = albumContent.getJSONArray("data").getJSONObject(projectIndex);
|
||||||
|
ParsedURL projectURL = parseURL(new URL(projectInfo.getString("permalink")));
|
||||||
|
projectIndex++;
|
||||||
|
return Http.url(projectURL.getLocation()).getJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IOException("No more projects");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IOException("Downloading a single project");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getURLsFromJSON(JSONObject json) {
|
||||||
|
List<String> assetURLs = new ArrayList<>();
|
||||||
|
JSONObject currentObject;
|
||||||
|
|
||||||
|
// Update project name variable from JSON data. Used by downloadURL() to create
|
||||||
|
// subfolders when input URL is URL_TYPE.USER_PORTFOLIO
|
||||||
|
projectName = json.getString("title");
|
||||||
|
|
||||||
|
for (int i = 0; i < json.getJSONArray("assets").length(); i++) {
|
||||||
|
currentObject = json.getJSONArray("assets").getJSONObject(i);
|
||||||
|
|
||||||
|
if (!currentObject.getString("image_url").isEmpty()) {
|
||||||
|
// TODO: Find a way to rip external content.
|
||||||
|
// ArtStation hosts only image content, everything else (videos, 3D Models, etc)
|
||||||
|
// is hosted in other websites and displayed through embedded HTML5 players
|
||||||
|
assetURLs.add(currentObject.getString("image_url"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return assetURLs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void downloadURL(URL url, int index) {
|
||||||
|
if (albumURL.getType() == URL_TYPE.USER_PORTFOLIO) {
|
||||||
|
// Replace not allowed characters with underlines
|
||||||
|
String folderName = projectName.replaceAll("[\\\\/:*?\"<>|]", "_");
|
||||||
|
|
||||||
|
// Folder name also can't end with dots or spaces, strip them
|
||||||
|
folderName = folderName.replaceAll("\\s+$", "");
|
||||||
|
folderName = folderName.replaceAll("\\.+$", "");
|
||||||
|
|
||||||
|
// Downloading multiple projects, separate each one in subfolders
|
||||||
|
addURLToDownload(url, "", folderName);
|
||||||
|
} else {
|
||||||
|
addURLToDownload(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String normalizeUrl(String url) {
|
||||||
|
// Strip URL parameters
|
||||||
|
return url.replaceAll("\\?\\w+$", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ParsedURL {
|
||||||
|
URL_TYPE urlType;
|
||||||
|
String jsonURL, urlID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new ParsedURL object.
|
||||||
|
*
|
||||||
|
* @param urlType URL_TYPE enum containing the URL type
|
||||||
|
* @param jsonURL String containing the JSON URL location
|
||||||
|
* @param urlID String containing the ID of this URL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
ParsedURL(URL_TYPE urlType, String jsonURL, String urlID) {
|
||||||
|
this.urlType = urlType;
|
||||||
|
this.jsonURL = jsonURL;
|
||||||
|
this.urlID = urlID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get URL Type of this ParsedURL object.
|
||||||
|
*
|
||||||
|
* @return URL_TYPE enum containing this object type
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
URL_TYPE getType() {
|
||||||
|
return this.urlType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get JSON location of this ParsedURL object.
|
||||||
|
*
|
||||||
|
* @return String containing the JSON URL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
String getLocation() {
|
||||||
|
return this.jsonURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ID of this ParsedURL object.
|
||||||
|
*
|
||||||
|
* @return For URL_TYPE.SINGLE_PROJECT, returns the project hash. For
|
||||||
|
* URL_TYPE.USER_PORTFOLIO, returns the account name
|
||||||
|
*/
|
||||||
|
String getID() {
|
||||||
|
return this.urlID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an ArtStation URL.
|
||||||
|
*
|
||||||
|
* @param url URL to an ArtStation user profile
|
||||||
|
* (https://www.artstation.com/username) or single project
|
||||||
|
* (https://www.artstation.com/artwork/projectid)
|
||||||
|
* @return ParsedURL object containing URL type, JSON location and ID (stores
|
||||||
|
* account name or project hash, depending of the URL type identified)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private ParsedURL parseURL(URL url) {
|
||||||
|
String htmlSource;
|
||||||
|
ParsedURL parsedURL;
|
||||||
|
|
||||||
|
// Load HTML Source of the specified URL
|
||||||
|
try {
|
||||||
|
htmlSource = Http.url(url).get().html();
|
||||||
|
} catch (IOException e) {
|
||||||
|
htmlSource = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if HTML Source of the specified URL references a project
|
||||||
|
Pattern p = Pattern.compile("'/projects/(\\w+)\\.json'");
|
||||||
|
Matcher m = p.matcher(htmlSource);
|
||||||
|
if (m.find()) {
|
||||||
|
parsedURL = new ParsedURL(URL_TYPE.SINGLE_PROJECT,
|
||||||
|
"https://www.artstation.com/projects/" + m.group(1) + ".json", m.group(1));
|
||||||
|
return parsedURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if HTML Source of the specified URL references a user profile
|
||||||
|
p = Pattern.compile("'/users/([\\w-]+)/quick\\.json'");
|
||||||
|
m = p.matcher(htmlSource);
|
||||||
|
if (m.find()) {
|
||||||
|
parsedURL = new ParsedURL(URL_TYPE.USER_PORTFOLIO,
|
||||||
|
"https://www.artstation.com/users/" + m.group(1) + "/projects.json", m.group(1));
|
||||||
|
return parsedURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML Source of the specified URL doesn't reference a user profile or project
|
||||||
|
parsedURL = new ParsedURL(URL_TYPE.UNKNOWN, null, null);
|
||||||
|
return parsedURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -19,7 +19,7 @@ import com.rarchives.ripme.utils.RipUtils;
|
|||||||
|
|
||||||
public class ChanRipper extends AbstractHTMLRipper {
|
public class ChanRipper extends AbstractHTMLRipper {
|
||||||
private static List<ChanSite> explicit_domains = Arrays.asList(
|
private static List<ChanSite> explicit_domains = Arrays.asList(
|
||||||
new ChanSite(Arrays.asList("boards.4chan.org"), Arrays.asList("4cdn.org", "is.4chan.org", "is2.4chan.org")),
|
new ChanSite(Arrays.asList("boards.4chan.org"), Arrays.asList("4cdn.org", "is.4chan.org", "is2.4chan.org", "is3.4chan.org")),
|
||||||
new ChanSite(Arrays.asList("4archive.org"), Arrays.asList("imgur.com")),
|
new ChanSite(Arrays.asList("4archive.org"), Arrays.asList("imgur.com")),
|
||||||
new ChanSite(Arrays.asList("archive.4plebs.org"), Arrays.asList("img.4plebs.org"))
|
new ChanSite(Arrays.asList("archive.4plebs.org"), Arrays.asList("img.4plebs.org"))
|
||||||
);
|
);
|
||||||
@ -85,8 +85,22 @@ public class ChanRipper extends AbstractHTMLRipper {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return url.toExternalForm().contains("/res/") // Most chans
|
if (url.toExternalForm().contains("desuchan.net") && url.toExternalForm().contains("/res/")) {
|
||||||
|| url.toExternalForm().contains("/thread/"); // 4chan, archive.moe
|
return true;
|
||||||
|
}
|
||||||
|
if (url.toExternalForm().contains("boards.420chan.org") && url.toExternalForm().contains("/res/")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (url.toExternalForm().contains("7chan.org") && url.toExternalForm().contains("/res/")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (url.toExternalForm().contains("xchan.pw") && url.toExternalForm().contains("/board/")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (url.toExternalForm().contains("desuarchive.org")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,7 +28,7 @@ public class CheveretoRipper extends AbstractHTMLRipper {
|
|||||||
super(url);
|
super(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> explicit_domains_1 = Arrays.asList("tag-fox.com");
|
private static List<String> explicit_domains = Arrays.asList("tag-fox.com", "kenzato.uk");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHost() {
|
public String getHost() {
|
||||||
@ -43,12 +43,8 @@ public class CheveretoRipper extends AbstractHTMLRipper {
|
|||||||
@Override
|
@Override
|
||||||
public boolean canRip(URL url) {
|
public boolean canRip(URL url) {
|
||||||
String url_name = url.toExternalForm();
|
String url_name = url.toExternalForm();
|
||||||
if (explicit_domains_1.contains(url_name.split("/")[2])) {
|
if (explicit_domains.contains(url_name.split("/")[2])) {
|
||||||
Pattern pa = Pattern.compile("(?:https?://)?(?:www\\.)?[a-z1-9-]*\\.[a-z1-9]*/album/([a-zA-Z1-9]*)/?$");
|
|
||||||
Matcher ma = pa.matcher(url.toExternalForm());
|
|
||||||
if (ma.matches()) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -70,7 +66,7 @@ public class CheveretoRipper extends AbstractHTMLRipper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getGID(URL url) throws MalformedURLException {
|
public String getGID(URL url) throws MalformedURLException {
|
||||||
Pattern p = Pattern.compile("(?:https?://)?(?:www\\.)?[a-z1-9-]*\\.[a-z1-9]*/album/([a-zA-Z1-9]*)/?$");
|
Pattern p = Pattern.compile("(?:https?://)?(?:www\\.)?[a-z1-9-]*\\.[a-z1-9]*(?:[a-zA-Z1-9]*)/album/([a-zA-Z1-9]*)/?$");
|
||||||
Matcher m = p.matcher(url.toExternalForm());
|
Matcher m = p.matcher(url.toExternalForm());
|
||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
return m.group(1);
|
return m.group(1);
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package com.rarchives.ripme.ripper.rippers;
|
package com.rarchives.ripme.ripper.rippers;
|
||||||
|
|
||||||
import com.rarchives.ripme.ripper.AbstractHTMLRipper;
|
import com.rarchives.ripme.ripper.AbstractJSONRipper;
|
||||||
import com.rarchives.ripme.utils.Base64;
|
import com.rarchives.ripme.utils.Base64;
|
||||||
import com.rarchives.ripme.utils.Http;
|
import com.rarchives.ripme.utils.Http;
|
||||||
|
import com.rarchives.ripme.utils.RipUtils;
|
||||||
import com.rarchives.ripme.utils.Utils;
|
import com.rarchives.ripme.utils.Utils;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
@ -17,15 +18,23 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import org.jsoup.Connection.Method;
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.jsoup.Connection.Response;
|
import org.jsoup.Connection.Response;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
import org.jsoup.safety.Whitelist;
|
|
||||||
import org.jsoup.select.Elements;
|
import org.jsoup.select.Elements;
|
||||||
|
|
||||||
public class DeviantartRipper extends AbstractHTMLRipper {
|
|
||||||
|
public class DeviantartRipper extends AbstractJSONRipper {
|
||||||
|
String requestID;
|
||||||
|
String galleryID;
|
||||||
|
String username;
|
||||||
|
String baseApiUrl = "https://www.deviantart.com/dapi/v1/gallery/";
|
||||||
|
String csrf;
|
||||||
|
Map<String, String> pageCookies = new HashMap<>();
|
||||||
|
|
||||||
private static final int PAGE_SLEEP_TIME = 3000,
|
private static final int PAGE_SLEEP_TIME = 3000,
|
||||||
IMAGE_SLEEP_TIME = 2000;
|
IMAGE_SLEEP_TIME = 2000;
|
||||||
@ -37,31 +46,37 @@ public class DeviantartRipper extends AbstractHTMLRipper {
|
|||||||
super(url);
|
super(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String loginCookies = "auth=__0f9158aaec09f417b235%3B%221ff79836392a515d154216d919eae573%22;" +
|
||||||
|
"auth_secure=__41d14dd0da101f411bb0%3B%2281cf2cf9477776162a1172543aae85ce%22;" +
|
||||||
|
"userinfo=__bf84ac233bfa8ae642e8%3B%7B%22username%22%3A%22grabpy%22%2C%22uniqueid%22%3A%22a0a876aa37dbd4b30e1c80406ee9c280%22%2C%22vd%22%3A%22BbHUXZ%2CBbHUXZ%2CA%2CU%2CA%2C%2CB%2CA%2CB%2CBbHUXZ%2CBbHUdj%2CL%2CL%2CA%2CBbHUdj%2C13%2CA%2CB%2CA%2C%2CA%2CA%2CB%2CA%2CA%2C%2CA%22%2C%22attr%22%3A56%7D";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHost() {
|
public String getHost() {
|
||||||
return "deviantart";
|
return "deviantart";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDomain() {
|
public String getDomain() {
|
||||||
return "deviantart.com";
|
return "deviantart.com";
|
||||||
}
|
}
|
||||||
@Override
|
|
||||||
public boolean hasDescriptionSupport() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@Override
|
@Override
|
||||||
public URL sanitizeURL(URL url) throws MalformedURLException {
|
public URL sanitizeURL(URL url) throws MalformedURLException {
|
||||||
String u = url.toExternalForm();
|
String u = url.toExternalForm();
|
||||||
|
if (u.contains("/gallery/")) {
|
||||||
if (u.replace("/", "").endsWith(".deviantart.com")) {
|
return url;
|
||||||
// Root user page, get all albums
|
|
||||||
if (!u.endsWith("/")) {
|
|
||||||
u += "/";
|
|
||||||
}
|
|
||||||
u += "gallery/?";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Pattern p = Pattern.compile("^https?://([a-zA-Z0-9\\-]+)\\.deviantart\\.com/favou?rites/([0-9]+)/*?$");
|
if (!u.endsWith("/gallery/") && !u.endsWith("/gallery")) {
|
||||||
|
if (!u.endsWith("/")) {
|
||||||
|
u += "/gallery/";
|
||||||
|
} else {
|
||||||
|
u += "gallery/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Pattern p = Pattern.compile("^https?://www\\.deviantart\\.com/([a-zA-Z0-9\\-]+)/favou?rites/([0-9]+)/*?$");
|
||||||
Matcher m = p.matcher(url.toExternalForm());
|
Matcher m = p.matcher(url.toExternalForm());
|
||||||
if (!m.matches()) {
|
if (!m.matches()) {
|
||||||
String subdir = "/";
|
String subdir = "/";
|
||||||
@ -75,7 +90,7 @@ public class DeviantartRipper extends AbstractHTMLRipper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getGID(URL url) throws MalformedURLException {
|
public String getGID(URL url) throws MalformedURLException {
|
||||||
Pattern p = Pattern.compile("^https?://([a-zA-Z0-9\\-]+)\\.deviantart\\.com(/gallery)?/?(\\?.*)?$");
|
Pattern p = Pattern.compile("^https?://www\\.deviantart\\.com/([a-zA-Z0-9\\-]+)(/gallery)?/?(\\?.*)?$");
|
||||||
Matcher m = p.matcher(url.toExternalForm());
|
Matcher m = p.matcher(url.toExternalForm());
|
||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
// Root gallery
|
// Root gallery
|
||||||
@ -86,24 +101,53 @@ public class DeviantartRipper extends AbstractHTMLRipper {
|
|||||||
return m.group(1);
|
return m.group(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p = Pattern.compile("^https?://([a-zA-Z0-9\\-]+)\\.deviantart\\.com/gallery/([0-9]+).*$");
|
p = Pattern.compile("^https?://www\\.deviantart\\.com/([a-zA-Z0-9\\-]+)/gallery/([0-9]+).*$");
|
||||||
m = p.matcher(url.toExternalForm());
|
m = p.matcher(url.toExternalForm());
|
||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
// Subgallery
|
// Subgallery
|
||||||
return m.group(1) + "_" + m.group(2);
|
return m.group(1) + "_" + m.group(2);
|
||||||
}
|
}
|
||||||
p = Pattern.compile("^https?://([a-zA-Z0-9\\-]+)\\.deviantart\\.com/favou?rites/([0-9]+)/.*?$");
|
p = Pattern.compile("^https?://www\\.deviantart\\.com/([a-zA-Z0-9\\-]+)/favou?rites/([0-9]+)/.*?$");
|
||||||
m = p.matcher(url.toExternalForm());
|
m = p.matcher(url.toExternalForm());
|
||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
return m.group(1) + "_faves_" + m.group(2);
|
return m.group(1) + "_faves_" + m.group(2);
|
||||||
}
|
}
|
||||||
p = Pattern.compile("^https?://([a-zA-Z0-9\\-]+)\\.deviantart\\.com/favou?rites/?$");
|
p = Pattern.compile("^https?://www\\.deviantart\\.com/([a-zA-Z0-9\\-]+)/favou?rites/?$");
|
||||||
m = p.matcher(url.toExternalForm());
|
m = p.matcher(url.toExternalForm());
|
||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
// Subgallery
|
// Subgallery
|
||||||
return m.group(1) + "_faves";
|
return m.group(1) + "_faves";
|
||||||
}
|
}
|
||||||
throw new MalformedURLException("Expected URL format: http://username.deviantart.com/[/gallery/#####], got: " + url);
|
throw new MalformedURLException("Expected URL format: http://www.deviantart.com/username[/gallery/#####], got: " + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUsernameFromURL(String u) {
|
||||||
|
Pattern p = Pattern.compile("^https?://www\\.deviantart\\.com/([a-zA-Z0-9\\-]+)/gallery/?(\\S+)?");
|
||||||
|
Matcher m = p.matcher(url.toExternalForm());
|
||||||
|
if (m.matches()) {
|
||||||
|
return m.group(1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFullsizedNSFWImage(String pageURL) {
|
||||||
|
try {
|
||||||
|
Document doc = Http.url(pageURL).cookies(cookies).get();
|
||||||
|
String imageToReturn = "";
|
||||||
|
String[] d = doc.select("img").attr("srcset").split(",");
|
||||||
|
|
||||||
|
String s = d[d.length -1].split(" ")[0];
|
||||||
|
LOGGER.info("2:" + s);
|
||||||
|
|
||||||
|
if (s == null || s.equals("")) {
|
||||||
|
LOGGER.error("Could not find full sized image at " + pageURL);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Could not find full sized image at " + pageURL);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,151 +159,133 @@ public class DeviantartRipper extends AbstractHTMLRipper {
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Document getFirstPage() throws IOException {
|
public JSONObject getFirstPage() throws IOException {
|
||||||
|
|
||||||
//Test to see if there is a login:
|
// Base64 da login
|
||||||
String username = Utils.getConfigString("deviantart.username", new String(Base64.decode("Z3JhYnB5")));
|
// username: Z3JhYnB5
|
||||||
String password = Utils.getConfigString("deviantart.password", new String(Base64.decode("ZmFrZXJz")));
|
// password: ZmFrZXJz
|
||||||
|
|
||||||
if (username == null || password == null) {
|
|
||||||
LOGGER.debug("No DeviantArt login provided.");
|
cookies = getDACookies();
|
||||||
cookies.put("agegate_state","1"); // Bypasses the age gate
|
if (cookies.isEmpty()) {
|
||||||
} else {
|
LOGGER.warn("Failed to get login cookies");
|
||||||
// Attempt Login
|
|
||||||
try {
|
|
||||||
cookies = loginToDeviantart();
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOGGER.warn("Failed to login: ", e);
|
|
||||||
cookies.put("agegate_state","1"); // Bypasses the age gate
|
cookies.put("agegate_state","1"); // Bypasses the age gate
|
||||||
}
|
}
|
||||||
|
cookies.put("agegate_state", "1");
|
||||||
|
|
||||||
|
Response res = Http.url(this.url)
|
||||||
|
.cookies(cookies)
|
||||||
|
.response();
|
||||||
|
Document page = res.parse();
|
||||||
|
|
||||||
|
JSONObject firstPageJSON = getFirstPageJSON(page);
|
||||||
|
requestID = firstPageJSON.getJSONObject("dapx").getString("requestid");
|
||||||
|
galleryID = getGalleryID(page);
|
||||||
|
username = getUsernameFromURL(url.toExternalForm());
|
||||||
|
csrf = firstPageJSON.getString("csrf");
|
||||||
|
pageCookies = res.cookies();
|
||||||
|
|
||||||
|
return requestPage(0, galleryID, username, requestID, csrf, pageCookies);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject requestPage(int offset, String galleryID, String username, String requestID, String csfr, Map<String, String> c) {
|
||||||
|
LOGGER.debug("offset: " + Integer.toString(offset));
|
||||||
|
LOGGER.debug("galleryID: " + galleryID);
|
||||||
|
LOGGER.debug("username: " + username);
|
||||||
|
LOGGER.debug("requestID: " + requestID);
|
||||||
|
String url = baseApiUrl + galleryID + "?iid=" + requestID;
|
||||||
|
try {
|
||||||
|
Document doc = Http.url(url).cookies(c).data("username", username).data("offset", Integer.toString(offset))
|
||||||
|
.data("limit", "24").data("_csrf", csfr).data("id", requestID)
|
||||||
|
.ignoreContentType().post();
|
||||||
|
return new JSONObject(doc.body().text());
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Got error trying to get page: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return Http.url(this.url)
|
|
||||||
.cookies(cookies)
|
|
||||||
.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private JSONObject getFirstPageJSON(Document doc) {
|
||||||
*
|
for (Element js : doc.select("script")) {
|
||||||
* @param page
|
if (js.html().contains("requestid")) {
|
||||||
* @param id
|
String json = js.html().replaceAll("window.__initial_body_data=", "").replaceAll("\\);", "")
|
||||||
* @return
|
.replaceAll(";__wake\\(.+", "");
|
||||||
*/
|
LOGGER.info("json: " + json);
|
||||||
private String jsonToImage(Document page, String id) {
|
JSONObject j = new JSONObject(json);
|
||||||
Elements js = page.select("script[type=\"text/javascript\"]");
|
return j;
|
||||||
for (Element tag : js) {
|
|
||||||
if (tag.html().contains("window.__pageload")) {
|
|
||||||
try {
|
|
||||||
String script = tag.html();
|
|
||||||
script = script.substring(script.indexOf("window.__pageload"));
|
|
||||||
if (!script.contains(id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
script = script.substring(script.indexOf(id));
|
|
||||||
// first },"src":"url" after id
|
|
||||||
script = script.substring(script.indexOf("},\"src\":\"") + 9, script.indexOf("\",\"type\""));
|
|
||||||
return script.replace("\\/", "/");
|
|
||||||
} catch (StringIndexOutOfBoundsException e) {
|
|
||||||
LOGGER.debug("Unable to get json link from " + page.location());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@Override
|
|
||||||
public List<String> getURLsFromPage(Document page) {
|
|
||||||
List<String> imageURLs = new ArrayList<>();
|
|
||||||
|
|
||||||
// Iterate over all thumbnails
|
public String getGalleryID(Document doc) {
|
||||||
for (Element thumb : page.select("div.zones-container span.thumb")) {
|
// If the url contains catpath we return 0 as the DA api will provide all galery images if you sent the
|
||||||
if (isStopped()) {
|
// gallery id to 0
|
||||||
break;
|
if (url.toExternalForm().contains("catpath=")) {
|
||||||
}
|
return "0";
|
||||||
Element img = thumb.select("img").get(0);
|
}
|
||||||
if (img.attr("transparent").equals("false")) {
|
Pattern p = Pattern.compile("^https?://www\\.deviantart\\.com/[a-zA-Z0-9\\-]+/gallery/([0-9]+)/?\\S+");
|
||||||
continue; // a.thumbs to other albums are invisible
|
Matcher m = p.matcher(url.toExternalForm());
|
||||||
}
|
if (m.matches()) {
|
||||||
// Get full-sized image via helper methods
|
return m.group(1);
|
||||||
String fullSize = null;
|
}
|
||||||
if (thumb.attr("data-super-full-img").contains("//orig")) {
|
for (Element el : doc.select("input[name=set]")) {
|
||||||
fullSize = thumb.attr("data-super-full-img");
|
try {
|
||||||
} else {
|
String galleryID = el.attr("value");
|
||||||
String spanUrl = thumb.attr("href");
|
return galleryID;
|
||||||
String fullSize1 = jsonToImage(page,spanUrl.substring(spanUrl.lastIndexOf('-') + 1));
|
} catch (NullPointerException e) {
|
||||||
if (fullSize1 == null || !fullSize1.contains("//orig")) {
|
|
||||||
fullSize = smallToFull(img.attr("src"), spanUrl);
|
|
||||||
}
|
|
||||||
if (fullSize == null && fullSize1 != null) {
|
|
||||||
fullSize = fullSize1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fullSize == null) {
|
|
||||||
if (thumb.attr("data-super-full-img") != null) {
|
|
||||||
fullSize = thumb.attr("data-super-full-img");
|
|
||||||
} else if (thumb.attr("data-super-img") != null) {
|
|
||||||
fullSize = thumb.attr("data-super-img");
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (triedURLs.contains(fullSize)) {
|
|
||||||
LOGGER.warn("Already tried to download " + fullSize);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
triedURLs.add(fullSize);
|
}
|
||||||
imageURLs.add(fullSize);
|
LOGGER.error("Could not find gallery ID");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (isThisATest()) {
|
public String getUsername(Document doc) {
|
||||||
// Only need one image for a test
|
return doc.select("meta[property=og:title]").attr("content")
|
||||||
break;
|
.replaceAll("'s DeviantArt gallery", "").replaceAll("'s DeviantArt Gallery", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getURLsFromJSON(JSONObject json) {
|
||||||
|
List<String> imageURLs = new ArrayList<>();
|
||||||
|
LOGGER.info(json);
|
||||||
|
JSONArray results = json.getJSONObject("content").getJSONArray("results");
|
||||||
|
for (int i = 0; i < results.length(); i++) {
|
||||||
|
Document doc = Jsoup.parseBodyFragment(results.getJSONObject(i).getString("html"));
|
||||||
|
if (doc.html().contains("ismature")) {
|
||||||
|
LOGGER.info("Downloading nsfw image");
|
||||||
|
String nsfwImage = getFullsizedNSFWImage(doc.select("span").attr("href"));
|
||||||
|
if (nsfwImage != null && nsfwImage.startsWith("http")) {
|
||||||
|
imageURLs.add(nsfwImage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
String imageURL = doc.select("span").first().attr("data-super-full-img");
|
||||||
|
if (!imageURL.isEmpty() && imageURL.startsWith("http")) {
|
||||||
|
imageURLs.add(imageURL);
|
||||||
|
}
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
LOGGER.info(i + " does not contain any images");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return imageURLs;
|
return imageURLs;
|
||||||
}
|
}
|
||||||
@Override
|
|
||||||
public List<String> getDescriptionsFromPage(Document page) {
|
|
||||||
List<String> textURLs = new ArrayList<>();
|
|
||||||
// Iterate over all thumbnails
|
|
||||||
for (Element thumb : page.select("div.zones-container span.thumb")) {
|
|
||||||
LOGGER.info(thumb.attr("href"));
|
|
||||||
if (isStopped()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Element img = thumb.select("img").get(0);
|
|
||||||
if (img.attr("transparent").equals("false")) {
|
|
||||||
continue; // a.thumbs to other albums are invisible
|
|
||||||
}
|
|
||||||
textURLs.add(thumb.attr("href"));
|
|
||||||
|
|
||||||
}
|
|
||||||
return textURLs;
|
|
||||||
}
|
|
||||||
@Override
|
@Override
|
||||||
public Document getNextPage(Document page) throws IOException {
|
public JSONObject getNextPage(JSONObject page) throws IOException {
|
||||||
if (isThisATest()) {
|
boolean hasMore = page.getJSONObject("content").getBoolean("has_more");
|
||||||
return null;
|
if (hasMore) {
|
||||||
|
return requestPage(page.getJSONObject("content").getInt("next_offset"), galleryID, username, requestID, csrf, pageCookies);
|
||||||
}
|
}
|
||||||
Elements nextButtons = page.select("link[rel=\"next\"]");
|
|
||||||
if (nextButtons.isEmpty()) {
|
throw new IOException("No more pages");
|
||||||
if (page.select("link[rel=\"prev\"]").isEmpty()) {
|
|
||||||
throw new IOException("No next page found");
|
|
||||||
} else {
|
|
||||||
throw new IOException("Hit end of pages");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Element a = nextButtons.first();
|
|
||||||
String nextPage = a.attr("href");
|
|
||||||
if (nextPage.startsWith("/")) {
|
|
||||||
nextPage = "http://" + this.url.getHost() + nextPage;
|
|
||||||
}
|
|
||||||
if (!sleep(PAGE_SLEEP_TIME)) {
|
|
||||||
throw new IOException("Interrupted while waiting to load next page: " + nextPage);
|
|
||||||
}
|
|
||||||
LOGGER.info("Found next page: " + nextPage);
|
|
||||||
return Http.url(nextPage)
|
|
||||||
.cookies(cookies)
|
|
||||||
.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -300,61 +326,7 @@ public class DeviantartRipper extends AbstractHTMLRipper {
|
|||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to download description for image.
|
|
||||||
* Comes in handy when people put entire stories in their description.
|
|
||||||
* If no description was found, returns null.
|
|
||||||
* @param url The URL the description will be retrieved from
|
|
||||||
* @param page The gallery page the URL was found on
|
|
||||||
* @return A String[] with first object being the description, and the second object being image file name if found.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String[] getDescription(String url,Document page) {
|
|
||||||
if (isThisATest()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// Fetch the image page
|
|
||||||
Response resp = Http.url(url)
|
|
||||||
.referrer(this.url)
|
|
||||||
.cookies(cookies)
|
|
||||||
.response();
|
|
||||||
cookies.putAll(resp.cookies());
|
|
||||||
|
|
||||||
// Try to find the description
|
|
||||||
Document documentz = resp.parse();
|
|
||||||
Element ele = documentz.select("div.dev-description").first();
|
|
||||||
if (ele == null) {
|
|
||||||
throw new IOException("No description found");
|
|
||||||
}
|
|
||||||
documentz.outputSettings(new Document.OutputSettings().prettyPrint(false));
|
|
||||||
ele.select("br").append("\\n");
|
|
||||||
ele.select("p").prepend("\\n\\n");
|
|
||||||
String fullSize = null;
|
|
||||||
Element thumb = page.select("div.zones-container span.thumb[href=\"" + url + "\"]").get(0);
|
|
||||||
if (!thumb.attr("data-super-full-img").isEmpty()) {
|
|
||||||
fullSize = thumb.attr("data-super-full-img");
|
|
||||||
String[] split = fullSize.split("/");
|
|
||||||
fullSize = split[split.length - 1];
|
|
||||||
} else {
|
|
||||||
String spanUrl = thumb.attr("href");
|
|
||||||
fullSize = jsonToImage(page,spanUrl.substring(spanUrl.lastIndexOf('-') + 1));
|
|
||||||
if (fullSize != null) {
|
|
||||||
String[] split = fullSize.split("/");
|
|
||||||
fullSize = split[split.length - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fullSize == null) {
|
|
||||||
return new String[] {Jsoup.clean(ele.html().replaceAll("\\\\n", System.getProperty("line.separator")), "", Whitelist.none(), new Document.OutputSettings().prettyPrint(false))};
|
|
||||||
}
|
|
||||||
fullSize = fullSize.substring(0, fullSize.lastIndexOf("."));
|
|
||||||
return new String[] {Jsoup.clean(ele.html().replaceAll("\\\\n", System.getProperty("line.separator")), "", Whitelist.none(), new Document.OutputSettings().prettyPrint(false)),fullSize};
|
|
||||||
// TODO Make this not make a newline if someone just types \n into the description.
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
LOGGER.info("Failed to get description at " + url + ": '" + ioe.getMessage() + "'");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If largest resolution for image at 'thumb' is found, starts downloading
|
* If largest resolution for image at 'thumb' is found, starts downloading
|
||||||
@ -426,47 +398,10 @@ public class DeviantartRipper extends AbstractHTMLRipper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs into deviant art. Required to rip full-size NSFW content.
|
* Returns DA cookies.
|
||||||
* @return Map of cookies containing session data.
|
* @return Map of cookies containing session data.
|
||||||
*/
|
*/
|
||||||
private Map<String, String> loginToDeviantart() throws IOException {
|
private Map<String, String> getDACookies() {
|
||||||
// Populate postData fields
|
return RipUtils.getCookiesFromString(Utils.getConfigString("deviantart.cookies", loginCookies));
|
||||||
Map<String,String> postData = new HashMap<>();
|
|
||||||
String username = Utils.getConfigString("deviantart.username", new String(Base64.decode("Z3JhYnB5")));
|
|
||||||
String password = Utils.getConfigString("deviantart.password", new String(Base64.decode("ZmFrZXJz")));
|
|
||||||
if (username == null || password == null) {
|
|
||||||
throw new IOException("could not find username or password in config");
|
|
||||||
}
|
|
||||||
Response resp = Http.url("http://www.deviantart.com/")
|
|
||||||
.response();
|
|
||||||
for (Element input : resp.parse().select("form#form-login input[type=hidden]")) {
|
|
||||||
postData.put(input.attr("name"), input.attr("value"));
|
|
||||||
}
|
|
||||||
postData.put("username", username);
|
|
||||||
postData.put("password", password);
|
|
||||||
postData.put("remember_me", "1");
|
|
||||||
|
|
||||||
// Send login request
|
|
||||||
resp = Http.url("https://www.deviantart.com/users/login")
|
|
||||||
.userAgent(USER_AGENT)
|
|
||||||
.data(postData)
|
|
||||||
.cookies(resp.cookies())
|
|
||||||
.method(Method.POST)
|
|
||||||
.response();
|
|
||||||
|
|
||||||
// Assert we are logged in
|
|
||||||
if (resp.hasHeader("Location") && resp.header("Location").contains("password")) {
|
|
||||||
// Wrong password
|
|
||||||
throw new IOException("Wrong password");
|
|
||||||
}
|
|
||||||
if (resp.url().toExternalForm().contains("bad_form")) {
|
|
||||||
throw new IOException("Login form was incorrectly submitted");
|
|
||||||
}
|
|
||||||
if (resp.cookie("auth_secure") == null ||
|
|
||||||
resp.cookie("auth") == null) {
|
|
||||||
throw new IOException("No auth_secure or auth cookies received");
|
|
||||||
}
|
|
||||||
// We are logged in, save the cookies
|
|
||||||
return resp.cookies();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -96,40 +96,40 @@ public class E621Ripper extends AbstractHTMLRipper{
|
|||||||
|
|
||||||
private String getTerm(URL url) throws MalformedURLException{
|
private String getTerm(URL url) throws MalformedURLException{
|
||||||
if(gidPattern==null)
|
if(gidPattern==null)
|
||||||
gidPattern=Pattern.compile("^https?://(www\\.)?e621\\.net/post/index/[^/]+/([a-zA-Z0-9$_.+!*'(),%-]+)(/.*)?(#.*)?$");
|
gidPattern=Pattern.compile("^https?://(www\\.)?e621\\.net/post/index/[^/]+/([a-zA-Z0-9$_.+!*'():,%\\-]+)(/.*)?(#.*)?$");
|
||||||
if(gidPatternPool==null)
|
if(gidPatternPool==null)
|
||||||
gidPatternPool=Pattern.compile("^https?://(www\\.)?e621\\.net/pool/show/([a-zA-Z0-9$_.+!*'(),%-]+)(\\?.*)?(/.*)?(#.*)?$");
|
gidPatternPool=Pattern.compile("^https?://(www\\.)?e621\\.net/pool/show/([a-zA-Z0-9$_.+!*'(),%:\\-]+)(\\?.*)?(/.*)?(#.*)?$");
|
||||||
|
|
||||||
Matcher m = gidPattern.matcher(url.toExternalForm());
|
Matcher m = gidPattern.matcher(url.toExternalForm());
|
||||||
if(m.matches())
|
if(m.matches()) {
|
||||||
return m.group(2);
|
LOGGER.info(m.group(2));
|
||||||
|
return m.group(2);
|
||||||
|
}
|
||||||
|
|
||||||
m = gidPatternPool.matcher(url.toExternalForm());
|
m = gidPatternPool.matcher(url.toExternalForm());
|
||||||
if(m.matches())
|
if(m.matches()) {
|
||||||
return m.group(2);
|
return m.group(2);
|
||||||
|
}
|
||||||
|
|
||||||
throw new MalformedURLException("Expected e621.net URL format: e621.net/post/index/1/searchterm - got "+url+" instead");
|
throw new MalformedURLException("Expected e621.net URL format: e621.net/post/index/1/searchterm - got "+url+" instead");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getGID(URL url) throws MalformedURLException {
|
public String getGID(URL url) throws MalformedURLException {
|
||||||
try {
|
|
||||||
String prefix="";
|
String prefix="";
|
||||||
if(url.getPath().startsWith("/pool/show/"))
|
if (url.getPath().startsWith("/pool/show/")) {
|
||||||
prefix="pool_";
|
prefix = "pool_";
|
||||||
|
}
|
||||||
|
|
||||||
return Utils.filesystemSafe(prefix+new URI(getTerm(url)).getPath());
|
return Utils.filesystemSafe(prefix+getTerm(url));
|
||||||
} catch (URISyntaxException ex) {
|
|
||||||
logger.error(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new MalformedURLException("Expected e621.net URL format: e621.net/post/index/1/searchterm - got "+url+" instead");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URL sanitizeURL(URL url) throws MalformedURLException {
|
public URL sanitizeURL(URL url) throws MalformedURLException {
|
||||||
if(gidPattern2==null)
|
if(gidPattern2==null)
|
||||||
gidPattern2=Pattern.compile("^https?://(www\\.)?e621\\.net/post/search\\?tags=([a-zA-Z0-9$_.+!*'(),%-]+)(/.*)?(#.*)?$");
|
gidPattern2=Pattern.compile("^https?://(www\\.)?e621\\.net/post/search\\?tags=([a-zA-Z0-9$_.+!*'():,%-]+)(/.*)?(#.*)?$");
|
||||||
|
|
||||||
Matcher m = gidPattern2.matcher(url.toExternalForm());
|
Matcher m = gidPattern2.matcher(url.toExternalForm());
|
||||||
if(m.matches())
|
if(m.matches())
|
||||||
|
@ -11,6 +11,7 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import com.rarchives.ripme.utils.Utils;
|
import com.rarchives.ripme.utils.Utils;
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.jsoup.Connection.Response;
|
import org.jsoup.Connection.Response;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
@ -116,25 +117,24 @@ public class EightmusesRipper extends AbstractHTMLRipper {
|
|||||||
image = thumb.attr("data-cfsrc");
|
image = thumb.attr("data-cfsrc");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
String imageHref = thumb.attr("href");
|
// Deobfustace the json data
|
||||||
if (imageHref.equals("")) continue;
|
String rawJson = deobfuscateJSON(page.select("script#ractive-public").html()
|
||||||
if (imageHref.startsWith("/")) {
|
.replaceAll(">", ">").replaceAll("<", "<").replace("&", "&"));
|
||||||
imageHref = "https://www.8muses.com" + imageHref;
|
JSONObject json = new JSONObject(rawJson);
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
LOGGER.info("Retrieving full-size image location from " + imageHref);
|
for (int i = 0; i != json.getJSONArray("pictures").length(); i++) {
|
||||||
image = getFullSizeImage(imageHref);
|
image = "https://www.8muses.com/image/fm/" + json.getJSONArray("pictures").getJSONObject(i).getString("publicUri");
|
||||||
URL imageUrl = new URL(image);
|
URL imageUrl = new URL(image);
|
||||||
if (Utils.getConfigBoolean("8muses.use_short_names", false)) {
|
if (Utils.getConfigBoolean("8muses.use_short_names", false)) {
|
||||||
addURLToDownload(imageUrl, getPrefixShort(x), getSubdir(page.select("title").text()), this.url.toExternalForm(), cookies, "", null, true);
|
addURLToDownload(imageUrl, getPrefixShort(x), getSubdir(page.select("title").text()), this.url.toExternalForm(), cookies, "", null, true);
|
||||||
} else {
|
} else {
|
||||||
addURLToDownload(imageUrl, getPrefixLong(x), getSubdir(page.select("title").text()), this.url.toExternalForm(), cookies, "", null, true);
|
addURLToDownload(imageUrl, getPrefixLong(x), getSubdir(page.select("title").text()), this.url.toExternalForm(), cookies, "", null, true);
|
||||||
|
}
|
||||||
|
// X is our page index
|
||||||
|
x++;
|
||||||
}
|
}
|
||||||
// X is our page index
|
|
||||||
x++;
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOGGER.error("Failed to get full-size image from " + imageHref);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,7 +154,7 @@ public class EightmusesRipper extends AbstractHTMLRipper {
|
|||||||
sendUpdate(STATUS.LOADING_RESOURCE, imageUrl);
|
sendUpdate(STATUS.LOADING_RESOURCE, imageUrl);
|
||||||
LOGGER.info("Getting full sized image from " + imageUrl);
|
LOGGER.info("Getting full sized image from " + imageUrl);
|
||||||
Document doc = new Http(imageUrl).get(); // Retrieve the webpage of the image URL
|
Document doc = new Http(imageUrl).get(); // Retrieve the webpage of the image URL
|
||||||
String imageName = doc.select("input[id=imageName]").attr("value"); // Select the "input" element from the page
|
String imageName = doc.select("div.photo > a > img").attr("src");
|
||||||
return "https://www.8muses.com/image/fm/" + imageName;
|
return "https://www.8muses.com/image/fm/" + imageName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,4 +189,25 @@ public class EightmusesRipper extends AbstractHTMLRipper {
|
|||||||
public String getPrefixShort(int index) {
|
public String getPrefixShort(int index) {
|
||||||
return String.format("%03d", index);
|
return String.format("%03d", index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String deobfuscateJSON(String obfuscatedString) {
|
||||||
|
StringBuilder deobfuscatedString = new StringBuilder();
|
||||||
|
// The first char in one of 8muses obfuscated strings is always ! so we replace it
|
||||||
|
for (char ch : obfuscatedString.replaceFirst("!", "").toCharArray()){
|
||||||
|
deobfuscatedString.append(deobfuscateChar(ch));
|
||||||
|
}
|
||||||
|
return deobfuscatedString.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String deobfuscateChar(char c) {
|
||||||
|
if ((int) c == 32) {
|
||||||
|
return fromCharCode(32);
|
||||||
|
}
|
||||||
|
return fromCharCode(33 + (c + 14) % 94);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fromCharCode(int... codePoints) {
|
||||||
|
return new String(codePoints, 0, codePoints.length);
|
||||||
|
}
|
||||||
}
|
}
|
@ -22,6 +22,8 @@ import com.rarchives.ripme.utils.Http;
|
|||||||
*/
|
*/
|
||||||
public class EromeRipper extends AbstractHTMLRipper {
|
public class EromeRipper extends AbstractHTMLRipper {
|
||||||
|
|
||||||
|
boolean rippingProfile;
|
||||||
|
|
||||||
|
|
||||||
public EromeRipper (URL url) throws IOException {
|
public EromeRipper (URL url) throws IOException {
|
||||||
super(url);
|
super(url);
|
||||||
@ -42,6 +44,27 @@ public class EromeRipper extends AbstractHTMLRipper {
|
|||||||
addURLToDownload(url, getPrefix(index));
|
addURLToDownload(url, getPrefix(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasQueueSupport() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean pageContainsAlbums(URL url) {
|
||||||
|
Pattern pa = Pattern.compile("https?://www.erome.com/([a-zA-Z0-9_-]*)/?");
|
||||||
|
Matcher ma = pa.matcher(url.toExternalForm());
|
||||||
|
return ma.matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getAlbumsToQueue(Document doc) {
|
||||||
|
List<String> urlsToAddToQueue = new ArrayList<>();
|
||||||
|
for (Element elem : doc.select("div#albums > div.album > a")) {
|
||||||
|
urlsToAddToQueue.add(elem.attr("href"));
|
||||||
|
}
|
||||||
|
return urlsToAddToQueue;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAlbumTitle(URL url) throws MalformedURLException {
|
public String getAlbumTitle(URL url) throws MalformedURLException {
|
||||||
try {
|
try {
|
||||||
@ -53,6 +76,8 @@ public class EromeRipper extends AbstractHTMLRipper {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Fall back to default album naming convention
|
// Fall back to default album naming convention
|
||||||
LOGGER.info("Unable to find title at " + url);
|
LOGGER.info("Unable to find title at " + url);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
return getHost() + "_" + getGID(url);
|
||||||
}
|
}
|
||||||
return super.getAlbumTitle(url);
|
return super.getAlbumTitle(url);
|
||||||
}
|
}
|
||||||
@ -66,21 +91,7 @@ public class EromeRipper extends AbstractHTMLRipper {
|
|||||||
@Override
|
@Override
|
||||||
public List<String> getURLsFromPage(Document doc) {
|
public List<String> getURLsFromPage(Document doc) {
|
||||||
List<String> URLs = new ArrayList<>();
|
List<String> URLs = new ArrayList<>();
|
||||||
//Pictures
|
return getMediaFromPage(doc);
|
||||||
Elements imgs = doc.select("div.img > img.img-front");
|
|
||||||
for (Element img : imgs) {
|
|
||||||
String imageURL = img.attr("src");
|
|
||||||
imageURL = "https:" + imageURL;
|
|
||||||
URLs.add(imageURL);
|
|
||||||
}
|
|
||||||
//Videos
|
|
||||||
Elements vids = doc.select("div.video > video > source");
|
|
||||||
for (Element vid : vids) {
|
|
||||||
String videoURL = vid.attr("src");
|
|
||||||
URLs.add("https:" + videoURL);
|
|
||||||
}
|
|
||||||
|
|
||||||
return URLs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -94,13 +105,13 @@ public class EromeRipper extends AbstractHTMLRipper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getGID(URL url) throws MalformedURLException {
|
public String getGID(URL url) throws MalformedURLException {
|
||||||
Pattern p = Pattern.compile("^https?://www.erome.com/a/([a-zA-Z0-9]*)/?$");
|
Pattern p = Pattern.compile("^https?://www.erome.com/[ai]/([a-zA-Z0-9]*)/?$");
|
||||||
Matcher m = p.matcher(url.toExternalForm());
|
Matcher m = p.matcher(url.toExternalForm());
|
||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
return m.group(1);
|
return m.group(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
p = Pattern.compile("^https?://erome.com/a/([a-zA-Z0-9]*)/?$");
|
p = Pattern.compile("^https?://www.erome.com/([a-zA-Z0-9_-]+)/?$");
|
||||||
m = p.matcher(url.toExternalForm());
|
m = p.matcher(url.toExternalForm());
|
||||||
|
|
||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
@ -110,34 +121,15 @@ public class EromeRipper extends AbstractHTMLRipper {
|
|||||||
throw new MalformedURLException("erome album not found in " + url + ", expected https://www.erome.com/album");
|
throw new MalformedURLException("erome album not found in " + url + ", expected https://www.erome.com/album");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<URL> getURLs(URL url) throws IOException{
|
private List<String> getMediaFromPage(Document doc) {
|
||||||
|
List<String> results = new ArrayList<>();
|
||||||
Response resp = Http.url(url)
|
for (Element el : doc.select("img.img-front")) {
|
||||||
.ignoreContentType()
|
results.add("https:" + el.attr("src"));
|
||||||
.response();
|
|
||||||
|
|
||||||
Document doc = resp.parse();
|
|
||||||
|
|
||||||
List<URL> URLs = new ArrayList<>();
|
|
||||||
//Pictures
|
|
||||||
Elements imgs = doc.getElementsByTag("img");
|
|
||||||
for (Element img : imgs) {
|
|
||||||
if (img.hasClass("album-image")) {
|
|
||||||
String imageURL = img.attr("src");
|
|
||||||
imageURL = "https:" + imageURL;
|
|
||||||
URLs.add(new URL(imageURL));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//Videos
|
for (Element el : doc.select("source[label=HD]")) {
|
||||||
Elements vids = doc.getElementsByTag("video");
|
results.add("https:" + el.attr("src"));
|
||||||
for (Element vid : vids) {
|
|
||||||
if (vid.hasClass("album-video")) {
|
|
||||||
Elements source = vid.getElementsByTag("source");
|
|
||||||
String videoURL = source.first().attr("src");
|
|
||||||
URLs.add(new URL(videoURL));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return results;
|
||||||
return URLs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,10 @@ import java.util.Map;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ui.RipStatusMessage;
|
||||||
import com.rarchives.ripme.utils.Utils;
|
import com.rarchives.ripme.utils.Utils;
|
||||||
import org.jsoup.Connection.Response;
|
import org.jsoup.Connection.Response;
|
||||||
|
import org.jsoup.HttpStatusException;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
@ -24,13 +26,27 @@ import com.rarchives.ripme.ripper.AbstractHTMLRipper;
|
|||||||
import com.rarchives.ripme.ripper.DownloadThreadPool;
|
import com.rarchives.ripme.ripper.DownloadThreadPool;
|
||||||
import com.rarchives.ripme.utils.Http;
|
import com.rarchives.ripme.utils.Http;
|
||||||
|
|
||||||
|
import static com.rarchives.ripme.utils.RipUtils.getCookiesFromString;
|
||||||
|
|
||||||
public class FuraffinityRipper extends AbstractHTMLRipper {
|
public class FuraffinityRipper extends AbstractHTMLRipper {
|
||||||
|
|
||||||
private static final String urlBase = "https://www.furaffinity.net";
|
private static final String urlBase = "https://www.furaffinity.net";
|
||||||
private static Map<String,String> cookies = new HashMap<>();
|
private Map<String,String> cookies = new HashMap<>();
|
||||||
static {
|
|
||||||
cookies.put("b", "bd5ccac8-51dc-4265-8ae1-7eac685ad667");
|
private void setCookies() {
|
||||||
cookies.put("a", "7c41b782-d01d-4b0e-b45b-62a4f0b2a369");
|
if (Utils.getConfigBoolean("furaffinity.login", true)) {
|
||||||
|
LOGGER.info("Logging in using cookies");
|
||||||
|
String faCookies = Utils.getConfigString("furaffinity.cookies", "a=897bc45b-1f87-49f1-8a85-9412bc103e7a;b=c8807f36-7a85-4caf-80ca-01c2a2368267");
|
||||||
|
warnAboutSharedAccount(faCookies);
|
||||||
|
cookies = getCookiesFromString(faCookies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void warnAboutSharedAccount(String loginCookies) {
|
||||||
|
if (loginCookies.equals("a=897bc45b-1f87-49f1-8a85-9412bc103e7a;b=c8807f36-7a85-4caf-80ca-01c2a2368267")) {
|
||||||
|
sendUpdate(RipStatusMessage.STATUS.DOWNLOAD_ERRORED,
|
||||||
|
"WARNING: Using the shared furaffinity account exposes both your IP and how many items you downloaded to the other users of the share account");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread pool for finding direct image links from "image" pages (html)
|
// Thread pool for finding direct image links from "image" pages (html)
|
||||||
@ -61,6 +77,8 @@ public class FuraffinityRipper extends AbstractHTMLRipper {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public Document getFirstPage() throws IOException {
|
public Document getFirstPage() throws IOException {
|
||||||
|
setCookies();
|
||||||
|
LOGGER.info(Http.url(url).cookies(cookies).get().html());
|
||||||
return Http.url(url).cookies(cookies).get();
|
return Http.url(url).cookies(cookies).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,12 +98,21 @@ public class FuraffinityRipper extends AbstractHTMLRipper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getImageFromPost(String url) {
|
private String getImageFromPost(String url) {
|
||||||
|
sleep(1000);
|
||||||
|
Document d = null;
|
||||||
try {
|
try {
|
||||||
LOGGER.info("found url " + Http.url(url).cookies(cookies).get().select("meta[property=og:image]").attr("content"));
|
d = Http.url(url).cookies(cookies).get();
|
||||||
return Http.url(url).cookies(cookies).get().select("meta[property=og:image]").attr("content");
|
Elements links = d.getElementsByTag("a");
|
||||||
|
for (Element link : links) {
|
||||||
|
if (link.text().equals("Download")) {
|
||||||
|
LOGGER.info("Found image " + link.attr("href"));
|
||||||
|
return "https:" + link.attr("href");
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return "";
|
return null;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -93,7 +120,12 @@ public class FuraffinityRipper extends AbstractHTMLRipper {
|
|||||||
List<String> urls = new ArrayList<>();
|
List<String> urls = new ArrayList<>();
|
||||||
Elements urlElements = page.select("figure.t-image > b > u > a");
|
Elements urlElements = page.select("figure.t-image > b > u > a");
|
||||||
for (Element e : urlElements) {
|
for (Element e : urlElements) {
|
||||||
urls.add(getImageFromPost(urlBase + e.select("a").first().attr("href")));
|
String urlToAdd = getImageFromPost(urlBase + e.select("a").first().attr("href"));
|
||||||
|
if (url != null) {
|
||||||
|
if (urlToAdd.startsWith("http")) {
|
||||||
|
urls.add(urlToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return urls;
|
return urls;
|
||||||
}
|
}
|
||||||
@ -200,16 +232,5 @@ public class FuraffinityRipper extends AbstractHTMLRipper {
|
|||||||
+ " instead");
|
+ " instead");
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FuraffinityDocumentThread extends Thread {
|
|
||||||
private URL url;
|
|
||||||
|
|
||||||
FuraffinityDocumentThread(URL url) {
|
|
||||||
super();
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,18 +1,22 @@
|
|||||||
package com.rarchives.ripme.ripper.rippers.video;
|
package com.rarchives.ripme.ripper.rippers;
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.AbstractSingleFileRipper;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.select.Elements;
|
import org.jsoup.select.Elements;
|
||||||
|
|
||||||
import com.rarchives.ripme.ripper.VideoRipper;
|
|
||||||
import com.rarchives.ripme.utils.Http;
|
import com.rarchives.ripme.utils.Http;
|
||||||
|
|
||||||
public class GfycatRipper extends VideoRipper {
|
|
||||||
|
public class GfycatRipper extends AbstractSingleFileRipper {
|
||||||
|
|
||||||
private static final String HOST = "gfycat.com";
|
private static final String HOST = "gfycat.com";
|
||||||
|
|
||||||
@ -20,9 +24,14 @@ public class GfycatRipper extends VideoRipper {
|
|||||||
super(url);
|
super(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDomain() {
|
||||||
|
return "gfycat.com";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHost() {
|
public String getHost() {
|
||||||
return HOST;
|
return "gfycat";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -37,6 +46,16 @@ public class GfycatRipper extends VideoRipper {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Document getFirstPage() throws IOException {
|
||||||
|
return Http.url(url).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadURL(URL url, int index) {
|
||||||
|
addURLToDownload(url, getPrefix(index));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getGID(URL url) throws MalformedURLException {
|
public String getGID(URL url) throws MalformedURLException {
|
||||||
Pattern p = Pattern.compile("^https?://[wm.]*gfycat\\.com/([a-zA-Z0-9]+).*$");
|
Pattern p = Pattern.compile("^https?://[wm.]*gfycat\\.com/([a-zA-Z0-9]+).*$");
|
||||||
@ -52,10 +71,15 @@ public class GfycatRipper extends VideoRipper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void rip() throws IOException {
|
public List<String> getURLsFromPage(Document doc) {
|
||||||
String vidUrl = getVideoURL(this.url);
|
List<String> result = new ArrayList<>();
|
||||||
addURLToDownload(new URL(vidUrl), "gfycat_" + getGID(this.url));
|
Elements videos = doc.select("source#mp4Source");
|
||||||
waitForThreads();
|
String vidUrl = videos.first().attr("src");
|
||||||
|
if (vidUrl.startsWith("//")) {
|
||||||
|
vidUrl = "http:" + vidUrl;
|
||||||
|
}
|
||||||
|
result.add(vidUrl);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
@ -8,13 +8,12 @@ import java.util.List;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.AbstractSingleFileRipper;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
|
||||||
|
|
||||||
import com.rarchives.ripme.ripper.AbstractHTMLRipper;
|
|
||||||
import com.rarchives.ripme.utils.Http;
|
import com.rarchives.ripme.utils.Http;
|
||||||
|
|
||||||
public class GfycatporntubeRipper extends AbstractHTMLRipper {
|
public class GfycatporntubeRipper extends AbstractSingleFileRipper {
|
||||||
|
|
||||||
public GfycatporntubeRipper(URL url) throws IOException {
|
public GfycatporntubeRipper(URL url) throws IOException {
|
||||||
super(url);
|
super(url);
|
||||||
|
@ -13,10 +13,11 @@ import org.jsoup.nodes.Document;
|
|||||||
|
|
||||||
import com.rarchives.ripme.ripper.AbstractHTMLRipper;
|
import com.rarchives.ripme.ripper.AbstractHTMLRipper;
|
||||||
import com.rarchives.ripme.utils.Http;
|
import com.rarchives.ripme.utils.Http;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
|
||||||
public class HitomiRipper extends AbstractHTMLRipper {
|
public class HitomiRipper extends AbstractHTMLRipper {
|
||||||
|
|
||||||
String galleryId = "";
|
private String galleryId = "";
|
||||||
|
|
||||||
public HitomiRipper(URL url) throws IOException {
|
public HitomiRipper(URL url) throws IOException {
|
||||||
super(url);
|
super(url);
|
||||||
@ -47,7 +48,7 @@ public class HitomiRipper extends AbstractHTMLRipper {
|
|||||||
@Override
|
@Override
|
||||||
public Document getFirstPage() throws IOException {
|
public Document getFirstPage() throws IOException {
|
||||||
// if we go to /GALLERYID.js we get a nice json array of all images in the gallery
|
// if we go to /GALLERYID.js we get a nice json array of all images in the gallery
|
||||||
return Http.url(new URL(url.toExternalForm().replaceAll(".html", ".js"))).ignoreContentType().get();
|
return Http.url(new URL(url.toExternalForm().replaceAll("hitomi", "ltn.hitomi").replaceAll(".html", ".js"))).ignoreContentType().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -64,6 +65,19 @@ public class HitomiRipper extends AbstractHTMLRipper {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlbumTitle(URL url) throws MalformedURLException {
|
||||||
|
try {
|
||||||
|
// Attempt to use album title and username as GID
|
||||||
|
Document doc = Http.url(url).get();
|
||||||
|
return getHost() + "_" + getGID(url) + "_" +
|
||||||
|
doc.select("title").text().replaceAll(" - Read Online - hentai artistcg \\| Hitomi.la", "");
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.info("Falling back");
|
||||||
|
}
|
||||||
|
return super.getAlbumTitle(url);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void downloadURL(URL url, int index) {
|
public void downloadURL(URL url, int index) {
|
||||||
addURLToDownload(url, getPrefix(index));
|
addURLToDownload(url, getPrefix(index));
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
package com.rarchives.ripme.ripper.rippers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.AbstractHTMLRipper;
|
||||||
|
import com.rarchives.ripme.utils.Http;
|
||||||
|
|
||||||
|
public class ImagearnRipper extends AbstractHTMLRipper {
|
||||||
|
|
||||||
|
public ImagearnRipper(URL url) throws IOException {
|
||||||
|
super(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHost() {
|
||||||
|
return "imagearn";
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String getDomain() {
|
||||||
|
return "imagearn.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getGID(URL url) throws MalformedURLException {
|
||||||
|
Pattern p = Pattern.compile("^.*imagearn.com/+gallery.php\\?id=([0-9]+).*$");
|
||||||
|
Matcher m = p.matcher(url.toExternalForm());
|
||||||
|
if (m.matches()) {
|
||||||
|
return m.group(1);
|
||||||
|
}
|
||||||
|
throw new MalformedURLException(
|
||||||
|
"Expected imagearn.com gallery formats: "
|
||||||
|
+ "imagearn.com/gallery.php?id=####..."
|
||||||
|
+ " Got: " + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public URL sanitizeURL(URL url) throws MalformedURLException {
|
||||||
|
Pattern p = Pattern.compile("^.*imagearn.com/+image.php\\?id=[0-9]+.*$");
|
||||||
|
Matcher m = p.matcher(url.toExternalForm());
|
||||||
|
if (m.matches()) {
|
||||||
|
// URL points to imagearn *image*, not gallery
|
||||||
|
try {
|
||||||
|
url = getGalleryFromImage(url);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("[!] " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private URL getGalleryFromImage(URL url) throws IOException {
|
||||||
|
Document doc = Http.url(url).get();
|
||||||
|
for (Element link : doc.select("a[href~=^gallery\\.php.*$]")) {
|
||||||
|
LOGGER.info("LINK: " + link.toString());
|
||||||
|
if (link.hasAttr("href")
|
||||||
|
&& link.attr("href").contains("gallery.php")) {
|
||||||
|
url = new URL("http://imagearn.com/" + link.attr("href"));
|
||||||
|
LOGGER.info("[!] Found gallery from given link: " + url);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IOException("Failed to find gallery at URL " + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Document getFirstPage() throws IOException {
|
||||||
|
return Http.url(url).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlbumTitle(URL url) throws MalformedURLException {
|
||||||
|
try {
|
||||||
|
Document doc = getFirstPage();
|
||||||
|
String title = doc.select("h3 > strong").first().text(); // profile name
|
||||||
|
return getHost() + "_" + title + "_" + getGID(url);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Fall back to default album naming convention
|
||||||
|
LOGGER.warn("Failed to get album title from " + url, e);
|
||||||
|
}
|
||||||
|
return super.getAlbumTitle(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getURLsFromPage(Document doc) {
|
||||||
|
List<String> imageURLs = new ArrayList<>();
|
||||||
|
for (Element thumb : doc.select("div#gallery > div > a")) {
|
||||||
|
String imageURL = thumb.attr("href");
|
||||||
|
try {
|
||||||
|
Document imagedoc = new Http("http://imagearn.com/" + imageURL).get();
|
||||||
|
String image = imagedoc.select("a.thickbox").first().attr("href");
|
||||||
|
imageURLs.add(image);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.warn("Was unable to download page: " + imageURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return imageURLs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadURL(URL url, int index) {
|
||||||
|
addURLToDownload(url, getPrefix(index));
|
||||||
|
sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
@ -125,11 +125,7 @@ public class ImagefapRipper extends AbstractHTMLRipper {
|
|||||||
if (!thumb.hasAttr("src") || !thumb.hasAttr("width")) {
|
if (!thumb.hasAttr("src") || !thumb.hasAttr("width")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String image = thumb.attr("src");
|
String image = getFullSizedImage("https://www.imagefap.com" + thumb.parent().attr("href"));
|
||||||
image = image.replaceAll(
|
|
||||||
"http://x.*.fap.to/images/thumb/",
|
|
||||||
"http://fap.to/images/full/");
|
|
||||||
image = image.replaceAll("w[0-9]+-h[0-9]+/", "");
|
|
||||||
imageURLs.add(image);
|
imageURLs.add(image);
|
||||||
if (isThisATest()) {
|
if (isThisATest()) {
|
||||||
break;
|
break;
|
||||||
@ -160,4 +156,13 @@ public class ImagefapRipper extends AbstractHTMLRipper {
|
|||||||
return super.getAlbumTitle(url);
|
return super.getAlbumTitle(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getFullSizedImage(String pageURL) {
|
||||||
|
try {
|
||||||
|
Document doc = Http.url(pageURL).get();
|
||||||
|
return doc.select("img#mainPhoto").attr("src");
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,10 @@ public class InstagramRipper extends AbstractJSONRipper {
|
|||||||
return url.replaceAll("/[A-Z0-9]{8}/", "/");
|
return url.replaceAll("/[A-Z0-9]{8}/", "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public boolean hasASAPRipping() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private List<String> getPostsFromSinglePage(JSONObject json) {
|
private List<String> getPostsFromSinglePage(JSONObject json) {
|
||||||
List<String> imageURLs = new ArrayList<>();
|
List<String> imageURLs = new ArrayList<>();
|
||||||
JSONArray datas;
|
JSONArray datas;
|
||||||
@ -231,9 +235,23 @@ public class InstagramRipper extends AbstractJSONRipper {
|
|||||||
return imageURL;
|
return imageURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAfter(JSONObject json) {
|
||||||
|
try {
|
||||||
|
return json.getJSONObject("entry_data").getJSONArray("ProfilePage").getJSONObject(0)
|
||||||
|
.getJSONObject("graphql").getJSONObject("user")
|
||||||
|
.getJSONObject("edge_owner_to_timeline_media").getJSONObject("page_info").getString("end_cursor");
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return json.getJSONObject("data").getJSONObject("user")
|
||||||
|
.getJSONObject("edge_owner_to_timeline_media").getJSONObject("page_info").getString("end_cursor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getURLsFromJSON(JSONObject json) {
|
public List<String> getURLsFromJSON(JSONObject json) {
|
||||||
List<String> imageURLs = new ArrayList<>();
|
List<String> imageURLs = new ArrayList<>();
|
||||||
|
if (!url.toExternalForm().contains("/p/")) {
|
||||||
|
nextPageID = getAfter(json);
|
||||||
|
}
|
||||||
|
|
||||||
// get the rhx_gis value so we can get the next page later on
|
// get the rhx_gis value so we can get the next page later on
|
||||||
if (rhx_gis == null) {
|
if (rhx_gis == null) {
|
||||||
@ -246,7 +264,8 @@ public class InstagramRipper extends AbstractJSONRipper {
|
|||||||
try {
|
try {
|
||||||
JSONArray profilePage = json.getJSONObject("entry_data").getJSONArray("ProfilePage");
|
JSONArray profilePage = json.getJSONObject("entry_data").getJSONArray("ProfilePage");
|
||||||
userID = profilePage.getJSONObject(0).getString("logging_page_id").replaceAll("profilePage_", "");
|
userID = profilePage.getJSONObject(0).getString("logging_page_id").replaceAll("profilePage_", "");
|
||||||
datas = profilePage.getJSONObject(0).getJSONObject("graphql").getJSONObject("user")
|
datas = json.getJSONObject("entry_data").getJSONArray("ProfilePage").getJSONObject(0)
|
||||||
|
.getJSONObject("graphql").getJSONObject("user")
|
||||||
.getJSONObject("edge_owner_to_timeline_media").getJSONArray("edges");
|
.getJSONObject("edge_owner_to_timeline_media").getJSONArray("edges");
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
datas = json.getJSONObject("data").getJSONObject("user")
|
datas = json.getJSONObject("data").getJSONObject("user")
|
||||||
@ -300,11 +319,10 @@ public class InstagramRipper extends AbstractJSONRipper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
|
LOGGER.info("Got MalformedURLException");
|
||||||
return imageURLs;
|
return imageURLs;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextPageID = data.getString("id");
|
|
||||||
|
|
||||||
if (isThisATest()) {
|
if (isThisATest()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -368,10 +386,11 @@ public class InstagramRipper extends AbstractJSONRipper {
|
|||||||
try {
|
try {
|
||||||
// Sleep for a while to avoid a ban
|
// Sleep for a while to avoid a ban
|
||||||
sleep(2500);
|
sleep(2500);
|
||||||
String vars = "{\"id\":\"" + userID + "\",\"first\":50,\"after\":\"" + nextPageID + "\"}";
|
String vars = "{\"id\":\"" + userID + "\",\"first\":12,\"after\":\"" + nextPageID + "\"}";
|
||||||
String ig_gis = getIGGis(vars);
|
String ig_gis = getIGGis(vars);
|
||||||
LOGGER.info(ig_gis);
|
LOGGER.info(ig_gis);
|
||||||
|
|
||||||
|
LOGGER.info("https://www.instagram.com/graphql/query/?query_hash=" + qHash + "&variables=" + vars);
|
||||||
toreturn = getPage("https://www.instagram.com/graphql/query/?query_hash=" + qHash + "&variables=" + vars, ig_gis);
|
toreturn = getPage("https://www.instagram.com/graphql/query/?query_hash=" + qHash + "&variables=" + vars, ig_gis);
|
||||||
if (!pageHasImages(toreturn)) {
|
if (!pageHasImages(toreturn)) {
|
||||||
throw new IOException("No more pages");
|
throw new IOException("No more pages");
|
||||||
@ -391,6 +410,7 @@ public class InstagramRipper extends AbstractJSONRipper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean pageHasImages(JSONObject json) {
|
private boolean pageHasImages(JSONObject json) {
|
||||||
|
LOGGER.info(json);
|
||||||
int numberOfImages = json.getJSONObject("data").getJSONObject("user")
|
int numberOfImages = json.getJSONObject("data").getJSONObject("user")
|
||||||
.getJSONObject("edge_owner_to_timeline_media").getJSONArray("edges").length();
|
.getJSONObject("edge_owner_to_timeline_media").getJSONArray("edges").length();
|
||||||
if (numberOfImages == 0) {
|
if (numberOfImages == 0) {
|
||||||
@ -451,26 +471,11 @@ public class InstagramRipper extends AbstractJSONRipper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!rippingTag) {
|
if (!rippingTag) {
|
||||||
Pattern jsP = Pattern.compile("o},queryId:.([a-zA-Z0-9]+).");
|
Pattern jsP = Pattern.compile("byUserId\\.get\\(t\\)\\)\\|\\|void 0===r\\?void 0:r\\.pagination},queryId:.([a-zA-Z0-9]+)");
|
||||||
Matcher m = jsP.matcher(sb.toString());
|
Matcher m = jsP.matcher(sb.toString());
|
||||||
if (m.find()) {
|
if (m.find()) {
|
||||||
return m.group(1);
|
return m.group(1);
|
||||||
}
|
}
|
||||||
jsP = Pattern.compile("n.pagination:n},queryId:.([a-zA-Z0-9]+).");
|
|
||||||
m = jsP.matcher(sb.toString());
|
|
||||||
if (m.find()) {
|
|
||||||
return m.group(1);
|
|
||||||
}
|
|
||||||
jsP = Pattern.compile("0:n.pagination},queryId:.([a-zA-Z0-9]+).");
|
|
||||||
m = jsP.matcher(sb.toString());
|
|
||||||
if (m.find()) {
|
|
||||||
return m.group(1);
|
|
||||||
}
|
|
||||||
jsP = Pattern.compile("o.pagination},queryId:.([a-zA-Z0-9]+).");
|
|
||||||
m = jsP.matcher(sb.toString());
|
|
||||||
if (m.find()) {
|
|
||||||
return m.group(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Pattern jsP = Pattern.compile("return e.tagMedia.byTagName.get\\(t\\).pagination},queryId:.([a-zA-Z0-9]+).");
|
Pattern jsP = Pattern.compile("return e.tagMedia.byTagName.get\\(t\\).pagination},queryId:.([a-zA-Z0-9]+).");
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
package com.rarchives.ripme.ripper.rippers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.AbstractHTMLRipper;
|
||||||
|
import com.rarchives.ripme.utils.Http;
|
||||||
|
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.select.Elements;
|
||||||
|
|
||||||
|
public class JabArchivesRipper extends AbstractHTMLRipper {
|
||||||
|
|
||||||
|
public JabArchivesRipper(URL url) throws IOException {
|
||||||
|
super(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHost() {
|
||||||
|
return "jabarchives";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDomain() {
|
||||||
|
return "jabarchives.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getGID(URL url) throws MalformedURLException {
|
||||||
|
Pattern p = Pattern.compile("^https?://jabarchives.com/main/view/([a-zA-Z0-9_]+).*$");
|
||||||
|
Matcher m = p.matcher(url.toExternalForm());
|
||||||
|
if (m.matches()) {
|
||||||
|
// Return the text contained between () in the regex
|
||||||
|
return m.group(1);
|
||||||
|
}
|
||||||
|
throw new MalformedURLException(
|
||||||
|
"Expected javarchives.com URL format: " +
|
||||||
|
"jabarchives.com/main/view/albumname - got " + url + " instead");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Document getFirstPage() throws IOException {
|
||||||
|
// "url" is an instance field of the superclass
|
||||||
|
return Http.url(url).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Document getNextPage(Document doc) throws IOException {
|
||||||
|
// Find next page
|
||||||
|
Elements hrefs = doc.select("a[title=\"Next page\"]");
|
||||||
|
if (hrefs.isEmpty()) {
|
||||||
|
throw new IOException("No more pages");
|
||||||
|
}
|
||||||
|
String nextUrl = "https://jabarchives.com" + hrefs.first().attr("href");
|
||||||
|
sleep(500);
|
||||||
|
return Http.url(nextUrl).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getURLsFromPage(Document doc) {
|
||||||
|
List<String> result = new ArrayList<String>();
|
||||||
|
for (Element el : doc.select("#contentMain img")) {
|
||||||
|
result.add("https://jabarchives.com" + el.attr("src").replace("thumb", "large"));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadURL(URL url, int index) {
|
||||||
|
addURLToDownload(url, getPrefix(index));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
package com.rarchives.ripme.ripper.rippers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.utils.Utils;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.AbstractHTMLRipper;
|
||||||
|
import com.rarchives.ripme.utils.Http;
|
||||||
|
|
||||||
|
public class LoveromRipper extends AbstractHTMLRipper {
|
||||||
|
|
||||||
|
public LoveromRipper(URL url) throws IOException {
|
||||||
|
super(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int bytesTotal = 1;
|
||||||
|
private int bytesCompleted = 1;
|
||||||
|
boolean multipart = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHost() {
|
||||||
|
return "loveroms";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDomain() {
|
||||||
|
return "loveroms.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getGID(URL url) throws MalformedURLException {
|
||||||
|
Pattern p = Pattern.compile("https://www.loveroms.com/download/([a-zA-Z0-9_-]+)/([a-zA-Z0-9_-]+)/\\d+");
|
||||||
|
Matcher m = p.matcher(url.toExternalForm());
|
||||||
|
if (!m.matches()) {
|
||||||
|
throw new MalformedURLException("Expected URL format: https://www.loveroms.com/download/CONSOLE/GAME, got: " + url);
|
||||||
|
}
|
||||||
|
return m.group(1) + "_" + m.group(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Document getFirstPage() throws IOException {
|
||||||
|
// "url" is an instance field of the superclass
|
||||||
|
return Http.url(url).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getURLsFromPage(Document doc) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
String downloadLink = doc.select("a#start_download_link").attr("href");
|
||||||
|
if (downloadLink != null && !downloadLink.isEmpty()) {
|
||||||
|
result.add(downloadLink);
|
||||||
|
} else {
|
||||||
|
multipart = true;
|
||||||
|
for (Element el : doc.select("a.multi-file-btn")) {
|
||||||
|
result.add(el.attr("href"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadURL(URL url, int index) {
|
||||||
|
if (multipart) {
|
||||||
|
addURLToDownload(url, "", "", "", null, null, "7z." + getPrefix(index));
|
||||||
|
} else {
|
||||||
|
addURLToDownload(url, "", "", "", null, null, "7z");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStatusText() {
|
||||||
|
if (multipart) {
|
||||||
|
return super.getStatusText();
|
||||||
|
}
|
||||||
|
return String.valueOf(getCompletionPercentage()) +
|
||||||
|
"% - " +
|
||||||
|
Utils.bytesToHumanReadable(bytesCompleted) +
|
||||||
|
" / " +
|
||||||
|
Utils.bytesToHumanReadable(bytesTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCompletionPercentage() {
|
||||||
|
if (multipart) {
|
||||||
|
return super.getCompletionPercentage();
|
||||||
|
}
|
||||||
|
return (int) (100 * (bytesCompleted / (float) bytesTotal));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytesTotal(int bytes) {
|
||||||
|
this.bytesTotal = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBytesCompleted(int bytes) {
|
||||||
|
this.bytesCompleted = bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean useByteProgessBar() {return true;}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean tryResumeDownload() {return true;}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrefix(int index) {
|
||||||
|
String prefix = "";
|
||||||
|
if (keepSortOrder() && Utils.getConfigBoolean("download.save_order", true)) {
|
||||||
|
prefix = String.format("7z.%03d", index);
|
||||||
|
}
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,9 @@ import java.io.IOException;
|
|||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -16,6 +18,8 @@ import com.rarchives.ripme.utils.Http;
|
|||||||
|
|
||||||
public class ModelmayhemRipper extends AbstractHTMLRipper {
|
public class ModelmayhemRipper extends AbstractHTMLRipper {
|
||||||
|
|
||||||
|
private Map<String,String> cookies = new HashMap<>();
|
||||||
|
|
||||||
public ModelmayhemRipper(URL url) throws IOException {
|
public ModelmayhemRipper(URL url) throws IOException {
|
||||||
super(url);
|
super(url);
|
||||||
}
|
}
|
||||||
@ -43,8 +47,10 @@ public class ModelmayhemRipper extends AbstractHTMLRipper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Document getFirstPage() throws IOException {
|
public Document getFirstPage() throws IOException {
|
||||||
|
// Bypass NSFW filter
|
||||||
|
cookies.put("worksafe", "0");
|
||||||
// "url" is an instance field of the superclass
|
// "url" is an instance field of the superclass
|
||||||
return Http.url(url).get();
|
return Http.url(url).cookies(cookies).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
package com.rarchives.ripme.ripper.rippers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.AbstractHTMLRipper;
|
||||||
|
import com.rarchives.ripme.utils.Http;
|
||||||
|
|
||||||
|
public class PicstatioRipper extends AbstractHTMLRipper {
|
||||||
|
|
||||||
|
public PicstatioRipper(URL url) throws IOException {
|
||||||
|
super(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFullSizedImageFromURL(String fileName) {
|
||||||
|
try {
|
||||||
|
LOGGER.info("https://www.picstatio.com/wallpaper/" + fileName + "/download");
|
||||||
|
return Http.url("https://www.picstatio.com/wallpaper/" + fileName + "/download").get().select("p.text-center > span > a").attr("href");
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHost() {
|
||||||
|
return "picstatio";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDomain() {
|
||||||
|
return "picstatio.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getGID(URL url) throws MalformedURLException {
|
||||||
|
Pattern p = Pattern.compile("https?://www.picstatio.com/([a-zA-Z1-9_-]*)/?$");
|
||||||
|
Matcher m = p.matcher(url.toExternalForm());
|
||||||
|
if (m.matches()) {
|
||||||
|
return m.group(1);
|
||||||
|
}
|
||||||
|
throw new MalformedURLException("Expected picstatio URL format: " +
|
||||||
|
"www.picstatio.com//ID - got " + url + " instead");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Document getFirstPage() throws IOException {
|
||||||
|
// "url" is an instance field of the superclass
|
||||||
|
return Http.url(url).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Document getNextPage(Document doc) throws IOException {
|
||||||
|
if (doc.select("a.next_page") != null) {
|
||||||
|
return Http.url("https://www.picstatio.com" + doc.select("a.next_page").attr("href")).get();
|
||||||
|
}
|
||||||
|
throw new IOException("No more pages");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getURLsFromPage(Document doc) {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
for (Element e : doc.select("img.img")) {
|
||||||
|
String imageName = e.parent().attr("href");
|
||||||
|
LOGGER.info(getFullSizedImageFromURL(imageName.split("/")[2]));
|
||||||
|
result.add(getFullSizedImageFromURL(imageName.split("/")[2]));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadURL(URL url, int index) {
|
||||||
|
addURLToDownload(url, getPrefix(index));
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import java.util.List;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ui.RipStatusMessage;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.json.JSONTokener;
|
import org.json.JSONTokener;
|
||||||
@ -17,6 +18,10 @@ import com.rarchives.ripme.ui.UpdateUtils;
|
|||||||
import com.rarchives.ripme.utils.Http;
|
import com.rarchives.ripme.utils.Http;
|
||||||
import com.rarchives.ripme.utils.RipUtils;
|
import com.rarchives.ripme.utils.RipUtils;
|
||||||
import com.rarchives.ripme.utils.Utils;
|
import com.rarchives.ripme.utils.Utils;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
|
||||||
|
import javax.swing.text.Document;
|
||||||
|
import javax.swing.text.Element;
|
||||||
|
|
||||||
public class RedditRipper extends AlbumRipper {
|
public class RedditRipper extends AlbumRipper {
|
||||||
|
|
||||||
@ -35,6 +40,10 @@ public class RedditRipper extends AlbumRipper {
|
|||||||
|
|
||||||
private long lastRequestTime = 0;
|
private long lastRequestTime = 0;
|
||||||
|
|
||||||
|
private Boolean shouldAddURL() {
|
||||||
|
return (alreadyDownloadedUrls >= Utils.getConfigInteger("history.end_rip_after_already_seen", 1000000000) && !isThisATest());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canRip(URL url) {
|
public boolean canRip(URL url) {
|
||||||
return url.getHost().endsWith(DOMAIN);
|
return url.getHost().endsWith(DOMAIN);
|
||||||
@ -61,6 +70,10 @@ public class RedditRipper extends AlbumRipper {
|
|||||||
public void rip() throws IOException {
|
public void rip() throws IOException {
|
||||||
URL jsonURL = getJsonURL(this.url);
|
URL jsonURL = getJsonURL(this.url);
|
||||||
while (true) {
|
while (true) {
|
||||||
|
if (shouldAddURL()) {
|
||||||
|
sendUpdate(RipStatusMessage.STATUS.DOWNLOAD_COMPLETE_HISTORY, "Already seen the last " + alreadyDownloadedUrls + " images ending rip");
|
||||||
|
break;
|
||||||
|
}
|
||||||
jsonURL = getAndParseAndReturnNext(jsonURL);
|
jsonURL = getAndParseAndReturnNext(jsonURL);
|
||||||
if (jsonURL == null || isThisATest() || isStopped()) {
|
if (jsonURL == null || isThisATest() || isStopped()) {
|
||||||
break;
|
break;
|
||||||
@ -179,6 +192,32 @@ public class RedditRipper extends AlbumRipper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private URL parseRedditVideoMPD(String vidURL) {
|
||||||
|
org.jsoup.nodes.Document doc = null;
|
||||||
|
try {
|
||||||
|
doc = Http.url(vidURL + "/DASHPlaylist.mpd").ignoreContentType().get();
|
||||||
|
int largestHeight = 0;
|
||||||
|
String baseURL = null;
|
||||||
|
// Loops over all the videos and finds the one with the largest height and sets baseURL to the base url of that video
|
||||||
|
for (org.jsoup.nodes.Element e : doc.select("MPD > Period > AdaptationSet > Representation")) {
|
||||||
|
String height = e.attr("height");
|
||||||
|
if (height.equals("")) {
|
||||||
|
height = "0";
|
||||||
|
}
|
||||||
|
if (largestHeight < Integer.parseInt(height)) {
|
||||||
|
largestHeight = Integer.parseInt(height);
|
||||||
|
baseURL = doc.select("MPD > Period > AdaptationSet > Representation[height=" + height + "]").select("BaseURL").text();
|
||||||
|
}
|
||||||
|
LOGGER.info("H " + e.attr("height") + " V " + e.attr("width"));
|
||||||
|
}
|
||||||
|
return new URL(vidURL + "/" + baseURL);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void handleURL(String theUrl, String id) {
|
private void handleURL(String theUrl, String id) {
|
||||||
URL originalURL;
|
URL originalURL;
|
||||||
try {
|
try {
|
||||||
@ -198,6 +237,11 @@ public class RedditRipper extends AlbumRipper {
|
|||||||
savePath += id + "-" + m.group(1) + ".jpg";
|
savePath += id + "-" + m.group(1) + ".jpg";
|
||||||
addURLToDownload(urls.get(0), new File(savePath));
|
addURLToDownload(urls.get(0), new File(savePath));
|
||||||
}
|
}
|
||||||
|
if (url.contains("v.redd.it")) {
|
||||||
|
String savePath = this.workingDir + File.separator;
|
||||||
|
savePath += id + "-" + url.split("/")[3] + ".mp4";
|
||||||
|
addURLToDownload(parseRedditVideoMPD(urls.get(0).toExternalForm()), new File(savePath));
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
addURLToDownload(urls.get(0), id + "-", "", theUrl, null);
|
addURLToDownload(urls.get(0), id + "-", "", theUrl, null);
|
||||||
}
|
}
|
||||||
|
@ -235,8 +235,12 @@ public class TumblrRipper extends AlbumRipper {
|
|||||||
for (int j = 0; j < photos.length(); j++) {
|
for (int j = 0; j < photos.length(); j++) {
|
||||||
photo = photos.getJSONObject(j);
|
photo = photos.getJSONObject(j);
|
||||||
try {
|
try {
|
||||||
if (Utils.getConfigBoolean("tumblr.get_raw_image", false)) {
|
String imageUrl = photo.getJSONObject("original_size").getString("url");
|
||||||
String urlString = photo.getJSONObject("original_size").getString("url").replaceAll("https", "http");
|
// If the url is shorter than 65 chars long we skip it because it's those images don't support grabbing them in fullsize
|
||||||
|
if (Utils.getConfigBoolean("tumblr.get_raw_image", false) &&
|
||||||
|
imageUrl.replaceAll("https", "http").length() > 65) {
|
||||||
|
// We have to change the link to http because tumblr uses an invalid cert for data.tumblr.com
|
||||||
|
String urlString = imageUrl.replaceAll("https", "http");
|
||||||
urlString = urlString.replaceAll("https?://[a-sA-Z0-9_\\-\\.]*\\.tumblr", "http://data.tumblr");
|
urlString = urlString.replaceAll("https?://[a-sA-Z0-9_\\-\\.]*\\.tumblr", "http://data.tumblr");
|
||||||
urlString = urlString.replaceAll("_\\d+\\.", "_raw.");
|
urlString = urlString.replaceAll("_\\d+\\.", "_raw.");
|
||||||
fileURL = new URL(urlString);
|
fileURL = new URL(urlString);
|
||||||
|
@ -20,6 +20,8 @@ import com.rarchives.ripme.utils.Utils;
|
|||||||
|
|
||||||
public class TwitterRipper extends AlbumRipper {
|
public class TwitterRipper extends AlbumRipper {
|
||||||
|
|
||||||
|
int downloadUrls = 1;
|
||||||
|
|
||||||
private static final String DOMAIN = "twitter.com",
|
private static final String DOMAIN = "twitter.com",
|
||||||
HOST = "twitter";
|
HOST = "twitter";
|
||||||
|
|
||||||
@ -123,15 +125,16 @@ public class TwitterRipper extends AlbumRipper {
|
|||||||
.append("&include_entities=true")
|
.append("&include_entities=true")
|
||||||
.append("&exclude_replies=true")
|
.append("&exclude_replies=true")
|
||||||
.append("&trim_user=true")
|
.append("&trim_user=true")
|
||||||
.append("&include_rts=false")
|
.append("&count=" + 200)
|
||||||
.append("&count=" + 200);
|
.append("&tweet_mode=extended");
|
||||||
break;
|
break;
|
||||||
case SEARCH:
|
case SEARCH:
|
||||||
req.append("https://api.twitter.com/1.1/search/tweets.json")
|
req.append("https://api.twitter.com/1.1/search/tweets.json")
|
||||||
.append("?q=" + this.searchText)
|
.append("?q=" + this.searchText)
|
||||||
.append("&include_entities=true")
|
.append("&include_entities=true")
|
||||||
.append("&result_type=recent")
|
.append("&result_type=recent")
|
||||||
.append("&count=100");
|
.append("&count=100")
|
||||||
|
.append("&tweet_mode=extended");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (maxID > 0) {
|
if (maxID > 0) {
|
||||||
@ -187,18 +190,32 @@ public class TwitterRipper extends AlbumRipper {
|
|||||||
url = media.getString("media_url");
|
url = media.getString("media_url");
|
||||||
if (media.getString("type").equals("video")) {
|
if (media.getString("type").equals("video")) {
|
||||||
JSONArray variants = media.getJSONObject("video_info").getJSONArray("variants");
|
JSONArray variants = media.getJSONObject("video_info").getJSONArray("variants");
|
||||||
|
int largestBitrate = 0;
|
||||||
|
String urlToDownload = null;
|
||||||
|
// Loop over all the video options and find the biggest video
|
||||||
for (int j = 0; j < medias.length(); j++) {
|
for (int j = 0; j < medias.length(); j++) {
|
||||||
JSONObject variant = (JSONObject) variants.get(i);
|
JSONObject variant = (JSONObject) variants.get(i);
|
||||||
if (variant.has("bitrate") && variant.getInt("bitrate") == 832000) {
|
LOGGER.info(variant);
|
||||||
addURLToDownload(new URL(variant.getString("url")));
|
// If the video doesn't have a bitrate it's a m3u8 file we can't download
|
||||||
parsedCount++;
|
if (variant.has("bitrate")) {
|
||||||
break;
|
if (variant.getInt("bitrate") > largestBitrate) {
|
||||||
|
largestBitrate = variant.getInt("bitrate");
|
||||||
|
urlToDownload = variant.getString("url");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (urlToDownload != null) {
|
||||||
|
addURLToDownload(new URL(urlToDownload), getPrefix(downloadUrls));
|
||||||
|
downloadUrls++;
|
||||||
|
} else {
|
||||||
|
LOGGER.error("URLToDownload was null");
|
||||||
|
}
|
||||||
|
parsedCount++;
|
||||||
} else if (media.getString("type").equals("photo")) {
|
} else if (media.getString("type").equals("photo")) {
|
||||||
if (url.contains(".twimg.com/")) {
|
if (url.contains(".twimg.com/")) {
|
||||||
url += ":orig";
|
url += ":orig";
|
||||||
addURLToDownload(new URL(url));
|
addURLToDownload(new URL(url), getPrefix(downloadUrls));
|
||||||
|
downloadUrls++;
|
||||||
parsedCount++;
|
parsedCount++;
|
||||||
} else {
|
} else {
|
||||||
LOGGER.debug("Unexpected media_url: " + url);
|
LOGGER.debug("Unexpected media_url: " + url);
|
||||||
@ -211,6 +228,10 @@ public class TwitterRipper extends AlbumRipper {
|
|||||||
return parsedCount;
|
return parsedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPrefix(int index) {
|
||||||
|
return String.format("%03d_", index);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void rip() throws IOException {
|
public void rip() throws IOException {
|
||||||
getAccessToken();
|
getAccessToken();
|
||||||
|
@ -62,18 +62,20 @@ public class XhamsterRipper extends AbstractHTMLRipper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canRip(URL url) {
|
public boolean canRip(URL url) {
|
||||||
Pattern p = Pattern.compile("^https?://[wmde.]*xhamster\\.com/photos/gallery/.*?(\\d+)$");
|
Pattern p = Pattern.compile("^https?://([\\w\\w]*\\.)?xhamster\\.com/photos/gallery/.*?(\\d+)$");
|
||||||
Matcher m = p.matcher(url.toExternalForm());
|
Matcher m = p.matcher(url.toExternalForm());
|
||||||
return m.matches();
|
return m.matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Document getNextPage(Document doc) throws IOException {
|
public Document getNextPage(Document doc) throws IOException {
|
||||||
if (!doc.select("a.next").first().attr("href").equals("")) {
|
if (doc.select("a.next").first() != null) {
|
||||||
return Http.url(doc.select("a.next").first().attr("href")).get();
|
if (doc.select("a.next").first().attr("href").startsWith("http")) {
|
||||||
} else {
|
return Http.url(doc.select("a.next").first().attr("href")).get();
|
||||||
throw new IOException("No more pages");
|
}
|
||||||
}
|
}
|
||||||
|
throw new IOException("No more pages");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -100,4 +102,23 @@ public class XhamsterRipper extends AbstractHTMLRipper {
|
|||||||
public void downloadURL(URL url, int index) {
|
public void downloadURL(URL url, int index) {
|
||||||
addURLToDownload(url, getPrefix(index));
|
addURLToDownload(url, getPrefix(index));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlbumTitle(URL url) throws MalformedURLException {
|
||||||
|
try {
|
||||||
|
// Attempt to use album title and username as GID
|
||||||
|
Document doc = getFirstPage();
|
||||||
|
Element user = doc.select("a.author").first();
|
||||||
|
String username = user.text();
|
||||||
|
String path = url.getPath();
|
||||||
|
Pattern p = Pattern.compile("^/photos/gallery/(.*)$");
|
||||||
|
Matcher m = p.matcher(path);
|
||||||
|
if (m.matches() && !username.isEmpty()) {
|
||||||
|
return getHost() + "_" + username + "_" + m.group(1);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Fall back to default album naming convention
|
||||||
|
}
|
||||||
|
return super.getAlbumTitle(url);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,19 +1,22 @@
|
|||||||
package com.rarchives.ripme.ripper.rippers.video;
|
package com.rarchives.ripme.ripper.rippers;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.AbstractSingleFileRipper;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
import org.jsoup.select.Elements;
|
import org.jsoup.select.Elements;
|
||||||
|
|
||||||
import com.rarchives.ripme.ripper.VideoRipper;
|
|
||||||
import com.rarchives.ripme.utils.Http;
|
import com.rarchives.ripme.utils.Http;
|
||||||
|
|
||||||
public class XvideosRipper extends VideoRipper {
|
public class XvideosRipper extends AbstractSingleFileRipper {
|
||||||
|
|
||||||
private static final String HOST = "xvideos";
|
private static final String HOST = "xvideos";
|
||||||
|
|
||||||
@ -21,11 +24,21 @@ public class XvideosRipper extends VideoRipper {
|
|||||||
super(url);
|
super(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Document getFirstPage() throws IOException {
|
||||||
|
return Http.url(this.url).get();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHost() {
|
public String getHost() {
|
||||||
return HOST;
|
return HOST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDomain() {
|
||||||
|
return HOST + ".com";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canRip(URL url) {
|
public boolean canRip(URL url) {
|
||||||
Pattern p = Pattern.compile("^https?://[wm.]*xvideos\\.com/video[0-9]+.*$");
|
Pattern p = Pattern.compile("^https?://[wm.]*xvideos\\.com/video[0-9]+.*$");
|
||||||
@ -33,11 +46,6 @@ public class XvideosRipper extends VideoRipper {
|
|||||||
return m.matches();
|
return m.matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL sanitizeURL(URL url) throws MalformedURLException {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getGID(URL url) throws MalformedURLException {
|
public String getGID(URL url) throws MalformedURLException {
|
||||||
Pattern p = Pattern.compile("^https?://[wm.]*xvideos\\.com/video([0-9]+).*$");
|
Pattern p = Pattern.compile("^https?://[wm.]*xvideos\\.com/video([0-9]+).*$");
|
||||||
@ -53,9 +61,8 @@ public class XvideosRipper extends VideoRipper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void rip() throws IOException {
|
public List<String> getURLsFromPage(Document doc) {
|
||||||
LOGGER.info(" Retrieving " + this.url);
|
List<String> results = new ArrayList<>();
|
||||||
Document doc = Http.url(this.url).get();
|
|
||||||
Elements scripts = doc.select("script");
|
Elements scripts = doc.select("script");
|
||||||
for (Element e : scripts) {
|
for (Element e : scripts) {
|
||||||
if (e.html().contains("html5player.setVideoUrlHigh")) {
|
if (e.html().contains("html5player.setVideoUrlHigh")) {
|
||||||
@ -64,13 +71,16 @@ public class XvideosRipper extends VideoRipper {
|
|||||||
for (String line: lines) {
|
for (String line: lines) {
|
||||||
if (line.contains("html5player.setVideoUrlHigh")) {
|
if (line.contains("html5player.setVideoUrlHigh")) {
|
||||||
String videoURL = line.replaceAll("\t", "").replaceAll("html5player.setVideoUrlHigh\\(", "").replaceAll("\'", "").replaceAll("\\);", "");
|
String videoURL = line.replaceAll("\t", "").replaceAll("html5player.setVideoUrlHigh\\(", "").replaceAll("\'", "").replaceAll("\\);", "");
|
||||||
addURLToDownload(new URL(videoURL), HOST + "_" + getGID(this.url));
|
results.add(videoURL);
|
||||||
waitForThreads();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IOException("Unable to find video url at " + this.url.toExternalForm());
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void downloadURL(URL url, int index) {
|
||||||
|
addURLToDownload(url, getPrefix(index));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -49,9 +49,8 @@ public class ZizkiRipper extends AbstractHTMLRipper {
|
|||||||
public String getAlbumTitle(URL url) throws MalformedURLException {
|
public String getAlbumTitle(URL url) throws MalformedURLException {
|
||||||
try {
|
try {
|
||||||
// Attempt to use album title as GID
|
// Attempt to use album title as GID
|
||||||
Element titleElement = getFirstPage().select("meta[name=description]").first();
|
Element titleElement = getFirstPage().select("h1.title").first();
|
||||||
String title = titleElement.attr("content");
|
String title = titleElement.text();
|
||||||
title = title.substring(title.lastIndexOf('/') + 1);
|
|
||||||
|
|
||||||
Element authorSpan = getFirstPage().select("span[class=creator]").first();
|
Element authorSpan = getFirstPage().select("span[class=creator]").first();
|
||||||
String author = authorSpan.select("a").first().text();
|
String author = authorSpan.select("a").first().text();
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
package com.rarchives.ripme.ripper.rippers.video;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import org.jsoup.nodes.Document;
|
|
||||||
import org.jsoup.select.Elements;
|
|
||||||
|
|
||||||
import com.rarchives.ripme.ripper.VideoRipper;
|
|
||||||
import com.rarchives.ripme.utils.Http;
|
|
||||||
|
|
||||||
public class VineRipper extends VideoRipper {
|
|
||||||
|
|
||||||
private static final String HOST = "vine";
|
|
||||||
|
|
||||||
public VineRipper(URL url) throws IOException {
|
|
||||||
super(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHost() {
|
|
||||||
return HOST;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canRip(URL url) {
|
|
||||||
// https://vine.co/v/hiqQrP0eUZx
|
|
||||||
Pattern p = Pattern.compile("^https?://[wm.]*vine\\.co/v/[a-zA-Z0-9]+.*$");
|
|
||||||
Matcher m = p.matcher(url.toExternalForm());
|
|
||||||
return m.matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL sanitizeURL(URL url) throws MalformedURLException {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getGID(URL url) throws MalformedURLException {
|
|
||||||
Pattern p = Pattern.compile("^https?://[wm.]*vine\\.co/v/([a-zA-Z0-9]+).*$");
|
|
||||||
Matcher m = p.matcher(url.toExternalForm());
|
|
||||||
if (m.matches()) {
|
|
||||||
return m.group(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new MalformedURLException(
|
|
||||||
"Expected vine format:"
|
|
||||||
+ "vine.co/v/####"
|
|
||||||
+ " Got: " + url);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void rip() throws IOException {
|
|
||||||
LOGGER.info(" Retrieving " + this.url.toExternalForm());
|
|
||||||
Document doc = Http.url(this.url).get();
|
|
||||||
Elements props = doc.select("meta[property=twitter:player:stream]");
|
|
||||||
if (props.isEmpty()) {
|
|
||||||
throw new IOException("Could not find meta property 'twitter:player:stream' at " + url);
|
|
||||||
}
|
|
||||||
String vidUrl = props.get(0).attr("content");
|
|
||||||
addURLToDownload(new URL(vidUrl), HOST + "_" + getGID(this.url));
|
|
||||||
waitForThreads();
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,6 +10,7 @@ import java.awt.event.WindowAdapter;
|
|||||||
import java.awt.event.WindowEvent;
|
import java.awt.event.WindowEvent;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@ -142,6 +143,10 @@ public final class MainWindow implements Runnable, RipStatusHandler {
|
|||||||
|
|
||||||
private ResourceBundle rb = Utils.getResourceBundle(null);
|
private ResourceBundle rb = Utils.getResourceBundle(null);
|
||||||
|
|
||||||
|
// All the langs ripme has been translated into
|
||||||
|
private static String[] supportedLanges = new String[] {"en_US", "de_DE", "es_ES", "fr_CH", "kr_KR", "pt_PT",
|
||||||
|
"fi_FI", "in_ID", "nl_NL", "porrisavvo_FI", "ru_RU"};
|
||||||
|
|
||||||
private void updateQueueLabel() {
|
private void updateQueueLabel() {
|
||||||
if (queueListModel.size() > 0) {
|
if (queueListModel.size() > 0) {
|
||||||
optionQueue.setText(rb.getString("Queue") + " (" + queueListModel.size() + ")");
|
optionQueue.setText(rb.getString("Queue") + " (" + queueListModel.size() + ")");
|
||||||
@ -496,7 +501,7 @@ public final class MainWindow implements Runnable, RipStatusHandler {
|
|||||||
configURLHistoryCheckbox = addNewCheckbox(rb.getString("remember.url.history"), "remember.url_history", true);
|
configURLHistoryCheckbox = addNewCheckbox(rb.getString("remember.url.history"), "remember.url_history", true);
|
||||||
|
|
||||||
configLogLevelCombobox = new JComboBox<>(new String[] {"Log level: Error", "Log level: Warn", "Log level: Info", "Log level: Debug"});
|
configLogLevelCombobox = new JComboBox<>(new String[] {"Log level: Error", "Log level: Warn", "Log level: Info", "Log level: Debug"});
|
||||||
configSelectLangComboBox = new JComboBox<>(new String[] {"en_US", "de_DE", "es_ES", "fr_CH", "kr_KR", "pt_PT", "fi_FI", "in_ID", "porrisavvo_FI"});
|
configSelectLangComboBox = new JComboBox<>(supportedLanges);
|
||||||
configLogLevelCombobox.setSelectedItem(Utils.getConfigString("log.level", "Log level: Debug"));
|
configLogLevelCombobox.setSelectedItem(Utils.getConfigString("log.level", "Log level: Debug"));
|
||||||
setLogLevel(configLogLevelCombobox.getSelectedItem().toString());
|
setLogLevel(configLogLevelCombobox.getSelectedItem().toString());
|
||||||
configSaveDirLabel = new JLabel();
|
configSaveDirLabel = new JLabel();
|
||||||
@ -1206,6 +1211,12 @@ public final class MainWindow implements Runnable, RipStatusHandler {
|
|||||||
appendLog("Downloaded " + msg.getObject(), Color.GREEN);
|
appendLog("Downloaded " + msg.getObject(), Color.GREEN);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case DOWNLOAD_COMPLETE_HISTORY:
|
||||||
|
if (LOGGER.isEnabledFor(Level.INFO)) {
|
||||||
|
appendLog("" + msg.getObject(), Color.GREEN);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case DOWNLOAD_ERRORED:
|
case DOWNLOAD_ERRORED:
|
||||||
if (LOGGER.isEnabledFor(Level.ERROR)) {
|
if (LOGGER.isEnabledFor(Level.ERROR)) {
|
||||||
appendLog((String) msg.getObject(), Color.RED);
|
appendLog((String) msg.getObject(), Color.RED);
|
||||||
|
@ -10,6 +10,7 @@ public class RipStatusMessage {
|
|||||||
DOWNLOAD_STARTED("Download Started"),
|
DOWNLOAD_STARTED("Download Started"),
|
||||||
DOWNLOAD_COMPLETE("Download Complete"),
|
DOWNLOAD_COMPLETE("Download Complete"),
|
||||||
DOWNLOAD_ERRORED("Download Errored"),
|
DOWNLOAD_ERRORED("Download Errored"),
|
||||||
|
DOWNLOAD_COMPLETE_HISTORY("Download Complete History"),
|
||||||
RIP_COMPLETE("Rip Complete"),
|
RIP_COMPLETE("Rip Complete"),
|
||||||
DOWNLOAD_WARN("Download problem"),
|
DOWNLOAD_WARN("Download problem"),
|
||||||
TOTAL_BYTES("Total bytes"),
|
TOTAL_BYTES("Total bytes"),
|
||||||
|
@ -20,7 +20,7 @@ import com.rarchives.ripme.utils.Utils;
|
|||||||
public class UpdateUtils {
|
public class UpdateUtils {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(UpdateUtils.class);
|
private static final Logger logger = Logger.getLogger(UpdateUtils.class);
|
||||||
private static final String DEFAULT_VERSION = "1.7.51";
|
private static final String DEFAULT_VERSION = "1.7.60";
|
||||||
private static final String REPO_NAME = "ripmeapp/ripme";
|
private static final String REPO_NAME = "ripmeapp/ripme";
|
||||||
private static final String updateJsonURL = "https://raw.githubusercontent.com/" + REPO_NAME + "/master/ripme.json";
|
private static final String updateJsonURL = "https://raw.githubusercontent.com/" + REPO_NAME + "/master/ripme.json";
|
||||||
private static final String mainFileName = "ripme.jar";
|
private static final String mainFileName = "ripme.jar";
|
||||||
@ -128,9 +128,9 @@ public class UpdateUtils {
|
|||||||
logger.info("Found newer version: " + latestVersion);
|
logger.info("Found newer version: " + latestVersion);
|
||||||
int result = JOptionPane.showConfirmDialog(
|
int result = JOptionPane.showConfirmDialog(
|
||||||
null,
|
null,
|
||||||
"<html><font color=\"green\">New version (" + latestVersion + ") is available!</font>"
|
String.format("<html><font color=\"green\">New version (%s) is available!</font>"
|
||||||
+ "<br><br>Recent changes:" + changeList
|
+ "<br><br>Recent changes: %s"
|
||||||
+ "<br><br>Do you want to download and run the newest version?</html>",
|
+ "<br><br>Do you want to download and run the newest version?</html>", latestVersion, changeList.replaceAll("\n", "")),
|
||||||
"RipMe Updater",
|
"RipMe Updater",
|
||||||
JOptionPane.YES_NO_OPTION);
|
JOptionPane.YES_NO_OPTION);
|
||||||
if (result != JOptionPane.YES_OPTION) {
|
if (result != JOptionPane.YES_OPTION) {
|
||||||
|
@ -3,9 +3,7 @@ package com.rarchives.ripme.utils;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -14,7 +12,7 @@ import com.rarchives.ripme.ripper.rippers.EroShareRipper;
|
|||||||
import com.rarchives.ripme.ripper.rippers.EromeRipper;
|
import com.rarchives.ripme.ripper.rippers.EromeRipper;
|
||||||
import com.rarchives.ripme.ripper.rippers.ImgurRipper;
|
import com.rarchives.ripme.ripper.rippers.ImgurRipper;
|
||||||
import com.rarchives.ripme.ripper.rippers.VidbleRipper;
|
import com.rarchives.ripme.ripper.rippers.VidbleRipper;
|
||||||
import com.rarchives.ripme.ripper.rippers.video.GfycatRipper;
|
import com.rarchives.ripme.ripper.rippers.GfycatRipper;
|
||||||
import org.apache.commons.lang.math.NumberUtils;
|
import org.apache.commons.lang.math.NumberUtils;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
@ -56,6 +54,15 @@ public class RipUtils {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("[!] Exception while loading album " + url, e);
|
logger.error("[!] Exception while loading album " + url, e);
|
||||||
}
|
}
|
||||||
|
} else if (url.getHost().endsWith("i.imgur.com") && url.toExternalForm().contains("gifv")) {
|
||||||
|
// links to imgur gifvs
|
||||||
|
try {
|
||||||
|
result.add(new URL(url.toExternalForm().replaceAll(".gifv", ".mp4")));
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.info("Couldn't get gifv from " + url);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (url.getHost().endsWith("gfycat.com")) {
|
else if (url.getHost().endsWith("gfycat.com")) {
|
||||||
try {
|
try {
|
||||||
@ -88,6 +95,9 @@ public class RipUtils {
|
|||||||
logger.warn("Exception while retrieving eroshare page:", e);
|
logger.warn("Exception while retrieving eroshare page:", e);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
} else if (url.toExternalForm().contains("v.redd.it")) {
|
||||||
|
result.add(url);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (url.toExternalForm().contains("erome.com")) {
|
else if (url.toExternalForm().contains("erome.com")) {
|
||||||
@ -279,4 +289,16 @@ public class RipUtils {
|
|||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Reads a cookie string (Key1=value1;key2=value2) from the config file and turns it into a hashmap
|
||||||
|
* @return Map of cookies containing session data.
|
||||||
|
*/
|
||||||
|
public static Map<String, String> getCookiesFromString(String line) {
|
||||||
|
Map<String,String> cookies = new HashMap<>();
|
||||||
|
for (String pair : line.split(";")) {
|
||||||
|
String[] kv = pair.split("=");
|
||||||
|
cookies.put(kv[0], kv[1]);
|
||||||
|
}
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,87 +1,103 @@
|
|||||||
package com.rarchives.ripme.utils;
|
package com.rarchives.ripme.utils;
|
||||||
|
|
||||||
import java.io.*;
|
import com.rarchives.ripme.ripper.AbstractRipper;
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.jar.JarEntry;
|
|
||||||
import java.util.jar.JarFile;
|
|
||||||
|
|
||||||
import javax.sound.sampled.AudioSystem;
|
|
||||||
import javax.sound.sampled.Clip;
|
|
||||||
import javax.sound.sampled.Line;
|
|
||||||
import javax.sound.sampled.LineEvent;
|
|
||||||
|
|
||||||
import org.apache.commons.configuration.ConfigurationException;
|
import org.apache.commons.configuration.ConfigurationException;
|
||||||
import org.apache.commons.configuration.PropertiesConfiguration;
|
import org.apache.commons.configuration.PropertiesConfiguration;
|
||||||
import org.apache.log4j.LogManager;
|
import org.apache.log4j.LogManager;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.apache.log4j.PropertyConfigurator;
|
import org.apache.log4j.PropertyConfigurator;
|
||||||
|
|
||||||
import com.rarchives.ripme.ripper.AbstractRipper;
|
import javax.sound.sampled.AudioSystem;
|
||||||
|
import javax.sound.sampled.Clip;
|
||||||
|
import javax.sound.sampled.Line;
|
||||||
|
import javax.sound.sampled.LineEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.MissingResourceException;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common utility functions used in various places throughout the project.
|
* Common utility functions used in various places throughout the project.
|
||||||
*/
|
*/
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
private static final String RIP_DIRECTORY = "rips";
|
private static final String RIP_DIRECTORY = "rips";
|
||||||
private static final String configFile = "rip.properties";
|
private static final String CONFIG_FILE = "rip.properties";
|
||||||
private static final String OS = System.getProperty("os.name").toLowerCase();
|
private static final String OS = System.getProperty("os.name").toLowerCase();
|
||||||
private static final Logger logger = Logger.getLogger(Utils.class);
|
private static final Logger LOGGER = Logger.getLogger(Utils.class);
|
||||||
|
private static final int SHORTENED_PATH_LENGTH = 12;
|
||||||
|
|
||||||
private static PropertiesConfiguration config;
|
private static PropertiesConfiguration config;
|
||||||
|
private static HashMap<String, HashMap<String, String>> cookieCache;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
cookieCache = new HashMap<>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String configPath = getConfigFilePath();
|
String configPath = getConfigFilePath();
|
||||||
File f = new File(configPath);
|
File file = new File(configPath);
|
||||||
if (!f.exists()) {
|
|
||||||
|
if (!file.exists()) {
|
||||||
// Use default bundled with .jar
|
// Use default bundled with .jar
|
||||||
configPath = configFile;
|
configPath = CONFIG_FILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
config = new PropertiesConfiguration(configPath);
|
config = new PropertiesConfiguration(configPath);
|
||||||
logger.info("Loaded " + config.getPath());
|
LOGGER.info("Loaded " + config.getPath());
|
||||||
if (f.exists()) {
|
|
||||||
|
if (file.exists()) {
|
||||||
// Config was loaded from file
|
// Config was loaded from file
|
||||||
if ( !config.containsKey("twitter.auth")
|
if (!config.containsKey("twitter.auth") || !config.containsKey("twitter.max_requests")
|
||||||
|| !config.containsKey("twitter.max_requests")
|
|| !config.containsKey("tumblr.auth") || !config.containsKey("error.skip404")
|
||||||
|| !config.containsKey("tumblr.auth")
|
|| !config.containsKey("gw.api") || !config.containsKey("page.timeout")
|
||||||
|| !config.containsKey("error.skip404")
|
|| !config.containsKey("download.max_size")) {
|
||||||
|| !config.containsKey("gw.api")
|
|
||||||
|| !config.containsKey("page.timeout")
|
|
||||||
|| !config.containsKey("download.max_size")
|
|
||||||
) {
|
|
||||||
// Config is missing key fields
|
// Config is missing key fields
|
||||||
// Need to reload the default config
|
// Need to reload the default config
|
||||||
// See https://github.com/4pr0n/ripme/issues/158
|
// See https://github.com/4pr0n/ripme/issues/158
|
||||||
logger.warn("Config does not contain key fields, deleting old config");
|
LOGGER.warn("Config does not contain key fields, deleting old config");
|
||||||
f.delete();
|
file.delete();
|
||||||
config = new PropertiesConfiguration(configFile);
|
config = new PropertiesConfiguration(CONFIG_FILE);
|
||||||
logger.info("Loaded " + config.getPath());
|
LOGGER.info("Loaded " + config.getPath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("[!] Failed to load properties file from " + configFile, e);
|
LOGGER.error("[!] Failed to load properties file from " + CONFIG_FILE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the root rips directory.
|
* Get the root rips directory.
|
||||||
* @return
|
*
|
||||||
* Root directory to save rips to.
|
* @return Root directory to save rips to.
|
||||||
* @throws IOException
|
|
||||||
*/
|
*/
|
||||||
public static File getWorkingDirectory() {
|
public static File getWorkingDirectory() {
|
||||||
String currentDir = ".";
|
String currentDir = ".";
|
||||||
try {
|
try {
|
||||||
currentDir = new File(".").getCanonicalPath() + File.separator + RIP_DIRECTORY + File.separator;
|
currentDir = new File(".").getCanonicalPath() + File.separator + RIP_DIRECTORY + File.separator;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Error while finding working dir: ", e);
|
LOGGER.error("Error while finding working dir: ", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
currentDir = getConfigString("rips.directory", currentDir);
|
currentDir = getConfigString("rips.directory", currentDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
File workingDir = new File(currentDir);
|
File workingDir = new File(currentDir);
|
||||||
if (!workingDir.exists()) {
|
if (!workingDir.exists()) {
|
||||||
workingDir.mkdirs();
|
workingDir.mkdirs();
|
||||||
@ -92,7 +108,7 @@ public class Utils {
|
|||||||
/**
|
/**
|
||||||
* Gets the value of a specific config key.
|
* Gets the value of a specific config key.
|
||||||
*
|
*
|
||||||
* @param key The name of the config parameter you want to find.
|
* @param key The name of the config parameter you want to find.
|
||||||
* @param defaultValue What the default value would be.
|
* @param defaultValue What the default value would be.
|
||||||
*/
|
*/
|
||||||
public static String getConfigString(String key, String defaultValue) {
|
public static String getConfigString(String key, String defaultValue) {
|
||||||
@ -100,36 +116,46 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String[] getConfigStringArray(String key) {
|
public static String[] getConfigStringArray(String key) {
|
||||||
String[] s = config.getStringArray(key);
|
String[] configStringArray = config.getStringArray(key);
|
||||||
if (s.length == 0) {
|
|
||||||
return null;
|
return configStringArray.length == 0 ? null : configStringArray;
|
||||||
} else {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getConfigInteger(String key, int defaultValue) {
|
public static int getConfigInteger(String key, int defaultValue) {
|
||||||
return config.getInt(key, defaultValue);
|
return config.getInt(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean getConfigBoolean(String key, boolean defaultValue) {
|
public static boolean getConfigBoolean(String key, boolean defaultValue) {
|
||||||
return config.getBoolean(key, defaultValue);
|
return config.getBoolean(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> getConfigList(String key) {
|
public static List<String> getConfigList(String key) {
|
||||||
List<String> result = new ArrayList<>();
|
List<String> result = new ArrayList<>();
|
||||||
for (Object obj : config.getList(key, new ArrayList<String>())) {
|
for (Object obj : config.getList(key, new ArrayList<String>())) {
|
||||||
if (obj instanceof String) {
|
if (obj instanceof String) {
|
||||||
result.add( (String) obj);
|
result.add((String) obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
public static void setConfigBoolean(String key, boolean value) { config.setProperty(key, value); }
|
|
||||||
public static void setConfigString(String key, String value) { config.setProperty(key, value); }
|
public static void setConfigBoolean(String key, boolean value) {
|
||||||
public static void setConfigInteger(String key, int value) { config.setProperty(key, value); }
|
config.setProperty(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setConfigString(String key, String value) {
|
||||||
|
config.setProperty(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setConfigInteger(String key, int value) {
|
||||||
|
config.setProperty(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
public static void setConfigList(String key, List<Object> list) {
|
public static void setConfigList(String key, List<Object> list) {
|
||||||
config.clearProperty(key);
|
config.clearProperty(key);
|
||||||
config.addProperty(key, list);
|
config.addProperty(key, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setConfigList(String key, Enumeration<Object> enumeration) {
|
public static void setConfigList(String key, Enumeration<Object> enumeration) {
|
||||||
config.clearProperty(key);
|
config.clearProperty(key);
|
||||||
List<Object> list = new ArrayList<>();
|
List<Object> list = new ArrayList<>();
|
||||||
@ -142,9 +168,9 @@ public class Utils {
|
|||||||
public static void saveConfig() {
|
public static void saveConfig() {
|
||||||
try {
|
try {
|
||||||
config.save(getConfigFilePath());
|
config.save(getConfigFilePath());
|
||||||
logger.info("Saved configuration to " + getConfigFilePath());
|
LOGGER.info("Saved configuration to " + getConfigFilePath());
|
||||||
} catch (ConfigurationException e) {
|
} catch (ConfigurationException e) {
|
||||||
logger.error("Error while saving configuration: ", e);
|
LOGGER.error("Error while saving configuration: ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +202,6 @@ public class Utils {
|
|||||||
return System.getenv("LOCALAPPDATA") + File.separator + "ripme";
|
return System.getenv("LOCALAPPDATA") + File.separator + "ripme";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the directory of where the config file is stored on a UNIX machine.
|
* Gets the directory of where the config file is stored on a UNIX machine.
|
||||||
*/
|
*/
|
||||||
@ -197,13 +222,14 @@ public class Utils {
|
|||||||
*/
|
*/
|
||||||
private static boolean portableMode() {
|
private static boolean portableMode() {
|
||||||
try {
|
try {
|
||||||
File f = new File(new File(".").getCanonicalPath() + File.separator + configFile);
|
File file = new File(new File(".").getCanonicalPath() + File.separator + CONFIG_FILE);
|
||||||
if(f.exists() && !f.isDirectory()) {
|
if (file.exists() && !file.isDirectory()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,6 +255,7 @@ public class Utils {
|
|||||||
return ".";
|
return ".";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the url history file
|
* Delete the url history file
|
||||||
*/
|
*/
|
||||||
@ -238,7 +265,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the path of the url history file
|
* Return the path of the url history file
|
||||||
*/
|
*/
|
||||||
public static String getURLHistoryFile() {
|
public static String getURLHistoryFile() {
|
||||||
return getConfigDir() + File.separator + "url_history.txt";
|
return getConfigDir() + File.separator + "url_history.txt";
|
||||||
@ -248,26 +275,23 @@ public class Utils {
|
|||||||
* Gets the path to the configuration file.
|
* Gets the path to the configuration file.
|
||||||
*/
|
*/
|
||||||
private static String getConfigFilePath() {
|
private static String getConfigFilePath() {
|
||||||
return getConfigDir() + File.separator + configFile;
|
return getConfigDir() + File.separator + CONFIG_FILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the current working directory (CWD) from a File.
|
* Removes the current working directory (CWD) from a File.
|
||||||
* @param saveAs
|
*
|
||||||
* The File path
|
* @param saveAs The File path
|
||||||
* @return
|
* @return saveAs in relation to the CWD
|
||||||
* saveAs in relation to the CWD
|
|
||||||
*/
|
*/
|
||||||
public static String removeCWD(File saveAs) {
|
public static String removeCWD(File saveAs) {
|
||||||
String prettySaveAs = saveAs.toString();
|
String prettySaveAs = saveAs.toString();
|
||||||
try {
|
try {
|
||||||
prettySaveAs = saveAs.getCanonicalPath();
|
prettySaveAs = saveAs.getCanonicalPath();
|
||||||
String cwd = new File(".").getCanonicalPath() + File.separator;
|
String cwd = new File(".").getCanonicalPath() + File.separator;
|
||||||
prettySaveAs = prettySaveAs.replace(
|
prettySaveAs = prettySaveAs.replace(cwd, "." + File.separator);
|
||||||
cwd,
|
|
||||||
"." + File.separator);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Exception: ", e);
|
LOGGER.error("Exception: ", e);
|
||||||
}
|
}
|
||||||
return prettySaveAs;
|
return prettySaveAs;
|
||||||
}
|
}
|
||||||
@ -276,9 +300,8 @@ public class Utils {
|
|||||||
* Strips away URL parameters, which usually appear at the end of URLs.
|
* Strips away URL parameters, which usually appear at the end of URLs.
|
||||||
* E.g. the ?query on PHP
|
* E.g. the ?query on PHP
|
||||||
*
|
*
|
||||||
* @param url The URL to filter/strip
|
* @param url The URL to filter/strip
|
||||||
* @param parameter The parameter to strip
|
* @param parameter The parameter to strip
|
||||||
*
|
|
||||||
* @return The stripped URL
|
* @return The stripped URL
|
||||||
*/
|
*/
|
||||||
public static String stripURLParameter(String url, String parameter) {
|
public static String stripURLParameter(String url, String parameter) {
|
||||||
@ -290,13 +313,13 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (paramIndex > 0) {
|
if (paramIndex > 0) {
|
||||||
int nextParam = url.indexOf("&", paramIndex+1);
|
int nextParam = url.indexOf('&', paramIndex + 1);
|
||||||
if (nextParam != -1) {
|
if (nextParam != -1) {
|
||||||
String c = "&";
|
String c = "&";
|
||||||
if (wasFirstParam) {
|
if (wasFirstParam) {
|
||||||
c = "?";
|
c = "?";
|
||||||
}
|
}
|
||||||
url = url.substring(0, paramIndex) + c + url.substring(nextParam+1, url.length());
|
url = url.substring(0, paramIndex) + c + url.substring(nextParam + 1, url.length());
|
||||||
} else {
|
} else {
|
||||||
url = url.substring(0, paramIndex);
|
url = url.substring(0, paramIndex);
|
||||||
}
|
}
|
||||||
@ -307,10 +330,9 @@ public class Utils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the current working directory from a given filename
|
* Removes the current working directory from a given filename
|
||||||
* @param file
|
*
|
||||||
* Path to the file
|
* @param file Path to the file
|
||||||
* @return
|
* @return 'file' without the leading current working directory
|
||||||
* 'file' without the leading current working directory
|
|
||||||
*/
|
*/
|
||||||
public static String removeCWD(String file) {
|
public static String removeCWD(String file) {
|
||||||
return removeCWD(new File(file));
|
return removeCWD(new File(file));
|
||||||
@ -320,12 +342,11 @@ public class Utils {
|
|||||||
* Get a list of all Classes within a package.
|
* Get a list of all Classes within a package.
|
||||||
* Works with file system projects and jar files!
|
* Works with file system projects and jar files!
|
||||||
* Borrowed from StackOverflow, but I don't have a link :[
|
* Borrowed from StackOverflow, but I don't have a link :[
|
||||||
* @param pkgname
|
*
|
||||||
* The name of the package
|
* @param pkgname The name of the package
|
||||||
* @return
|
* @return List of classes within the package
|
||||||
* List of classes within the package
|
|
||||||
*/
|
*/
|
||||||
public static ArrayList<Class<?>> getClassesForPackage(String pkgname) {
|
public static List<Class<?>> getClassesForPackage(String pkgname) {
|
||||||
ArrayList<Class<?>> classes = new ArrayList<>();
|
ArrayList<Class<?>> classes = new ArrayList<>();
|
||||||
String relPath = pkgname.replace('.', '/');
|
String relPath = pkgname.replace('.', '/');
|
||||||
URL resource = ClassLoader.getSystemClassLoader().getResource(relPath);
|
URL resource = ClassLoader.getSystemClassLoader().getResource(relPath);
|
||||||
@ -334,7 +355,8 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String fullPath = resource.getFile();
|
String fullPath = resource.getFile();
|
||||||
File directory = null;
|
File directory;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
directory = new File(resource.toURI());
|
directory = new File(resource.toURI());
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
@ -356,8 +378,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// Load from JAR
|
// Load from JAR
|
||||||
try {
|
try {
|
||||||
String jarPath = fullPath
|
String jarPath = fullPath
|
||||||
@ -376,7 +397,7 @@ public class Utils {
|
|||||||
try {
|
try {
|
||||||
classes.add(Class.forName(className));
|
classes.add(Class.forName(className));
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
logger.error("ClassNotFoundException loading " + className);
|
LOGGER.error("ClassNotFoundException loading " + className);
|
||||||
jarFile.close(); // Resource leak fix?
|
jarFile.close(); // Resource leak fix?
|
||||||
throw new RuntimeException("ClassNotFoundException loading " + className);
|
throw new RuntimeException("ClassNotFoundException loading " + className);
|
||||||
}
|
}
|
||||||
@ -384,20 +405,18 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
jarFile.close(); // Eclipse said not closing it would have a resource leak
|
jarFile.close(); // Eclipse said not closing it would have a resource leak
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Error while loading jar file:", e);
|
LOGGER.error("Error while loading jar file:", e);
|
||||||
throw new RuntimeException(pkgname + " (" + directory + ") does not appear to be a valid package", e);
|
throw new RuntimeException(pkgname + " (" + directory + ") does not appear to be a valid package", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final int SHORTENED_PATH_LENGTH = 12;
|
|
||||||
/**
|
/**
|
||||||
* Shortens the path to a file
|
* Shortens the path to a file
|
||||||
* @param path
|
*
|
||||||
* String of the path to the file
|
* @param path String of the path to the file
|
||||||
* @return
|
* @return The simplified path to the file.
|
||||||
* The simplified path to the file.
|
|
||||||
*/
|
*/
|
||||||
public static String shortenPath(String path) {
|
public static String shortenPath(String path) {
|
||||||
return shortenPath(new File(path));
|
return shortenPath(new File(path));
|
||||||
@ -405,10 +424,9 @@ public class Utils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortens the path to a file
|
* Shortens the path to a file
|
||||||
* @param file
|
*
|
||||||
* File object that you want the shortened path of.
|
* @param file File object that you want the shortened path of.
|
||||||
* @return
|
* @return The simplified path to the file.
|
||||||
* The simplified path to the file.
|
|
||||||
*/
|
*/
|
||||||
public static String shortenPath(File file) {
|
public static String shortenPath(File file) {
|
||||||
String path = removeCWD(file);
|
String path = removeCWD(file);
|
||||||
@ -422,10 +440,9 @@ public class Utils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitizes a string so that a filesystem can handle it
|
* Sanitizes a string so that a filesystem can handle it
|
||||||
* @param text
|
*
|
||||||
* The text to be sanitized.
|
* @param text The text to be sanitized.
|
||||||
* @return
|
* @return The sanitized text.
|
||||||
* The sanitized text.
|
|
||||||
*/
|
*/
|
||||||
public static String filesystemSanitized(String text) {
|
public static String filesystemSanitized(String text) {
|
||||||
text = text.replaceAll("[^a-zA-Z0-9.-]", "_");
|
text = text.replaceAll("[^a-zA-Z0-9.-]", "_");
|
||||||
@ -434,8 +451,8 @@ public class Utils {
|
|||||||
|
|
||||||
public static String filesystemSafe(String text) {
|
public static String filesystemSafe(String text) {
|
||||||
text = text.replaceAll("[^a-zA-Z0-9.-]", "_")
|
text = text.replaceAll("[^a-zA-Z0-9.-]", "_")
|
||||||
.replaceAll("__", "_")
|
.replaceAll("__", "_")
|
||||||
.replaceAll("_+$", "");
|
.replaceAll("_+$", "");
|
||||||
if (text.length() > 100) {
|
if (text.length() > 100) {
|
||||||
text = text.substring(0, 99);
|
text = text.substring(0, 99);
|
||||||
}
|
}
|
||||||
@ -451,7 +468,7 @@ public class Utils {
|
|||||||
public static String getOriginalDirectory(String path) {
|
public static String getOriginalDirectory(String path) {
|
||||||
|
|
||||||
int index;
|
int index;
|
||||||
if(isUnix() || isMacOS()) {
|
if (isUnix() || isMacOS()) {
|
||||||
index = path.lastIndexOf('/');
|
index = path.lastIndexOf('/');
|
||||||
} else {
|
} else {
|
||||||
// current OS is windows - nothing to do here
|
// current OS is windows - nothing to do here
|
||||||
@ -459,17 +476,17 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String original = path; // needs to be checked if lowercase exists
|
String original = path; // needs to be checked if lowercase exists
|
||||||
String lastPart = original.substring(index+1).toLowerCase(); // setting lowercase to check if it exists
|
String lastPart = original.substring(index + 1).toLowerCase(); // setting lowercase to check if it exists
|
||||||
|
|
||||||
// Get a List of all Directories and check its lowercase
|
// Get a List of all Directories and check its lowercase
|
||||||
// if file exists return it
|
// if file exists return it
|
||||||
File f = new File(path.substring(0, index));
|
File file = new File(path.substring(0, index));
|
||||||
ArrayList<String> names = new ArrayList<String>(Arrays.asList(f.list()));
|
ArrayList<String> names = new ArrayList<>(Arrays.asList(file.list()));
|
||||||
|
|
||||||
for (String s : names) {
|
for (String name : names) {
|
||||||
if(s.toLowerCase().equals(lastPart)) {
|
if (name.toLowerCase().equals(lastPart)) {
|
||||||
// Building Path of existing file
|
// Building Path of existing file
|
||||||
return path.substring(0, index) + File.separator + s;
|
return path.substring(0, index) + File.separator + name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,14 +495,13 @@ public class Utils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an integer into a human readable string
|
* Converts an integer into a human readable string
|
||||||
* @param bytes
|
*
|
||||||
* Non-human readable integer.
|
* @param bytes Non-human readable integer.
|
||||||
* @return
|
* @return Human readable interpretation of a byte.
|
||||||
* Human readable interpretation of a byte.
|
|
||||||
*/
|
*/
|
||||||
public static String bytesToHumanReadable(int bytes) {
|
public static String bytesToHumanReadable(int bytes) {
|
||||||
float fbytes = (float) bytes;
|
float fbytes = (float) bytes;
|
||||||
String[] mags = new String[] {"", "K", "M", "G", "T"};
|
String[] mags = new String[]{"", "K", "M", "G", "T"};
|
||||||
int magIndex = 0;
|
int magIndex = 0;
|
||||||
while (fbytes >= 1024) {
|
while (fbytes >= 1024) {
|
||||||
fbytes /= 1024;
|
fbytes /= 1024;
|
||||||
@ -496,6 +512,7 @@ public class Utils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets and returns a list of all the album rippers present in the "com.rarchives.ripme.ripper.rippers" package.
|
* Gets and returns a list of all the album rippers present in the "com.rarchives.ripme.ripper.rippers" package.
|
||||||
|
*
|
||||||
* @return List<String> of all album rippers present.
|
* @return List<String> of all album rippers present.
|
||||||
*/
|
*/
|
||||||
public static List<String> getListOfAlbumRippers() throws Exception {
|
public static List<String> getListOfAlbumRippers() throws Exception {
|
||||||
@ -508,6 +525,7 @@ public class Utils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets and returns a list of all video rippers present in the "com.rarchives.rime.rippers.video" package
|
* Gets and returns a list of all video rippers present in the "com.rarchives.rime.rippers.video" package
|
||||||
|
*
|
||||||
* @return List<String> of all the video rippers.
|
* @return List<String> of all the video rippers.
|
||||||
*/
|
*/
|
||||||
public static List<String> getListOfVideoRippers() throws Exception {
|
public static List<String> getListOfVideoRippers() throws Exception {
|
||||||
@ -520,8 +538,8 @@ public class Utils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays a sound from a file.
|
* Plays a sound from a file.
|
||||||
* @param filename
|
*
|
||||||
* Path to the sound file
|
* @param filename Path to the sound file
|
||||||
*/
|
*/
|
||||||
public static void playSound(String filename) {
|
public static void playSound(String filename) {
|
||||||
URL resource = ClassLoader.getSystemClassLoader().getResource(filename);
|
URL resource = ClassLoader.getSystemClassLoader().getResource(filename);
|
||||||
@ -535,7 +553,7 @@ public class Utils {
|
|||||||
clip.open(AudioSystem.getAudioInputStream(resource));
|
clip.open(AudioSystem.getAudioInputStream(resource));
|
||||||
clip.start();
|
clip.start();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed to play sound " + filename, e);
|
LOGGER.error("Failed to play sound " + filename, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,57 +562,55 @@ public class Utils {
|
|||||||
*/
|
*/
|
||||||
public static void configureLogger() {
|
public static void configureLogger() {
|
||||||
LogManager.shutdown();
|
LogManager.shutdown();
|
||||||
String logFile;
|
String logFile = getConfigBoolean("log.save", false) ? "log4j.file.properties" : "log4j.properties";
|
||||||
if (getConfigBoolean("log.save", false)) {
|
|
||||||
logFile = "log4j.file.properties";
|
try (InputStream stream = Utils.class.getClassLoader().getResourceAsStream(logFile)) {
|
||||||
|
if (stream == null) {
|
||||||
|
PropertyConfigurator.configure("src/main/resources/" + logFile);
|
||||||
|
} else {
|
||||||
|
PropertyConfigurator.configure(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Loaded " + logFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
logFile = "log4j.properties";
|
|
||||||
}
|
|
||||||
InputStream stream = Utils.class.getClassLoader().getResourceAsStream(logFile);
|
|
||||||
if (stream == null) {
|
|
||||||
PropertyConfigurator.configure("src/main/resources/" + logFile);
|
|
||||||
} else {
|
|
||||||
PropertyConfigurator.configure(stream);
|
|
||||||
}
|
|
||||||
logger.info("Loaded " + logFile);
|
|
||||||
try {
|
|
||||||
stream.close();
|
|
||||||
} catch (IOException e) { }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets list of strings between two strings.
|
* Gets list of strings between two strings.
|
||||||
|
*
|
||||||
* @param fullText Text to retrieve from.
|
* @param fullText Text to retrieve from.
|
||||||
* @param start String that precedes the desired text
|
* @param start String that precedes the desired text
|
||||||
* @param finish String that follows the desired text
|
* @param finish String that follows the desired text
|
||||||
* @return List of all strings that are between 'start' and 'finish'
|
* @return List of all strings that are between 'start' and 'finish'
|
||||||
*/
|
*/
|
||||||
public static List<String> between(String fullText, String start, String finish) {
|
public static List<String> between(String fullText, String start, String finish) {
|
||||||
List<String> result = new ArrayList<>();
|
List<String> result = new ArrayList<>();
|
||||||
int i, j;
|
int i = fullText.indexOf(start);
|
||||||
i = fullText.indexOf(start);
|
|
||||||
while (i >= 0) {
|
while (i >= 0) {
|
||||||
i += start.length();
|
i += start.length();
|
||||||
j = fullText.indexOf(finish, i);
|
int j = fullText.indexOf(finish, i);
|
||||||
if (j < 0) {
|
if (j < 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
result.add(fullText.substring(i, j));
|
result.add(fullText.substring(i, j));
|
||||||
i = fullText.indexOf(start, j + finish.length());
|
i = fullText.indexOf(start, j + finish.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an URL query
|
* Parses an URL query
|
||||||
*
|
*
|
||||||
* @param query
|
* @param query The query part of an URL
|
||||||
* The query part of an URL
|
|
||||||
* @return The map of all query parameters
|
* @return The map of all query parameters
|
||||||
*/
|
*/
|
||||||
public static Map<String,String> parseUrlQuery(String query) {
|
public static Map<String, String> parseUrlQuery(String query) {
|
||||||
Map<String,String> res = new HashMap<>();
|
Map<String, String> res = new HashMap<>();
|
||||||
|
|
||||||
if (query.equals("")) {
|
if (query.equals("")) {
|
||||||
return res;
|
return res;
|
||||||
@ -622,10 +638,8 @@ public class Utils {
|
|||||||
/**
|
/**
|
||||||
* Parses an URL query and returns the requested parameter's value
|
* Parses an URL query and returns the requested parameter's value
|
||||||
*
|
*
|
||||||
* @param query
|
* @param query The query part of an URL
|
||||||
* The query part of an URL
|
* @param key The key whose value is requested
|
||||||
* @param key
|
|
||||||
* The key whose value is requested
|
|
||||||
* @return The associated value or null if key wasn't found
|
* @return The associated value or null if key wasn't found
|
||||||
*/
|
*/
|
||||||
public static String parseUrlQuery(String query, String key) {
|
public static String parseUrlQuery(String query, String key) {
|
||||||
@ -655,18 +669,13 @@ public class Utils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HashMap<String, HashMap<String, String>> cookieCache;
|
|
||||||
static {
|
|
||||||
cookieCache = new HashMap<String, HashMap<String, String>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all the cookies from a certain host
|
* Gets all the cookies from a certain host
|
||||||
*/
|
*/
|
||||||
public static Map<String, String> getCookies(String host) {
|
public static Map<String, String> getCookies(String host) {
|
||||||
HashMap<String, String> domainCookies = cookieCache.get(host);
|
HashMap<String, String> domainCookies = cookieCache.get(host);
|
||||||
if (domainCookies == null) {
|
if (domainCookies == null) {
|
||||||
domainCookies = new HashMap<String, String>();
|
domainCookies = new HashMap<>();
|
||||||
String cookiesConfig = getConfigString("cookies." + host, "");
|
String cookiesConfig = getConfigString("cookies." + host, "");
|
||||||
for (String pair : cookiesConfig.split(" ")) {
|
for (String pair : cookiesConfig.split(" ")) {
|
||||||
pair = pair.trim();
|
pair = pair.trim();
|
||||||
@ -690,20 +699,68 @@ public class Utils {
|
|||||||
if (langSelect == null) {
|
if (langSelect == null) {
|
||||||
if (!getConfigString("lang", "").equals("")) {
|
if (!getConfigString("lang", "").equals("")) {
|
||||||
String[] langCode = getConfigString("lang", "").split("_");
|
String[] langCode = getConfigString("lang", "").split("_");
|
||||||
logger.info("Setting locale to " + getConfigString("lang", ""));
|
LOGGER.info("Setting locale to " + getConfigString("lang", ""));
|
||||||
return ResourceBundle.getBundle("LabelsBundle", new Locale(langCode[0], langCode[1]), new UTF8Control());
|
return ResourceBundle.getBundle("LabelsBundle", new Locale(langCode[0], langCode[1]), new UTF8Control());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
String[] langCode = langSelect.split("_");
|
String[] langCode = langSelect.split("_");
|
||||||
logger.info("Setting locale to " + langSelect);
|
LOGGER.info("Setting locale to " + langSelect);
|
||||||
return ResourceBundle.getBundle("LabelsBundle", new Locale(langCode[0], langCode[1]), new UTF8Control());
|
return ResourceBundle.getBundle("LabelsBundle", new Locale(langCode[0], langCode[1]), new UTF8Control());
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
logger.info("Setting locale to default");
|
LOGGER.info("Setting locale to default");
|
||||||
return ResourceBundle.getBundle("LabelsBundle", Locale.getDefault(), new UTF8Control());
|
return ResourceBundle.getBundle("LabelsBundle", Locale.getDefault(), new UTF8Control());
|
||||||
} catch (MissingResourceException e) {
|
} catch (MissingResourceException e) {
|
||||||
logger.info("Setting locale to root");
|
LOGGER.info("Setting locale to root");
|
||||||
return ResourceBundle.getBundle("LabelsBundle", Locale.ROOT);
|
return ResourceBundle.getBundle("LabelsBundle", Locale.ROOT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats and reuturns the status text for rippers using the byte progress bar
|
||||||
|
*
|
||||||
|
* @param completionPercentage An int between 0 and 100 which repersents how close the download is to complete
|
||||||
|
* @param bytesCompleted How many bytes have been downloaded
|
||||||
|
* @param bytesTotal The total size of the file that is being downloaded
|
||||||
|
* @return Returns the formatted status text for rippers using the byte progress bar
|
||||||
|
*/
|
||||||
|
public static String getByteStatusText(int completionPercentage, int bytesCompleted, int bytesTotal) {
|
||||||
|
return String.valueOf(completionPercentage) +
|
||||||
|
"% - " +
|
||||||
|
Utils.bytesToHumanReadable(bytesCompleted) +
|
||||||
|
" / " +
|
||||||
|
Utils.bytesToHumanReadable(bytesTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getEXTFromMagic(byte[] magic) {
|
||||||
|
if (Arrays.equals(magic, new byte[]{-1, -40, -1, -37, 0, 0, 0, 0})) {
|
||||||
|
return "jpeg";
|
||||||
|
} else {
|
||||||
|
LOGGER.info("Unknown magic number " + Arrays.toString(magic));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if a file exists ignoring it's extension.
|
||||||
|
// Code from: https://stackoverflow.com/a/17698068
|
||||||
|
public static boolean fuzzyExists(File folder, String fileName) {
|
||||||
|
if (!folder.exists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
File[] listOfFiles = folder.listFiles();
|
||||||
|
if (listOfFiles == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (File file : listOfFiles) {
|
||||||
|
if (file.isFile()) {
|
||||||
|
String[] filename = file.getName().split("\\.(?=[^\\.]+$)"); //split filename from it's extension
|
||||||
|
if(filename[0].equalsIgnoreCase(fileName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
37
src/main/resources/LabelsBundle_it_IT.properties
Normal file
37
src/main/resources/LabelsBundle_it_IT.properties
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
Log = Log
|
||||||
|
History = Cronologia
|
||||||
|
created = creato
|
||||||
|
modified = modificato
|
||||||
|
Queue = Coda
|
||||||
|
Configuration = Configurazione
|
||||||
|
|
||||||
|
# Keys for the Configuration menu
|
||||||
|
|
||||||
|
current.version = Versione Corrente
|
||||||
|
check.for.updates = Controlla Aggiornamenti
|
||||||
|
auto.update = Aggiornamento automatico?
|
||||||
|
max.download.threads = Thread massini
|
||||||
|
timeout.mill = Timeout (in millisecondi):
|
||||||
|
retry.download.count = Tentativi di download
|
||||||
|
overwrite.existing.files = Sovrascrivi file esistenti?
|
||||||
|
sound.when.rip.completes = Suono al terminie
|
||||||
|
preserve.order = Conserva ordine
|
||||||
|
save.logs = Salva log
|
||||||
|
notification.when.rip.starts = Notifica inizio
|
||||||
|
save.urls.only = Salva solo URL
|
||||||
|
save.album.titles = Salva titoli album
|
||||||
|
autorip.from.clipboard = Scarica da appunti
|
||||||
|
save.descriptions = Salva descrizioni
|
||||||
|
prefer.mp4.over.gif = Preferisci MP4 a GIF
|
||||||
|
restore.window.position = Ripristina posizione della finestra
|
||||||
|
remember.url.history = Riscorda la cronologia degli URL
|
||||||
|
loading.history.from = Carica cronologia da
|
||||||
|
|
||||||
|
# Misc UI keys
|
||||||
|
|
||||||
|
loading.history.from.configuration = Caricamento cronologia da configurazione
|
||||||
|
interrupted.while.waiting.to.rip.next.album = Interrotto mentre scaricavo album successivo
|
||||||
|
inactive = Inattivo
|
||||||
|
re-rip.checked = Re-rip selezionato
|
||||||
|
remove = Rimuovi
|
||||||
|
clear = Pulisci
|
37
src/main/resources/LabelsBundle_nl_NL.properties
Normal file
37
src/main/resources/LabelsBundle_nl_NL.properties
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
Log = Logboek
|
||||||
|
History = Geschiedenis
|
||||||
|
created = gemaakt
|
||||||
|
modified = aangepast
|
||||||
|
Queue = Wachtrij
|
||||||
|
Configuration = Configuratie
|
||||||
|
|
||||||
|
# Keys for the Configuration menu
|
||||||
|
|
||||||
|
current.version = Huidige versie
|
||||||
|
check.for.updates = Controleer op updates
|
||||||
|
auto.update = Auto-update?
|
||||||
|
max.download.threads = Maximale downloadthreads
|
||||||
|
timeout.mill = Timeout (in milliseconden):
|
||||||
|
retry.download.count = Aantal keren opnieuw proberen te downloaden
|
||||||
|
overwrite.existing.files = Bestaande bestanden overschrijven?
|
||||||
|
sound.when.rip.completes = Geluid wanneer rip klaar is
|
||||||
|
preserve.order = Volgorde behouden
|
||||||
|
save.logs = Logbestanden opslaan
|
||||||
|
notification.when.rip.starts = Notificatie wanneer rip start
|
||||||
|
save.urls.only = Alleen URLs opslaan
|
||||||
|
save.album.titles = Album titels opslaan
|
||||||
|
autorip.from.clipboard = Rip automatisch van klembord
|
||||||
|
save.descriptions = Beschrijvingen opslaan
|
||||||
|
prefer.mp4.over.gif = Geef de voorkeur aan MP4 over GIF
|
||||||
|
restore.window.position = Vensterpositie herstellen
|
||||||
|
remember.url.history = URL geschiedenis onthouden
|
||||||
|
loading.history.from = Geschiedenis laden van
|
||||||
|
|
||||||
|
# Misc UI keys
|
||||||
|
|
||||||
|
loading.history.from.configuration = Geschiedenis laden van configuratie
|
||||||
|
interrupted.while.waiting.to.rip.next.album = Onderbroken tijdens het wachten om volgend album te rippen
|
||||||
|
inactive = Inactief
|
||||||
|
re-rip.checked = Re-rip Gecheckt
|
||||||
|
remove = Verwijderen
|
||||||
|
clear = Opruimen
|
37
src/main/resources/LabelsBundle_ru_RU.properties
Normal file
37
src/main/resources/LabelsBundle_ru_RU.properties
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
Log = Лог
|
||||||
|
History = История
|
||||||
|
created = создано
|
||||||
|
modified = изменено
|
||||||
|
Queue = Очередь
|
||||||
|
Configuration = Настройки
|
||||||
|
|
||||||
|
# Keys for the Configuration menu
|
||||||
|
|
||||||
|
current.version = Текущая версия
|
||||||
|
check.for.updates = Проверить обновления
|
||||||
|
auto.update = Автообновление?
|
||||||
|
max.download.threads = Максимальное число потоков:
|
||||||
|
timeout.mill = Задержка (в миллисекундах):
|
||||||
|
retry.download.count = Число повторов
|
||||||
|
overwrite.existing.files = Перезаписать существующие файлы?
|
||||||
|
sound.when.rip.completes = Звук при завершении
|
||||||
|
preserve.order = Сохранять порядок
|
||||||
|
save.logs = Сохранять логи
|
||||||
|
notification.when.rip.starts = Уведомление при запуске
|
||||||
|
save.urls.only = Сохранять только ссылки
|
||||||
|
save.album.titles = Сохранять названия альбомов
|
||||||
|
autorip.from.clipboard = Автоскачивание из буфера
|
||||||
|
save.descriptions = Сохранять описания
|
||||||
|
prefer.mp4.over.gif = Предпочесть MP4 вместо GIF
|
||||||
|
restore.window.position = Восстановить положение окна
|
||||||
|
remember.url.history = Запоминать историю запросов
|
||||||
|
loading.history.from = Загрузить историю из
|
||||||
|
|
||||||
|
# Misc UI keys
|
||||||
|
|
||||||
|
loading.history.from.configuration = Загрузить историю из настроек
|
||||||
|
interrupted.while.waiting.to.rip.next.album = Прервано во время ожидания скачивания следующего альбома
|
||||||
|
inactive = Неактивно
|
||||||
|
re-rip.checked = Перекачать выбранное
|
||||||
|
remove = Удалить
|
||||||
|
clear = Очистить
|
45
src/test/java/com/rarchives/ripme/tst/UtilsTest.java
Normal file
45
src/test/java/com/rarchives/ripme/tst/UtilsTest.java
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package com.rarchives.ripme.tst;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
import com.rarchives.ripme.utils.Utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class UtilsTest extends TestCase {
|
||||||
|
|
||||||
|
public void testGetEXTFromMagic() {
|
||||||
|
assertEquals("jpeg", Utils.getEXTFromMagic(new byte[]{-1, -40, -1, -37, 0, 0, 0, 0}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testStripURLParameter() {
|
||||||
|
assertEquals("http://example.tld/image.ext",
|
||||||
|
Utils.stripURLParameter("http://example.tld/image.ext?param", "param"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testShortenPath() {
|
||||||
|
String path = "/test/test/test/test/test/test/test/test/";
|
||||||
|
assertEquals("/test/test1", Utils.shortenPath("/test/test1"));
|
||||||
|
assertEquals("/test/test/t...st/test/test", Utils.shortenPath(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBytesToHumanReadable() {
|
||||||
|
assertEquals("10.00iB", Utils.bytesToHumanReadable(10));
|
||||||
|
assertEquals("1.00KiB", Utils.bytesToHumanReadable(1024));
|
||||||
|
assertEquals("1.00MiB", Utils.bytesToHumanReadable(1024 * 1024));
|
||||||
|
assertEquals("1.00GiB", Utils.bytesToHumanReadable(1024 * 1024 * 1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetListOfAlbumRippers() throws Exception{
|
||||||
|
assert(!Utils.getListOfAlbumRippers().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetByteStatusText() {
|
||||||
|
assertEquals("5% - 500.00iB / 97.66KiB", Utils.getByteStatusText(5, 500, 100000));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBetween() {
|
||||||
|
assertEquals(Arrays.asList(" is a "), Utils.between("This is a test", "This", "test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.rarchives.ripme.tst.ripper.rippers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.rippers.ArtStationRipper;
|
||||||
|
|
||||||
|
public class ArtStationRipperTest extends RippersTest {
|
||||||
|
|
||||||
|
public void testArtStationProjects() throws IOException {
|
||||||
|
List<URL> contentURLs = new ArrayList<>();
|
||||||
|
contentURLs.add(new URL("https://www.artstation.com/artwork/the-dwarf-mortar"));
|
||||||
|
contentURLs.add(new URL("https://www.artstation.com/artwork/K36GR"));
|
||||||
|
contentURLs.add(new URL("http://artstation.com/artwork/5JJQw"));
|
||||||
|
for (URL url : contentURLs) {
|
||||||
|
ArtStationRipper ripper = new ArtStationRipper(url);
|
||||||
|
testRipper(ripper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testArtStationUserProfiles() throws IOException {
|
||||||
|
List<URL> contentURLs = new ArrayList<>();
|
||||||
|
contentURLs.add(new URL("https://www.artstation.com/heitoramatsu"));
|
||||||
|
contentURLs.add(new URL("https://artstation.com/kuvshinov_ilya"));
|
||||||
|
contentURLs.add(new URL("http://artstation.com/givemeapiggy"));
|
||||||
|
for (URL url : contentURLs) {
|
||||||
|
ArtStationRipper ripper = new ArtStationRipper(url);
|
||||||
|
testRipper(ripper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,8 +6,9 @@ import java.net.URL;
|
|||||||
import com.rarchives.ripme.ripper.rippers.BcfakesRipper;
|
import com.rarchives.ripme.ripper.rippers.BcfakesRipper;
|
||||||
|
|
||||||
public class BcfakesRipperTest extends RippersTest {
|
public class BcfakesRipperTest extends RippersTest {
|
||||||
public void testRip() throws IOException {
|
// 21/06/2018 This test was disbaled as the site has experienced notable downtime
|
||||||
BcfakesRipper ripper = new BcfakesRipper(new URL("http://www.bcfakes.com/celebritylist/olivia-wilde/"));
|
// public void testRip() throws IOException {
|
||||||
testRipper(ripper);
|
// BcfakesRipper ripper = new BcfakesRipper(new URL("http://www.bcfakes.com/celebritylist/olivia-wilde/"));
|
||||||
}
|
// testRipper(ripper);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
@ -10,4 +10,9 @@ public class CheveretoRipperTest extends RippersTest {
|
|||||||
CheveretoRipper ripper = new CheveretoRipper(new URL("http://tag-fox.com/album/Thjb"));
|
CheveretoRipper ripper = new CheveretoRipper(new URL("http://tag-fox.com/album/Thjb"));
|
||||||
testRipper(ripper);
|
testRipper(ripper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testSubdirAlbum() throws IOException {
|
||||||
|
CheveretoRipper ripper = new CheveretoRipper(new URL("https://kenzato.uk/booru/album/TnEc"));
|
||||||
|
testRipper(ripper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,24 +2,48 @@ package com.rarchives.ripme.tst.ripper.rippers;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.rarchives.ripme.ripper.rippers.DeviantartRipper;
|
import com.rarchives.ripme.ripper.rippers.DeviantartRipper;
|
||||||
|
import com.rarchives.ripme.utils.Http;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
|
||||||
public class DeviantartRipperTest extends RippersTest {
|
public class DeviantartRipperTest extends RippersTest {
|
||||||
public void testDeviantartAlbum() throws IOException {
|
public void testDeviantartAlbum() throws IOException {
|
||||||
DeviantartRipper ripper = new DeviantartRipper(new URL("http://airgee.deviantart.com/gallery/"));
|
DeviantartRipper ripper = new DeviantartRipper(new URL("https://www.deviantart.com/airgee/gallery/"));
|
||||||
testRipper(ripper);
|
testRipper(ripper);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDeviantartNSFWAlbum() throws IOException {
|
public void testDeviantartNSFWAlbum() throws IOException {
|
||||||
// NSFW gallery
|
// NSFW gallery
|
||||||
DeviantartRipper ripper = new DeviantartRipper(new URL("http://faterkcx.deviantart.com/gallery/"));
|
DeviantartRipper ripper = new DeviantartRipper(new URL("https://www.deviantart.com/faterkcx/gallery/"));
|
||||||
testRipper(ripper);
|
testRipper(ripper);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGetGID() throws IOException {
|
public void testGetGID() throws IOException {
|
||||||
URL url = new URL("http://airgee.deviantart.com/gallery/");
|
URL url = new URL("https://www.deviantart.com/airgee/gallery/");
|
||||||
DeviantartRipper ripper = new DeviantartRipper(url);
|
DeviantartRipper ripper = new DeviantartRipper(url);
|
||||||
assertEquals("airgee", ripper.getGID(url));
|
assertEquals("airgee", ripper.getGID(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testGetGalleryIDAndUsername() throws IOException {
|
||||||
|
URL url = new URL("https://www.deviantart.com/airgee/gallery/");
|
||||||
|
DeviantartRipper ripper = new DeviantartRipper(url);
|
||||||
|
Document doc = Http.url(url).get();
|
||||||
|
assertEquals("airgee", ripper.getUsername(doc));
|
||||||
|
assertEquals("714589", ripper.getGalleryID(doc));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSanitizeURL() throws IOException {
|
||||||
|
List<URL> urls = new ArrayList<URL>();
|
||||||
|
urls.add(new URL("https://www.deviantart.com/airgee/"));
|
||||||
|
urls.add(new URL("https://www.deviantart.com/airgee"));
|
||||||
|
urls.add(new URL("https://www.deviantart.com/airgee/gallery/"));
|
||||||
|
|
||||||
|
for (URL url : urls) {
|
||||||
|
DeviantartRipper ripper = new DeviantartRipper(url);
|
||||||
|
assertEquals("https://www.deviantart.com/airgee/gallery/", ripper.sanitizeURL(url).toExternalForm());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.rarchives.ripme.tst.ripper.rippers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.rippers.EromeRipper;
|
||||||
|
|
||||||
|
public class EromeRipperTest extends RippersTest {
|
||||||
|
|
||||||
|
public void testGetGIDProfilePage() throws IOException {
|
||||||
|
URL url = new URL("https://www.erome.com/Jay-Jenna");
|
||||||
|
EromeRipper ripper = new EromeRipper(url);
|
||||||
|
assertEquals("Jay-Jenna", ripper.getGID(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetGIDAlbum() throws IOException {
|
||||||
|
URL url = new URL("https://www.erome.com/a/KbDAM1XT");
|
||||||
|
EromeRipper ripper = new EromeRipper(url);
|
||||||
|
assertEquals("KbDAM1XT", ripper.getGID(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGetAlbumsToQueue() throws IOException {
|
||||||
|
URL url = new URL("https://www.erome.com/Jay-Jenna");
|
||||||
|
EromeRipper ripper = new EromeRipper(url);
|
||||||
|
assert(2 >= ripper.getAlbumsToQueue(ripper.getFirstPage()).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPageContainsAlbums() throws IOException {
|
||||||
|
URL url = new URL("https://www.erome.com/Jay-Jenna");
|
||||||
|
EromeRipper ripper = new EromeRipper(url);
|
||||||
|
assert(ripper.pageContainsAlbums(url));
|
||||||
|
assert(!ripper.pageContainsAlbums(new URL("https://www.erome.com/a/KbDAM1XT")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRip() throws IOException {
|
||||||
|
URL url = new URL("https://www.erome.com/a/4FqeUxil");
|
||||||
|
EromeRipper ripper = new EromeRipper(url);
|
||||||
|
testRipper(ripper);
|
||||||
|
}
|
||||||
|
}
|
@ -16,4 +16,12 @@ public class FuraffinityRipperTest extends RippersTest {
|
|||||||
FuraffinityRipper ripper = new FuraffinityRipper(url);
|
FuraffinityRipper ripper = new FuraffinityRipper(url);
|
||||||
assertEquals("mustardgas", ripper.getGID(url));
|
assertEquals("mustardgas", ripper.getGID(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testLogin() throws IOException {
|
||||||
|
URL url = new URL("https://www.furaffinity.net/gallery/mustardgas/");
|
||||||
|
FuraffinityRipper ripper = new FuraffinityRipper(url);
|
||||||
|
// Check if the first page contain the username of ripmes shared account
|
||||||
|
Boolean containsUsername = ripper.getFirstPage().html().contains("ripmethrowaway");
|
||||||
|
assert containsUsername;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package com.rarchives.ripme.tst.ripper.rippers;
|
package com.rarchives.ripme.tst.ripper.rippers;
|
||||||
|
|
||||||
import com.rarchives.ripme.ripper.rippers.video.GfycatRipper;
|
import com.rarchives.ripme.ripper.rippers.GfycatRipper;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.rarchives.ripme.tst.ripper.rippers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.rippers.ImagearnRipper;
|
||||||
|
|
||||||
|
public class ImagearnRipperTest extends RippersTest {
|
||||||
|
public void testImagearnRip() throws IOException {
|
||||||
|
ImagearnRipper ripper = new ImagearnRipper(new URL("http://imagearn.com//gallery.php?id=578682"));
|
||||||
|
testRipper(ripper);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.rarchives.ripme.tst.ripper.rippers;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.rippers.LoveromRipper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public class LoveromRipperTest extends RippersTest{
|
||||||
|
|
||||||
|
}
|
@ -7,7 +7,13 @@ import com.rarchives.ripme.ripper.rippers.ManganeloRipper;
|
|||||||
|
|
||||||
public class ManganeloRipperTest extends RippersTest {
|
public class ManganeloRipperTest extends RippersTest {
|
||||||
public void testRip() throws IOException {
|
public void testRip() throws IOException {
|
||||||
ManganeloRipper ripper = new ManganeloRipper(new URL("http://manganelo.com/manga/black_clover"));
|
ManganeloRipper ripper = new ManganeloRipper(new URL("https://manganelo.com/manga/demonic_housekeeper"));
|
||||||
testRipper(ripper);
|
testRipper(ripper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testGetGID() throws IOException {
|
||||||
|
URL url = new URL("https://manganelo.com/manga/demonic_housekeeper");
|
||||||
|
ManganeloRipper ripper = new ManganeloRipper(url);
|
||||||
|
assertEquals("demonic_housekeeper", ripper.getGID(url));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,4 +21,18 @@ public class MyhentaicomicsRipperTest extends RippersTest {
|
|||||||
// Test a tag
|
// Test a tag
|
||||||
assertEquals("2409", ripper.getGID(new URL("http://myhentaicomics.com/index.php/tag/2409/")));
|
assertEquals("2409", ripper.getGID(new URL("http://myhentaicomics.com/index.php/tag/2409/")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testGetAlbumsToQueue() throws IOException {
|
||||||
|
URL url = new URL("https://myhentaicomics.com/index.php/tag/3167/");
|
||||||
|
MyhentaicomicsRipper ripper = new MyhentaicomicsRipper(url);
|
||||||
|
assertEquals(15, ripper.getAlbumsToQueue(ripper.getFirstPage()).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPageContainsAlbums() throws IOException {
|
||||||
|
URL url = new URL("https://myhentaicomics.com/index.php/tag/3167/");
|
||||||
|
URL url2 = new URL("https://myhentaicomics.com/index.php/search?q=test");
|
||||||
|
MyhentaicomicsRipper ripper = new MyhentaicomicsRipper(url);
|
||||||
|
assertTrue(ripper.pageContainsAlbums(url));
|
||||||
|
assertTrue(ripper.pageContainsAlbums(url2));
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.rarchives.ripme.tst.ripper.rippers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.rippers.PicstatioRipper;
|
||||||
|
|
||||||
|
public class PicstatioRipperTest extends RippersTest {
|
||||||
|
|
||||||
|
public void testRip() throws IOException {
|
||||||
|
PicstatioRipper ripper = new PicstatioRipper(new URL("https://www.picstatio.com/aerial-view-wallpapers"));
|
||||||
|
testRipper(ripper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testGID() throws IOException {
|
||||||
|
PicstatioRipper ripper = new PicstatioRipper(new URL("https://www.picstatio.com/aerial-view-wallpapers"));
|
||||||
|
assertEquals("aerial-view-wallpapers", ripper.getGID(new URL("https://www.picstatio.com/aerial-view-wallpapers")));
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,6 @@ import java.io.IOException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
import com.rarchives.ripme.ripper.rippers.RedditRipper;
|
import com.rarchives.ripme.ripper.rippers.RedditRipper;
|
||||||
import com.rarchives.ripme.ripper.rippers.video.GfycatRipper;
|
|
||||||
|
|
||||||
public class RedditRipperTest extends RippersTest {
|
public class RedditRipperTest extends RippersTest {
|
||||||
// https://github.com/RipMeApp/ripme/issues/253 - Disabled tests: RedditRipperTest#testRedditSubreddit*Rip is flaky
|
// https://github.com/RipMeApp/ripme/issues/253 - Disabled tests: RedditRipperTest#testRedditSubreddit*Rip is flaky
|
||||||
|
@ -7,10 +7,8 @@ import java.util.List;
|
|||||||
|
|
||||||
import com.rarchives.ripme.ripper.VideoRipper;
|
import com.rarchives.ripme.ripper.VideoRipper;
|
||||||
import com.rarchives.ripme.ripper.rippers.video.PornhubRipper;
|
import com.rarchives.ripme.ripper.rippers.video.PornhubRipper;
|
||||||
import com.rarchives.ripme.ripper.rippers.video.TwitchVideoRipper;
|
|
||||||
import com.rarchives.ripme.ripper.rippers.video.VineRipper;
|
|
||||||
import com.rarchives.ripme.ripper.rippers.video.XhamsterRipper;
|
import com.rarchives.ripme.ripper.rippers.video.XhamsterRipper;
|
||||||
import com.rarchives.ripme.ripper.rippers.video.XvideosRipper;
|
import com.rarchives.ripme.ripper.rippers.XvideosRipper;
|
||||||
import com.rarchives.ripme.ripper.rippers.video.YoupornRipper;
|
import com.rarchives.ripme.ripper.rippers.video.YoupornRipper;
|
||||||
import com.rarchives.ripme.ripper.rippers.video.YuvutuRipper;
|
import com.rarchives.ripme.ripper.rippers.video.YuvutuRipper;
|
||||||
|
|
||||||
@ -59,16 +57,6 @@ public class VideoRippersTest extends RippersTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testXvideosRipper() throws IOException {
|
|
||||||
List<URL> contentURLs = new ArrayList<>();
|
|
||||||
contentURLs.add(new URL("https://www.xvideos.com/video19719109/ziggy_star_ultra_hard_anal_pounding"));
|
|
||||||
contentURLs.add(new URL("https://www.xvideos.com/video23515878/dee_s_pool_toys"));
|
|
||||||
for (URL url : contentURLs) {
|
|
||||||
XvideosRipper ripper = new XvideosRipper(url);
|
|
||||||
videoTestHelper(ripper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPornhubRipper() throws IOException {
|
public void testPornhubRipper() throws IOException {
|
||||||
List<URL> contentURLs = new ArrayList<>();
|
List<URL> contentURLs = new ArrayList<>();
|
||||||
contentURLs.add(new URL("https://www.pornhub.com/view_video.php?viewkey=ph5a329fa707269"));
|
contentURLs.add(new URL("https://www.pornhub.com/view_video.php?viewkey=ph5a329fa707269"));
|
||||||
@ -78,18 +66,6 @@ public class VideoRippersTest extends RippersTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/RipMeApp/ripme/issues/186
|
|
||||||
/*
|
|
||||||
public void testVineRipper() throws IOException {
|
|
||||||
List<URL> contentURLs = new ArrayList<>();
|
|
||||||
contentURLs.add(new URL("https://vine.co/v/hiqQrP0eUZx"));
|
|
||||||
for (URL url : contentURLs) {
|
|
||||||
VineRipper ripper = new VineRipper(url);
|
|
||||||
videoTestHelper(ripper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public void testYoupornRipper() throws IOException {
|
public void testYoupornRipper() throws IOException {
|
||||||
List<URL> contentURLs = new ArrayList<>();
|
List<URL> contentURLs = new ArrayList<>();
|
||||||
contentURLs.add(new URL("http://www.youporn.com/watch/7669155/mrs-li-amateur-69-orgasm/?from=categ"));
|
contentURLs.add(new URL("http://www.youporn.com/watch/7669155/mrs-li-amateur-69-orgasm/?from=categ"));
|
||||||
|
@ -17,6 +17,11 @@ public class XhamsterRipperTest extends RippersTest {
|
|||||||
testRipper(ripper);
|
testRipper(ripper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testBrazilianXhamster() throws IOException {
|
||||||
|
XhamsterRipper ripper = new XhamsterRipper(new URL("https://pt.xhamster.com/photos/gallery/silvana-7105696"));
|
||||||
|
testRipper(ripper);
|
||||||
|
}
|
||||||
|
|
||||||
public void testGetGID() throws IOException {
|
public void testGetGID() throws IOException {
|
||||||
URL url = new URL("https://xhamster.com/photos/gallery/japanese-dolls-4-asahi-mizuno-7254664");
|
URL url = new URL("https://xhamster.com/photos/gallery/japanese-dolls-4-asahi-mizuno-7254664");
|
||||||
XhamsterRipper ripper = new XhamsterRipper(url);
|
XhamsterRipper ripper = new XhamsterRipper(url);
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.rarchives.ripme.tst.ripper.rippers;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.rippers.XvideosRipper;
|
||||||
|
import com.rarchives.ripme.tst.ripper.rippers.RippersTest;
|
||||||
|
|
||||||
|
public class XvideosRipperTest extends RippersTest {
|
||||||
|
|
||||||
|
public void testXhamsterAlbum1() throws IOException {
|
||||||
|
XvideosRipper ripper = new XvideosRipper(new URL("https://www.xvideos.com/video23515878/dee_s_pool_toys"));
|
||||||
|
testRipper(ripper);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user