package com.sdsmdg.harjot.MusicDNA.lyrics;
import com.sdsmdg.harjot.MusicDNA.annotations.Reflection;
import com.sdsmdg.harjot.MusicDNA.utilities.Levenshtein;
import com.sdsmdg.harjot.MusicDNA.utilities.Net;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import static com.sdsmdg.harjot.MusicDNA.lyrics.Lyrics.NEGATIVE_RESULT;
import static com.sdsmdg.harjot.MusicDNA.lyrics.Lyrics.NO_RESULT;
import static com.sdsmdg.harjot.MusicDNA.lyrics.Lyrics.POSITIVE_RESULT;
import static com.sdsmdg.harjot.MusicDNA.lyrics.Lyrics.SEARCH_ITEM;
public class ViewLyrics {
/*
* Needed data
*/
private static final String url = "http://search.crintsoft.com/searchlyrics.htm";
//ACTUAL: http://search.crintsoft.com/searchlyrics.htm
//CLASSIC: http://www.viewlyrics.com:1212/searchlyrics.htm
private static final String clientUserAgent = "MiniLyrics4Android";
//NORMAL: MiniLyrics <version> for <player>
//EXAMPLE: MiniLyrics 7.6.44 for Windows Media Player
//MOBILE: MiniLyrics4Android
private static final String clientTag = "client=\"ViewLyricsOpenSearcher\"";
//NORMAL: MiniLyrics
//MOBILE: MiniLyricsForAndroid
private static final String searchQueryBase = "<?xml version='1.0' encoding='utf-8' ?><searchV1 artist=\"%s\" title=\"%s\" OnlyMatched=\"1\" %s/>";
private static final String searchQueryPage = " RequestPage='%d'";
private static final byte[] magickey = "Mlv1clt4.0".getBytes();
/*
* Search function
*/
@Reflection
public static Lyrics fromURL(String url, String artist, String title){
// TODO: support ViewLyrics URL
return new Lyrics(NO_RESULT);
}
public static Lyrics fromMetaData(String artist, String title) throws IOException, NoSuchAlgorithmException, SAXException, ParserConfigurationException {
ArrayList<Lyrics> results =
search(
String.format(searchQueryBase, artist, title, clientTag +
String.format(searchQueryPage, 0)) // Create XMLQuery String
);
if (results.size() == 0)
return new Lyrics(NEGATIVE_RESULT);
String url = results.get(0).getURL();
url = url.replace("minilyrics", "viewlyrics");
int artistDistance = Levenshtein.distance(results.get(0).getArtist(), artist);
int titleDistance = Levenshtein.distance(results.get(0).getTrack(), title);
if (url.endsWith("txt") || artistDistance > 6 || titleDistance > 6)
return new Lyrics(NEGATIVE_RESULT);
Lyrics result = new Lyrics(POSITIVE_RESULT);
result.setTitle(title);
result.setArtist(artist);
result.setLRC(url.endsWith("lrc"));
result.setText(Net.getUrlAsString(url).replaceAll("(\\[(?=.[a-z]).+\\]|<.+?>|www.*[\\s])", "").replaceAll("[\n\r]", " ").replaceAll("\\[", "\n\\["));
result.setSource(clientUserAgent);
return result;
}
private static ArrayList<Lyrics> search(String searchQuery) throws IOException, ParserConfigurationException, SAXException, NoSuchAlgorithmException {
OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(10, TimeUnit.SECONDS);
client.setReadTimeout(30, TimeUnit.SECONDS);
RequestBody body = RequestBody.create(MediaType.parse("application/text"), assembleQuery(searchQuery.getBytes("UTF-8")));
Request request = new Request.Builder()
.header("User-Agent", clientUserAgent)
.post(body)
.url(url)
.build();
Response response = client.newCall(request).execute();
BufferedReader rd = new BufferedReader
(new InputStreamReader(response.body().byteStream(), "ISO_8859_1"));
// Get full result
StringBuilder builder = new StringBuilder();
char[] buffer = new char[8192];
int read;
while ((read = rd.read(buffer, 0, buffer.length)) > 0) {
builder.append(buffer, 0, read);
}
String full = builder.toString();
// Decrypt, parse, store, and return the result list
return parseResultXML(decryptResultXML(full));
}
/*
* Add MD5 and Encrypts Search Query
*/
public static byte[] assembleQuery(byte[] valueBytes) throws NoSuchAlgorithmException, IOException {
// Create the variable POG to be used in a dirt code
byte[] pog = new byte[valueBytes.length + magickey.length]; //TODO Give a better name then POG
// POG = XMLQuery + Magic Key
System.arraycopy(valueBytes, 0, pog, 0, valueBytes.length);
System.arraycopy(magickey, 0, pog, valueBytes.length, magickey.length);
// POG is hashed using MD5
byte[] pog_md5 = MessageDigest.getInstance("MD5").digest(pog);
//TODO Thing about using encryption or k as 0...
// Prepare encryption key
int j = 0;
for (byte octet : valueBytes) {
j += octet;
}
int k = (byte) (j / valueBytes.length);
// Value is encrypted
for (int m = 0; m < valueBytes.length; m++)
valueBytes[m] = (byte) (k ^ valueBytes[m]);
// Prepare result code
ByteArrayOutputStream result = new ByteArrayOutputStream();
// Write Header
result.write(0x02);
result.write(k);
result.write(0x04);
result.write(0x00);
result.write(0x00);
result.write(0x00);
// Write Generated MD5 of POG problaby to be used in a search cache
result.write(pog_md5);
// Write encrypted value
result.write(valueBytes);
// Return magic encoded query
return result.toByteArray();
}
/*
* Decrypts only the XML from the entire result
*/
public static String decryptResultXML(String value) {
// Get Magic key value
char magickey = value.charAt(1);
// Prepare output
ByteArrayOutputStream neomagic = new ByteArrayOutputStream();
// Decrypts only the XML
for (int i = 22; i < value.length(); i++)
neomagic.write((byte) (value.charAt(i) ^ magickey));
// Return value
return neomagic.toString();
}
/*
* Create the ArrayList<LyricInfo>
*/
private static String readStrFromAttr(Element elem, String attr, String def) {
String data = elem.getAttribute(attr);
try {
if (data != null && data.length() > 0)
return data;
} catch (NumberFormatException e) {
e.printStackTrace();
}
return def;
}
public static ArrayList<Lyrics> parseResultXML(String resultXML) throws SAXException, IOException, ParserConfigurationException {
// Create array for storing the results
ArrayList<Lyrics> availableLyrics = new ArrayList<>();
// Parse XML
ByteArrayInputStream resultBA = new ByteArrayInputStream(resultXML.getBytes("UTF-8"));
Element resultRootElem = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(resultBA).getDocumentElement();
String server_url = readStrFromAttr(resultRootElem, "server_url", "http://www.viewlyrics.com/");
NodeList resultItemList = resultRootElem.getElementsByTagName("fileinfo");
for (int i = 0; i < resultItemList.getLength(); i++) {
Element itemElem = (Element) resultItemList.item(i);
Lyrics item = new Lyrics(SEARCH_ITEM);
item.setURL(server_url + readStrFromAttr(itemElem, "link", ""));
item.setArtist(readStrFromAttr(itemElem, "artist", ""));
item.setTitle(readStrFromAttr(itemElem, "title", ""));
//item.setLyricsFileName(readStrFromAttr(itemElem, "filename", ""));
//itemInfo.setFType(readIntFromAttr(itemElem, "file_type", 0));
//itemInfo.setMatchVal(readFloatFromAttr(itemElem, "match_value", 0.0F));
//itemInfo.setTimeLenght(readIntFromAttr(itemElem, "timelength", 0));
availableLyrics.add(item);
}
return availableLyrics;
}
}