diff --git a/README-en.md b/README-en.md index 2b528963..433a6480 100644 --- a/README-en.md +++ b/README-en.md @@ -32,15 +32,15 @@ Try online [Here](https://useful-scripts-extension.github.io/useful-script/popup Current Versions: -- **v1.7**: tiktok update 28/07/2024 +- **v1.8**: douyin + facebook + youtube update 22/08/2024
Old versions -- v1.69: small update (14/07/2024) +- v1.7: tiktok update (28/07/2024) - v1.68: big facebook update (01/07/2024) -- v1.67 - huge update (29/05/2024) -- v1.66 - big update (27/04/2024) +- v1.67: huge update (29/05/2024) +- v1.66: big update (27/04/2024) - v1.65-hotfix (08/04/2024) - v1.64-hotfix (07/04/2024) - v1.63 (03/04/2024) @@ -69,7 +69,7 @@ Current Versions: Expected that this useful-script extension will include the functions of [RevealDeletedFBMessages](https://github.com/HoangTran0410/RevealDeletedFBMessages) and [FBMediaDownloader](https://github.com/HoangTran0410/FBMediaDownloader) -[![Star History Chart](https://api.star-history.com/svg?repos=HoangTran0410/useful-script,HoangTran0410/FBMediaDownloader,HoangTran0410/RevealDeletedFBMessages&type=Date)](https://star-history.com/#HoangTran0410/useful-script&HoangTran0410/FBMediaDownloader&HoangTran0410/RevealDeletedFBMessages&Date) +[![Star History Chart](https://api.star-history.com/svg?repos=Useful-Scripts-Extension/useful-script,HoangTran0410/FBMediaDownloader,HoangTran0410/RevealDeletedFBMessages&type=Date)](https://star-history.com/#Useful-Scripts-Extension/useful-script&HoangTran0410/FBMediaDownloader&HoangTran0410/RevealDeletedFBMessages&Date) ## For developer (Demo) diff --git a/README.md b/README.md index 221f706f..a3098ae7 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - [Dành cho dev (Demo)](#dành-cho-dev-demo) - [Contribute](#contribute) -Donate? Muốn hỗ trợ mình 1 ly cafe <3 [Donate tại đây](https://github.com/HoangTran0410/HoangTran0410/blob/main/DONATE.md) +Donate? Muốn hỗ trợ mình 1 ly cafe <3 [Donate tại đây](https://hoangtran0410.github.io/HoangTran0410/DONATE) ## Giới thiệu @@ -32,15 +32,16 @@ Xem [Lịch sử cập nhật](/md/CHANGELOGS.md) Phiên bản hiện tại: -- **v1.7**: tiktok update 28/07/2024 +- **v1.8**: douyin + facebook + youtube update 22/08/2024
Xem phiên bản cũ hơn +- v1.7: tiktok update (28/07/2024) - v1.69: small update (14/07/2024) - v1.68: bản cập nhật facebook lớn (01/07/2024) -- v1.67 - bản cập nhật siêu lớn (29/05/2024) -- v1.66 - bản cập nhật lớn (27/04/2024) +- v1.67: bản cập nhật siêu lớn (29/05/2024) +- v1.66: bản cập nhật lớn (27/04/2024) - v1.65-hotfix (08/04/2024) - v1.64-hotfix (07/04/2024) - v1.63 (03/04/2024) @@ -69,7 +70,7 @@ Phiên bản hiện tại: Dự kiến extension useful-script này sẽ bao gồm cả chức năng của [RevealDeletedFBMessages](https://github.com/HoangTran0410/RevealDeletedFBMessages) và [FBMediaDownloader](https://github.com/HoangTran0410/FBMediaDownloader) -[![Star History Chart](https://api.star-history.com/svg?repos=HoangTran0410/useful-script,HoangTran0410/FBMediaDownloader,HoangTran0410/RevealDeletedFBMessages&type=Date)](https://star-history.com/#HoangTran0410/useful-script&HoangTran0410/FBMediaDownloader&HoangTran0410/RevealDeletedFBMessages&Date) +[![Star History Chart](https://api.star-history.com/svg?repos=Useful-Scripts-Extension/useful-script,HoangTran0410/FBMediaDownloader,HoangTran0410/RevealDeletedFBMessages&type=Date)](https://star-history.com/#Useful-Scripts-Extension/useful-script&HoangTran0410/FBMediaDownloader&HoangTran0410/RevealDeletedFBMessages&Date) ## Dành cho dev (Demo) diff --git a/_metadata/generated_indexed_rulesets/_ruleset1 b/_metadata/generated_indexed_rulesets/_ruleset1 new file mode 100644 index 00000000..9c3f4a78 Binary files /dev/null and b/_metadata/generated_indexed_rulesets/_ruleset1 differ diff --git a/config.js b/config.js index d165db01..1a26255b 100644 --- a/config.js +++ b/config.js @@ -1,7 +1,7 @@ export default { version_check: - "https://raw.githubusercontent.com/HoangTran0410/useful-script/main/manifest.json", - source_code: "https://github.com/HoangTran0410/useful-script", + "https://raw.githubusercontent.com/Useful-Scripts-Extension/useful-script/main/manifest.json", + source_code: "https://github.com/Useful-Scripts-Extension/useful-script", store: "https://chrome.google.com/webstore/devconsole/ad27ff83-24b1-4348-810d-9cfdc30a5331/heoejcamgchindphgghdhmjpgmldnepl/edit/status", }; diff --git a/index.html b/index.html index 7bc65451..2929c3b1 100644 --- a/index.html +++ b/index.html @@ -9,7 +9,7 @@ diff --git a/manifest.json b/manifest.json index 7a70224c..14efa872 100644 --- a/manifest.json +++ b/manifest.json @@ -3,8 +3,8 @@ "manifest_version": 3, "name": "Useful Scripts", "description": "Scripts that can make your life faster and better", - "homepage_url": "https://github.com/HoangTran0410/useful-script", - "version": "1.7", + "homepage_url": "https://github.com/Useful-Scripts-Extension/useful-script", + "version": "1.8", "icons": { "16": "./assets/icon16.png", "32": "./assets/icon32.png", diff --git a/md/CHANGELOGS.md b/md/CHANGELOGS.md index e2cf4225..bad474a7 100644 --- a/md/CHANGELOGS.md +++ b/md/CHANGELOGS.md @@ -1,5 +1,28 @@ ## Change logs +
+ v1.8 - 22/08/2024 + +- Facebook + - NEW / SUPER HOT: Facebook All in one [web](https://facebook-all-in-one.com) + - NEW: auto duyệt bài spam group facebook [source](/scripts/fb_autoRemoveSpamPostGroup.js) + +- Douyin + - NEW: batch download [source](/scripts/douyin_batchDownload.js) + +- Google + - FIX: download private (shared to you) videos that dont have download button [source](/scripts/ggdrive_downloadVideo.js) + +- Youtube + - FIX: change country [source](/scripts/youtube_changeCountry.js) + +- More + - FIX: show hidden fields [source](/scripts/showHiddenFields.js) + - FIX: dino hack [source](/scripts/dino_hack.js) + - FIX: create invisible text (web version) [source](/scripts/createInvisibleText.js) + +
+
v1.7 - 28/07/2024 @@ -35,11 +58,11 @@ - click to disable/enable for current website [source](/scripts/smoothScroll.js) - Merge request - - [fix: 🐛 fix fireship_vip script](https://github.com/HoangTran0410/useful-script/pull/28) + - [fix: 🐛 fix fireship_vip script](https://github.com/Useful-Scripts-Extension/useful-script/pull/28) - [feat: youglish search - ](https://github.com/HoangTran0410/useful-script/pull/30) - - [feat: youtube download video UI](https://github.com/HoangTran0410/useful-script/pull/31) - - [style: 🪄 beautify ui for viewScriptSource](https://github.com/HoangTran0410/useful-script/pull/32) + ](https://github.com/Useful-Scripts-Extension/useful-script/pull/30) + - [feat: youtube download video UI](https://github.com/Useful-Scripts-Extension/useful-script/pull/31) + - [style: 🪄 beautify ui for viewScriptSource](https://github.com/Useful-Scripts-Extension/useful-script/pull/32) - More shortcuts: google trend, time.is, aiforthat, cobalt, nirsoft
diff --git a/popup/index.js b/popup/index.js index 2eca57c0..b4aea16b 100644 --- a/popup/index.js +++ b/popup/index.js @@ -1046,7 +1046,10 @@ async function initShowDonate() { ); } if (res.isDenied) { - window.open("https://github.com/HoangTran0410/useful-script", "_blank"); + window.open( + "https://github.com/Useful-Scripts-Extension/useful-script", + "_blank" + ); } } } diff --git a/popup/popup.html b/popup/popup.html index 3f79fdac..3077d06c 100644 --- a/popup/popup.html +++ b/popup/popup.html @@ -25,7 +25,7 @@

donate - + source code diff --git a/popup/recommend.js b/popup/recommend.js index 4fc8e136..c66f30f6 100644 --- a/popup/recommend.js +++ b/popup/recommend.js @@ -886,4 +886,37 @@ export const Recommend = { ), }, }, + insta_bulkDownload: { + id: "recommend_fbAIOInstagram", + icon: "https://static.cdninstagram.com/rsrc.php/v3/yI/r/VsNE-OHk_8a.png", + name: { + en: "Instagram - Bulk download", + vi: "Instagram - Tải hàng loạt", + }, + description: { + en: "Download all user's media on instagram (video/photo/reels/highlight) on Facebook AIO", + vi: "Tải mọi ảnh/video/reel/highlight của người dùng Instagram với Facebook AIO", + }, + badges: [BADGES.new, BADGES.hot], + popupScript: { + onClick: () => + window.open("https://facebook-all-in-one.com/dist/#/bulk-downloader"), + }, + }, + facebook_aio_stalker: { + id: "recommend_facebook_aio_stalker", + icon: "/scripts/fb_aio.png", + name: { + en: "Stalk anyone on Facebook AIO", + vi: "Stalk bất cứ ai với Facebook AIO", + }, + description: { + en: "Download all Stories, Photos ,Videos, Albums, Reels, Groups, Pages, ... on Facebook AIO", + vi: "Tải mọi thứ Story, Ảnh, Video, Album, Reels, Nhóm, Trang, ... với Facebook AIO", + }, + badges: [BADGES.recommend, BADGES.hot], + popupScript: { + onClick: () => window.open("https://facebook-all-in-one.com/"), + }, + }, }; diff --git a/popup/tabs.js b/popup/tabs.js index 7fe04b35..ed9f432d 100644 --- a/popup/tabs.js +++ b/popup/tabs.js @@ -123,7 +123,6 @@ const tabs = [ s.fb_getAvatarFromUid, s.fb_exportSaved, createTitle("--- Hot ---", "--- Nổi bật ---"), - s.fb_autoLike, s.fb_revealDeletedMessages, s.fb_moreReactionStory, s.fb_toggleLight, @@ -132,8 +131,11 @@ const tabs = [ s.fb_blockSeenStory, s.fb_getPostReactionCount, s.fb_whoIsTyping, + s.fb_autoLike, + s.fb_autoRemoveSpamPostGroup, // s.fb_blockSeenAndTyping, createTitle("--- Statistic ---", "--- Thống kê ---"), + R.facebook_aio_stalker, s.fb_searchGroupForOther, s.fb_searchPageForOther, s.fb_searchPostsForOther, @@ -168,11 +170,11 @@ const tabs = [ { ...CATEGORY.instagram, scripts: [ + R.insta_bulkDownload, + // s.insta_getAllUserMedia, s.insta_getUserInfo, s.insta_injectDownloadBtn, s.insta_anonymousStoryViewer, - createTitle("--- Bulk Download ---", "--- Tải hàng loạt ---"), - s.insta_getAllUserMedia, s.insta_getFollowForOther, ], }, @@ -197,10 +199,11 @@ const tabs = [ ...CATEGORY.tiktok, scripts: [ createTitle("--- Tiktok ---", "--- Tiktok ---"), + s.tiktok_batchDownload, s.tiktok_downloadWatchingVideo, s.tiktok_downloadVideo, - s.tiktok_batchDownload, createTitle("--- Douyin ---", "--- Douyin ---"), + s.douyin_batchDownload, s.douyin_downloadWachingVideo, s.douyin_downloadAllVideoUser, ], diff --git a/scripts/@index.js b/scripts/@index.js index 36e07c54..ced82c0e 100644 --- a/scripts/@index.js +++ b/scripts/@index.js @@ -171,3 +171,5 @@ export { default as youtube_changeCountry } from "./youtube_changeCountry.js"; export { default as fb_autoLike } from "./fb_autoLike.js"; export { default as guland_VIP } from "./guland_VIP.js"; export { default as pip_anything } from "./pip_anything.js"; +export { default as douyin_batchDownload } from "./douyin_batchDownload.js"; +export { default as fb_autoRemoveSpamPostGroup } from "./fb_autoRemoveSpamPostGroup.js"; diff --git a/scripts/backup/ext/fb-group-ext/_metadata/generated_indexed_rulesets/_ruleset1 b/scripts/backup/ext/fb-group-ext/_metadata/generated_indexed_rulesets/_ruleset1 new file mode 100644 index 00000000..fd4044a6 Binary files /dev/null and b/scripts/backup/ext/fb-group-ext/_metadata/generated_indexed_rulesets/_ruleset1 differ diff --git a/scripts/backup/ext/fb-group-ext/assets/icon.png b/scripts/backup/ext/fb-group-ext/assets/icon.png new file mode 100644 index 00000000..b8bfbff1 Binary files /dev/null and b/scripts/backup/ext/fb-group-ext/assets/icon.png differ diff --git a/scripts/backup/ext/fb-group-ext/assets/icon_default_128.png b/scripts/backup/ext/fb-group-ext/assets/icon_default_128.png new file mode 100644 index 00000000..5146d5b8 Binary files /dev/null and b/scripts/backup/ext/fb-group-ext/assets/icon_default_128.png differ diff --git a/scripts/backup/ext/fb-group-ext/assets/icon_default_16.png b/scripts/backup/ext/fb-group-ext/assets/icon_default_16.png new file mode 100644 index 00000000..228b9af4 Binary files /dev/null and b/scripts/backup/ext/fb-group-ext/assets/icon_default_16.png differ diff --git a/scripts/backup/ext/fb-group-ext/assets/icon_default_48.png b/scripts/backup/ext/fb-group-ext/assets/icon_default_48.png new file mode 100644 index 00000000..f385c007 Binary files /dev/null and b/scripts/backup/ext/fb-group-ext/assets/icon_default_48.png differ diff --git a/scripts/backup/ext/fb-group-ext/manifest.json b/scripts/backup/ext/fb-group-ext/manifest.json new file mode 100644 index 00000000..5679790e --- /dev/null +++ b/scripts/backup/ext/fb-group-ext/manifest.json @@ -0,0 +1,32 @@ +{ + "manifest_version": 3, + "name": "Facebook Group Extension", + "description": "Quản lý nhóm facebook", + "version": "1.0", + "action": { + "default_popup": "./popup/index.html" + }, + "icons": { + "16": "assets/icon_default_16.png", + "48": "assets/icon_default_48.png", + "128": "assets/icon_default_128.png" + }, + "permissions": [ + "tabs", + "cookies", + "scripting", + "declarativeNetRequest", + "declarativeNetRequestFeedback", + "declarativeNetRequestWithHostAccess" + ], + "host_permissions": ["*://*/*", ""], + "declarative_net_request": { + "rule_resources": [ + { + "id": "ruleset_1", + "enabled": true, + "path": "./rules.json" + } + ] + } +} diff --git a/scripts/backup/ext/fb-group-ext/popup/auto_duyet_bai_group/index.html b/scripts/backup/ext/fb-group-ext/popup/auto_duyet_bai_group/index.html new file mode 100644 index 00000000..a3e6e118 --- /dev/null +++ b/scripts/backup/ext/fb-group-ext/popup/auto_duyet_bai_group/index.html @@ -0,0 +1,43 @@ + + + + + + + Quản lý nhóm Facebook + + + + + +

Auto duyệt bài spam group fb

+ +

Chọn hành động

+ + + +
+

Thời gian chờ (giây):

+ (ngẫu nhiên trong khoảng)
+
+ ➡️ +
+
+ +

Duyệt tối đa bao nhiêu bài?

+ (nhập 0 để duyệt hết)
+ + + + + + + + diff --git a/scripts/backup/ext/fb-group-ext/popup/auto_duyet_bai_group/main.js b/scripts/backup/ext/fb-group-ext/popup/auto_duyet_bai_group/main.js new file mode 100644 index 00000000..5887c3c4 --- /dev/null +++ b/scripts/backup/ext/fb-group-ext/popup/auto_duyet_bai_group/main.js @@ -0,0 +1,248 @@ +const waitMinInp = document.getElementById("inputWaitMin"); +const waitMaxInp = document.getElementById("inputWaitMax"); +const inputMaxPosts = document.getElementById("max-posts"); +const radioAction = document.getElementsByName("action"); +const startBtn = document.getElementById("start-btn"); + +function initCacheInput(input, cacheName) { + if (localStorage.getItem(cacheName)) { + input.value = localStorage.getItem(cacheName); + } + input.addEventListener("input", () => { + localStorage.setItem(cacheName, input.value); + }); +} + +function renderTime(time, fixed = 1) { + return (time / 1000).toFixed(fixed) + "s"; +} + +async function main() { + initCacheInput(waitMinInp, "wait-min"); + initCacheInput(waitMaxInp, "wait-max"); + initCacheInput(inputMaxPosts, "max-posts"); + + const tab = await getCurrentTab(); + + if (!tab.url.includes("groups") || !tab.url.includes("spam")) { + return prompt( + "\n\nBạn cần mở trang duyệt bài spam của group trước.\n\nLink hiện tại:" + + tab.url + + "\nLink đúng ví dụ:", + "https://www.facebook.com/groups/gamecode/spam" + ); + } + + startBtn.addEventListener("click", async () => { + const state = await getCurrentState(tab); + if (state?.running) { + return stop(tab); + } + + let action = radioAction[0].checked ? 1 : 2; + let maxPosts = parseInt(inputMaxPosts.value); + let waitMin = parseInt(waitMinInp.value) * 1000; + let waitMax = parseInt(waitMaxInp.value) * 1000; + + if (waitMin > waitMax) { + return alert( + "Thời gian chờ không hợp lệ\nBên trái phải bé hơn hoặc bằng bên phải" + ); + } + + runScriptInTab({ + target: { tabId: tab.id }, + func: start, + args: [action, maxPosts, waitMin, waitMax], + }); + }); + + // check is running + (async function checkIsRunning() { + const state = await getCurrentState(tab); + const { running, nextExecuteTime, count, action } = state || {}; + if (running) { + startBtn.innerHTML = + "Đang " + + (action === 1 ? "đăng" : "từ chối") + + "... " + + count + + " bài (chờ " + + renderTime(nextExecuteTime - Date.now(), 0) + + ")
(Bấm để dừng)"; + startBtn.classList.add("running"); + } else { + startBtn.innerHTML = "Bắt đầu"; + startBtn.classList.remove("running"); + } + setTimeout(checkIsRunning, 1000); + })(); +} + +function getCurrentState(tab) { + return runScriptInTab({ + target: { tabId: tab.id }, + func: () => window.fb_group_ext, + }); +} + +function stop(tab) { + runScriptInTab({ + target: { tabId: tab.id }, + func: () => { + window.fb_group_ext.stop = true; + }, + }); +} + +function start(action, maxPosts, waitMin, waitMax) { + const selector = + action == 1 + ? '[role="main"] [aria-label="Đăng"]' + : '[role="main"] [aria-label="Từ chối"]'; + + if (maxPosts == 0) maxPosts = Infinity; + + const btns = Array.from(document.querySelectorAll(selector)); + + if (!btns.length) { + alert("Không tìm thấy bài nào"); + return; + } + + onElementsAdded(selector, (nodes) => { + for (let node of nodes) { + if (btns.includes(node)) continue; + btns.push(node); + } + }); + + async function main() { + window.fb_group_ext = { + action, + running: true, + nextExecuteTime: 0, + stop: false, + count: 0, + }; + + while (window.fb_group_ext.count < maxPosts && !window.fb_group_ext.stop) { + if (btns.length > 0) { + const btn = btns.shift(); + + btn.scrollIntoView({ block: "center", behavior: "smooth" }); + console.log("click", btn); + btn.click(); + + window.fb_group_ext.count++; + const waitTime = ranInt(waitMin, waitMax); + window.fb_group_ext.nextExecuteTime = Date.now() + waitTime; + await sleep(waitTime, () => window.fb_group_ext.stop); + } else { + // scroll to end + window.scrollTo(0, document.body.scrollHeight); + // wait for load more + await sleep(1000); + } + } + + window.fb_group_ext.running = false; + alert("Duyệt xong " + window.fb_group_ext.count + " bài"); + } + + main(); + + function ranInt(min, max) { + return Math.floor(Math.random() * (max - min) + min); + } + + function sleep(time, cancelFn) { + return new Promise((resolve) => { + const timeout = setTimeout(resolve, time); + if (cancelFn) { + const interval = setInterval(() => { + if (cancelFn()) { + clearInterval(interval); + clearTimeout(timeout); + resolve(); + } + }, 100); + } + }); + } + + function onElementsAdded(selector, callback, once) { + let nodes = document.querySelectorAll(selector); + if (nodes?.length) { + callback(nodes); + if (once) return; + } + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (!mutation.addedNodes) return; + + for (let node of mutation.addedNodes) { + if (node.nodeType != 1) continue; // only process Node.ELEMENT_NODE + + let n = node.matches(selector) + ? [node] + : Array.from(node.querySelectorAll(selector)); + + if (n?.length) { + callback(n); + if (once) observer.disconnect(); + } + } + }); + }); + + observer.observe(document, { + childList: true, + subtree: true, + attributes: false, + characterData: false, + }); + + // return disconnect function + return () => observer.disconnect(); + } +} + +async function getCurrentTab() { + let tabs = await chrome.tabs.query({ + active: true, + currentWindow: true, + }); + return tabs[0]; +} + +const runScriptInTab = async (config = {}) => { + return new Promise((resolve, reject) => { + chrome.scripting.executeScript( + mergeObject( + { + world: "MAIN", + injectImmediately: true, + }, + config + ), + (injectionResults) => { + if (chrome.runtime.lastError) { + console.error(chrome.runtime.lastError); + reject(chrome.runtime.lastError); + } + // https://developer.chrome.com/docs/extensions/reference/scripting/#handling-results + else resolve(injectionResults?.find?.((_) => _.result)?.result); + } + ); + }); +}; +const mergeObject = (...objs) => { + // merge without null value + let res = {}; + for (let obj of objs) for (let key in obj) if (obj[key]) res[key] = obj[key]; + return res; +}; + +main(); diff --git a/scripts/backup/ext/fb-group-ext/popup/auto_duyet_bai_group/style.css b/scripts/backup/ext/fb-group-ext/popup/auto_duyet_bai_group/style.css new file mode 100644 index 00000000..ed4f3892 --- /dev/null +++ b/scripts/backup/ext/fb-group-ext/popup/auto_duyet_bai_group/style.css @@ -0,0 +1,116 @@ +body { + min-width: 350px; + min-height: 200px; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-size: 16px; +} + + +h3 { + text-decoration: underline; + margin-bottom: 8px; +} + + +/* The container */ +.container { + display: block; + position: relative; + padding-left: 35px; + margin-bottom: 12px; + cursor: pointer; + font-size: 22px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +/* Hide the browser's default radio button */ +.container input { + position: absolute; + opacity: 0; + cursor: pointer; +} + +/* Create a custom radio button */ +.checkmark { + position: absolute; + top: 0; + left: 0; + height: 25px; + width: 25px; + background-color: #eee; + border-radius: 50%; +} + +/* On mouse-over, add a grey background color */ +.container:hover input~.checkmark { + background-color: #ccc; +} + +/* When the radio button is checked, add a blue background */ +.container input:checked~.checkmark { + background-color: #2196F3; +} + +/* Create the indicator (the dot/circle - hidden when not checked) */ +.checkmark:after { + content: ""; + position: absolute; + display: none; +} + +/* Show the indicator (dot/circle) when checked */ +.container input:checked~.checkmark:after { + display: block; +} + +/* Style the indicator (dot/circle) */ +.container .checkmark:after { + top: 9px; + left: 9px; + width: 8px; + height: 8px; + border-radius: 50%; + background: white; +} + + +/* ======================= Range ====================== */ + +input[type=number] { + padding: 5px; + font-size: large; +} + +.button { + background-color: #04AA6D; + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + cursor: pointer; + width: 100%; + margin-top: 30px; +} + +.button.running { + background-color: rgb(151, 12, 12); + /* cursor: not-allowed; */ +} + +.input-row { + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: row; + white-space: pre; +} + +.input-row input[type=number] { + /* max-width: 80px; */ +} diff --git a/scripts/backup/ext/fb-group-ext/popup/auto_gioihan_member/index.html b/scripts/backup/ext/fb-group-ext/popup/auto_gioihan_member/index.html new file mode 100644 index 00000000..1c831855 --- /dev/null +++ b/scripts/backup/ext/fb-group-ext/popup/auto_gioihan_member/index.html @@ -0,0 +1,48 @@ + + + + + + + Auto giới hạn members + + + + + +

Auto giới hạn members

+ + + + + + + + + + + diff --git a/scripts/backup/ext/fb-group-ext/popup/auto_gioihan_member/main.js b/scripts/backup/ext/fb-group-ext/popup/auto_gioihan_member/main.js new file mode 100644 index 00000000..edf8ef2d --- /dev/null +++ b/scripts/backup/ext/fb-group-ext/popup/auto_gioihan_member/main.js @@ -0,0 +1,153 @@ +import { fetchGraphQl, findDataObject, getMyUid } from "../helpers/facebook.js"; +import { sleep } from "../helpers/utils.js"; + +async function main(groupIds = [], excludeUids = []) { + // getAllMemberUidHavePendingPosts({ + // groupId: "2138246213171561", + // onProgress: async ({ current, all }) => { + // console.log(current); + // }, + // }); + + let stop = false; + for (let groupId of groupIds) { + } +} +main(); + +async function getAllMemberUidHavePendingPosts({ groupId, onProgress }) { + const memUids = new Set(); + const allMembers = []; + await getAllPendingPosts({ + groupId, + onProgress: async ({ all, current }) => { + const newMem = current + ?.map?.((d) => d?.node?.comet_sections?.content?.story?.actors || []) + .flat() + .filter((u) => { + if (!u) return false; + if (memUids.has(u)) return false; + memUids.add(u); + return true; + }); + + allMembers.push(...newMem); + + await onProgress?.({ + current: newMem, + all: allMembers, + }); + }, + }); + return allMembers; +} + +async function getAllPendingPosts({ groupId, onProgress, cursor = "" }) { + const allPosts = []; + while (true) { + try { + const { edges, page_info } = await getPendingPosts(groupId, cursor); + if (!page_info?.has_next_page || edges.length === 0) break; + allPosts.push(...edges); + await onProgress?.({ + current: edges, + all: allPosts, + }); + cursor = page_info.end_cursor; + await sleep(500); + } catch (e) { + console.log(e); + break; + } + } + return allPosts; +} + +async function getPendingPosts(groupId, cursor = "") { + const res = await fetchGraphQl({ + fb_api_req_friendly_name: "GroupsCometPendingPostsFeedPaginationQuery", + variables: { + count: 3, + cursor: cursor, + feedLocation: "GROUP_PENDING", + feedbackSource: 0, + focusCommentID: null, + hoistedPostID: null, + pendingStoriesOrderBy: null, + privacySelectorRenderLocation: "COMET_STREAM", + renderLocation: "group_pending_queue", + scale: 1, + useDefaultActor: false, + id: groupId, + __relay_internal__pv__CometImmersivePhotoCanUserDisable3DMotionrelayprovider: false, + __relay_internal__pv__IsWorkUserrelayprovider: false, + __relay_internal__pv__IsMergQAPollsrelayprovider: false, + __relay_internal__pv__CometUFIReactionsEnableShortNamerelayprovider: false, + __relay_internal__pv__CometUFIShareActionMigrationrelayprovider: true, + __relay_internal__pv__IncludeCommentWithAttachmentrelayprovider: true, + __relay_internal__pv__StoriesArmadilloReplyEnabledrelayprovider: true, + __relay_internal__pv__EventCometCardImage_prefetchEventImagerelayprovider: false, + }, + doc_id: "8078135725563420", + }); + const json = JSON.parse(res.split("\n")[0]); + // console.log(json); + const { edges, page_info } = findDataObject(json) || {}; + return { edges, page_info }; +} + +async function limitMember({ groupId, uid, actorId = "", willLimit = true }) { + const res = await fetchGraphQl({ + fb_api_req_friendly_name: "GroupsCometMemberSetContentControlsMutation", + variables: { + input: { + admin_notes: "", + group_id: groupId, + rate_limit_settings: [ + { + duration: 86400, + limit_per_time_period: 10, + limit_type: "RATE_LIMIT_COMMENT_IN_GROUP", + should_rate_limit_target: willLimit, + time_period: 3600, + }, + { + duration: 86400, + limit_per_time_period: 2, + limit_type: "RATE_LIMIT_POST_IN_GROUP", + should_rate_limit_target: willLimit, + time_period: 86400, + }, + ], + selected_rules: [], + share_feedback: false, + target_user_id: uid, + actor_id: actorId || (await getMyUid()), + client_mutation_id: "33", // auto increment?? + }, + memberID: uid, + }, + doc_id: "7103666173041398", + }); +} + +async function getCurrentLimit({ groupId, uid }) { + const res = await fetchGraphQl({ + fb_api_req_friendly_name: + "GroupsCometMembersSetMemberContentControlDialogQuery", + variables: { + groupID: groupId, + memberID: uid, + scale: 1, + }, + doc_id: "7779093765537808", + }); + + const json = JSON.parse(res); + console.log(json); +} + +// getCurrentLimit({ +// groupId: "2138246213171561", +// uid: "100024071323401", +// }); diff --git a/scripts/backup/ext/fb-group-ext/popup/auto_gioihan_member/style.css b/scripts/backup/ext/fb-group-ext/popup/auto_gioihan_member/style.css new file mode 100644 index 00000000..e69de29b diff --git a/scripts/backup/ext/fb-group-ext/popup/helpers/facebook.js b/scripts/backup/ext/fb-group-ext/popup/helpers/facebook.js new file mode 100644 index 00000000..8c29fa76 --- /dev/null +++ b/scripts/backup/ext/fb-group-ext/popup/helpers/facebook.js @@ -0,0 +1,107 @@ +const CACHED = { + uid: null, + fb_dtsg: null, +}; + +export async function getMyUid() { + if (CACHED.uid) return CACHED.uid; + const d = await runExtFunc("chrome.cookies.get", [ + { url: "https://www.facebook.com", name: "c_user" }, + ]); + CACHED.uid = d?.value; + return CACHED.uid; +} + +export async function fetchGraphQl(params, url) { + let query = ""; + if (typeof params === "string") query = "&q=" + encodeURIComponent(params); + else + query = wrapGraphQlParams({ + dpr: 1, + __a: 1, + __aaid: 0, + __ccg: "GOOD", + server_timestamps: true, + ...params, + }); + + const res = await fetch(url || "https://www.facebook.com/api/graphql/", { + body: query + "&fb_dtsg=" + (await getFbDtsg()), + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + credentials: "include", + }); + const text = await res.text(); + + // check error response + try { + const json = JSON.parse(text); + if (json.errors) { + const { summary, message, description_raw } = json.errors[0]; + if (summary) { + console.log(json); + + const div = document.createElement("div"); + div.innerHTML = description_raw?.__html; + const description = div.innerText; + + // notification.error({ + // message: i18n.t("Facebook response Error"), + // description: summary + ". " + message + ". " + description, + // duration: 0, + // }); + } + } + } catch (e) {} + + return text; +} + +export function wrapGraphQlParams(params = {}) { + const formBody = []; + for (const property in params) { + const encodedKey = encodeURIComponent(property); + const value = + typeof params[property] === "string" + ? params[property] + : JSON.stringify(params[property]); + const encodedValue = encodeURIComponent(value); + formBody.push(encodedKey + "=" + encodedValue); + } + return formBody.join("&"); +} + +export async function getFbDtsg() { + if (CACHED.fb_dtsg) return CACHED.fb_dtsg; + let res = await fetch("https://mbasic.facebook.com/photos/upload/"); + let text = await res.text(); + let dtsg = RegExp(/name="fb_dtsg" value="(.*?)"/).exec(text)?.[1]; + if (!dtsg) { + res = await fetch("https://m.facebook.com/home.php", { + headers: { + Accept: "text/html", + }, + }); + text = res.text(); + dtsg = + RegExp(/"dtsg":{"token":"([^"]+)"/).exec(text)?.[1] || + RegExp(/"name":"fb_dtsg","value":"([^"]+)/).exec(text)?.[1]; + } + CACHED.fb_dtsg = dtsg || null; + return CACHED.fb_dtsg; +} + +export function findDataObject(object) { + if (!object) return null; + + // Check if the current object has edges and page_info properties + if (object.edges && object.page_info) return object; + + for (let key in object) { + if (typeof object[key] === "object" && object[key] !== null) { + let found = findDataObject(object[key]); + if (found) return found; + } + } + return null; +} diff --git a/scripts/backup/ext/fb-group-ext/popup/helpers/utils.js b/scripts/backup/ext/fb-group-ext/popup/helpers/utils.js new file mode 100644 index 00000000..fd40992f --- /dev/null +++ b/scripts/backup/ext/fb-group-ext/popup/helpers/utils.js @@ -0,0 +1,5 @@ +export function sleep(time) { + return new Promise((resolve) => { + setTimeout(resolve, time); + }); +} diff --git a/scripts/backup/ext/fb-group-ext/popup/index.html b/scripts/backup/ext/fb-group-ext/popup/index.html new file mode 100644 index 00000000..b864866d --- /dev/null +++ b/scripts/backup/ext/fb-group-ext/popup/index.html @@ -0,0 +1,37 @@ + + + + + + + Document + + + + + +

Faecbook tools

+ +
📝 Auto duyệt bài group + 🙋🏻‍♂️ Auto giới hạn members + + + diff --git a/scripts/backup/ext/fb-group-ext/rules.json b/scripts/backup/ext/fb-group-ext/rules.json new file mode 100644 index 00000000..b10f252c --- /dev/null +++ b/scripts/backup/ext/fb-group-ext/rules.json @@ -0,0 +1,26 @@ +[ + { + "id": 1, + "priority": 1, + "action": { + "type": "modifyHeaders", + "requestHeaders": [ + { + "header": "referer", + "operation": "set", + "value": "https://www.facebook.com" + }, + { + "header": "origin", + "operation": "set", + "value": "https://www.facebook.com" + } + ] + }, + "condition": { + "domain": "extension://*", + "urlFilter": "https://www.facebook.com", + "resourceTypes": ["xmlhttprequest"] + } + } +] diff --git a/scripts/content-scripts/ufs_global.js b/scripts/content-scripts/ufs_global.js index 07b65868..1d359c5a 100644 --- a/scripts/content-scripts/ufs_global.js +++ b/scripts/content-scripts/ufs_global.js @@ -12,6 +12,9 @@ export const UfsGlobal = { waitForTabToLoad, }, DOM: { + getSelectionText, + keyDown, + keyUp, closest, notifyStack, notify, @@ -139,6 +142,22 @@ function download(options) { // #endregion // #region DOM + +function getSelectionText() { + var text = ""; + if (window.getSelection) { + text = window.getSelection().toString(); + } else if (document.selection && document.selection.type != "Control") { + text = document.selection.createRange().text; + } + return text; +} + +//prettier-ignore +function keyDown(e){let n=document.createEvent("KeyboardEvent");Object.defineProperty(n,"keyCode",{get:function(){return this.keyCodeVal}}),n.initKeyboardEvent?n.initKeyboardEvent("keydown",!0,!0,document.defaultView,e,e,"","",!1,""):n.initKeyEvent("keydown",!0,!0,document.defaultView,!1,!1,!1,!1,e,0),n.keyCodeVal=e,document.body.dispatchEvent(n)} +//prettier-ignore +function keyUp(e){let n=document.createEvent("KeyboardEvent");Object.defineProperty(n,"keyCode",{get:function(){return this.keyCodeVal}}),n.initKeyboardEvent?n.initKeyboardEvent("keyup",!0,!0,document.defaultView,e,e,"","",!1,""):n.initKeyEvent("keyup",!0,!0,document.defaultView,!1,!1,!1,!1,e,0),n.keyCodeVal=e,document.body.dispatchEvent(n)} + function closest(element, selector) { let el = element; while (el !== null) { @@ -977,7 +996,7 @@ UfsGlobal.DEBUG = { JSON.parse('"' + res.replace(/\"/g, '\\"') + '"') ); } - return res; + return res.replaceAll("\\/", "/"); }, // https://stackoverflow.com/a/8649003 diff --git a/scripts/createInvisibleText.js b/scripts/createInvisibleText.js index bf71a734..61f0c6b8 100644 --- a/scripts/createInvisibleText.js +++ b/scripts/createInvisibleText.js @@ -20,7 +20,7 @@ export default { }, onClick: () => window.open( - "https://hoangtran0410.github.io/useful-script/scripts/createInvisibleText.html" + "https://Useful-Scripts-Extension.github.io/useful-script/scripts/createInvisibleText.html" ), }, ], diff --git a/scripts/dino_hack.js b/scripts/dino_hack.js index 21787822..97359d83 100644 --- a/scripts/dino_hack.js +++ b/scripts/dino_hack.js @@ -8,6 +8,9 @@ export default { en: "A bot that plays the Google Chrome T-Rex game for you", vi: "Tự động chơi game Google Chrome T-Rex", }, + changeLogs: { + "2024-07-31": "hotfix", + }, pageScript: { onClick: function () { @@ -15,9 +18,9 @@ export default { function hack() { //prettier-ignore - function keyDown(e){Podium={};var n=document.createEvent("KeyboardEvent");Object.defineProperty(n,"keyCode",{get:function(){return this.keyCodeVal}}),n.initKeyboardEvent?n.initKeyboardEvent("keydown",!0,!0,document.defaultView,e,e,"","",!1,""):n.initKeyEvent("keydown",!0,!0,document.defaultView,!1,!1,!1,!1,e,0),n.keyCodeVal=e,document.body.dispatchEvent(n)} + function keyDown(e){var n=document.createEvent("KeyboardEvent");Object.defineProperty(n,"keyCode",{get:function(){return this.keyCodeVal}}),n.initKeyboardEvent?n.initKeyboardEvent("keydown",!0,!0,document.defaultView,e,e,"","",!1,""):n.initKeyEvent("keydown",!0,!0,document.defaultView,!1,!1,!1,!1,e,0),n.keyCodeVal=e,document.body.dispatchEvent(n)} //prettier-ignore - function keyUp(e){Podium={};var n=document.createEvent("KeyboardEvent");Object.defineProperty(n,"keyCode",{get:function(){return this.keyCodeVal}}),n.initKeyboardEvent?n.initKeyboardEvent("keyup",!0,!0,document.defaultView,e,e,"","",!1,""):n.initKeyEvent("keyup",!0,!0,document.defaultView,!1,!1,!1,!1,e,0),n.keyCodeVal=e,document.body.dispatchEvent(n)} + function keyUp(e){var n=document.createEvent("KeyboardEvent");Object.defineProperty(n,"keyCode",{get:function(){return this.keyCodeVal}}),n.initKeyboardEvent?n.initKeyboardEvent("keyup",!0,!0,document.defaultView,e,e,"","",!1,""):n.initKeyEvent("keyup",!0,!0,document.defaultView,!1,!1,!1,!1,e,0),n.keyCodeVal=e,document.body.dispatchEvent(n)} let timeoutId = setInterval(function () { Runner.instance_.horizon.obstacles.length > 0 && diff --git a/scripts/douyin_batchDownload.js b/scripts/douyin_batchDownload.js new file mode 100644 index 00000000..64727978 --- /dev/null +++ b/scripts/douyin_batchDownload.js @@ -0,0 +1,448 @@ +import { UfsGlobal } from "./content-scripts/ufs_global.js"; +import { BADGES } from "./helpers/badge.js"; +import { hookXHR } from "./libs/ajax-hook/index.js"; +import { scrollToVeryEnd } from "./scrollToVeryEnd.js"; + +export default { + icon: "https://www.douyin.com/favicon.ico", + name: { + en: "Douyin - Batch download", + vi: "Douyin - Tải hàng loạt", + }, + description: { + en: "Select and download all douyin video (user profile, tiktok explore).

Same as tiktok batch download.", + vi: "Tải hàng loạt video douyin (trang người dùng, trang tìm kiếm), có giao diện chọn video muốn tải.

Giống chức năng tải hàng loạt tiktok.", + img: "/scripts/douyin_batchDownload.png", + }, + + badges: [BADGES.new, BADGES.hot], + + changeLogs: { + "2024-07-30": "init", + }, + + whiteList: ["https://www.douyin.com/*"], + + pageScript: { + onDocumentStart: async (details) => { + const CACHED = { + list: [], + hasNew: true, + videoById: new Map(), + }; + + window.ufs_douyin_batchDownload = CACHED; + + hookXHR({ + onAfterSend: async ( + { method, url, async, user, password }, + dataSend, + response + ) => { + try { + const json = + typeof response == "string" ? JSON.parse(response) : response; + + if (json?.aweme_list?.length) { + console.log(json); + + CACHED.list.push(...json.aweme_list); + json.aweme_list.forEach((item) => { + if (!CACHED.videoById.has(item.aweme_id)) { + CACHED.videoById.set(item.aweme_id, item); + CACHED.hasNew = true; + } + }); + } + + if (json?.cards?.length) { + const list = json.cards.map((c) => JSON.parse(c.aweme)); + list.forEach((item) => { + if (!CACHED.videoById.has(item.aweme_id)) { + CACHED.videoById.set(item.aweme_id, item); + CACHED.hasNew = true; + } + }); + } + } catch (e) { + console.log("ERROR:", e); + } + }, + }); + + UfsGlobal.Extension.getURL("/scripts/tiktok_batchDownload.css").then( + UfsGlobal.DOM.injectCssFile + ); + await UfsGlobal.DOM.injectScriptSrcAsync( + await UfsGlobal.Extension.getURL("/scripts/libs/vue/index.js") + ); + + const div = document.createElement("div"); + document.documentElement.appendChild(div); + + const formatter = UfsGlobal.Utils.getNumberFormatter("compactShort"); + function getNow() { + // return year + month + day + hour + minute + second + const day = new Date(); + return ( + [day.getFullYear(), day.getMonth() + 1, day.getDate()].join("-") + + "_" + + [day.getHours(), day.getMinutes(), day.getSeconds()].join("-") + ); + } + + const app = new Vue({ + template: /*html*/ ` +
+
📥 {{totalCount}}
+
+
+

Douyin - Useful Scripts

+

Found {{totalCount}} videos

+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
#🎬 VideoTitle👤 UserLikeLengthDownload

No video

{{v.index}}
+ +
+ + + +

{{v.desc}}

+ + {{v.author.nickname}}
+
+ {{v.author.uid}} +
{{format(v.statistics.digg_count)}}{{formatTime(v.video.duration)}}s +

+ 🎬 Video
+ 🖼️ Cover
+ + 👤 Avatar +
+ + 🎧 Music: {{v.music.title}} + +

+
+ + +
+
+
+
`, + created() { + setInterval(() => { + if (CACHED.hasNew) { + this.videos = Array.from(CACHED.videoById.values()) + // inject index + .map((v, i) => ({ ...v, index: i + 1 })); + + CACHED.hasNew = false; + } + }, 1000); + }, + data() { + return { + showModal: false, + videos: [], + search: "", + sortBy: "index", + sortDir: "asc", + downloading: { + video: null, + audio: null, + }, + selected: {}, + }; + }, + computed: { + selectedIds() { + return Object.entries(this.selected) + .filter((v) => v[1]) + .map((v) => v[0]); + }, + selectedCount() { + return Object.values(this.selected).filter((v) => v).length; + }, + hasSelected() { + return this.selectedCount > 0; + }, + videoToDownload() { + return this.hasSelected + ? this.videosToShow.filter((v) => this.selected[v.aweme_id]) + : this.videosToShow; + }, + audioToDownload() { + const list = this.hasSelected + ? this.videosToShow.filter((v) => this.selected[v.aweme_id]) + : this.videosToShow; + + // get unique + const result = new Map(); + for (const item of list) { + if (item.music?.id && !result.has(item.music?.id)) + result.set(item.music.id, item); + } + return Array.from(result.values()); + }, + videoTitle() { + if (Number.isInteger(this.downloading.video)) { + return ( + "Downloading " + + this.downloading.video + + "/" + + this.videoToDownload.length + + " video" + ); + } + return ( + "Download " + + this.videoToDownload.length + + (this.hasSelected ? " selected" : "") + + " video" + ); + }, + audioTitle() { + if (Number.isInteger(this.downloading.audio)) { + return ( + "Downloading " + + this.downloading.audio + + "/" + + this.audioToDownload.length + + " audio" + ); + } + return ( + "Download " + + this.audioToDownload.length + + (this.hasSelected ? " selected" : "") + + " audio" + ); + }, + totalCount() { + return this.videos.length; + }, + videosToShow() { + return ( + this.videos + // filter by search + .filter((v) => { + return [ + v.desc, + v.author?.uid, + v.author?.nickname, + v.author?.sec_uid, + ].some((s) => + s?.toLowerCase()?.includes(this.search.toLowerCase()) + ); + }) + // sorting + .sort((a, b) => { + switch (this.sortBy) { + case "index": + return this.sortDir === "asc" + ? a.index - b.index + : b.index - a.index; + case "title": + return this.sortDir === "asc" + ? a.desc.localeCompare(b.desc) + : b.desc.localeCompare(a.desc); + case "author": + return this.sortDir === "asc" + ? a.author.nickname.localeCompare(b.author.nickname) + : b.author.nickname.localeCompare(a.author.nickname); + case "like": + return this.sortDir === "asc" + ? a.statistics.digg_count - b.statistics.digg_count + : b.statistics.digg_count - a.statistics.digg_count; + case "duration": + return this.sortDir === "asc" + ? a.video.duration - b.video.duration + : b.video.duration - a.video.duration; + } + }) + ); + }, + }, + methods: { + async downloadVideo() { + const total = this.videoToDownload.length; + if (!total) return; + let success = 0; + await download({ + folderName: "douyin_videos_" + getNow(), + expectBlobTypes: ["video/mp4"], + data: this.videoToDownload + .map((_, i) => { + return { + url: _.video.play_addr.url_list.at(-1), + filename: + i + + 1 + + "_" + + UfsGlobal.Utils.sanitizeName(_.aweme_id, false) + + ".mp4", + }; + }) + .flat() + .filter((_) => _.url), + onProgressItem: (i, total) => { + this.downloading.video = i; + }, + onFinishItem: (i, total) => { + success++; + }, + }); + this.downloading.video = false; + alert("Downloaded " + success + "/" + total + " videos!"); + }, + async downloadAudio() { + const total = this.audioToDownload.length; + if (!total) return; + let success = 0; + await download({ + folderName: "douyin_musics_" + getNow(), + data: this.audioToDownload.map((_, i) => ({ + url: _.music.playUrl, + filename: + i + + 1 + + "_" + + UfsGlobal.Utils.sanitizeName( + _.music.title.substr(0, 50) || "audio", + false + ) + + ".mp3", + })), + onProgressItem: (i, total) => { + this.downloading.audio = i; + }, + onFinishItem: (i, total) => { + success++; + }, + }); + this.downloading.audio = false; + alert("Downloaded " + success + "/" + total + " videos!"); + }, + downloadJson() { + UfsGlobal.Utils.downloadData( + JSON.stringify(this.videosToShow, null, 4), + this.videosToShow.length + "_videos_douyin.json" + ); + }, + scrollToVeryEnd() { + setTimeout(() => scrollToVeryEnd(false), 100); + }, + scrollToTop(e) { + e.target.parentElement.scrollTo({ top: 0, behavior: "smooth" }); + }, + clearSelected() { + this.selectedIds.forEach((vidId) => { + CACHED.videoById.delete(vidId); + }); + this.selected = {}; + }, + clear() { + if (confirm("Are you sure want to clear all?")) { + CACHED.videoById.clear(); + this.videos = []; + } + }, + setSortBy(key) { + this.sortBy = key; + if (key === this.sortBy) + this.sortDir = this.sortDir === "asc" ? "desc" : "asc"; + }, + openUser(id) { + window.open("https://www.douyin.com/user/" + id, "_blank"); + }, + format(v) { + return formatter.format(v); + }, + formatTime(seconds) { + // return hh:mm:ss + return new Date(seconds) + .toISOString() + .substr(11, 8) + .split(":") + .filter((v) => v !== "00") + .join(":"); + }, + }, + }).$mount(div); + + async function download({ + folderName = "douyin", + expectBlobTypes, + data, + onProgressItem, + onFinishItem, + }) { + const dir = await UfsGlobal.Utils.chooseFolderToDownload(folderName); + onProgressItem?.(0, data.length); + + UfsGlobal.Extension.trackEvent("douyin_batchDownload-download"); + for (let i = 0; i < data.length; ++i) { + try { + onProgressItem?.(i + 1, data.length); + const { url, filename } = data[i]; + const realUrl = await UfsGlobal.Utils.getRedirectedUrl(url); + await UfsGlobal.Utils.downloadToFolder({ + url: realUrl, + fileName: filename, + dirHandler: dir, + expectBlobTypes, + }); + onFinishItem?.(i + 1, data.length); + } catch (e) { + console.error(e); + } + } + } + }, + }, +}; diff --git a/scripts/douyin_batchDownload.png b/scripts/douyin_batchDownload.png new file mode 100644 index 00000000..8b2b47ed Binary files /dev/null and b/scripts/douyin_batchDownload.png differ diff --git a/scripts/fb_aio.png b/scripts/fb_aio.png new file mode 100644 index 00000000..e0ac99ba Binary files /dev/null and b/scripts/fb_aio.png differ diff --git a/scripts/fb_allInOne.js b/scripts/fb_allInOne.js index 3794bc6e..38acc152 100644 --- a/scripts/fb_allInOne.js +++ b/scripts/fb_allInOne.js @@ -6,7 +6,7 @@ const GLOBAL = { }; export default { - icon: '', + icon: "/scripts/fb_aio.png", name: { en: "Facebook - All In One", vi: "Facebook - All In One", @@ -19,7 +19,7 @@ export default { popupScript: { onClick: () => { - window.open("https://facebook-all-in-one.com/dist"); + window.open("https://facebook-all-in-one.com"); }, }, diff --git a/scripts/fb_autoRemoveSpamPostGroup.css b/scripts/fb_autoRemoveSpamPostGroup.css new file mode 100644 index 00000000..e53c6878 --- /dev/null +++ b/scripts/fb_autoRemoveSpamPostGroup.css @@ -0,0 +1,118 @@ +body { + min-width: 350px; + min-height: 200px; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-size: 16px; + background-color: #333; + color: #ccc +} + + +h3 { + text-decoration: underline; + margin-bottom: 8px; +} + + +/* The container */ +.container { + display: block; + position: relative; + padding-left: 35px; + margin-bottom: 12px; + cursor: pointer; + font-size: 22px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +/* Hide the browser's default radio button */ +.container input { + position: absolute; + opacity: 0; + cursor: pointer; +} + +/* Create a custom radio button */ +.checkmark { + position: absolute; + top: 0; + left: 0; + height: 25px; + width: 25px; + background-color: #eee; + border-radius: 50%; +} + +/* On mouse-over, add a grey background color */ +.container:hover input~.checkmark { + background-color: #ccc; +} + +/* When the radio button is checked, add a blue background */ +.container input:checked~.checkmark { + background-color: #2196F3; +} + +/* Create the indicator (the dot/circle - hidden when not checked) */ +.checkmark:after { + content: ""; + position: absolute; + display: none; +} + +/* Show the indicator (dot/circle) when checked */ +.container input:checked~.checkmark:after { + display: block; +} + +/* Style the indicator (dot/circle) */ +.container .checkmark:after { + top: 9px; + left: 9px; + width: 8px; + height: 8px; + border-radius: 50%; + background: white; +} + + +/* ======================= Range ====================== */ + +input[type=number] { + padding: 5px; + font-size: large; +} + +.button { + background-color: #04AA6D; + border: none; + color: white; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + cursor: pointer; + width: 100%; + margin-top: 30px; +} + +.button.running { + background-color: rgb(151, 12, 12); + /* cursor: not-allowed; */ +} + +.input-row { + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: row; + white-space: pre; +} + +.input-row input[type=number] { + /* max-width: 80px; */ +} diff --git a/scripts/fb_autoRemoveSpamPostGroup.html b/scripts/fb_autoRemoveSpamPostGroup.html new file mode 100644 index 00000000..65edc16b --- /dev/null +++ b/scripts/fb_autoRemoveSpamPostGroup.html @@ -0,0 +1,43 @@ + + + + + + + Auto duyệt bài spam group fb + + + + + +

Auto duyệt bài spam group fb

+ +

Chọn hành động

+ + + +
+

Thời gian chờ (giây):

+ (ngẫu nhiên trong khoảng)
+
+ ➡️ +
+
+ +

Duyệt tối đa bao nhiêu bài?

+ (nhập 0 để duyệt hết)
+ + + + + + + + diff --git a/scripts/fb_autoRemoveSpamPostGroup.js b/scripts/fb_autoRemoveSpamPostGroup.js new file mode 100644 index 00000000..0f7cee4c --- /dev/null +++ b/scripts/fb_autoRemoveSpamPostGroup.js @@ -0,0 +1,26 @@ +import { BADGES } from "./helpers/badge.js"; + +export default { + icon: '', + name: { + en: "📝 Auto remove fb group's spam posts", + vi: "📝 Auto duyệt bài spam group fb", + }, + description: { + en: "Auto Accept / Reject spam posts on your facebook group.", + vi: "Tự động Đăng / Xoá những bài spam trong group facebook của bạn.", + img: "/scripts/fb_autoRemoveSpamPostGroup.png", + }, + badges: [BADGES.new], + + changeLogs: { + "2024-08-09": "init", + }, + whiteList: ["https://www.facebook.com/*"], + + popupScript: { + onClick: () => { + window.open("/scripts/fb_autoRemoveSpamPostGroup.html", "_self"); + }, + }, +}; diff --git a/scripts/fb_autoRemoveSpamPostGroup.png b/scripts/fb_autoRemoveSpamPostGroup.png new file mode 100644 index 00000000..e55f3e4b Binary files /dev/null and b/scripts/fb_autoRemoveSpamPostGroup.png differ diff --git a/scripts/fb_autoRemoveSpamPostGroup_main.js b/scripts/fb_autoRemoveSpamPostGroup_main.js new file mode 100644 index 00000000..8e64d881 --- /dev/null +++ b/scripts/fb_autoRemoveSpamPostGroup_main.js @@ -0,0 +1,248 @@ +const waitMinInp = document.getElementById("inputWaitMin"); +const waitMaxInp = document.getElementById("inputWaitMax"); +const inputMaxPosts = document.getElementById("max-posts"); +const radioAction = document.getElementsByName("action"); +const startBtn = document.getElementById("start-btn"); + +function initCacheInput(input, cacheName) { + if (localStorage.getItem(cacheName)) { + input.value = localStorage.getItem(cacheName); + } + input.addEventListener("input", () => { + localStorage.setItem(cacheName, input.value); + }); +} + +function renderTime(time, fixed = 1) { + return (time / 1000).toFixed(fixed) + "s"; +} + +async function main() { + initCacheInput(waitMinInp, "wait-min"); + initCacheInput(waitMaxInp, "wait-max"); + initCacheInput(inputMaxPosts, "max-posts"); + + const tab = await getCurrentTab(); + + if (!tab.url.includes("groups") || !tab.url.includes("spam")) { + return prompt( + "\n\nBạn cần mở trang duyệt bài spam của group trước.\n\nLink hiện tại: " + + tab.url + + "\n\n\nLink đúng ví dụ:", + "https://www.facebook.com/groups/gamecode/spam" + ); + } + + startBtn.addEventListener("click", async () => { + const state = await getCurrentState(tab); + if (state?.running) { + return stop(tab); + } + + let action = radioAction[0].checked ? 1 : 2; + let maxPosts = parseInt(inputMaxPosts.value); + let waitMin = parseInt(waitMinInp.value) * 1000; + let waitMax = parseInt(waitMaxInp.value) * 1000; + + if (waitMin > waitMax) { + return alert( + "Thời gian chờ không hợp lệ\nBên trái phải bé hơn hoặc bằng bên phải" + ); + } + + runScriptInTab({ + target: { tabId: tab.id }, + func: start, + args: [action, maxPosts, waitMin, waitMax], + }); + }); + + // check is running + (async function checkIsRunning() { + const state = await getCurrentState(tab); + const { running, nextExecuteTime, count, action } = state || {}; + if (running) { + startBtn.innerHTML = + "Đang " + + (action === 1 ? "đăng" : "từ chối") + + "... " + + count + + " bài (chờ " + + renderTime(nextExecuteTime - Date.now(), 0) + + ")
(Bấm để dừng)"; + startBtn.classList.add("running"); + } else { + startBtn.innerHTML = "Bắt đầu"; + startBtn.classList.remove("running"); + } + setTimeout(checkIsRunning, 1000); + })(); +} + +function getCurrentState(tab) { + return runScriptInTab({ + target: { tabId: tab.id }, + func: () => window.fb_group_ext, + }); +} + +function stop(tab) { + runScriptInTab({ + target: { tabId: tab.id }, + func: () => { + window.fb_group_ext.stop = true; + }, + }); +} + +function start(action, maxPosts, waitMin, waitMax) { + const selector = + action == 1 + ? '[role="main"] [aria-label="Đăng"]' + : '[role="main"] [aria-label="Từ chối"]'; + + if (maxPosts == 0) maxPosts = Infinity; + + const btns = Array.from(document.querySelectorAll(selector)); + + if (!btns.length) { + alert("Không tìm thấy bài nào"); + return; + } + + onElementsAdded(selector, (nodes) => { + for (let node of nodes) { + if (btns.includes(node)) continue; + btns.push(node); + } + }); + + async function main() { + window.fb_group_ext = { + action, + running: true, + nextExecuteTime: 0, + stop: false, + count: 0, + }; + + while (window.fb_group_ext.count < maxPosts && !window.fb_group_ext.stop) { + if (btns.length > 0) { + const btn = btns.shift(); + + btn.scrollIntoView({ block: "center", behavior: "smooth" }); + console.log("click", btn); + btn.click(); + + window.fb_group_ext.count++; + const waitTime = ranInt(waitMin, waitMax); + window.fb_group_ext.nextExecuteTime = Date.now() + waitTime; + await sleep(waitTime, () => window.fb_group_ext.stop); + } else { + // scroll to end + window.scrollTo(0, document.body.scrollHeight); + // wait for load more + await sleep(1000); + } + } + + window.fb_group_ext.running = false; + alert("Duyệt xong " + window.fb_group_ext.count + " bài"); + } + + main(); + + function ranInt(min, max) { + return Math.floor(Math.random() * (max - min) + min); + } + + function sleep(time, cancelFn) { + return new Promise((resolve) => { + const timeout = setTimeout(resolve, time); + if (cancelFn) { + const interval = setInterval(() => { + if (cancelFn()) { + clearInterval(interval); + clearTimeout(timeout); + resolve(); + } + }, 100); + } + }); + } + + function onElementsAdded(selector, callback, once) { + let nodes = document.querySelectorAll(selector); + if (nodes?.length) { + callback(nodes); + if (once) return; + } + + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (!mutation.addedNodes) return; + + for (let node of mutation.addedNodes) { + if (node.nodeType != 1) continue; // only process Node.ELEMENT_NODE + + let n = node.matches(selector) + ? [node] + : Array.from(node.querySelectorAll(selector)); + + if (n?.length) { + callback(n); + if (once) observer.disconnect(); + } + } + }); + }); + + observer.observe(document, { + childList: true, + subtree: true, + attributes: false, + characterData: false, + }); + + // return disconnect function + return () => observer.disconnect(); + } +} + +async function getCurrentTab() { + let tabs = await chrome.tabs.query({ + active: true, + currentWindow: true, + }); + return tabs[0]; +} + +const runScriptInTab = async (config = {}) => { + return new Promise((resolve, reject) => { + chrome.scripting.executeScript( + mergeObject( + { + world: "MAIN", + injectImmediately: true, + }, + config + ), + (injectionResults) => { + if (chrome.runtime.lastError) { + console.error(chrome.runtime.lastError); + reject(chrome.runtime.lastError); + } + // https://developer.chrome.com/docs/extensions/reference/scripting/#handling-results + else resolve(injectionResults?.find?.((_) => _.result)?.result); + } + ); + }); +}; +const mergeObject = (...objs) => { + // merge without null value + let res = {}; + for (let obj of objs) for (let key in obj) if (obj[key]) res[key] = obj[key]; + return res; +}; + +main(); diff --git a/scripts/fb_searchGroupForOther.js b/scripts/fb_searchGroupForOther.js index d6de31cf..e0b86328 100644 --- a/scripts/fb_searchGroupForOther.js +++ b/scripts/fb_searchGroupForOther.js @@ -1,5 +1,3 @@ -import { BADGES } from "./helpers/badge.js"; - export default { icon: '', name: { @@ -10,7 +8,6 @@ export default { en: "Know about your friends's joined groups (public groups) on facebook", vi: "Biết bạn bè của bạn đang tham gia các nhóm (công khai) nào trên facebook", }, - badges: [BADGES.hot], popupScript: { onClick: async () => { diff --git a/scripts/fb_searchPageForOther.js b/scripts/fb_searchPageForOther.js index 24dc7ca2..d0b05756 100644 --- a/scripts/fb_searchPageForOther.js +++ b/scripts/fb_searchPageForOther.js @@ -1,5 +1,3 @@ -import { BADGES } from "./helpers/badge.js"; - export default { icon: '', name: { @@ -10,7 +8,6 @@ export default { en: "Know about your friends's liked pages (public pages) on facebook", vi: "Biết bạn bè của bạn đang thích các trang (công khai) nào trên facebook", }, - badges: [BADGES.hot], popupScript: { onClick: async () => { diff --git a/scripts/ggDrive_downloadAllVideosInFolder.js b/scripts/ggDrive_downloadAllVideosInFolder.js index 5c27b20b..e15f57dc 100644 --- a/scripts/ggDrive_downloadAllVideosInFolder.js +++ b/scripts/ggDrive_downloadAllVideosInFolder.js @@ -47,7 +47,9 @@ export default {

${name}



Lỗi: ${errors.length} video
- ${errors.map(({ id, name, e }) => name).join("
")} + ${errors + .map(({ id, name, e }) => name + ": " + e.message) + .join("
")}
`); try { // prettier-ignore @@ -58,6 +60,8 @@ export default { } } + if (!result.length) throw new Error("Không tải được video nào"); + // =========== Render Data =========== let allUrls = {}; let tableHtml = result diff --git a/scripts/ggdrive_downloadVideo.js b/scripts/ggdrive_downloadVideo.js index 7e5e86dd..a4a5e042 100644 --- a/scripts/ggdrive_downloadVideo.js +++ b/scripts/ggdrive_downloadVideo.js @@ -15,6 +15,7 @@ export default { "https://www.facebook.com/groups/j2team.community/posts/974953859503401/", changeLogs: { + "2024-08-14": "get video url directly", "2024-07-25": "add backup plan", }, @@ -63,13 +64,14 @@ export default { }, contentScript: { onClick_: async () => { + const { openPopupWithHtml } = await import("./helpers/utils.js"); + let url = new URL(location.href); const player_response = url.searchParams.get("player_response"); if (player_response) { const json = JSON.parse(player_response); console.log(document.title, json); - const { openPopupWithHtml } = await import("./helpers/utils.js"); openPopupWithHtml( `

${document.title}

${json.streamingData.formats @@ -83,6 +85,23 @@ export default { 700, 700 ); + } else { + const videos = document.querySelectorAll("video"); + if (videos.length) { + openPopupWithHtml( + `

${document.title}

+ ${Array.from(videos) + .map((_) => { + return /*html*/ `
+

Link

+
`; + }) + .join("
")}`, + 700, + 700 + ); + } } }, }, @@ -126,32 +145,43 @@ export const shared = { } async function getLink(docid) { - let res = await fetch( - "https://drive.google.com/get_video_info?docid=" + docid - ); + let lastError; + for (let u of ["", 0, 1, 2]) { + let res = await fetch( + "https://drive.google.com/" + + (u !== "" ? `u/${u}/` : "") + + "get_video_info?docid=" + + docid + ); - let text = await res.text(); - let json = parse(text); + let text = await res.text(); + let json = parse(text); - if (json?.status === "fail") { - throw Error("FAILED: " + json.reason); - } + if (json?.status === "fail") { + lastError = "FAILED: " + json.reason; + console.log(u, docid, lastError); + continue; + } - json.url_encoded_fmt_stream_map = parseStream( - json.url_encoded_fmt_stream_map - ); + json.url_encoded_fmt_stream_map = parseStream( + json.url_encoded_fmt_stream_map + ); - let result = json.url_encoded_fmt_stream_map.map(function (stream) { - let name = json.title.replace(/\+/g, " "); - return { - idfile: docid, - name: name, - quality: stream.quality, - url: stream.url, - }; - }); + let result = json.url_encoded_fmt_stream_map.map(function (stream) { + let name = json.title.replace(/\+/g, " "); + return { + idfile: docid, + name: name, + quality: stream.quality, + url: stream.url, + }; + }); + + return result; + } - return result; + if (lastError) throw new Error(lastError); + return null; } return await getLink(docid); diff --git a/scripts/helpers/utils.js b/scripts/helpers/utils.js index 97459bd8..782aac26 100644 --- a/scripts/helpers/utils.js +++ b/scripts/helpers/utils.js @@ -142,7 +142,7 @@ export function runFunc(fnPath = "", params = [], global = {}) { export async function trackEvent(scriptId) { console.log("trackEvent", scriptId, version); - // return; + return; try { let res = await fetch( // "http://localhost:3000/count", diff --git a/scripts/insta_GLOBAL.js b/scripts/insta_GLOBAL.js index c7fc4d7b..dc07aee4 100644 --- a/scripts/insta_GLOBAL.js +++ b/scripts/insta_GLOBAL.js @@ -25,6 +25,8 @@ export function getUniversalCdnUrl(cdnLink) { return cdnLink; } } + +// WARNING: not working anymore?? export async function getAllMedia({ uid, progressCallback, limit = 0 }) { let all_urls = []; let after = ""; diff --git a/scripts/libs/ajax-hook/index.js b/scripts/libs/ajax-hook/index.js index cc371639..d09ae7a9 100644 --- a/scripts/libs/ajax-hook/index.js +++ b/scripts/libs/ajax-hook/index.js @@ -62,7 +62,7 @@ function initFetch() { } } - let response = await originalFetch(urlOrRequest); + let response = await originalFetch(urlOrRequest, options); for (const { fn } of onAfterFetchFn) { const res = await fn?.(request.url, request.options, response)?.catch( console.error diff --git a/scripts/magnify_image.js b/scripts/magnify_image.js index f31cca6b..cf9f77c8 100644 --- a/scripts/magnify_image.js +++ b/scripts/magnify_image.js @@ -509,7 +509,7 @@ function getImgSrcsFromElement(ele) { results = results.concat(srcs.map((src) => relativeUrlToAbsolute(src))); } } catch (e) { - console.log("error", e); + // console.log("error", e); } } return results; diff --git a/scripts/net-request-rules/rules.json b/scripts/net-request-rules/rules.json index 6f5d5554..7a47e4da 100644 --- a/scripts/net-request-rules/rules.json +++ b/scripts/net-request-rules/rules.json @@ -80,48 +80,24 @@ { "header": "referer", "operation": "set", - "value": "https://www.bing.com/images/create/" + "value": "https://www.instagram.com" }, { "header": "origin", "operation": "set", - "value": "https://www.bing.com" + "value": "https://www.instagram.com" } ] }, "condition": { "domain": "extension://*", - "urlFilter": "https://www.bing.com/images/create/", + "urlFilter": "https://www.instagram.com", "resourceTypes": ["xmlhttprequest"] } }, { "id": 5, "priority": 1, - "action": { - "type": "modifyHeaders", - "requestHeaders": [ - { - "header": "referer", - "operation": "set", - "value": "https://doutu.be/" - }, - { - "header": "origin", - "operation": "set", - "value": "https://doutu.be" - } - ] - }, - "condition": { - "domain": "extension://*", - "urlFilter": "https://||doutube.*/*", - "resourceTypes": ["xmlhttprequest"] - } - }, - { - "id": 6, - "priority": 1, "action": { "type": "modifyHeaders", "requestHeaders": [ @@ -144,49 +120,7 @@ } }, { - "id": 7, - "priority": 1, - "action": { - "type": "modifyHeaders", - "requestHeaders": [ - { - "header": "User-Agent", - "operation": "set", - "value": "com.ss.android.ugc.trill/2613 (Linux; U; Android 10; en_US; Pixel 4; Build/QQ3A.200805.001; Cronet/58.0.2991.0)" - }, - { - "header": "Sec-Fetch-Dest", - "operation": "set", - "value": "document" - }, - { - "header": "Sec-Fetch-Mode", - "operation": "set", - "value": "navigate" - }, - { - "header": "Sec-Fetch-Site", - "operation": "set", - "value": "cross-site" - }, - { - "header": "referer", - "operation": "remove" - }, - { - "header": "origin", - "operation": "remove" - } - ] - }, - "condition": { - "domain": "extension://*", - "urlFilter": "*://api22-normal-c-useast2a.tiktokv.com/*", - "resourceTypes": ["main_frame", "xmlhttprequest"] - } - }, - { - "id": 8, + "id": 6, "priority": 1, "action": { "type": "modifyHeaders", diff --git a/scripts/showHiddenFields.js b/scripts/showHiddenFields.js index d664f61f..6da4a409 100644 --- a/scripts/showHiddenFields.js +++ b/scripts/showHiddenFields.js @@ -20,11 +20,12 @@ export default { div, label, ne, - found = false; + found = false, + D = document, + count = 0; for (i = 0; (f = document.forms[i]); ++i) for (j = 0; (e = f[j]); ++j) if (e.type == "hidden") { - D = document; function C(t) { return D.createElement(t); } @@ -45,9 +46,11 @@ export default { --j; /*for moz*/ found = true; + count++; } if (!found) alert("Nothing is hidden! / Không có thành phần nào bị ẩn!"); + alert("Showed " + count + " hidden fields."); }, }, }; diff --git a/scripts/test.js b/scripts/test.js index 986166a3..979198c3 100644 --- a/scripts/test.js +++ b/scripts/test.js @@ -1,5 +1,5 @@ import { UfsGlobal } from "./content-scripts/ufs_global.js"; -import { fetchGraphQl, getFbdtsg } from "./fb_GLOBAL.js"; +import { hookFetch } from "./libs/ajax-hook/index.js"; export default { icon: "", @@ -12,6 +12,8 @@ export default { vi: "", }, + whiteList: ["https://chatgpt.com/*"], + popupScript: { onClick: async () => { function getAverageRGB(img) { @@ -329,6 +331,26 @@ export default { }, pageScript: { + onDocumentStart: () => { + hookFetch({ + onBefore: (url, options) => { + if ( + url === "https://chatgpt.com/backend-anon/conversation" || + url === "https://chatgpt.com/backend-api/conversation" + ) { + console.log(url, options); + const body = JSON.parse(options.body); + // body.model = "gpt-4o"; + body.messages.forEach((m) => { + m.author.role = "system"; + // m.content.parts = ["what is your name"]; + }); + console.log("body", body); + options.body = JSON.stringify(body); + } + }, + }); + }, // download album _onClick: async () => { (async () => { diff --git a/scripts/tiktok_batchDownload.css b/scripts/tiktok_batchDownload.css index b9575d00..45779529 100644 --- a/scripts/tiktok_batchDownload.css +++ b/scripts/tiktok_batchDownload.css @@ -70,6 +70,7 @@ .ufs_popup td { border: 1px solid #aaa; border-collapse: collapse; + vertical-align: middle; } .ufs_popup table td { diff --git a/scripts/tiktok_batchDownload.js b/scripts/tiktok_batchDownload.js index bfb6dcdb..d76246ba 100644 --- a/scripts/tiktok_batchDownload.js +++ b/scripts/tiktok_batchDownload.js @@ -96,7 +96,7 @@ export default {
📥 {{totalCount}}
-

Tiktok - Useful Scripts

+

Tiktok - Useful Scripts

Found {{totalCount}} videos

@@ -191,7 +191,10 @@ export default { search: "", sortBy: "index", sortDir: "asc", - downloading: {}, + downloading: { + video: null, + audio: null, + }, selected: {}, }; }, @@ -225,7 +228,7 @@ export default { return Array.from(result.values()); }, videoTitle() { - if (this.downloading.video) { + if (Number.isInteger(this.downloading.video)) { return ( "Downloading " + this.downloading.video + @@ -242,7 +245,7 @@ export default { ); }, audioTitle() { - if (this.downloading.audio) { + if (Number.isInteger(this.downloading.audio)) { return ( "Downloading " + this.downloading.audio + @@ -313,22 +316,25 @@ export default { expectBlobTypes: ["video/mp4", "image/jpeg"], data: this.videoToDownload .map((_, i) => { + const all = []; // image const imgs = _.imagePost?.images; if (imgs?.length) { - return imgs.map((img, j) => ({ - url: - img.imageURL?.urlList?.[1] || - img.imageURL?.urlList?.[0], - filename: - i + - 1 + - "_" + - (j + 1) + - "_" + - UfsGlobal.Utils.sanitizeName(_.id, false) + - ".jpg", - })); + all.push( + ...imgs.map((img, j) => ({ + url: + img.imageURL?.urlList?.[1] || + img.imageURL?.urlList?.[0], + filename: + i + + 1 + + "_" + + (j + 1) + + "_" + + UfsGlobal.Utils.sanitizeName(_.id, false) + + ".jpg", + })) + ); } // video @@ -337,7 +343,7 @@ export default { (b) => b.Bitrate === _.video.bitrate )?.PlayAddr?.UrlList || []; const bestUrl = urlList[urlList.length - 1]; - return { + all.push({ url: bestUrl || _.video.playAddr, filename: i + @@ -345,7 +351,8 @@ export default { "_" + UfsGlobal.Utils.sanitizeName(_.id, false) + ".mp4", - }; + }); + return all; }) .flat() .filter((_) => _.url), @@ -403,6 +410,7 @@ export default { this.selectedIds.forEach((vidId) => { CACHED.videoById.delete(vidId); }); + CACHED.hasNew = true; this.selected = {}; }, clear() { @@ -422,9 +430,6 @@ export default { format(v) { return formatter.format(v); }, - onClickContainer(e) { - if (e.target === this.$el) this.showModal = false; - }, }, }).$mount(div); diff --git a/scripts/ufs_statistic.css b/scripts/ufs_statistic.css index 281ad85f..b44be618 100644 --- a/scripts/ufs_statistic.css +++ b/scripts/ufs_statistic.css @@ -9,8 +9,9 @@ canvas { max-height: 500px; } -li { +ol.log-list li { position: relative; + white-space: pre; } li:hover { @@ -18,20 +19,31 @@ li:hover { color: white; } -li a { - display: inline-block; +ol.log-list li a { + position: absolute; + display: flex; + align-items: center; + color: yellow; + right: 0; + top: 0; +} + +li a span { + margin-right: 5px; } -li a img { +ol.log-list li a:visited { + color: green; +} + +li a img, +.avatar { width: 30px; transition: all 0.2s ease; - position: absolute; - right: 0; - top: 0; } -li:hover img { - transform: scale(2); +li a:hover img { + transform: scale(3.5); z-index: 2; } @@ -54,3 +66,101 @@ button:hover { .btn-active { box-shadow: inset 0 0 10px 5px #333; } + +.show-on-hover { + display: inline-block; + width: 0; + opacity: 0; + transition: all 0.5s ease 2s; + overflow: hidden; +} + +li:hover .show-on-hover { + width: max-content; + opacity: 1; + transition: all 0s ease 0.5s; +} + +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 9999; +} + +.modal:has(.modal-content:hover) { + background: rgba(0, 0, 0, 0.5); +} + +.modal-content { + background: #2c2c2c; + padding: 20px; + border-radius: 5px; + box-shadow: 0 0 10px #000; + max-width: 90vw; + max-height: 90vh; + overflow-y: auto; + overflow-x: hidden; + min-width: 500px; + display: flex; + flex-direction: column; + align-items: center; + opacity: 0.05; + transition: opacity 0.1s ease; +} + +.modal-content:hover { + opacity: 1; +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.modal-body { + margin-bottom: 10px; + width: 100%; +} + +.modal .close { + cursor: pointer; + position: absolute; + top: 10px; + right: 10px; + width: 20px; + height: 20px; + cursor: pointer; +} + +.modal ol { + padding: 0; + list-style: none; +} + +.modal ol a { + color: white; + text-decoration: none; +} + +.modal li { + width: 100%; + clear: right; +} + +[data-search] { + cursor: pointer; + float: right; +} + +[data-search]:hover { + text-decoration: underline; +} diff --git a/scripts/ufs_statistic.js b/scripts/ufs_statistic.js index 376594ba..9ce588aa 100644 --- a/scripts/ufs_statistic.js +++ b/scripts/ufs_statistic.js @@ -1,4 +1,5 @@ import { UfsGlobal } from "./content-scripts/ufs_global.js"; +import { getUserAvatarFromUid } from "./fb_GLOBAL.js"; export default { icon: "/assets/icon32.png", @@ -35,15 +36,24 @@ async function onDocumentEnd() { let hasLog = logs[0] != "Log not found" && logs[0] != "Waking up"; const allLogs = logs.map((log) => { - return { + const data = { log, uid: extractUid(log), time: new Date(extractTime(log)), - timeString: extractTime(log), + timeString: extractTime(log).replace(/\d+\/\d+\/\d+, /, ""), eventName: extractEventName(log), version: extractVersion(log), + totalCount: extractTotalCount(log), isScript: isScript(log), }; + const eventNameWithoutVersion = data.eventName + .replace("(" + data.version + ")", "") + .trim(); + const version = padStr(data.version, 4, " "); + if (version && eventNameWithoutVersion) + data.logPretty = `${data.timeString} | ${version} | ${eventNameWithoutVersion} ${data.totalCount} ${data.uid}`; + else data.logPretty = data.log; + return data; }); const container = document.createElement("div"); @@ -53,29 +63,35 @@ async function onDocumentEnd() { UfsGlobal.DOM.injectCssFile ); - // #region add search box + // #region re-make UI document.body.innerText = ""; + // #region create list const ol = document.createElement("ol"); + ol.classList.add("log-list"); ol.setAttribute("reversed", true); document.body.appendChild(ol); const all_li = allLogs.map((data) => { let li = document.createElement("li"); if (isFbUid(data?.uid)) { li.innerHTML = - data.log + - ` - - - fb + data.logPretty + + ` + fb + `; } else { - li.textContent = data.log; + li.innerHTML = data.logPretty; } ol.appendChild(li); return { li, data }; }); console.log(all_li); + // #endregion + // #region trace uid const traceUidCheckmark = document.createElement("input"); traceUidCheckmark.id = "trace-uid"; traceUidCheckmark.type = "checkbox"; @@ -88,7 +104,9 @@ async function onDocumentEnd() { label.textContent = "Trace by uid"; label.setAttribute("for", traceUidCheckmark.id); container.appendChild(label); + // #endregion + // #region search box const searchBox = document.createElement("input"); searchBox.placeholder = "Search logs..."; container.prepend(searchBox); @@ -170,6 +188,7 @@ async function onDocumentEnd() { } } }); + // #endregion // #endregion @@ -362,7 +381,7 @@ async function onDocumentEnd() { // #endregion - // ======================== show scripts only ======================== + // #region ======================== show scripts only ======================== let scriptOnlyState = false; const scriptOnlyToggle = document.createElement("button"); @@ -381,8 +400,9 @@ async function onDocumentEnd() { } }); }; + // #endregion - // ======================== Average section ======================== + // #region ======================== Average section ======================== const scriptsUsed = new Map(); allLogs.forEach((data) => { if (data.isScript) { @@ -404,17 +424,152 @@ async function onDocumentEnd() { ${scriptsUsed.size} unique scripts

${logByUid.size} unique users
${fbUsers.length} facebook users`; + // #endregion + + // #region ======================== modal sortby uid/script ======================== + const modalByUid = document.createElement("div"); + modalByUid.classList.add("modal"); + modalByUid.innerHTML = ` + `; + + const modalByScript = document.createElement("div"); + modalByScript.classList.add("modal"); + modalByScript.innerHTML = ` + `; + + const btnOpenModalByUid = document.createElement("button"); + btnOpenModalByUid.textContent = "Rank by uid"; + btnOpenModalByUid.onclick = () => { + modalByUid.style.display = "flex"; + }; + + const btnOpenModalByScript = document.createElement("button"); + btnOpenModalByScript.textContent = "Rank by event name"; + btnOpenModalByScript.onclick = () => { + modalByScript.style.display = "flex"; + }; + + document.body.addEventListener("click", (event) => { + if (event.target.classList.contains("modal")) { + event.target.style.display = "none"; + } + const dataSearch = event.target.getAttribute("data-search"); + if (dataSearch) { + searchBox.value = dataSearch; + searchBox.dispatchEvent(new Event("input", { bubbles: true })); + } + }); + + document.body.append(modalByUid, modalByScript); + // #endregion - // ======================== Append Charts ======================== container.prepend( h1, toggleShowHideAllBtn, canvas_eventPerHour, + btnOpenModalByScript, canvas_events, + btnOpenModalByUid, canvas_uid, scriptOnlyToggle ); // #endregion + + // #region load fb profiles + const allUid = allLogs + .filter((log) => isFbUid(log?.uid)) + .map((log) => log.uid); + + const uniqueUid = [...new Set(allUid)].filter(Boolean); + + if (uniqueUid.length) { + initCache().then(() => { + const promises = uniqueUid.map( + (uid) => () => + getFbProfile(uid).then((info) => { + document + .querySelectorAll(`[data-profile-name="${uid}"]`) + .forEach((el) => { + el.textContent = limitString(info.name, 40); + }); + document + .querySelectorAll(`[data-profile-avatar="${uid}"]`) + .forEach((el) => { + el.src = info.avatar.replace(/\\\//g, "/") || el.src; + + let tried = 0, + loading = false; + + // in case cached avatqr expired + el.onerror = () => { + if (loading) return; + tried++; + if (tried > 3) el.src = getUserAvatarFromUid(uid); + else { + loading = true; + getFbProfile(uid, true) + .then((info) => { + el.src = info.avatar.replace(/\\\//g, "/") || el.src; + }) + .finally(() => { + loading = false; + }); + } + }; + }); + }) + ); + UfsGlobal.Utils.promiseAllStepN(5, promises).then(() => { + if (CACHED.newFbUsers.size) { + const arr = Array.from(CACHED.newFbUsers.values()); + console.log("New users", arr); + alert( + CACHED.newFbUsers.size + + " new users:\n\n" + + arr.map((_) => _.name).join("\n") + ); + } + }); + }); + } + // #endregion } // #region add date selector @@ -452,6 +607,26 @@ async function onDocumentEnd() { document.body.prepend(container); // #endregion + + // #region auto get fb info of selected text on press A + + document.addEventListener("keydown", (event) => { + const selectedText = UfsGlobal.DOM.getSelectionText(); + if (selectedText) { + if (event.key === "a") { + getEntityAbout(selectedText) + .then((data) => + alert(data.type + ":\n" + data.name + "\n\n" + data.url) + ) + .catch((e) => alert("ERROR: " + e.message)); + } + if (event.key === "d") { + window.open("https://fb.com/" + selectedText, "_blank"); + } + } + }); + + // #endregion } function getCurrentLogDate() { @@ -489,31 +664,155 @@ function isFbUid(uid) { (uid?.startsWith("100") || (uid?.length && uid?.length != 13)) ); } -function fbAvatarFromUid(uid) { - return `https://graph.facebook.com/${uid}/picture?height=50&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`; + +const CACHED = { + fbProfile: null, + fb_dtsg: null, + newFbUsers: new Map(), +}; + +async function initCache() { + if (!CACHED.fbProfile) { + CACHED.fbProfile = new Map(); + try { + const c = localStorage.getItem("fbProfile"); + if (c) { + const arr = JSON.parse(c); + console.log(arr); + arr.forEach((info) => CACHED.fbProfile.set(info.uid, info)); + } + } catch (e) { + console.error(e); + } + } + + if (!CACHED.fb_dtsg) { + let res = await UfsGlobal.Extension.runInBackground("fetch", [ + "https://mbasic.facebook.com/photos/upload/", + ]); + CACHED.fb_dtsg = RegExp(/name="fb_dtsg" value="(.*?)"/).exec(res.body)?.[1]; + } +} + +async function getFbProfile(uid, force = false) { + if (CACHED.fbProfile.has(uid) && !force) return CACHED.fbProfile.get(uid); + + let res = await UfsGlobal.Extension.runInBackground("fetch", [ + "https://www.facebook.com/api/graphql/", + { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + fb_api_req_friendly_name: "ProfileCometHeaderQuery", + fb_dtsg: CACHED.fb_dtsg, + variables: JSON.stringify({ + userID: uid, + shouldDeferProfilePic: false, + useVNextHeader: false, + scale: 1.5, + }), + doc_id: "4159355184147969", + }).toString(), + }, + ]); + + let text = await res.body; + const info = { + uid: uid, + name: UfsGlobal.DEBUG.decodeEscapedUnicodeString( + /"name":"(.*?)"/.exec(text)?.[1] + ), + avatar: UfsGlobal.DEBUG.decodeEscapedUnicodeString( + /"profilePicLarge":{"uri":"(.*?)"/.exec(text)?.[1] || + /"profilePicMedium":{"uri":"(.*?)"/.exec(text)?.[1] || + /"profilePicSmall":{"uri":"(.*?)"/.exec(text)?.[1] || + /"profilePic160":{"uri":"(.*?)"/.exec(text)?.[1] + ), + gender: /"gender":"(.*?)"/.exec(text)?.[1], + alternateName: UfsGlobal.DEBUG.decodeEscapedUnicodeString( + /"alternate_name":"(.*?)"/.exec(text)?.[1] + ), + }; + CACHED.newFbUsers.set(uid, info); + CACHED.fbProfile.set(uid, info); + localStorage.setItem( + "fbProfile", + JSON.stringify(Array.from(CACHED.fbProfile.values())) + ); + console.log(info); + return info; +} + +export const TargetType = { + User: "user", + Page: "page", + Group: "group", + IGUser: "ig_user", +}; + +export async function getEntityAbout(entityID, context = "DEFAULT") { + let res = await UfsGlobal.Extension.runInBackground("fetch", [ + "https://www.facebook.com/api/graphql/", + { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + fb_api_req_friendly_name: "CometHovercardQueryRendererQuery", + fb_dtsg: CACHED.fb_dtsg, + variables: JSON.stringify({ + actionBarRenderLocation: "WWW_COMET_HOVERCARD", + context: context, + entityID: entityID, + includeTdaInfo: true, + scale: 1, + }), + doc_id: "7257793420991802", + }).toString(), + }, + ]); + console.log(res); + const text = await res.body; + const json = JSON.parse(text); + const node = json.data.node; + if (!node) throw new Error("Wrong ID / Entity not found"); + const typeText = node.__typename.toLowerCase(); + if (!Object.values(TargetType).includes(typeText)) + throw new Error("Not supported type: " + typeText); + const card = node.comet_hovercard_renderer[typeText]; + const type = + typeText === "user" + ? card.profile_plus_transition_path?.startsWith("PAGE") + ? TargetType.Page + : TargetType.User + : TargetType.Group; + return { + type, + id: node.id || card.id, + name: card.name, + avatar: card.profile_picture.uri, + url: card.profile_url || card.url, + raw: json, + }; } // log example: 5/31/2024, 9:13:41 AM: OPEN-TAB-unlock (1.67-1717121281787) -> 43 function extractUid(log) { - return /-(\d+)\)/.exec(log)?.[1]; + return /-(\d+)\)/.exec(log)?.[1] || "?"; } function extractTime(log) { - let lastColon = log.lastIndexOf(":"); - let time = log.substring(0, lastColon); - return time; + return ( + /\d{1,2}\/\d{1,2}\/\d{4}, \d{1,2}:\d{1,2}:\d{1,2} \w{2}/.exec(log)?.[0] || + "" + ); } function extractEventName(log) { - let logWithoutUid = log.replace(/-\d+/, ""); - let lastColon = logWithoutUid.lastIndexOf(":"); - let lastArrow = logWithoutUid.lastIndexOf("->"); - let scriptName = logWithoutUid.substring(lastColon + 1, lastArrow - 1).trim(); - return scriptName; + return /: (.*?) \(/.exec(log)?.[1] || ""; +} +function extractTotalCount(log) { + return / -> (\d+)/.exec(log)?.[1] || ""; } - function extractVersion(log) { - let lastBracket = log.lastIndexOf("("); - let nextDash = log.indexOf("-", lastBracket); - return log.substring(lastBracket + 1, nextDash - 1).trim(); + return / \((.*?)-\d*\)/.exec(log)?.[1] || ""; } function isScript(log) { @@ -524,6 +823,23 @@ function isScript(log) { log.includes("CLICK_") || log.includes("-INFO") || log.includes("-FAVORITE") || - log.includes("-VIEW-SOURCE") + log.includes("-VIEW-SOURCE") || + log.includes("CHECK-FOR-UPDATE") || + log.includes("RESTORE") || + log.includes("BACKUP") || + log.includes("CHANGE-THEME") || + log.includes("CHANGE-SMOOTH-SCROLL") + ); +} + +function limitString(string, length) { + if (string.length <= length) return string; + return string.substring(0, length - 3) + "..."; +} + +function padStr(string, length, char = " ") { + return ( + string + + (length - string.length > 0 ? char.repeat(length - string.length) : "") ); } diff --git a/scripts/youtube_changeCountry.js b/scripts/youtube_changeCountry.js index ce126cd4..d75c4835 100644 --- a/scripts/youtube_changeCountry.js +++ b/scripts/youtube_changeCountry.js @@ -1,3 +1,5 @@ +import { UfsGlobal } from "./content-scripts/ufs_global.js"; + export default { icon: '', name: { @@ -22,7 +24,7 @@ export default { const popup = document.createElement("div"); popup.id = id; - popup.innerHTML = ` + popup.innerHTML = UfsGlobal.DOM.createTrustedHtml(`