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나 직렬화 파일에 저장하는 것을 추천.
'개발이야기 > java' 카테고리의 다른 글
Java Reflection - private 변수값 읽어오기 (0) | 2022.01.20 |
---|---|
개인정보 (이름) 마스킹 로직 (0) | 2022.01.18 |
파파고 번역 구현 (0) | 2022.01.17 |
Instagram4j - Maven Dependency (0) | 2022.01.14 |
x진법 (최대 62진수) 까지 표현해주는 함수 (0) | 2022.01.14 |