content: package me.tagavari.airmessage.task;
  
  import android.webkit.URLUtil;
  import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
  import io.reactivex.rxjava3.annotations.CheckReturnValue;
  import io.reactivex.rxjava3.core.Single;
  import io.reactivex.rxjava3.core.SingleEmitter;
  import io.reactivex.rxjava3.schedulers.Schedulers;
  import org.jsoup.Jsoup;
  import org.jsoup.nodes.Document;
  import org.jsoup.nodes.Element;
  import org.jsoup.select.Elements;
  
  import java.io.IOException;
  import java.net.URI;
  import java.net.URISyntaxException;
  
  //Based on https://github.com/PonnamKarthik/RichLinkPreview (no longer maintained)
  public class RichPreviewTask {
  	@CheckReturnValue
  	public static Single<Metadata> fetchMetadata(String url) {
  		return Single.create((SingleEmitter<Metadata> emitter) -> {
  			//Creating the metadata
  			Metadata metaData = new Metadata();
  			
  			try {
  				//Connecting to the site
  				Document document = Jsoup.connect(url)
  						.timeout(15 * 1000)
  						.get();
  				
  				//Finding meta elements
  				Elements elements = document.getElementsByTag("meta");
  				
  				//Getting the article title
  				{
  					String title = document.select("meta[property=og:title]").attr("content");
  					if(title == null || title.isEmpty()) title = document.title();
  					metaData.setTitle(title);
  				}
  				
  				//Getting the article description
  				{
  					String description = document.select("meta[name=description]").attr("content");
  					if(description == null || description.isEmpty()) description = document.select("meta[name=Description]").attr("content");
  					if(description == null || description.isEmpty()) description = document.select("meta[property=og:description]").attr("content");
  					metaData.setDescription(description);
  				}
  				
  				// getMediaType
  				Elements mediaTypes = document.select("meta[name=medium]");
  				String type;
  				if(mediaTypes.size() > 0) {
  					String media = mediaTypes.attr("content");
  					type = media.equals("image") ? "photo" : media;
  				} else {
  					type = document.select("meta[property=og:type]").attr("content");
  				}
  				metaData.setMediaType(type);
  				
  				//getImages
  				Elements imageElements = document.select("meta[property=og:image]");
  				if(imageElements.size() > 0) {
  					String image = imageElements.attr("content");
  					if(!image.isEmpty()) {
  						metaData.setImageURL(resolveURL(url, image));
  					}
  				}
  				if(metaData.getImageURL().isEmpty()) {
  					String src = document.select("link[rel=image_src]").attr("href");
  					if(!src.isEmpty()) {
  						metaData.setImageURL(resolveURL(url, src));
  					} else {
  						src = document.select("link[rel=apple-touch-icon]").attr("href");
  						if(!src.isEmpty()) {
  							metaData.setImageURL(resolveURL(url, src));
  							metaData.setFavicon(resolveURL(url, src));
  						} else {
  							src = document.select("link[rel=icon]").attr("href");
  							if(!src.isEmpty()) {
  								metaData.setImageURL(resolveURL(url, src));
  								metaData.setFavicon(resolveURL(url, src));
  							}
  						}
  					}
  				}
  				
  				//Getting the favicon
  				{
  					String src = document.select("link[rel=apple-touch-icon]").attr("href");
  					if(src == null || src.isEmpty()) src = document.select("link[rel=icon]").attr("href");
  					if(src == null || src.isEmpty()) metaData.setFavicon(resolveURL(url, src));
  				}
  				
  				//Resolving basic site information
  				for(Element element : elements) {
  					if(!element.hasAttr("property")) continue;
  					
  					String property = element.attr("property").trim();
  					if(property.equals("og:url")) metaData.setUrl(element.attr("content"));
  					else if(property.equals("og:site_name")) metaData.setSiteName(element.attr("content"));
  				}
  				
  				//Checking if an OG url couldn't be found
  				if(metaData.getUrl().isEmpty()) {
  					try {
  						//Attempting to resolve the hostname from the original URL
  						URI uri = new URI(url);
  						metaData.setUrl(uri.getHost());
  					} catch(URISyntaxException exception) {
  						//Printing the stack trace
  						exception.printStackTrace();
  						
  						//Falling back to the raw original URL
  						metaData.setUrl(url);
  					}
  				}
  				
  				//Returning the metadata
  				emitter.onSuccess(metaData);
  			} catch(IOException exception) {
  				//Printing the stack trace
  				exception.printStackTrace();
  				
  				//Emitting the error
  				emitter.onError(exception);
  			}
  		}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
  	}
  	
  	private static String resolveURL(String url, String part) {
  		if(URLUtil.isValidUrl(part)) {
  			return part;
  		} else {
  			try {
  				URI baseURI = new URI(url);
  				baseURI = baseURI.resolve(part);
  				return baseURI.toString();
  			} catch(URISyntaxException | IllegalArgumentException exception) {
  				exception.printStackTrace();
  				return "";
  			}
  		}
  	}
  	
  	public static class Metadata {
  		private String url = "";
  		private String imageURL = "";
  		private String title = "";
  		private String description = "";
  		private String siteName = "";
  		private String mediaType = "";
  		private String favicon = "";
  		
  		public String getUrl() {
  			return url;
  		}
  		
  		public void setUrl(String url) {
  			this.url = url;
  		}
  		
  		public String getImageURL() {
  			return imageURL;
  		}
  		
  		public void setImageURL(String imageURL) {
  			this.imageURL = imageURL;
  		}
  		
  		public String getTitle() {
  			return title;
  		}
  		
  		public void setTitle(String title) {
  			this.title = title;
  		}
  		
  		public String getDescription() {
  			return description;
  		}
  		
  		public void setDescription(String description) {
  			this.description = description;
  		}
  		
  		public String getSiteName() {
  			return siteName;
  		}
  		
  		public void setSiteName(String siteName) {
  			this.siteName = siteName;
  		}
  		
  		public String getMediaType() {
  			return mediaType;
  		}
  		
  		public void setMediaType(String mediaType) {
  			this.mediaType = mediaType;
  		}
  		
  		public String getFavicon() {
  			return favicon;
  		}
  		
  		public void setFavicon(String favicon) {
  			this.favicon = favicon;
  		}
  	}
  }