package common.util.api.sns;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import com.github.instagram4j.instagram4j.IGClient;
import com.github.instagram4j.instagram4j.IGClient.Builder;
import com.github.instagram4j.instagram4j.actions.users.UserAction;
import com.github.instagram4j.instagram4j.models.media.ImageVersions;
import com.github.instagram4j.instagram4j.models.media.ImageVersionsMeta;
import com.github.instagram4j.instagram4j.models.media.timeline.Comment.Caption;
import com.github.instagram4j.instagram4j.models.media.timeline.ImageCarouselItem;
import com.github.instagram4j.instagram4j.models.user.User;
import com.github.instagram4j.instagram4j.models.media.timeline.TimelineMedia;
import com.github.instagram4j.instagram4j.models.media.timeline.VideoCarouselItem;
import com.github.instagram4j.instagram4j.requests.feed.FeedUserRequest;
import com.github.instagram4j.instagram4j.responses.feed.FeedUserResponse;

import common.util.CryptoUtil;
import common.util.ReflectUtil;
import common.util.StringUtil;
import common.util.stream.FileUtil;
import common.util.stream.StreamUtil;
import mvc.Config;
import mvc.model.DataMap;
import mvc.model.DataSet;

/**
 * 인스타그램 Feed 가져오기
 * 
 * @apiNote 인스타그램 Feed 가져오기
 * @since 2020-01-01
 * @author 청록비
 * @version 2020-01-31
 */

public class InstagramManager {
	private static Builder builder;
	private static IGClient client;
	private static String uploadPath;
	private static String uploadUrl;
	
