diff --git a/list_pages.txt b/BEST_GROUPs_AND_PAGEs.txt similarity index 61% rename from list_pages.txt rename to BEST_GROUPs_AND_PAGEs.txt index b585bf3..3427f7e 100644 --- a/list_pages.txt +++ b/BEST_GROUPs_AND_PAGEs.txt @@ -1,4 +1,4 @@ -Danh sách page nhiều girl xinh: +Danh sách PAGE nhiều girl xinh: ColourfulSpace 33841 ảnh Gentle ∆ 1460 ảnh girlxinhpro 302 ảnh @@ -8,7 +8,11 @@ NgamGaiDep 248 ảnh viral.tiktok.2019 1282 ảnh 369Choedphong 772 ảnh -Danh sách page nhiều hình đẹp: +Danh sách PAGE nhiều hình đẹp: deviantart 10536 ảnh 4k.hd.walpaper 123 ảnh -khohinhnen 1877 ảnh \ No newline at end of file +khohinhnen 1877 ảnh + +Danh sách GROUP nhiều girl xinh: +J2team girls 8135 ảnh +VSBG ? diff --git a/config.js b/config.js index 2ba6f6f..839a860 100644 --- a/config.js +++ b/config.js @@ -1,10 +1,9 @@ -// do not modify this -export const FB_API_HOST = "https://graph.facebook.com/v12.0"; - // you can modify all the variables below -export const ACCESS_TOKEN = "YOUR_ACCESS_TOKEN"; +export const ACCESS_TOKEN = "EAAAAZAw4FxQIBAON6KcVr9O2gvkVJHq1QmCc8uV2wyJTf7Uwk0vqDLmaZA39ZAD4CdXRT0Yspp7WS6zNHY9B3t5iQ8iVGbwkn7769qBJod8gn9YeERZBnQ0r8Yz5ljk7DAQhQn1HO77c36UqjtPJfNiDgQUJybiuNrkVDENkRAZDZD"; export const WAIT_BEFORE_NEXT_FETCH = 0; export const ID_LINK_SEPERATOR = ";"; export const PHOTO_FILE_FORMAT = 'png'; // OR jpg +export const VIDEO_FILE_FORMAT = 'mp4'; // OR wav ? export const FOLDER_TO_SAVE_LINKS = "downloads/links"; -export const FOLDER_TO_SAVE_IMAGES = 'downloads/images'; \ No newline at end of file +export const FOLDER_TO_SAVE_IMAGES = 'downloads/images'; +export const FOLDER_TO_SAVE_GROUP_MEDIA = 'downloads/group_media'; \ No newline at end of file diff --git a/downloads/group_media/697332711026460/1549259735237129.mp4 b/downloads/group_media/697332711026460/1549259735237129.mp4 new file mode 100644 index 0000000..5ad0a71 Binary files /dev/null and b/downloads/group_media/697332711026460/1549259735237129.mp4 differ diff --git a/index.js b/index.js index 3929ff2..10df570 100644 --- a/index.js +++ b/index.js @@ -13,15 +13,15 @@ import { // menu(); // ============ Lấy thông tin album timeline từ page id trên fb ============ -(async () => { - const album_id = await fetchTimeLineAlbumId("ColourfulSpace"); - console.log(album_id); +// (async () => { +// const album_id = await fetchTimeLineAlbumId("ColourfulSpace"); +// console.log(album_id); - if (album_id) { - const album_info = await fetchAlbumInfo(album_id); - console.log(album_info); - } -})(); +// if (album_id) { +// const album_info = await fetchAlbumInfo(album_id); +// console.log(album_info); +// } +// })(); // ============ Lưu tất cả hình trong timeline album của 1 page fb ============ // saveTimeLineAlbum_FBPage("BoxGirlVn"); @@ -30,7 +30,7 @@ import { // saveTimeLineAlbumPhotoLinks_FBPage("BoxGirlVn"); // ============ Lưu tất cả hình trong 1 album bất kỳ (nếu biết trước id của album) ============ -// saveAlbumPhoto("245004546697321"); +saveAlbumPhoto("245004546697321"); // ============ Lưu tất cả id ảnh và link ảnh trong 1 album bất kỳ (nếu biết trước id của album) ============ // saveAlbumPhotoLinks("245004546697321"); diff --git a/scripts/bookmarks.js b/scripts/bookmarks.js index c456636..e632a46 100644 --- a/scripts/bookmarks.js +++ b/scripts/bookmarks.js @@ -74,6 +74,13 @@ javascript: (function () { return; } } + + const location_check = /(?<=\/groups\/)(.*?)($|(?=&))/.exec(location.href); + if (location_check && location_check[0]) { + window.prompt(`GROUP ID của ${group_name}:`, location_check[0]); + return; + } + window.prompt( "Không tìm thấy GROUP ID nào trong trang web!\nBạn có đang ở đúng trang group chưa?\nTrang web Ví dụ:", "https://www.facebook.com/groups/j2team.community.girls" diff --git a/scripts/constants.js b/scripts/constants.js new file mode 100644 index 0000000..c5856de --- /dev/null +++ b/scripts/constants.js @@ -0,0 +1,5 @@ +export const FB_API_HOST = "https://graph.facebook.com/v12.0"; +export const MEDIA_TYPE = { + PHOTO: "photo", + VIDEO: "video", +}; diff --git a/scripts/download_album.js b/scripts/download_album.js index 2a8e6fc..1fbee0d 100644 --- a/scripts/download_album.js +++ b/scripts/download_album.js @@ -1,7 +1,6 @@ -import fetch from "node-fetch"; +import { FB_API_HOST } from "./constants.js"; import { ACCESS_TOKEN, - FB_API_HOST, WAIT_BEFORE_NEXT_FETCH, ID_LINK_SEPERATOR, FOLDER_TO_SAVE_LINKS, @@ -12,43 +11,35 @@ import { createIfNotExistDir, deleteFile, downloadFileSync, + myFetch, saveToFile, sleep, } from "./utils.js"; // Hàm này fetch và trả về 2 thứ: -// 1. Toàn bộ link ảnh (max 100) từ 1 vị trí (cursor) nhất định trong album ảnh +// 1. Toàn bộ link ảnh (max 100) từ 1 vị trí (cursor) nhất định trong album ảnh. Định dạng: [[{id: .., url: ...}, ...] // 2. Vị trí của ảnh tiếp theo (next cursor) (nếu có) const fetchAlbumPhotosFromCursor = async ({ albumId, cursor, limit = 100 }) => { // create link to fetch let url = `${FB_API_HOST}/${albumId}/photos?fields=largest_image&limit=${limit}&access_token=${ACCESS_TOKEN}`; - if (cursor) { - url += `&after=${cursor}`; - } + if (cursor) url += `&after=${cursor}`; - try { - // fetch data - const response = await fetch(url); - const json = await response.json(); + const json = await myFetch(url); - // return imgData + next cursor - return { - imgData: json.data.map( - (_) => _.id + ID_LINK_SEPERATOR + _.largest_image.source - ), - nextCursor: json.paging?.cursors?.after || null, - }; - } catch (e) { - return {}; - } + // return imgData + next cursor + return { + imgData: json.data?.map((_) => ({ id: _.id, url: _.largest_image.source })), + nextCursor: json.paging?.cursors?.after || null, + }; }; // Hàm này fetch về toàn bộ ảnh từ 1 album. Sử dụng hàm fetchAlbumPhotosFromCursor // Liên tục fetch ảnh và lấy nextCursor, rồi lại fetch ảnh tiếp ở cursor mới. Liên tục cho tới khi không còn nextCursor +// Dữ liệu trả về là 1 mảng chứa dữ liệu {id, url} của từng ảnh. Có dạng [{id: .., url: ...}, {id: .., url: ...}, ...] const fetchAlbumPhotos = async ({ albumId, - pageNum = Infinity, - pageSize = 100, // max is 100 in facebook graph API + pageSize = 100, // max is 100 + pageLimit = Infinity, pageFetchedCallback = async () => {}, }) => { let currentPage = 1; @@ -56,7 +47,7 @@ const fetchAlbumPhotos = async ({ let nextCursor = null; let allImgsData = []; - while (hasNextCursor && currentPage <= pageNum) { + while (hasNextCursor && currentPage <= pageLimit) { console.log(`Fetching page: ${currentPage}, pageSize: ${pageSize}...`); const data = await fetchAlbumPhotosFromCursor({ @@ -67,7 +58,7 @@ const fetchAlbumPhotos = async ({ if (data.imgData) { // concat data to result array - allImgsData = allImgsData.concat(data.imgData); + allImgsData.push(...data.imgData); console.log( `> Fetched ${data.imgData.length} photos. (Total: ${allImgsData.length})` @@ -89,6 +80,7 @@ const fetchAlbumPhotos = async ({ } else { // FAILED => re-fetch currentPage console.log("FAILED."); + break; } } @@ -106,7 +98,7 @@ export const fetchAlbumInfo = async (albumId) => { const response = await fetch(url); const json = await response.json(); - if(json.error) throw json.error; + if (json.error) throw json.error; // return album infomation return { @@ -131,7 +123,11 @@ export const saveAlbumPhotoLinks = async (albumId) => { fetchAlbumPhotos({ albumId, pageFetchedCallback: (pageImgsData) => { - saveToFile(fileName, pageImgsData.join("\n"), false); + saveToFile( + fileName, + pageImgsData.map((_) => _.id + ID_LINK_SEPERATOR + _.url).join("\n"), + false + ); }, }); }; @@ -151,20 +147,18 @@ export const saveAlbumPhoto = async (albumId) => { const promises = []; for (let data of pageImgsData) { - const seperated = data.split(ID_LINK_SEPERATOR); - const photo_id = seperated[0]; - const link = seperated[1]; + const { id: photo_id, url: photo_url } = data; const savePath = `${dir}/${photo_id}.${PHOTO_FILE_FORMAT}`; promises.push( downloadFileSync({ - uri: link, + uri: photo_url, filename: savePath, successCallback: () => { console.log(`> Saved ${savePath}`); }, failedCallback: (e) => { - console.log(`ERROR while save image ${savePath}`); + console.log(`ERROR while save image ${savePath}`, e.toString()); }, }) ); diff --git a/scripts/download_groupfeeds_media.js b/scripts/download_groupfeeds_media.js index e6e0d69..906ffb3 100644 --- a/scripts/download_groupfeeds_media.js +++ b/scripts/download_groupfeeds_media.js @@ -1,5 +1,11 @@ -import fetch from "node-fetch"; -import { ACCESS_TOKEN, FB_API_HOST } from "../config.js"; +import { FB_API_HOST, MEDIA_TYPE } from "./constants.js"; +import { + ACCESS_TOKEN, + FOLDER_TO_SAVE_GROUP_MEDIA, + PHOTO_FILE_FORMAT, + VIDEO_FILE_FORMAT, +} from "../config.js"; +import { createIfNotExistDir, downloadFileSync, myFetch } from "./utils.js"; // Lấy ra các thông tin cần thiết (id, ảnh, video) từ dữ liệu attachment. const getMediaFromAttachment = (attachment) => { @@ -27,14 +33,14 @@ const getMediaFromAttachment = (attachment) => { }*/ if (type === "photo") { filtered_media.push({ - type: "photo", + type: MEDIA_TYPE.PHOTO, id: id, url: attachment.media.image.src, }); } /* - Attachment LOẠI VIDEO_AUTOPLAY có định dạng như sau + Attachment LOẠI VIDEO_AUTOPLAY, VIDEO_INLINE, VIDEO có định dạng như sau { "media": { "image": { @@ -51,9 +57,13 @@ const getMediaFromAttachment = (attachment) => { "type": "video_autoplay", "url": "https://www.facebook.com/groups/j2team.community.girls/permalink/1045907852835609/" } */ - if (type === "video_autoplay") { + if ( + type === "video_autoplay" || + type === "video_inline" || + type === "video" + ) { filtered_media.push({ - type: "video", + type: MEDIA_TYPE.VIDEO, id: id, url: attachment.media.source, }); @@ -94,27 +104,20 @@ const getMediaFromAttachment = (attachment) => { return filtered_media; }; -// fetch tất cả bài post (feed) trong 1 group, và lấy ra các media (ảnh, video, ..) trong các bài post đó +// fetch tất cả bài post (feed) trong 1 group, và lấy ra các media (ảnh, video, ..) trong các bài post đó (NẾU CÓ) // Trả về danh sách chứa {id, url} của từng media -export const fetchGroupFeeds = async (groupId, page_limit = Infinity) => { - const fetchOnce = async (_url) => { - try { - const response = await fetch(_url); - const json = await response.json(); - return json; - } catch (e) { - return {}; - } - }; - - const PAGE_LIMIT = 2; - const all_media = []; // store all media {id, url} +const fetchGroupPostMedia = async ({ + groupId, + pageLimit = Infinity, // Số lần fetch, mỗi lần fetch được khoảng 25 bài post (?) + pageFetchedCallback = () => {}, +}) => { + const all_media = []; // store all media {id, url, type} let page = 1; - let url = `${FB_API_HOST}/${groupId}/feed?fields=attachments&access_token=${ACCESS_TOKEN}`; + let url = `${FB_API_HOST}/${groupId}/feed?fields=attachments{media,type,subattachments,target}&access_token=${ACCESS_TOKEN}`; - while (url && page <= PAGE_LIMIT) { + while (url && page <= pageLimit) { console.log(`FETCHING page ${page}...`); - const fetchData = await fetchOnce(url); + const fetchData = await myFetch(url); page++; if (fetchData?.data) { @@ -126,8 +129,13 @@ export const fetchGroupFeeds = async (groupId, page_limit = Infinity) => { }); }); - console.log(`> Found ${media.length} media.`); all_media.push(...media); + console.log( + `> Found ${media.length} media. (Total: ${all_media.length})` + ); + + // callback when each page fetched + await pageFetchedCallback(media); // get next paging url = fetchData?.paging?.next; @@ -136,7 +144,76 @@ export const fetchGroupFeeds = async (groupId, page_limit = Infinity) => { } } - console.log(`> TOTAL FOUND: ${all_media.length} media.`); + return all_media; }; -fetchGroupFeeds(697332711026460); +// Hàm này fetch tất cả các bài post của 1 group, và tải về media (photo, video) có trong các bài post +export const saveGroupPostMedia = async ({ + groupId, + downloadVideo = true, + pageLimit = Infinity, +}) => { + console.log(`FETCHING POST MEDIA IN GROUP ${groupId}...`); + fetchGroupPostMedia({ + groupId: groupId, + pageLimit: pageLimit, + pageFetchedCallback: async (media) => { + // create dir if not exist + const dir = `${FOLDER_TO_SAVE_GROUP_MEDIA}/${groupId}`; + createIfNotExistDir(dir); + + // save all photo to directory + console.log(`Saving media ...`); + const promises = []; + + for (let data of media) { + const { id: media_id, url: media_url, type: media_type } = data; + + if (!downloadVideo && media_type === MEDIA_TYPE.VIDEO) continue; + + const file_format = + media_type === MEDIA_TYPE.PHOTO + ? PHOTO_FILE_FORMAT + : VIDEO_FILE_FORMAT; + + const savePath = `${dir}/${media_id}.${file_format}`; + + promises.push( + downloadFileSync({ + uri: media_url, + filename: savePath, + successCallback: () => { + console.log(`> Saved ${savePath}`); + }, + failedCallback: (e) => { + console.log(`ERROR while save media ${savePath}`, e.toString()); + }, + }) + ); + } + + try { + await Promise.all(promises); + console.log(`> Saved ${promises.length} media.`); + } catch (e) {} + }, + }); +}; + +saveGroupPostMedia({ + groupId: 2769931233237192, //697332711026460, + downloadVideo: true, + pageLimit: 2, +}); +// fetchGroupPostMedia({ groupId: 697332711026460, pageLimit: 1 }); + +// downloadFileSync({ +// uri: "https://video.fsgn2-2.fna.fbcdn.net/v/t42.1790-2/242040606_1052870295479615_1737332562906232233_n.mp4?_nc_cat=100&ccb=1-5&_nc_sid=985c63&efg=eyJybHIiOjM1NCwicmxhIjo1MTIsInZlbmNvZGVfdGFnIjoic3ZlX3NkIn0%3D&_nc_ohc=V8NFPv8kz40AX__P_dn&rl=354&vabr=197&_nc_ht=video.fsgn2-2.fna&oh=cf8a3a478db83801cdb58a470b450d23&oe=6147FC49", +// filename: "test.mp4", +// successCallback: () => { +// console.log("saved"); +// }, +// failedCallback: () => { +// console.log("failed"); +// }, +// }); diff --git a/scripts/download_timeline_album.js b/scripts/download_timeline_album.js index 35ed879..ef9eb1f 100644 --- a/scripts/download_timeline_album.js +++ b/scripts/download_timeline_album.js @@ -3,7 +3,8 @@ // album này sẽ không hiện trên trang web facebook (hoặc có mà mình ko biết cách tìm ở đâu), cần dùng tool để lấy import fetch from "node-fetch"; -import { ACCESS_TOKEN, FB_API_HOST } from "../config.js"; +import { FB_API_HOST } from "./constants.js"; +import { ACCESS_TOKEN } from "../config.js"; import { saveAlbumPhoto, saveAlbumPhotoLinks } from "./download_album.js"; export const fetchTimeLineAlbumId = async (page_id) => { diff --git a/scripts/utils.js b/scripts/utils.js index c7073e7..fc1ecf2 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -1,6 +1,18 @@ import fs from "fs"; +import fetch from "node-fetch"; import request from "request"; +export const myFetch = async (_url) => { + try { + const response = await fetch(_url); + const json = await response.json(); + return json; + } catch (e) { + console.log("ERROR", e.toString()); + return null; + } +}; + export const sleep = (ms) => { return new Promise((resolve) => { setTimeout(resolve, ms);