	static {
		uploadUrl = Config.get("path.upload")+"/social/insta";
		uploadPath = Config.get("path.root") + uploadUrl;
	}
	
	
	private static void getInstance() {
		String username = Config.get("account.instagram.username");
		String password = Config.get("account.instagram.password");
		try {
			if (builder == null) {
				builder = IGClient.builder();
			}

			if(client==null) {
				client = builder.username(username).password(password).login();
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static DataMap getUserTimeline(String targetName) throws Exception {
		return getUserTimeline(targetName, null);
	}
	
	public static DataMap getUserTimeline(String targetName, String maxId) throws Exception {

		// 초 : 1000밀리 / 60초 / 30분 : 30분이 지날 때마다 피드 html을 web에서 가져온다.
		long time = System.currentTimeMillis() / 1000 / 60 / 30;

		String feedSerFilePath = null;
		
		if(StringUtil.isEmpty(maxId)) {
			feedSerFilePath = uploadPath + "/" +targetName+ "/feed_first_" +time+ ".ser";
		} else {
			feedSerFilePath = uploadPath + "/" +targetName+ "/feed_" +maxId+ "_" +time+ ".ser";
		}
		
		File feedSerFile = new File(feedSerFilePath);

		if (feedSerFile.exists() && feedSerFile.length() > 100) {
			DataMap serialFeedSet = getSerialObject(feedSerFile);
			return serialFeedSet;
		}
		
		getInstance();
		
		DataMap feedResult = new DataMap();
		DataSet feedSet = new DataSet();
		feedResult.put("data", feedSet);
		
		try {

			// String auth = client.getAuthorization();
			// String token = client.getCsrfToken();

			CompletableFuture<FeedUserResponse> feedActs = null;
			
			InstaUserInfo info = getUserInfo(targetName);
			long userPk = info.getUserPk();
			
			FeedUserRequest request = null;
			if(StringUtil.isEmpty(maxId)) {
				request = new FeedUserRequest(userPk);
			} else {
				request = new FeedUserRequest(userPk, maxId);
			}
			
			feedActs = client.sendRequest(request);
			FeedUserResponse feedResponse = feedActs.get();
			
			maxId = feedResponse.getNext_max_id();
			feedResult.put("maxId", maxId);
			
			Iterator<TimelineMedia> feedIt = feedResponse.getItems().iterator();
			FeedUploadWorker uploadWorker = new FeedUploadWorker(targetName);
			
			while (feedIt.hasNext()) {
				TimelineMedia feed = feedIt.next();
				String id = feed.getId();
				String code = feed.getCode();
				Caption caption = feed.getCaption();
				String text = "";
				if(caption!=null) {
					text = caption.getText();
				}

				File picDir = new File(uploadPath + "/" + feed.getUser().getUsername());
				String[] picFiles = picDir.list(new FilenameFilter() {
					@Override
					public boolean accept(File dir, String name) {
						return name.startsWith(id);
					}
				});
				

				String picture = null;
				String pictureUrl = null;

				if(picFiles!=null && picFiles.length>0) {
					picture = uploadUrl + "/" + feed.getUser().getUsername() + "/" + picFiles[0];
				} else {
					try {
						Object mediaValue = null;
						int mediaType = Integer.parseInt(feed.getMedia_type());
						switch (mediaType) {
							case 1:
							case 2:
							case 8: {
								mediaValue = ReflectUtil.getDeclaredFieldValue(feed, "carousel_media");
								
								if(mediaValue==null) {
									mediaValue = ReflectUtil.getDeclaredFieldValue(feed, "image_versions2");
								}
								
								break;
							}
							
							default: {
								break;
							}
						}
	
						if (mediaValue != null && mediaValue instanceof List) {
							List<?> mediaList = (List<?>) mediaValue;
							if (mediaList.size() > 0) {
								Object objMedia = mediaList.get(0);
	
								ImageVersionsMeta media = null;
								if (objMedia instanceof ImageVersionsMeta) {
									media = (ImageVersionsMeta) objMedia;

								} else if (objMedia instanceof VideoCarouselItem) {
									VideoCarouselItem item = (VideoCarouselItem) objMedia;
									mediaList = (List<?>) item.getImage_versions2().getCandidates();
									if(mediaList.size()>1) {
										media = (ImageVersionsMeta) mediaList.get(1);
									} else {
										media = (ImageVersionsMeta) mediaList.get(0);	
									}
									
								} else if (objMedia instanceof ImageCarouselItem) {
									ImageCarouselItem item = (ImageCarouselItem) objMedia;
									mediaList = (List<?>) item.getImage_versions2().getCandidates();
									if(mediaList.size()>1) {
										media = (ImageVersionsMeta) mediaList.get(1);
									} else {
										media = (ImageVersionsMeta) mediaList.get(0);	
									}
								}
	
								pictureUrl = ReflectUtil.getDeclaredFieldValue(media, "url").toString();
							}
						} else if(mediaValue instanceof ImageVersions) {
							ImageVersions mediaImages = (ImageVersions)mediaValue;
							List<ImageVersionsMeta> mediaList = mediaImages.getCandidates();
							if(mediaImages.getCandidates().size()>1) {
								pictureUrl = mediaList.get(1).getUrl();	
							} else {
								pictureUrl = mediaList.get(0).getUrl();
							}
						}
						
						String pictureFileName = pictureUrl.split("\\?")[0];
						String pictureFileExt = pictureFileName.substring(pictureFileName.lastIndexOf("."));

						picture = uploadUrl + "/" + feed.getUser().getUsername() + "/" + id + pictureFileExt;
						
						if(Boolean.TRUE) {
							File file = new File(Config.get("path.root") + picture);
							
							if (!file.exists()) {
								uploadWorker.add(pictureUrl, file);
							}
						}
					} catch (Exception e) {
						e.printStackTrace();
						continue;
					}
				}
				
				feedSet.addRow();
				feedSet.put("id", id);
				feedSet.put("code", code);
				feedSet.put("text", text);
				feedSet.put("picture", picture);
			}

			if(uploadWorker.size()>0) {
				Thread uploadThread = new Thread(uploadWorker);
				uploadThread.start();
			}
			
			try {
				FileUtil.doSerialize(feedSerFile, feedResult);
			} catch (Exception e) {
				throw new RuntimeException(e);
			} finally {

				System.gc();
			}

		} catch (Exception e) {
			throw e;
		}

		return feedResult;
	}

	private static InstaUserInfo getUserInfo(String targetName) throws Exception {
		File userFile = new File(Config.get("path.root")+"/upload/social/insta/_user/"+CryptoUtil.md5(targetName));
		
		InstaUserInfo info = null;
		
		if(userFile.exists()) {
			info = FileUtil.deSerialize(userFile);
			
		} else {

			info = new InstaUserInfo();

			CompletableFuture<UserAction> userActs = client.actions().users().findByUsername(targetName);
			User user = userActs.get().getUser();
			
			info.setUserPk(user.getPk());
			
			InputStream userThumbIn = null;

			try {
				userThumbIn = StreamUtil.getWebStream(user.getProfile_pic_url());
				String fileUrl = user.getProfile_pic_url().replaceFirst("(.*)\\?(.*)", "$1");
				String fileExt = fileUrl.substring(fileUrl.lastIndexOf("."));
				File userThumbFile = new File(Config.get("path.root")+"/upload/social/insta/_user/"+CryptoUtil.md5(targetName)+fileExt);
				FileUtil.writeContent(userThumbIn, userThumbFile);
				
				info.setProfileImageUrl("/upload/social/insta/_user/"+CryptoUtil.md5(targetName)+fileExt);
				
				FileUtil.doSerialize(userFile, info);
				
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				if(userThumbIn!=null) {
					userThumbIn.close();
				}
			}

		}
		
		return info;
	}

	private static <T>T getSerialObject(File serFile) throws Exception {
		T serialObj;
		
		try {
			serialObj = FileUtil.deSerialize(serFile);

			File[] files = FileUtil.findFilesByExt(serFile.getParent(), ".ser", FileUtil.SortType.DATE_ASC);

			if (files.length > 7) {
				for (int i = 0; i < files.length - 3; i++) {
					if (files[i].equals(serFile)) {
						continue;
					}

					files[i].delete();
				}
			}

		} catch (Exception e) {
			File[] arrFile = FileUtil.getChildrenFiles(serFile.getParent(), FileUtil.SortType.DATE_DESC);

			for (File file : arrFile) {

				serialObj = FileUtil.deSerialize(file);
				return serialObj;
			}

			throw e;
		}

		return serialObj;
	}
	
	public static boolean isUploadComplete(String user) {
		return FeedUploadWorker.isComplete(user);
	}
}

class FeedUploadWorker implements Runnable {
	private static DataMap cmap;
	private String user;
	private DataSet uploadSet;
	
	static {
		cmap = new DataMap();
	}
	
	public static boolean isComplete(String user) {
		return cmap.b(user, true);
	}
	
	public FeedUploadWorker(String user) {
		uploadSet = new DataSet();
		this.user = user;
	}
	
	public void add(String pictureHttpUrl, File file) {
		uploadSet.addRow();
		uploadSet.put("url", pictureHttpUrl);
		uploadSet.put("file", file);
	}
	
	@Override
	public void run() {
		uploadSet.first();
		cmap.put(user, false);
		
		while(uploadSet.next()) {
			
			String pictureHttpUrl = uploadSet.getRow().s("url");
			File file = (File)uploadSet.getRow().get("file");
			
			InputStream in = null;
			try {
				in = StreamUtil.getWebStream(pictureHttpUrl);
				FileUtil.writeContent(in, file);
				
				if (in != null) {
					in.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		cmap.put(user, true);
		
	}
	
	public int size() {
		return uploadSet.size();
	}
}

class InstaUserInfo implements Serializable {
	private static final long serialVersionUID = 671723062770310318L;
	
	private String profileImageUrl;
	private Long userPk;
	public String getProfileImageUrl() {
		return profileImageUrl;
	}
	public void setProfileImageUrl(String profileImageUrl) {
		this.profileImageUrl = profileImageUrl;
	}
	public Long getUserPk() {
		return userPk;
	}
	public void setUserPk(Long userPk) {
		this.userPk = userPk;
	}
}

 

호출할 때마다 인스타그램에 접속하면 나중에 접속제한에 걸리므로,

일정한 시간 단위로 호출하되 이미지와 텍스트는 따로 DB나 직렬화 파일에 저장하는 것을 추천.

+ Recent posts