diff --git a/README-en.md b/README-en.md index f4975c91..eb3a7607 100644 --- a/README-en.md +++ b/README-en.md @@ -6,6 +6,7 @@ - [Useful scripts - Chrome extension](#useful-scripts---chrome-extension) - [Overview](#overview) + - [Demo](#demo) - [Change logs](#change-logs) - [ScreenShots](#screenshots) - [Install](#install) @@ -21,7 +22,9 @@ An extension includes a lot of small extensions. Make your life easier. - Please join [FACEBOOK GROUP](https://www.facebook.com/groups/1154059318582088) of this extension -- View list all scripts [HERE](./md/LIST_SCRIPTS_EN.md) +## Demo + +Try online [Here](https://hoangtran0410.github.io/useful-script/popup/popup.html) ## Change logs @@ -29,11 +32,12 @@ An extension includes a lot of small extensions. Make your life easier. Current Versions: -- **v1.69**: small update (14/07/2024) +- **v1.7**: tiktok update 28/07/2024
Old versions +- v1.69: small update (14/07/2024) - v1.68: big facebook update (01/07/2024) - v1.67 - huge update (29/05/2024) - v1.66 - big update (27/04/2024) diff --git a/README.md b/README.md index 07526738..fd9b2f20 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ - [Useful scripts - Chrome extension](#useful-scripts---chrome-extension) - [Giới thiệu](#giới-thiệu) + - [Demo](#demo) - [Phiên bản](#phiên-bản) - [Ảnh chụp màn hình](#ảnh-chụp-màn-hình) - [Cài đặt](#cài-đặt) @@ -21,7 +22,9 @@ Donate? Muốn hỗ trợ mình 1 ly cafe <3 [Donate tại đây](https://github - Hãy tham gia ngay [GROUP FACEBOOK](https://www.facebook.com/groups/1154059318582088) của tiện ích -- Xem tất cả chức năng [TẠI ĐÂY](./md/LIST_SCRIPTS_VI.md) +## Demo + +Dùng thử online [Tại đây](https://hoangtran0410.github.io/useful-script/popup/popup.html) ## Phiên bản @@ -29,11 +32,12 @@ Xem [Lịch sử cập nhật](/md/CHANGELOGS.md) Phiên bản hiện tại: -- **v1.69**: small update (14/07/2024) +- **v1.7**: tiktok update 28/07/2024
Xem phiên bản cũ hơn +- 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) diff --git a/index.html b/index.html index 3ddf7873..7bc65451 100644 --- a/index.html +++ b/index.html @@ -8,18 +8,8 @@ - google.com - - - - - diff --git a/manifest.json b/manifest.json index e698f238..7a70224c 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "Useful Scripts", "description": "Scripts that can make your life faster and better", "homepage_url": "https://github.com/HoangTran0410/useful-script", - "version": "1.69", + "version": "1.7", "icons": { "16": "./assets/icon16.png", "32": "./assets/icon32.png", diff --git a/md/CHANGELOGS.md b/md/CHANGELOGS.md index b39999ae..e2cf4225 100644 --- a/md/CHANGELOGS.md +++ b/md/CHANGELOGS.md @@ -1,5 +1,23 @@ ## Change logs +
+ v1.7 - 28/07/2024 + +- Tiktok + - NEW batch download [source](/scripts/tiktok_batchDownload.js) + +- Google + - Download private video [source](/scripts/ggdrive_downloadVideo.js) + +- More + - Magnify image: support config min-size for hover [source](/scripts/magnify_image.js) + - Fix Medium VIP [source](/scripts/medium_readFullArticle.js) + - Guland VIP [source](/scripts/guland_VIP.js) + - Picture in picture anything (beta) [source](/scripts/pip_anything.js) + - Optimize performance UfsGlobal [source](/scripts/content-scripts/ufs_global.js) + +
+
v1.69 - 14/07/2024 diff --git a/md/CONTRIBUTE.md b/md/CONTRIBUTE.md index 4ef37910..a6a5637f 100644 --- a/md/CONTRIBUTE.md +++ b/md/CONTRIBUTE.md @@ -20,7 +20,7 @@ Nếu bạn có 1 `script hay`, hoặc `bookmarklets hay`, muốn `thêm vào ex 3. Ghi nội dung script: - Đọc cấu trúc code và comment trong file [templates/full.js](/templates/full.js) để biết thêm chi tiết. -4. Import script của bạn trong file [/scripts/_index.js](/scripts/_index.js) +4. Import script của bạn trong file [/scripts/@index.js](/scripts/@index.js) 5. Ghi tên script của bạn trong biến `tabs` trong file [/popup/tabs.js](/popup/tabs.js) diff --git a/md/LIST_SCRIPTS_EN.md b/md/LIST_SCRIPTS_EN.md deleted file mode 100644 index bbab297b..00000000 --- a/md/LIST_SCRIPTS_EN.md +++ /dev/null @@ -1,1850 +0,0 @@ -### Search - -
- 1. There's an AI for that - - [View source](/scripts/recommend_theresanaiforthat.js) - - Collection of thousand of AI tools. Easy to search by category - -
-
- 2. Time.is - Check your time - - [View source](/scripts/recommend_timeis.js) - - Exact time for any time zone. - -
-
- 3. Google trending - See what trending now - - [View source](/scripts/recommend_googleTrending.js) - - See what people are searching on Google. Top treding every day, realtime. - -
-
- 4. Find alternative web - - [View source](/scripts/similarWeb.js) - - SimilarWeb - Access behind-the-scenes analytics for every site online - -
-
- 5. Bypass limit in SimilarWeb - - [View source](/scripts/similarWeb_bypassLimit.js) - - You can use SimilarWeb forever without login.

How it work:

Your cookies will be deleted each times enter this web.
- - ![](/scripts/similarWeb_bypassLimit.png) - -
-
- 6. Find shared login - - [View source](/scripts/search_sharedAccount.js) - - Get free shared account on internet - -
-
- 7. Internet archive - Free library - - [View source](/scripts/recommend_archive.js) - - Non-profit library of millions of free books, movies, software, music, websites, and more. - - ![](/scripts/internet_archive.png) - -
-
- 8. Wappalyzer - view website stacks - - [View source](/scripts/recommend_wappalyzer.js) - - Technology that current website is using - -
-
- 9. Who.is - - [View source](/scripts/whois.js) - - Want to find out who owns a domain? Click on this! - -
-
- 10. See web meta info (SEO) - - [View source](/scripts/viewWebMetaInfo.js) - - Instantly shows meta information about the current site in an on-page iFrame. - -
-
- 11. Top global treding music? - - [View source](/scripts/recommend_search_musicTreding.js) - - The web to find all kinds of music-related data. - -
-
- 12. Where to find papers/books/pdf/... - - [View source](/scripts/search_paperWhere.js) - - Learn more and more - -
-
- 13. Search guitar chords - - [View source](/scripts/search_hopamchuan.js) - - Search guitar chords - -
-
- 14. Dowfor - Check web die - - [View source](/scripts/checkWebDie.js) - - Check web die using downforeveryoneorjustme - -
-
- 15. DownDetector - View report web crash - - [View source](/scripts/downDetector.js) - - View web bug reports - -
-
- 16. Open wayback url - - [View source](/scripts/openWaybackUrl.js) - - Open wayback url for website - -
-
- 17. Archive the current Page online - - [View source](/scripts/archiveToday.js) - - Creates an archive of the current page on archive.today. - -
-
- 18. Search Userscripts - - [View source](/scripts/recommend_search_userscript.js) - - Search Userscripts on Usersript.zone - -
- -### Download - ---- All in one --- -
- 19. Cobalt - Media downloader - - [View source](/scripts/recommend_cobalt.js) - - Support youtube, tiktok, instagram, twitter/x, bilibili, twitch, vimeo, soundcloud, dailymotion, pinterest, reddit, tumblr, ... - -
-
- 20. Save All Video - - [View source](/scripts/saveAllVideo.js) - - Download video from Douyin Twitter Instagram TikTok Youtube - -
-
- 21. Vuiz - Get link audio/video/album - - [View source](/scripts/vuiz_getLink.js) - - Support youtube, facebook, tiktok, zing tv, zing mp3, xVideo, nhac.vn, mixcloud, soundcloud, keeng.vn, chiasenhac, nhaccuatui, mediafire, ggdrive, dropbox, ondrive - -
-
- 22. SaveVideo - Download video - - [View source](/scripts/savevideo_me.js) - - Download videos from Dailymotion, Facebook, Vimeo, Twitter, Instagram / Reels, TikTok, Rumble.com, Streamable.com, Aol Video, Bilibili.com (哔哩哔哩), Bilibili.tv, Coub, DouYin (抖音), Flickr Videos, Focus.de, GMX.net / WEB.DE, ItemFix, Magisto, Reddit, Sapo.pt, T.me (Telegram), Tiscali.it Video, Tudou, Veoh, Vidmax.com, Vine (archive), WorldStarHipHop, Youku - -
-
- 23. Get audio/video (luanxt) - - [View source](/scripts/getLinkLuanxt_newtab.js) - - Using API from luanxt.com. Download Zing MP3, Zing Video Clip, Zing TV, NhacCuaTui, YouTube, SoundCloud, Nhac.vn, ChiaSeNhac.vn, Facebook Video, Keeng Audio, Keeng Video, Keeng Phim - -
- ---- Photos --- -
- 24. Twitter X - Add download button - - [View source](/scripts/twitter_downloadButton.js) - - Add the download button to all video and photo in Twitter - - ![](/scripts/twitter_downloadButton.png) - -
-
- 25. Download favicon from website - - [View source](/scripts/getFavicon.js) - - Get favicon link of current website - -
-
- 26. Picviewer CE+ download images - - [View source](/scripts/recommend_picviewer_ce+.js) - - Powerful picture viewing tool online, which can popup/scale/rotate/batch save pictures automatically - - ![](https://v2fy.com/asset/063_picviewer_ce/73130353-c4598e00-4031-11ea-810e-9498677a40d1.gif) - -
-
- 27. File-converter.io - change image type - - [View source](/scripts/recommend_file_converter.js) - - Powerful tool which allows you to convert and compress files using the context menu in windows explorer. - - ![](https://file-converter.io/images/file-converter-usage.gif) - -
-
- 28. Squoosh.app - compress images - - [View source](/scripts/recommend_squoosh_app.js) - - Make images smaller using best-in-class codecs, right in the browser. - -
- ---- Music --- -
- 29. Spotify - Add download button - - [View source](/scripts/spotify_downloadButton.js) - - Add song/playlist download button on Spotify. Use spotify-downloader.com - - ![](/scripts/spotify_downloadButton.png) - -
-
- 30. Soundcloud - Add download button - - [View source](/scripts/soundcloud_downloadMusic.js) - - Add download button on soundcloud (before like button). Use soundcloud API, no external service - - ![](/scripts/soundcloud_downloadMusic.png) - -
-
- 31. Nhaccuatui music/lyric downloader - - [View source](/scripts/nhaccuatui_downloader.js) - - Download the song that be playing in Nhaccuatui - -
-
- 32. Zingmp3 music dowloader (API) - - [View source](/scripts/zingmp3_downloadMusic.js) - - Download music on mp3.zing.vn and zingmp3.vn using zingmp3 API - -
-
- 33. Show all audio in website - - [View source](/scripts/showTheAudios.js) - - Will display all audio in website, easy to download/get link. - -
- ---- Videos --- -
- 34. Download watching video - - [View source](/scripts/download_watchingVideo.js) - - tiktok, doutu.be, phimmoi2... - -
-
- 35. Vimeo - video downloader - - [View source](/scripts/vimeo_downloader.js) - - Download video from vimeo - -
-
- 36. Show all videos in website - - [View source](/scripts/showTheVideos.js) - - Download video easier - -
- ---- Document --- -
- 37. Studocu - Download documents - - [View source](/scripts/studocu_downs.js) - - Download documents on Studocu.com for free - -
-
- 38. Scribd - Download documents - - [View source](/scripts/scribd_downloadDocuments.js) - - Download document on Scribd for free - -
-
- 39. Download free from tailieu.vn - - [View source](/scripts/tailieu_vn.js) - - Download any document on tailieu.vn without login - -
-
- 40. DocDownloader - Download document - - [View source](/scripts/recommend_docsdownloader.js) - - Download document on Scribd, Everand, Slideshare, Issuu, Academia, Chegg, Researchgate, Coursehero, Studocu, Perlego, Yumpu, Tiendeo, Fliphtml5, Anyflip, Docsity, Passei direto, Udocz - -
-
- 41. Export bookmarks to file - - [View source](/scripts/bookmark_exporter.js) - - Export all your browser's bookmarks to JSON file - -
-
- 42. Bookmark Sidebar - - [View source](/scripts/recommend_BookmarkSidebar.js) - - Very good Bookmark manager, find your bookmarks faster. - -
- -### Google - ---- Download --- -
- 43. Google drive - generate direct link - - [View source](/scripts/ggdrive_generateDirectLink.js) - - Generate a direct download link to files stored in Google Drive. A direct link will immediately start downloading the file. - -
-
- 44. GG Drive - Download PDF - - [View source](/scripts/ggdrive_downloadPdf.js) - - Download google drive PDF file that dont have download button.
Pages will be convert to image, cannot edit.
- -
-
- 45. GG Drive - Download PowerPoint (Slides/Presentation) - - [View source](/scripts/ggdrive_downloadPresentation.js) - - Download google drive Presentation file that dont have download button, covert to HTML file. - -
-
- 46. GG Drive - Download Document (to PDF) - - [View source](/scripts/ggdrive_downloadDoc.js) - - Download google drive Document file that dont have download button. Pages will be convert to PDF, cannot edit. - -
-
- 47. GG Drive - Download Sheet (copy Text) - - [View source](/scripts/ggdrive_copySheetText.js) - - Copy google drive sheet (excel) that dont have download button (Just copy text, can't copy formula) - -
-
- 48. GG Drive - Download video - - [View source](/scripts/ggdrive_downloadVideo.js) - - Download google drive video that dont have download button - -
-
- 49. Google - Download all your data - - [View source](/scripts/google_downloadAllYourData.js) - - Download all your data on Google - -
- ---- Bulk Download --- -
- 50. GGDrive - Download all videos in folder - - [View source](/scripts/ggDrive_downloadAllVideosInFolder.js) - - Download all videos in folder of google drive (bypass download permission) - -
- ---- More --- -
- 51. Google search advanced - - [View source](/scripts/recommend_googleAdvanced.js) - - Search google with a lot of advanced features - -
-
- 52. Check total indexed pages - - [View source](/scripts/search_totalIndexedPages.js) - - Know how many pages of current website is indexed in Google - -
-
- 53. Google site search - - [View source](/scripts/search_googleSite.js) - - Search in google while limiting the search result to currently opened webpage. - -
-
- 54. Google shortcuts - - [View source](/scripts/googleShortcuts.js) - - Create new google doc/sheet/slide/form/site/keep/calendar - -
-
- 55. View Google cache of website - - [View source](/scripts/googleCache.js) - - View blocked website - -
-
- 56. Google mirror - I'm elgooG - - [View source](/scripts/google_mirror.js) - - Google games. We create, restore, and discover interactive Google Easter Eggs. Just click and play them online for free. - -
- -### Facebook - ---- All in one --- -
- 57. Facebook - All In One - - [View source](/scripts/fb_allInOne.js) - - Combine all bulk download / statistic features on facebook into single page - -
- ---- Download --- -
- 58. Download watching fb video - - [View source](/scripts/fb_downloadWatchingVideo.js) - - Download any facebook video that you are watching (watch / story / comment / reel / chat) - -
-
- 59. Download watching fb Story/Comment - - [View source](/scripts/fb_storySaver.js) - - Download facebook story / comment video that you are watching - -
-
- 60. Download fb video/reel/watch from url - - [View source](/scripts/fb_videoDownloader.js) - - Download facebook video/reel/watch - -
-
- 61. Get avatar from fb user id - - [View source](/scripts/fb_getAvatarFromUid.js) - - Get avatar from list facebook user ids - -
-
- 62. Export saved facebook items - - [View source](/scripts/fb_exportSaved.js) - - Export all your saved items on facebook to JSON file - -
- ---- Hot --- -
- 63. Auto like post on Facebook - - [View source](/scripts/fb_autoLike.js) - - Auto like post on Facebook. -
    -
  • Support all post types (page, group, user, feed, ...)
  • -
  • Support bulk remove/add reactions
  • -
  • Support all reaction types
  • -
- -
-
- 64. Facebook - Reveal deleted messages - - [View source](/scripts/fb_revealDeletedMessages.js) - - View deleted messages (since function was turned on) on facebook messenger.

WARNING

Not work with end-to-end encryption
- -
-
- 65. Facebook Story - More reactions - - [View source](/scripts/fb_moreReactionStory.js) - - React story Facebook with more emojis - - ![](/scripts/fb_moreReactionStory.png) - -
-
- 66. Turn off light facebook newfeed - - [View source](/scripts/fb_toggleLight.js) - - Hide Navigator bar and complementary bar in facebook.

  • Click to temporarily hide/show on this page.
  • Enable autorun to auto hide whenever open facebook.
- -
-
- 67. Hide facebook new feed - - [View source](/scripts/fb_toggleNewFeed.js) - - Hide facebook new feed (home page) for better focus to work - -
-
- 68. Stop new feed facebook - - [View source](/scripts/fb_stopNewFeed.js) - - Stop load new feed on facebook, better for work performance
-
    -
  • Support feeds: stories, home, video, group, marketplace
  • -
- -
-
- 69. 👀 Block seen story facebook - - [View source](/scripts/fb_blockSeenStory.js) - - Block 'Seen' story in facebook. Your friend will not know that you have seen his/her stories. - -
-
- 70. Show facebook post reaction count - - [View source](/scripts/fb_getPostReactionCount.js) - - Show total reaction count on facebook posts when hover mouse over post's reaction section - - ![](/scripts/fb_getPostReactionCount.jpg) - -
-
- 71. Facebook - Who is typing to you? - - [View source](/scripts/fb_whoIsTyping.js) - - Notify when someone is typing chat to you.

WARNING

Not work with end-to-end encryption
- -
- ---- Statistic --- -
- 72. Facebook - View your friends's joined groups - - [View source](/scripts/fb_searchGroupForOther.js) - - Know about your friends's joined groups (public groups) on facebook - -
-
- 73. Facebook - View your friend's liked pages - - [View source](/scripts/fb_searchPageForOther.js) - - Know about your friends's liked pages (public pages) on facebook - -
-
- 74. Facebook - Find all posts of your friends - - [View source](/scripts/fb_searchPostsForOther.js) - - Search all public posts of your friends on facebook. Include posts in group, page, wall, ... - -
- ---- Access Token --- -
- 75. Check fb access token - - [View source](/scripts/fb_checkToken.js) - - Check type, permissions, created date, expired date, ... of faceboook access token - -
-
- 76. Get fb token EAAB (instagram) - - [View source](/scripts/fb_getTokenFacebook.js) - - Get facebook access token EAAB from www.facebook.com - -
-
- 77. Get fb token EAADo1 (messenger) - - [View source](/scripts/fb_getTokenMessage.js) - - Get facebook access token from www.facebook.com (messenger_for_android) - -
-
- 78. Get fb token EAAG (business_locations) - - [View source](/scripts/fb_getTokenBussinessLocation.js) - - Get facebook token EAAG from business.facebook.com - -
-
- 79. Get fb token EAAB (campaigns) - - [View source](/scripts/fb_getTokenCampaigns.js) - - Get facebook token EAAB from www.facebook.com (campaigns) - -
-
- 80. Get fb token from cookie (ffb.vn) - - [View source](/scripts/fb_getTokenFfb.js) - - Post your facebook cookie to ffb.vn API - -
- ---- Get ID --- -
- 81. Get fb User ID - - [View source](/scripts/fb_getUid.js) - - Get id of user in facebook website - -
-
- 82. Get fb Page ID - - [View source](/scripts/fb_getPageId.js) - - Get id of page in facebook website - -
-
- 83. Get fb Group ID - - [View source](/scripts/fb_getGroupId.js) - - Get id of group in facebook website - -
-
- 84. Get fb Album ID - - [View source](/scripts/fb_getAlbumId.js) - - Get id of facebook album in current website - -
-
- 85. Get all fb Album ID from current page - - [View source](/scripts/fb_getAllAlbumIdFromCurrentWebsite.js) - - Get all id of album in facebook website - -
-
- 86. Get fb User ID from url - - [View source](/scripts/fb_getUidFromUrl.js) - - Get id of facebook user from entered url - -
-
- 87. Get all fb User ID from search page - - [View source](/scripts/fb_getAllUidFromFbSearch.js) - - Get id of all user from facebook search page - -
-
- 88. Get all fb User ID from group - - [View source](/scripts/fb_getAllUidOfGroupMembers.js) - - Get id of all user from group members facebook - -
- ---- Shortcut --- -
- 89. View your facebook saved - - [View source](/scripts/fb_openSaved.js) - - View saved contents on Facebook - -
-
- 90. View your memories on facebook - - [View source](/scripts/fb_openMemories.js) - - View your memories on facebook - -
-
- 91. View your ads activities - - [View source](/scripts/fb_openAdsActivities.js) - - View ads you have seen on facebook - -
-
- 92. Check your activities on Facebook - - [View source](/scripts/fb_openAllActivities.js) - - Check all your activities on facebook - -
-
- 93. Video you watched on facebook - - [View source](/scripts/fb_openVideoActivities.js) - - View all videos you watched on facebook - -
-
- 94. Events joined on facebook - - [View source](/scripts/fb_openPassEvents.js) - - View pass events that you have joined on facebook. - -
-
- 95. Facebook friend's birthdays - - [View source](/scripts/fb_openBirthdays.js) - - View your friend's birthdays each month on facebook - -
-
- 96. Change language facebook - - [View source](/scripts/fb_openChangeLanguage.js) - - Change display language on facebook - -
-
- 97. Recover facebook account - - [View source](/scripts/fb_openAccountHacked.js) - - Your fb account has been hacked? Facebook can help you. - -
- -### Instagram - -
- 98. Get insta user info (uid, avatar, ...) - - [View source](/scripts/insta_getUserInfo.js) - - Get instagram uid, avatar, name, ... - -
-
- 99. Add Instagram download button - - [View source](/scripts/insta_injectDownloadBtn.js) - - Add a download button to all photo/video/post/story on Instagram - - ![](/scripts/insta_injectDownloadBtn.png) - -
-
- 100. Insta - Anonymous story viewer - - [View source](/scripts/insta_anonymousStoryViewer.js) - - Watch instagram stories anonymously - -
- ---- Bulk Download --- -
- 101. Get all media of insta user (API) - - [View source](/scripts/insta_getAllUserMedia.js) - - Get all media of instagram user (use instagram API) - -
-
- 102. Insta - Export all following/followers - - [View source](/scripts/insta_getFollowForOther.js) - - Know about your (or your friends's) following / followers on instagram. Export to json file - -
- -### Youtube - -
- 103. Picture in Picture - - [View source](/scripts/pictureInPicture.js) - - Watch videos in a floating window - -
-
- 104. Download youtube video/audio - - [View source](/scripts/youtube_downloadVideo.js) - - Bypass age restriction, without login -
    -
  • Click once to download current video
  • -
  • Enable autorun to render download button
  • -
- - ![](/scripts/youtube_downloadVideo.png) - -
-
- 105. Get Youtube video's captions - - [View source](/scripts/youtube_getVideoCaption.js) - - - Click to get all captions of playing youtube video
- Enable autorun to show realtime captions (transcript)
- - ![](/scripts/youtube_getVideoCaption.png) - -
-
- 106. Return youtube dislike - - [View source](/scripts/youtube_viewDislikes.js) - - Returns ability to see dislikes of youtube video/short - -
-
- 107. Youtube nonstop - - [View source](/scripts/youtube_nonstop.js) - - Kiss the annoying "Video paused. Continue watching?" confirmation goodbye! - - ![](/scripts/youtube_nonstop.png) - -
-
- 108. Youtube change country - - [View source](/scripts/youtube_changeCountry.js) - - Change youtube country to view content in other country - -
-
- 109. Get Youtube video's thumbnail - - [View source](/scripts/youtube_getVideoThumbnail.js) - - Get largest thumbnail of playing youtube video - -
-
- 110. Toggle light youtube - - [View source](/scripts/youtube_toggleLight.js) - - Toggle light on/off to focus to youtube video - -
-
- 111. PIP full website - - [View source](/scripts/pip_fullWebsite.js) - - Picture in picture mode for full website - -
-
- 112. PIP for canvas - - [View source](/scripts/pip_canvas.js) - - Picture in picture mode for canvas - -
-
- 113. Improve YouTube - 85+ features - - [View source](/scripts/recommend_improve_youtube.js) - - Make YouTube more beautiful, faster, and more useful! - -
- -### Tiktok - ---- Tiktok --- -
- 114. Tiktok - Download watching video - - [View source](/scripts/tiktok_downloadWatchingVideo.js) - - Download tiktok video you are watching (no watermark) - -
-
- 115. Tiktok - Download video from URL - - [View source](/scripts/tiktok_downloadVideo.js) - - Download tiktok video from url (no watermark) - -
-
- 116. Tiktok - Batch download - - [View source](/scripts/tiktok_batchDownload.js) - - Select and download all tiktok video (user profile, tiktok explore). - - ![](/scripts/tiktok_batchDownload.jpg) - -
- ---- Douyin --- -
- 117. Douyin - Download watching video - - [View source](/scripts/douyin_downloadWachingVideo.js) - - Show all downloadable videos in current douyin webpage - -
-
- 118. Douyin - Download all user videos - - [View source](/scripts/douyin_downloadAllVideoUser.js) - - Download all videos in douyin user profile. - -
- -### Automation - ---- Utility --- -
- 119. Web timer - - [View source](/scripts/web_timer.js) - - Keep track of how you spend your time on the web.
-

CLICK TO OPEN GRAPH.

- - ![](/scripts/web_timer.png) - -
-
- 120. Auto lock websites - - [View source](/scripts/auto_lockWebsite.js) - - Auto lock websites. Enter password to unlock.
-
    -
  • Click to temporarly lock current website.
  • -
  • Click to open settings.
  • -
- -
-
- 121. Super smooth scroll - - [View source](/scripts/smoothScroll.js) - - Scroll smoothly on all websites with your mouse and keyboard.
-
    -
  • Suggested if you use mouse (turn off if use touchpad)
  • -
  • Click to Disable/Enable for current website
  • -
  • Support middle click to scroll
  • -

- -
-
- 122. Magnify any Image - - [View source](/scripts/magnify_image.js) - - View any images in magnified window
- Where you are able to zoom in/out, rotate, drag, and more.
- Auto find large version of image to show. -

-

4 ways to use:

- Using now: -
    -
  1. Right click in website > click magnify image
  2. -
  3. Left click this feature then click image
  4. -
- After turn-on auto-run: -
    -
  1. Hover on any image/video > click magnify button
  2. -
  3. Double Ctrl on any image
  4. -
-
- -
-
- 123. Auto - view largest image - - [View source](/scripts/auto_redirectLargestImageSrc.js) - -
    -
  • When viewing an image in new tab.
  • -
  • This script will auto find and redirect to largest image.
  • -
  • Support hundred of websites.
  • -
- - ![](/scripts/auto_redirectLargestImageSrc.jpg) - -
-
- 124. Show image on hover link - - [View source](/scripts/showImageOnHoverLink.js) - - Show preview image when you hover mouse over an image link - -
-
- 125. Prevent tracking url - - [View source](/scripts/remove_tracking_in_url.js) - - Remove tracking parameters from url, prevent tracking from Facebook, Google, Tiktok, Twitter etc.
    -
  • fbclid
  • -
  • ttclid
  • -
  • utm_*
  • -
  • ...
  • -
- -
-
- 126. Don't close browser with last tab - - [View source](/scripts/prevent_closeBrowser_lastTab.js) - - Prevent closing of Browser after close the last tab
- Auto create new empty tab if no tab left
- -
-
- 127. Block trackers, spy and malware - - [View source](/scripts/chongLuaDao.js) - - Alert when you access a dangerous website, malware or spy content
-
    -
  • Click to analyze current website
  • -
  • Click to fetch newest websites database
  • -
- -
-
- 128. Shorten URL - - [View source](/scripts/shortenURL.js) - - Support tinyurl, tnyim, cuttly, bitly, j2team, ... - -
-
- 129. Unshorten link - - [View source](/scripts/unshorten.js) - - Get origin URL of shortened url - -
-
- 130. Create invisible message - - [View source](/scripts/createInvisibleText.js) - - Create invisible text to hide secret messages. Receiver to use this feature to decode messages. - -
- ---- Automation --- -
- 131. Web to PDF - - [View source](/scripts/webToPDF.js) - - Convert current website to PDF - -
-
- 132. Screenshot full webpage - - [View source](/scripts/screenshotFullPage.js) - - Taking a screenshot of an entire webpage - -
-
- 133. Screenshot webpage - - [View source](/scripts/screenshotVisiblePage.js) - - Taking a screenshot of visible webpage (bypass websites that block screenshots such as Netflix, ...) - -
-
- 134. Scroll to very end - - [View source](/scripts/scrollToVeryEnd.js) - - Scoll to end, then wait for load data, then scroll again... Mouse click to cancel

Tips

You can press middle mouse button to auto scroll up/down in any website.
- -
-
- 135. Extract all Emails from website - - [View source](/scripts/getAllEmailsInWeb.js) - - Extracts all emails and displays them in a popup iFrame (enable popups!) - -
-
- 136. Enable/Disable Hack T-Rex Dino Game - - [View source](/scripts/dino_hack.js) - - A bot that plays the Google Chrome T-Rex game for you - -
-
- 137. Password generator - - [View source](/scripts/passwordGenerator.js) - - You only have to remember 1 password - -
- ---- Tools --- -
- 138. Text to QRCode - - [View source](/scripts/textToQrCode.js) - - Convert text/url to QRCode - -
-
- 139. Text to Speech (j2team) - - [View source](/scripts/textToSpeech.js) - - Convert text to speech using j2team tool - -
-
- 140. Audio output switcher - - [View source](/scripts/changeAudioOutput.js) - - Pick a default audio output device, customizable for each browser tab.

eg. listen to youtube by headphone in tab 1, play music by external speaker in tab 2.
- -
-
- 141. Send - Share file faster - - [View source](/scripts/send_shareFiles.js) - - Open send.zcyph.cc - share large file up to 20Gb - -
-
- 142. Vuiz - create logo WAP online - - [View source](/scripts/vuiz_createLogo.js) - - Create logo from text online - -
-
- 143. Performance Analyzer - - [View source](/scripts/performanceAnalyzer.js) - - Check performance metrics of website - -
-
- 144. IT Tools - All for Developers - - [View source](/scripts/recommend_ItTools.js) - - Handy tools for developers (open source) - -
-
- 145. CopyIcon - FREE emoji, icon, generator - - [View source](/scripts/recommend_copyicon.js) - - 285,000 free Icons, Emoji, SVG generator, and more... - -
-
- 146. Beautify Tools - - [View source](/scripts/recommend_beautifytools.js) - - Handy tools for developers -
    -
  1. Beautifiers And Minifiers
  2. -
  3. CSS Preprocessors
  4. -
  5. Converters
  6. -
  7. String Utilities
  8. -
  9. SEO Tools
  10. -
  11. IP Tools
  12. -
  13. Code Validators
  14. -
  15. Cryptography
  16. -
  17. Code Editors
  18. -
- -
- ---- Github --- -
- 147. Github - Go to any commit - - [View source](/scripts/github_goToAnyCommit.js) - - Go to any commit of github repo. Included first commit. - -
-
- 148. Github - HTML preview - - [View source](/scripts/github_HTMLPreview.js) - - Preview github's HTML file in any repo without download code. - -
-
- 149. Github - Open repo pages - - [View source](/scripts/github_openRepoPages.js) - - Switch between github.com repo and github.io live demo pages - username.github.io/repo
- github.com/username/repo
- -
-
- 150. Github - Open repo in github.dev - - [View source](/scripts/githubdev.js) - - Open current repo in github.dev

Tip

Press dot (.) when in github repo to open github.dev
- -
-
- 151. Github - Open repo in github1s.com - - [View source](/scripts/github1s.js) - - Open current repo in github1s.com - -
-
- 152. Cloc - count line of code - - [View source](/scripts/recommend_cloc.js) - - Count blank lines, comment lines, and physical lines of source code in many programming languages. - - ![](/scripts/recommend_cloc.png) - -
-
- 153. Refined GitHub - - [View source](/scripts/recommend_refined_github.js) - - Simplifies the GitHub interface and adds useful features - -
- ---- Shopping --- -
- 154. Shopee - Top variation - - [View source](/scripts/shopee_topVariation.js) - - See how many times each product variant was purchased - - ![](/scripts/shopee_topVariation.png) - -
-
- 155. Shopee - Total spend money - - [View source](/scripts/shopee_totalSpendMoney.js) - - See how much money you have spend on Shopee - -
-
- 156. Shopee - Export order history (Excel) - - [View source](/scripts/shopee_totalSpendMoney_excel.js) - - Export all of your order history from Shopee to Excel file - -
-
- 157. Tiki - Total spend money? - - [View source](/scripts/tiki_totalSpendMoney.js) - - See how much money you have spend on Tiki - -
-
- 158. Beecost - - [View source](/scripts/recommend_Beecost.js) - - Check deals/prices in ecommerce websites - -
- ---- PDF --- -
- 159. FastDoc - Convert PDF/Photo to Word/Excel - - [View source](/scripts/recommend_fastDoc.js) - - Convert Photos & PDF to Excel, Word, Searchable PDF for free - -
-
- 160. SmartPDF - Tools for PDF - - [View source](/scripts/recommend_smartPDF.js) - - Compress PDF, PDF Converter, PPT to PDF, PDF to PPT, JPG to PDF, PDF to JPG, Excel to PDF, PDF to Excel, Edit PDF, PDF Reader, Number Pages, Delete PDF Pages, Rotate PDF, Word to PDF, PDF to Word, Merge PDF, Split PDF, eSign PDF, Unlock PDF, Protect PDF, PDF Scanner - -
-
- 161. PDF Stuffs - Tools for PDF - - [View source](/scripts/recommend_pdfstuffs.js) - - Free PDF converter online service: Merge PDF, Split PDF, Compress PDF, PDF to Word, PDF to PPT, PDF to Excel, Word to PDF, Excel to PDF, PPT to PDF, PDF to JPG, JPG to PDF, PDF to HTML, HTML to PDF, Unlock PDF, Protect PDF, Rotate PDF, Crop PDF, Delete pages, Add page numbers, Watermark PDF - -
- -### Unlock - ---- Unlock web --- -
- 162. Hack Duck race - - [View source](/scripts/duckRace_cheat.js) - - Hack result of Duck race, always get the result you want - - ![](/scripts/duckRage_cheat.png) - -
-
- 163. Hack Wheel of Names - - [View source](/scripts/wheelOfNames_hack.js) - - Hack result of
  • wheelofnames.com
  • wheelrandom.com
  • spinthewheel.io
always get the result you want.
- -
-
- 164. Read full medium article - - [View source](/scripts/medium_readFullArticle.js) - - Read full medium article without login - -
-
- 165. Medium - Fix vietnamese font - - [View source](/scripts/medium_fixVietnameseFont.js) - - Fix vietnamese font in Medium
-
    -
  • Click 1 time to fix font in current Medium page (dont need to reload)
  • -
  • Enable autorun for next times you enter Medium
  • -
- -
-
- 166. Fireship - PRO unlocked - - [View source](/scripts/fireship_vip.js) - - Unlock all Fireship PRO courses/lessons (saved $399 USD) - -
-
- 167. Studocu - Bypass preview - - [View source](/scripts/studocu_bypassPreview.js) - - View VIP document on Studocu.com, bypass preview popup / reveal blurred content. - -
-
- 168. Scribd - bypass preview - - [View source](/scripts/scribd_bypassPreview.js) - - View VIP document on Scribd.com, bypass preview popup / reveal blurred content. - -
-
- 169. Studyphim - Watch free movies - - [View source](/scripts/studyphim_unlimited.js) - - Watch movies on Studyphim for free without login - -
-
- 170. Mở khoá Learn Anything - - [View source](/scripts/bypass_learnAnything.js) - - View learn-anything.xyz content without become a member - - ![](/scripts/bypass_LearnAnything.png) - -
- ---- Unlock function --- -
- 171. Enable/Disable allow copy - - [View source](/scripts/simpleAllowCopy.js) - - Allow Copy on every websites
-

NOTES:

-
    -
  • Need to enable autorun first
  • -
  • Click this button to TURN ON cheat to allow copy/right-click for current website.
  • -
  • Click again to TURN OFF.
  • -
- -
-
- 172. Detect Zero-Width Characters - - [View source](/scripts/detect_zeroWidthCharacters.js) - - Detects zero-width characters, highlights the characters and containing DOM element. - -
-
- 173. Inject script to website - - [View source](/scripts/injectScriptToWebsite.js) - - Inject script url to current website, eg. jquery, library, etc. - -
-
- 174. Show hidden fields - - [View source](/scripts/showHiddenFields.js) - - Reveals hidden fields on a webpage. Find things like tokens, etc - -
-
- 175. View cookies - - [View source](/scripts/viewCookies.js) - - View cookies saved in current website - -
-
- 176. Remove cookies - - [View source](/scripts/removeCookies.js) - - Remove cookies from current website - -
- ---- Other --- -
- 177. Make browser super fast - - [View source](/scripts/recommend_chromeFlags.js) - - Some flags experiments that can make your browser super fast - -
-
- 178. View saved wifi passwords - - [View source](/scripts/recommend_viewSavedWifiPass.js) - - PowerShell script to view saved wifi passwords on your computer - -
-
- 179. Leak check - your password has been leaked? - - [View source](/scripts/recommend_leakCheck.js) - - Check your password has been leaked on internet or not - -
- -### Web UI - ---- Hot --- -
- 180. Darkmode for pdf - - [View source](/scripts/darkModePDF.js) - - Enable/Disable darkmode for PDF - -
-
- 181. Toggle edit page - - [View source](/scripts/toggleEditPage.js) - - Edit all text in website - -
-
- 182. Show FPS - - [View source](/scripts/showFPS.js) - - Show frames per second of current website (inject stat.js library) - -
-
- 183. Show FPS - ver 2 - - [View source](/scripts/showFps_v2.js) - - Show frames per second of current website (use debugger) - -
-
- 184. Hide/Show password field - - [View source](/scripts/toggle_passwordField.js) - - Show/hide value of password fields in website
(eg. **** -> 1234)
- -
-
- 185. Dark reader - - [View source](/scripts/recommend_DarkReader.js) - - Darkmode for every website - -
-
- 186. CSS Portal - Empowered your CSS skills - - [View source](/scripts/recommend_cssportal.js) - - Empowered your CSS skills with hundreds of CSS tools. - -
-
- 187. CSS Loaders - 600+ css loader - - [View source](/scripts/recommend_cssloaders.js) - - The Biggest Collection of Loading Animations. Over 600+ CSS-only loaders made using a single element - -
-
- 188. UIverse - Open-Source UI elements - - [View source](/scripts/recommend_uiverse.js) - - Open-Source UI elements for any project. - -
- ---- View --- -
- 189. Font Rendering - better font display - - [View source](/scripts/recommend_fontRendering.js) - - Improve browser displaying, font rewriting, smoothing, scaling, stroke, shadow, special style elements, custom monospaced, etc - -
-
- 190. What font - - [View source](/scripts/whatFont.js) - - Check font used in webpage - -
-
- 191. Show all javascript events - - [View source](/scripts/visualEvent.js) - - Visual Event - Visually show Javascript events on a page - -
-
- 192. View all images in web - - [View source](/scripts/listAllImagesInWeb.js) - - View all images in web - -
-
- 193. View all links - - [View source](/scripts/viewAllLinks.js) - - Show all links and anchor text of current page. - -
-
- 194. View scripts used - - [View source](/scripts/viewScriptsUsed.js) - - View all scripts used in current website - -
-
- 195. View stylesheet used - - [View source](/scripts/viewStylesUsed.js) - - View all stylesheet used in current website - -
-
- 196. CSS selector viewer - - [View source](/scripts/cssSelectorViewer.js) - - Inspect css at specific element on the web - -
- ---- Remove --- -
- 197. Remove all colors in web - - [View source](/scripts/removeColours.js) - - Remove all colours in the web.
Click again to undo.
- -
-
- 198. Remove stylesheet - - [View source](/scripts/removeStylesheet.js) - - Remove all stylesheet from website.
Click again to undo.
- -
-
- 199. Remove images - - [View source](/scripts/removeImages.js) - - Remove all images from website.
Click again to undo.
- -
-
- 200. Remove bloat (iframe, embed) - - [View source](/scripts/removeBloat.js) - - Remove iframe, embeds, applets from website - -
- ---- Table --- -
- 201. Add sort to table - - [View source](/scripts/table_addSortTable.js) - - Add sort functions to table - -
-
- 202. Add number columns - - [View source](/scripts/table_addNumberColumn.js) - - Add number columns to table - -
-
- 203. Swap rows and columns - - [View source](/scripts/table_swapRowAndColumn.js) - - Swap rows and columns (transpose) - -
- ---- More --- -
- 204. Highlight internal/external link - - [View source](/scripts/internalOrExternalLink.js) - - +Red = Internal_link -+Orange = Currently_opened_link -+Blue = External_link - -
-
- 205. Get window size - - [View source](/scripts/getWindowSize.js) - - Alerts the width and height in pixels of the inner window. - -
-
- 206. Let it snow - - [View source](/scripts/letItSnow.js) - - Make website like it snowing - -
diff --git a/md/LIST_SCRIPTS_VI.md b/md/LIST_SCRIPTS_VI.md deleted file mode 100644 index 34a456eb..00000000 --- a/md/LIST_SCRIPTS_VI.md +++ /dev/null @@ -1,1849 +0,0 @@ -### Tìm kiếm - -
- 1. There's an AI for that - Tìm AI - - [Xem mã nguồn](/scripts/recommend_theresanaiforthat.js) - - Tổng hợp hàng ngàn công cụ AI hiện có. Dễ dàng tìm kiếm theo chủ đề - -
-
- 2. Time.is - Kiểm tra thời gian - - [Xem mã nguồn](/scripts/recommend_timeis.js) - - Đồng hồ chính xác nhất. Kiểm tra đồng hồ trên máy của bạn nhanh hay chậm. - -
-
- 3. Google trending - Nội dung nổi bật - - [Xem mã nguồn](/scripts/recommend_googleTrending.js) - - Xem mọi người đang tìm gì trên google. Thống kê từng ngày, thời gian thực. - -
-
- 4. Tìm trang web tương tự - - [Xem mã nguồn](/scripts/similarWeb.js) - - SimilarWeb - Phân tích chi tiết cho mọi trang web trực tuyến - -
-
- 5. SimilarWeb - không bị giới hạn - - [Xem mã nguồn](/scripts/similarWeb_bypassLimit.js) - - Sử dụng SimilarWeb không giới hạn, không cần đăng nhập.

Cách hoạt động:

Cookies của bạn sẽ được xoá mỗi khi vào trang web
- - ![](/scripts/similarWeb_bypassLimit.png) - -
-
- 6. Tìm tài khoản miễn phí - - [Xem mã nguồn](/scripts/search_sharedAccount.js) - - Tìm tài khoản được chia sẻ trên mạng cho trang web hiện tại - -
-
- 7. Internet archive - Thư viện miễn phí - - [Xem mã nguồn](/scripts/recommend_archive.js) - - Thư viện với hàng triệu sách, báo, phim, phần mềm, nhạc, website, ... miễn phí - - ![](/scripts/internet_archive.png) - -
-
- 8. Wappalyzer - Web dùng công nghệ gì? - - [Xem mã nguồn](/scripts/recommend_wappalyzer.js) - - Xem những công nghệ/thư viện trang web đang dùng - -
-
- 9. Who.is - - [Xem mã nguồn](/scripts/whois.js) - - Muốn biết ai đang giữ domain này? Click ngay! - -
-
- 10. Xem thông tin meta của web (SEO) - - [Xem mã nguồn](/scripts/viewWebMetaInfo.js) - - Xem thông tin meta của website trực tiếp trong trang web - -
-
- 11. Bài nhạc top treding toàn cầu? - - [Xem mã nguồn](/scripts/recommend_search_musicTreding.js) - - Trang web thống kê top trending âm nhạc toàn cầu. - -
-
- 12. Tìm bài báo/sách/pdf/...ở đâu? - - [Xem mã nguồn](/scripts/search_paperWhere.js) - - Học, học nữa, học mãi - -
-
- 13. Tìm hợp âm guitar - - [Xem mã nguồn](/scripts/search_hopamchuan.js) - - Tra cứu hợp âm chuẩn dành cho người chơi guitar - -
-
- 14. Dowfor - Kiểm tra web die - - [Xem mã nguồn](/scripts/checkWebDie.js) - - Dùng bên thứ 3 để kiểm tra xem website có bị die thật không - -
-
- 15. DownDetector - Thống kê sự cố web - - [Xem mã nguồn](/scripts/downDetector.js) - - Xem thống kê các báo cáo về sự cố web - -
-
- 16. Xem wayback url của website - - [Xem mã nguồn](/scripts/openWaybackUrl.js) - - Giúp xem nội dung website trong quá khứ - -
-
- 17. Lưu trữ online trang hiện tại - - [Xem mã nguồn](/scripts/archiveToday.js) - - Lưu trang web hiện tại lên archive.today - -
-
- 18. Tìm Userscripts - - [Xem mã nguồn](/scripts/recommend_search_userscript.js) - - Tìm Userscripts trên Usersript.zone - -
- -### Tải xuống - ---- Tổng hợp --- -
- 19. Cobalt - Tải video/nhạc - - [Xem mã nguồn](/scripts/recommend_cobalt.js) - - Hỗ trợ youtube, tiktok, instagram, twitter/x, bilibili, twitch, vimeo, soundcloud, dailymotion, pinterest, reddit, tumblr, ... - -
-
- 20. Save All Video - - [Xem mã nguồn](/scripts/saveAllVideo.js) - - Tải video từ Douyin Twitter Instagram TikTok Youtube - -
-
- 21. Vuiz - Get link nhạc/video/album - - [Xem mã nguồn](/scripts/vuiz_getLink.js) - - Hỗ trợ youtube, facebook, tiktok, zing tv, zing mp3, xVideo, nhac.vn, mixcloud, soundcloud, keeng.vn, chiasenhac, nhaccuatui, mediafire, ggdrive, dropbox, ondrive - -
-
- 22. SaveVideo - Tải video - - [Xem mã nguồn](/scripts/savevideo_me.js) - - Tải videos từ Dailymotion, Facebook, Vimeo, Twitter, Instagram / Reels, TikTok, Rumble.com, Streamable.com, Aol Video, Bilibili.com (哔哩哔哩), Bilibili.tv, Coub, DouYin (抖音), Flickr Videos, Focus.de, GMX.net / WEB.DE, ItemFix, Magisto, Reddit, Sapo.pt, T.me (Telegram), Tiscali.it Video, Tudou, Veoh, Vidmax.com, Vine (archive), WorldStarHipHop, Youku - -
-
- 23. Tải nhạc/video (luanxt) - - [Xem mã nguồn](/scripts/getLinkLuanxt_newtab.js) - - Sử dụng API của luanxt.com. Tải Zing MP3, Zing Video Clip, Zing TV, NhacCuaTui, YouTube, SoundCloud, Nhac.vn, ChiaSeNhac.vn, Facebook Video, Keeng Audio, Keeng Video, Keeng Phim - -
- ---- Ảnh --- -
- 24. Twitter X - Thêm nút tải video/ảnh - - [Xem mã nguồn](/scripts/twitter_downloadButton.js) - - Thêm nút tải cho mọi video/ảnh trên Twitter - - ![](/scripts/twitter_downloadButton.png) - -
-
- 25. Tải favicon của trang web - - [Xem mã nguồn](/scripts/getFavicon.js) - - Lấy link favicon của trang web - -
-
- 26. Picviewer CE+ tải ảnh - - [Xem mã nguồn](/scripts/recommend_picviewer_ce+.js) - - Công cụ mạnh mẽ để xem/tải ảnh hàng loạt, cho tất cả trang web - - ![](https://v2fy.com/asset/063_picviewer_ce/73130353-c4598e00-4031-11ea-810e-9498677a40d1.gif) - -
-
- 27. File-converter.io - chuyển đổi ảnh - - [Xem mã nguồn](/scripts/recommend_file_converter.js) - - Công cụ nén ảnh, đổi định dạng ảnh hàng loạt, trực tiếp bằng chuột phải. - - ![](https://file-converter.io/images/file-converter-usage.gif) - -
-
- 28. Squoosh.app - nén ảnh - - [Xem mã nguồn](/scripts/recommend_squoosh_app.js) - - Công cụ nén ảnh mạnh mẽ, giảm kích thước ngay trên trình duyệt - -
- ---- Nhạc --- -
- 29. Spotify - Thêm nút tải nhạc - - [Xem mã nguồn](/scripts/spotify_downloadButton.js) - - Thêm nút tải nhạc/playlist trên Spotify. Sử dụng spotify-downloader.com - - ![](/scripts/spotify_downloadButton.png) - -
-
- 30. Soundcloud - Thêm nút tải nhạc - - [Xem mã nguồn](/scripts/soundcloud_downloadMusic.js) - - Thêm nút tải nhạc trên soundcloud (trước nút like). Sử dụng trực tiếp soundcloud API - - ![](/scripts/soundcloud_downloadMusic.png) - -
-
- 31. Nhaccuatui tải nhạc/lời - - [Xem mã nguồn](/scripts/nhaccuatui_downloader.js) - - Tải bài nhạc / lời bài hát đang nghe trên Nhaccuatui - -
-
- 32. Zingmp3 tải nhạc (API) - - [Xem mã nguồn](/scripts/zingmp3_downloadMusic.js) - - Tải nhạc trên mp3.zing.vn và zingmp3.vn thông qua zingmp3 API - -
-
- 33. Hiển thị mọi audio trong trang web - - [Xem mã nguồn](/scripts/showTheAudios.js) - - Hiển thị tất cả tag audio/âm thanh trong trang web, giúp dễ dàng tải xuống/lấy link. - -
- ---- Video --- -
- 34. Tải video đang xem - - [Xem mã nguồn](/scripts/download_watchingVideo.js) - - tiktok, doutu.be, phimmoi2... - -
-
- 35. Vimeo - tải video - - [Xem mã nguồn](/scripts/vimeo_downloader.js) - - Tải video trên vimeo - -
-
- 36. Hiển thị mọi video có trong web - - [Xem mã nguồn](/scripts/showTheVideos.js) - - Tải video dễ dàng hơn - -
- ---- Tài liệu --- -
- 37. Studocu - Tải documents - - [Xem mã nguồn](/scripts/studocu_downs.js) - - Tải tài liệu trên Studocu.com miễn phí - -
-
- 38. Scribd - Tải documents - - [Xem mã nguồn](/scripts/scribd_downloadDocuments.js) - - Tải miễn phí document trên Scribd - -
-
- 39. Tải miễn phí từ tailieu.vn - - [Xem mã nguồn](/scripts/tailieu_vn.js) - - Tải bất kỳ tài liệu nào trên tailieu.vn không cần đăng nhập - -
-
- 40. DocDownloader - Tải document - - [Xem mã nguồn](/scripts/recommend_docsdownloader.js) - - Tải document từ Scribd, Everand, Slideshare, Issuu, Academia, Chegg, Researchgate, Coursehero, Studocu, Perlego, Yumpu, Tiendeo, Fliphtml5, Anyflip, Docsity, Passei direto, Udocz - -
-
- 41. Xuất bookmarks ra file - - [Xem mã nguồn](/scripts/bookmark_exporter.js) - - Xuất tất cả bookmarks trong trình duyệt của bạn ra file JSON - -
-
- 42. Bookmark Sidebar - - [Xem mã nguồn](/scripts/recommend_BookmarkSidebar.js) - - Trình quản lý bookmark ngon, tìm kiếm bookmark nhanh hơn bao giờ hết. - -
- -### Google - ---- Tải xuống --- -
- 43. Google drive - tạo link tải ngay - - [Xem mã nguồn](/scripts/ggdrive_generateDirectLink.js) - - Tạo đường link direct cho file trên google drive. Bấm vào đường link sẽ tải file trực tiếp thay vì mở trang xem trước file. - -
-
- 44. GG Drive - Tải PDF - - [Xem mã nguồn](/scripts/ggdrive_downloadPdf.js) - - Tải file PDF không có nút download trên google drive.
Tải về định dạng hình ảnh, không thể sửa nội dung.
- -
-
- 45. GG Drive - Tải PowerPoint (Slides) - - [Xem mã nguồn](/scripts/ggdrive_downloadPresentation.js) - - Tải file Powerpoint (Slides) không có nút download trên google drive, tải về định dạng HTML. - -
-
- 46. GG Drive - Tải Document (sang PDF) - - [Xem mã nguồn](/scripts/ggdrive_downloadDoc.js) - - Tải file Doc không có nút download trên google drive. Tải về định dạng PDF, không thể sửa nội dung. - -
-
- 47. GG Drive - Tải Sheet (copy nội dung) - - [Xem mã nguồn](/scripts/ggdrive_copySheetText.js) - - Copy nội dung file sheet (excel) không có nút tải xuống (Chỉ copy text, không copy được công thức) - -
-
- 48. GG Drive - Tải video - - [Xem mã nguồn](/scripts/ggdrive_downloadVideo.js) - - Tải video không có nút download trên google drive - -
-
- 49. Google - Tải xuống dữ liệu của bạn - - [Xem mã nguồn](/scripts/google_downloadAllYourData.js) - - Tải xuống thông tin của bạn trên Google - -
- ---- Tải hàng loạt --- -
- 50. GGDrive - Tải mọi video trong folder - - [Xem mã nguồn](/scripts/ggDrive_downloadAllVideosInFolder.js) - - Tải tất cả video trong thư mục google drive (tải được video không cho phép tải) - -
- ---- Khác --- -
- 51. Google tìm kiếm nâng cao - - [Xem mã nguồn](/scripts/recommend_googleAdvanced.js) - - Tìm kiếm google với nhiều tuỳ chọn nâng cao - -
-
- 52. Xem các pages được google quét - - [Xem mã nguồn](/scripts/search_totalIndexedPages.js) - - Biết có bao nhiêu trang con của website hiện tại đã được quét bởi Google - -
-
- 53. Tìm kiếm trên trang web này - - [Xem mã nguồn](/scripts/search_googleSite.js) - - Sử dụng google site search - -
-
- 54. Google phím tắt - - [Xem mã nguồn](/scripts/googleShortcuts.js) - - Tạo mới google doc/sheet/slide/form/site/keep/calendar - -
-
- 55. Xem Google cache của trang web - - [Xem mã nguồn](/scripts/googleCache.js) - - Phù hơp để xem các trang web bị block - -
-
- 56. Google mirror - I'm elgooG - - [Xem mã nguồn](/scripts/google_mirror.js) - - Chơi các trò chơi (minigame) từng có trên google tìm kiếm - -
- -### Facebook - ---- Tất cả trong một --- -
- 57. Facebook - All In One - - [Xem mã nguồn](/scripts/fb_allInOne.js) - - Tổng hợp tất cả chức năng tải hàng loạt / thống kê facebook - -
- ---- Tải xuống --- -
- 58. Tải video fb đang xem - - [Xem mã nguồn](/scripts/fb_downloadWatchingVideo.js) - - Tải bất kỳ video facebook nào mà bạn đang xem (watch / story / comment / reel / chat / bình luận / tin nhắn) - -
-
- 59. Tải Story/Comment fb đang xem - - [Xem mã nguồn](/scripts/fb_storySaver.js) - - Tải facebook story / video bình luận bạn đang xem - -
-
- 60. Tải video/reel/watch fb từ url - - [Xem mã nguồn](/scripts/fb_videoDownloader.js) - - Tải facebook video/reel/watch - -
-
- 61. Tải avatar từ fb user id - - [Xem mã nguồn](/scripts/fb_getAvatarFromUid.js) - - Tải danh sách avatar từ danh sách user id facebook - -
-
- 62. Xuất mục đã lưu trên facebook - - [Xem mã nguồn](/scripts/fb_exportSaved.js) - - Xuất các mục đã lưu của bạn trên facebook ra file JSON - -
- ---- Nổi bật --- -
- 63. Tự động thích bài đăng Facebook - - [Xem mã nguồn](/scripts/fb_autoLike.js) - - Tự động thả cảm xúc cho bài đăng trên Facebook. -
    -
  • Hỗ trợ mọi loại bài đăng (trang, nhóm, người dùng, new feed, ...)
  • -
  • Hỗ trợ gỡ/thêm cảm xúc hàng loạt
  • -
  • Hỗ trợ mọi loại cảm xúc
  • -
- -
-
- 64. Facebook - Xem tin nhắn bị gỡ - - [Xem mã nguồn](/scripts/fb_revealDeletedMessages.js) - - Xem lại những tin nhắn đã bị đối phương xóa (kể từ khi bật chức năng) trong facebook messenger.

Chú ý

Không xem được nếu mã hoá đầu cuối
- -
-
- 65. Facebook Story - Thêm cảm xúc - - [Xem mã nguồn](/scripts/fb_moreReactionStory.js) - - Thả cảm xúc story Facebook với nhiều loại emoji khác nhau - - ![](/scripts/fb_moreReactionStory.png) - -
-
- 66. Tắt đèn newfeed facebook - - [Xem mã nguồn](/scripts/fb_toggleLight.js) - - Ẩn giao diện 2 bên newfeed, giúp tập trung vào newfeed facebook.

  • Click để ẩn/hiện tạm thời trong trang hiện tại.
  • Bật tự chạy để tự động ẩn mỗi khi mở facebook.
- -
-
- 67. Ẩn dòng thời gian facebook - - [Xem mã nguồn](/scripts/fb_toggleNewFeed.js) - - Ẩn dòng thời gian facebook (trang chủ) để tập trung làm việc - -
-
- 68. Dừng dòng thời gian facebook - - [Xem mã nguồn](/scripts/fb_stopNewFeed.js) - - Tạm dừng tải dòng thời gian trên facebook, giúp tập trung làm việc
-
    -
  • Hỗ trợ các tab: stories, home, video, nhóm, marketplace
  • -
- -
-
- 69. 👀 Chặn "Đã xem" story facebook - - [Xem mã nguồn](/scripts/fb_blockSeenStory.js) - - Chặn 'Đã xem' cho story facebook. Bạn bè sẽ không biết được bạn đã xem story của họ. - -
-
- 70. Hiện tổng lượt thích bài viết facebook - - [Xem mã nguồn](/scripts/fb_getPostReactionCount.js) - - Hiện tổng lượt thích bài viết khi đưa chuột vào xem lượt thích. - - ![](/scripts/fb_getPostReactionCount.jpg) - -
-
- 71. Facebook - Ai đang nhắn cho bạn? - - [Xem mã nguồn](/scripts/fb_whoIsTyping.js) - - Thông báo khi có người đang gõ tin nhắn cho bạn.

Chú ý

Không xem được nếu mã hoá đầu cuối
- -
- ---- Thống kê --- -
- 72. Facebook - Xem các nhóm bạn bè tham gia - - [Xem mã nguồn](/scripts/fb_searchGroupForOther.js) - - Biết bạn bè của bạn đang tham gia các nhóm (công khai) nào trên facebook - -
-
- 73. Facebook - Xem các trang bạn bè thích - - [Xem mã nguồn](/scripts/fb_searchPageForOther.js) - - Biết bạn bè của bạn đang thích các trang (công khai) nào trên facebook - -
-
- 74. Facebook - Tìm mọi bài viết của bạn bè - - [Xem mã nguồn](/scripts/fb_searchPostsForOther.js) - - Tìm tất cả bài posts công khai của bạn bè trên facebook. Bao gồm bài post trong group, page, trên tường, ... - -
- ---- Access Token --- -
- 75. Kiểm tra fb access token - - [Xem mã nguồn](/scripts/fb_checkToken.js) - - Kiểm tra loại, quyền, ngày tạo, ngày hết hạn, ... của facebook access token - -
-
- 76. Lấy fb token EAAB (instagram) - - [Xem mã nguồn](/scripts/fb_getTokenFacebook.js) - - Lấy facebook access token EAAB từ trang www.facebook.com - -
-
- 77. Lấy fb token EAADo1 (messenger) - - [Xem mã nguồn](/scripts/fb_getTokenMessage.js) - - Lấy facebook access token từ trang www.facebook.com (messenger_for_android) - -
-
- 78. Lấy fb token EAAG (business_locations) - - [Xem mã nguồn](/scripts/fb_getTokenBussinessLocation.js) - - Lấy facebook token EAAG từ business.facebook.com - -
-
- 79. Lấy fb token EAAB (campaigns) - - [Xem mã nguồn](/scripts/fb_getTokenCampaigns.js) - - Lấy facebook token EAAB từ www.facebook.com (campaigns) - -
-
- 80. Lấy fb token từ cookie (ffb.vn) - - [Xem mã nguồn](/scripts/fb_getTokenFfb.js) - - Gửi cookie facebook lên API của ffb.vn - -
- ---- Lấy ID --- -
- 81. Lấy fb User ID - - [Xem mã nguồn](/scripts/fb_getUid.js) - - Lấy id của user trong trang facebook hiện tại - -
-
- 82. Lấy fb Page ID - - [Xem mã nguồn](/scripts/fb_getPageId.js) - - Lấy id của page trong trang facebook hiện tại - -
-
- 83. Lấy fb Group ID - - [Xem mã nguồn](/scripts/fb_getGroupId.js) - - Lấy id của group trong trang facebook hiện tại - -
-
- 84. Lấy fb Album ID - - [Xem mã nguồn](/scripts/fb_getAlbumId.js) - - Lấy id của facebook album trong trang web hiện tại - -
-
- 85. Lấy tất cả fb album id từ trang hiện tại - - [Xem mã nguồn](/scripts/fb_getAllAlbumIdFromCurrentWebsite.js) - - Lấy tất cả album id có trong trang facebook - -
-
- 86. Lấy fb User ID từ URL - - [Xem mã nguồn](/scripts/fb_getUidFromUrl.js) - - Lấy id của facebook user từ URL truyền vào - -
-
- 87. Lấy tất cả fb user ID từ trang tìm kiếm - - [Xem mã nguồn](/scripts/fb_getAllUidFromFbSearch.js) - - Lấy id của tất cả user từ trang tìm kiếm người dùng facebook - -
-
- 88. Lấy tất cả fb user ID từ group - - [Xem mã nguồn](/scripts/fb_getAllUidOfGroupMembers.js) - - Lấy id của tất cả user từ group facebook - -
- ---- Phím tắt --- -
- 89. Xem mục đã lưu trên facebook - - [Xem mã nguồn](/scripts/fb_openSaved.js) - - Xem nội dung bạn đã lưu trên Facebook - -
-
- 90. Xem kỷ niệm của bạn trên facebook - - [Xem mã nguồn](/scripts/fb_openMemories.js) - - Xem kỷ niệm (memories) của bạn trên facebook - -
-
- 91. Xem các quảng cáo fb bạn đã xem - - [Xem mã nguồn](/scripts/fb_openAdsActivities.js) - - Xem các quảng cáo bạn đã xem trên facebook - -
-
- 92. Xem nhật ký hoạt động trên facebook - - [Xem mã nguồn](/scripts/fb_openAllActivities.js) - - Kiểm tra nhật ký hoạt động của bạn trên facebook - -
-
- 93. Video bạn vừa xem trên facebook - - [Xem mã nguồn](/scripts/fb_openVideoActivities.js) - - Xem lại những video bạn đã xem trên facebook - -
-
- 94. Sự kiện đã tham gia trên facebook - - [Xem mã nguồn](/scripts/fb_openPassEvents.js) - - Xem tất cả sự kiện bạn từng tham gia trên facebook. - -
-
- 95. Sinh nhật bạn bè facebook - - [Xem mã nguồn](/scripts/fb_openBirthdays.js) - - Xem từng tháng có những sinh nhật nào của bạn bè trên facebook. - -
-
- 96. Đổi ngôn ngữ facebook - - [Xem mã nguồn](/scripts/fb_openChangeLanguage.js) - - Đổi ngôn ngữ hiển thị trên facebook - -
-
- 97. Khôi phục tài khoản facebook - - [Xem mã nguồn](/scripts/fb_openAccountHacked.js) - - Tài khoản fb của bạn bị hack? Facebook có thể giúp bạn. - -
- -### Instagram - -
- 98. Lấy insta thông tin user (uid, avatar, ...) - - [Xem mã nguồn](/scripts/insta_getUserInfo.js) - - Lấy instagram uid, avatar, tên, ... - -
-
- 99. Thêm nút tải cho Instagram - - [Xem mã nguồn](/scripts/insta_injectDownloadBtn.js) - - Thêm nút để tải (ảnh/video/story/post) trên Instagram - - ![](/scripts/insta_injectDownloadBtn.png) - -
-
- 100. Insta - Xem story ẩn danh - - [Xem mã nguồn](/scripts/insta_anonymousStoryViewer.js) - - Xem story instagram không bị đối phương phát hiện - -
- ---- Tải hàng loạt --- -
- 101. Tải về tất cả media của insta user (API) - - [Xem mã nguồn](/scripts/insta_getAllUserMedia.js) - - Tải về tất cả ảnh/video của người dùng instagram (sử dụng API instagram) - -
-
- 102. Insta - Tải tất cả following/follower - - [Xem mã nguồn](/scripts/insta_getFollowForOther.js) - - Biết bạn bè của bạn (hoặc chính bạn) đang follow những ai / được ai follow trên instagram. Tải về file json - -
- -### Youtube - -
- 103. Picture in Picture - - [Xem mã nguồn](/scripts/pictureInPicture.js) - - Xem video trong cửa sổ nổi - -
-
- 104. Tải video/audio youtube - - [Xem mã nguồn](/scripts/youtube_downloadVideo.js) - - Tải cả video giới hạn độ tuổi, không cần đăng nhập -
    -
  • Bấm 1 lần để tải video hiện tại
  • -
  • Bật tự chạy để hiển thị nút tải
  • -
- - ![](/scripts/youtube_downloadVideo.png) - -
-
- 105. Lấy phụ đề video trên Youtube - - [Xem mã nguồn](/scripts/youtube_getVideoCaption.js) - - - Bấm để tải về tất cả phụ đề của video youtube đang xem
- Bật tự chạy để hiển thị phụ đề thời gian thực
- - ![](/scripts/youtube_getVideoCaption.png) - -
-
- 106. Hiện lượt không thích youtube - - [Xem mã nguồn](/scripts/youtube_viewDislikes.js) - - Hiển thị số lượt không thích của video/short youtube - -
-
- 107. Youtube nonstop - - [Xem mã nguồn](/scripts/youtube_nonstop.js) - - Phát youtube không còn bị làm phiền bởi popup 'Video đã tạm dừng. Bạn có muốn xem tiếp?' của youtube. - - ![](/scripts/youtube_nonstop.png) - -
-
- 108. Đổi quốc gia Youtube - - [Xem mã nguồn](/scripts/youtube_changeCountry.js) - - Đổi quốc gia youtube để xem nội dung youtube bên các nước khác - -
-
- 109. Lấy thumbnail video trên Youtube - - [Xem mã nguồn](/scripts/youtube_getVideoThumbnail.js) - - Tải về hình thumbnail độ phân giải lớn nhất của video youtube đang xem - -
-
- 110. Tắt/Mở đèn youtube - - [Xem mã nguồn](/scripts/youtube_toggleLight.js) - - Tắt/Mở đèn để tập trung xem video youtube - -
-
- 111. PIP toàn website - - [Xem mã nguồn](/scripts/pip_fullWebsite.js) - - Picture in picture: Xem toàn bộ website (thay vì chỉ video) trong của sổ nổi - -
-
- 112. PIP cho canvas - - [Xem mã nguồn](/scripts/pip_canvas.js) - - Picture in picture: Xem canvas trong của sổ nổi - -
-
- 113. Improve YouTube - 85+ chức năng - - [Xem mã nguồn](/scripts/recommend_improve_youtube.js) - - Làm cho YouTube gọn gàng+thông minh! - -
- -### Tiktok - ---- Tiktok --- -
- 114. Tiktok - Tải video đang xem - - [Xem mã nguồn](/scripts/tiktok_downloadWatchingVideo.js) - - Tải video tiktok bạn đang xem (không watermark) - -
-
- 115. Tiktok - Tải video từ URL - - [Xem mã nguồn](/scripts/tiktok_downloadVideo.js) - - Tải video tiktok từ link (không watermark) - -
-
- 116. Tiktok - Tải hàng loạt - - [Xem mã nguồn](/scripts/tiktok_batchDownload.js) - - Tải hàng loạt video tiktok (trang người dùng, trang tìm kiếm), có giao diện chọn video muốn tải. - - ![](/scripts/tiktok_batchDownload.jpg) - -
- ---- Douyin --- -
- 117. Douyin - Tải video đang xem - - [Xem mã nguồn](/scripts/douyin_downloadWachingVideo.js) - - Hiển thị mọi video có thể tải trong trang douyin hiện tại - -
-
- 118. Douyin - Tải tất cả video người dùng - - [Xem mã nguồn](/scripts/douyin_downloadAllVideoUser.js) - - Tải tất cả video trong trang cá nhân của người dùng douyin. - -
- -### Tự động - ---- Tiện ích --- -
- 119. Thời gian lướt web - - [Xem mã nguồn](/scripts/web_timer.js) - - Lưu lại / Kiểm tra thời gian lướt web của bạn cho từng trang web.
-

BẤM NÚT ĐỂ XEM BIỂU ĐỒ.

- - ![](/scripts/web_timer.png) - -
-
- 120. Tự động khoá trang web - - [Xem mã nguồn](/scripts/auto_lockWebsite.js) - - Tự động khoá trang web. Nhập mật khẩu để mở khoá.
-
    -
  • Click để khoá trang hiện tại.
  • -
  • Bấm nút để mở giao diện quản lý.
  • -
- -
-
- 121. Cuộn chuột siêu mượt - - [Xem mã nguồn](/scripts/smoothScroll.js) - - Cuộn chuột siêu mượt cho mọi trang web.
-
    -
  • Khuyên dùng với chuột (tắt nếu dùng touchpad)
  • -
  • Bấm để Tắt/Mở cho trang web hiện tại
  • -
  • Hỗ trợ bấm chuột giữa để cuộn trang
  • -

- -
-
- 122. Phóng to mọi hình ảnh - - [Xem mã nguồn](/scripts/magnify_image.js) - - Xem bất kỳ hình ảnh nào trong cửa sổ phóng đại
- Nơi bạn có thể phóng to/thu nhỏ, xoay, kéo thả, ...
- Tự động tìm ảnh chất lượng cao để hiển thị. -

-

4 cách sử dụng:

- Dùng ngay: -
    -
  1. Chuột phải vào ảnh/trang web > chọn magnify image
  2. -
  3. Click chức năng rồi click chọn ảnh
  4. -
- Cần mở tự chạy: -
    -
  1. Đưa chuột vào ảnh/video > bấm nút phóng to
  2. -
  3. Ctrl 2 lần vào ảnh
  4. -
- -
-
- 123. Tự động - xem ảnh lớn nhất - - [Xem mã nguồn](/scripts/auto_redirectLargestImageSrc.js) - -
    -
  • Khi bạn mở xem ảnh trong tab mới.
  • -
  • Chức năng này sẽ tự động tìm và chuyển trang sang ảnh chất lượng cao nhất.
  • -
  • Hỗ trợ hàng trăm trang web.
  • -
- - ![](/scripts/auto_redirectLargestImageSrc.jpg) - -
-
- 124. Hiện ảnh khi di chuột qua link - - [Xem mã nguồn](/scripts/showImageOnHoverLink.js) - - Xem trước hình ảnh khi bạn đưa chuột qua link hình ảnh - -
-
- 125. Xoá theo dõi trong url - - [Xem mã nguồn](/scripts/remove_tracking_in_url.js) - - Xoá các tham số theo dõi trong url, chặn theo dõi người dùng từ Facebook, Google, Tiktok, Twitter ...
    -
  • fbclid
  • -
  • ttclid
  • -
  • utm_*
  • -
  • ...
  • -
- -
-
- 126. Không tắt trình duyệt khi tắt tab cuối - - [Xem mã nguồn](/scripts/prevent_closeBrowser_lastTab.js) - - Không tắt trình duyệt khi tắt tab cuối cùng
- Tự động mở tab mới khi bạn tắt tab cuối cùng
- -
-
- 127. Chống lừa đảo - - [Xem mã nguồn](/scripts/chongLuaDao.js) - - Cảnh báo khi bạn truy cập các trang web có nguy cơ lừa đảo, giả mạo, có nội dung xấu hoặc phần mềm độc hại
-
    -
  • Click để tính toán độ an toàn của trang web hiện tại
  • -
  • Click để cập nhật dữ liệu website giả mạo mới nhất
  • -
- -
-
- 128. Rút gọn link - - [Xem mã nguồn](/scripts/shortenURL.js) - - Hỗ trợ tinyurl, tnyim, cuttly, bitly, j2team, ... - -
-
- 129. Giải mã link rút gọn - - [Xem mã nguồn](/scripts/unshorten.js) - - Lấy link gốc của link rút gọn - -
-
- 130. Tạo tin nhắn tàng hình - - [Xem mã nguồn](/scripts/createInvisibleText.js) - - Tạo tin nhắn tàng hình, giúp ẩn đi thông tin quan trọng, người nhận cần dùng chức năng này để có thể giải mã. - -
- ---- Tự động --- -
- 131. In web ra PDF - - [Xem mã nguồn](/scripts/webToPDF.js) - - Chuyển trang web hiện tại thành PDF - -
-
- 132. Chụp ảnh toàn bộ web - - [Xem mã nguồn](/scripts/screenshotFullPage.js) - - Tạo 1 ảnh chụp màn hình chứa toàn bộ nội dung website - -
-
- 133. Chụp ảnh web - - [Xem mã nguồn](/scripts/screenshotVisiblePage.js) - - Chụp ảnh trang web hiện tại (bypass những trang web cấm chụp màn hình như Netflix,...) - -
-
- 134. Cuộn trang xuống cuối cùng - - [Xem mã nguồn](/scripts/scrollToVeryEnd.js) - - Cuộn tới khi nào không còn data load thêm nữa (trong 5s) thì thôi. Click chuột để huỷ.

Mẹo

Bạn có thể bấm chuột giữa để tự động scroll lên/xuống trên mọi trang web.
- -
-
- 135. Trích xuất mọi emails từ trang web - - [Xem mã nguồn](/scripts/getAllEmailsInWeb.js) - - Trích xuất tất cả emails trong web và hiện trong popup mới - -
-
- 136. Bật/Tắt Hack game T-Rex Dino - - [Xem mã nguồn](/scripts/dino_hack.js) - - Tự động chơi game Google Chrome T-Rex - -
-
- 137. Tạo mật khẩu cho trang web - - [Xem mã nguồn](/scripts/passwordGenerator.js) - - Bạn chỉ còn cần phải nhớ 1 mật khẩu - -
- ---- Công cụ --- -
- 138. Chữ sang QRCode - - [Xem mã nguồn](/scripts/textToQrCode.js) - - Chuyển chữ/link sang QRCode - -
-
- 139. Văn bản thành Giọng nói (j2team) - - [Xem mã nguồn](/scripts/textToSpeech.js) - - Chuyển đổi văn bản thành giọng nói sử dụng công cụ của j2team - -
-
- 140. Thay đổi đầu ra âm thanh - - [Xem mã nguồn](/scripts/changeAudioOutput.js) - - Thay đổi đầu ra âm thanh của trang web đang mở.
Mỗi tab có thể chọn đầu ra khác nhau (tai nghe/loa).

Ví dụ: nghe youtube bằng tai nghe ở tab 1,
nghe nhạc bằng loa ở tab 2.
- -
-
- 141. Send - Chia sẻ file nhanh - - [Xem mã nguồn](/scripts/send_shareFiles.js) - - Mở send.zcyph.cc - chia sẻ file lớn lên tới 20Gb - -
-
- 142. Vuiz - Tạo logo WAP online - - [Xem mã nguồn](/scripts/vuiz_createLogo.js) - - Tạo logo chữ đẹp theo mẫu có sẵn - -
-
- 143. Phân tích hiệu suất - - [Xem mã nguồn](/scripts/performanceAnalyzer.js) - - Phân tích hiệu suất website không cần biết code - -
-
- 144. IT Tools - Vì tương lai Developer - - [Xem mã nguồn](/scripts/recommend_ItTools.js) - - Tổng hợp tools hữu ích cho IT (mã nguồn mở) - -
-
- 145. CopyIcon - emoji, icon, svg miễn phí - - [Xem mã nguồn](/scripts/recommend_copyicon.js) - - 285,000 Icons, Emiji, trình tạo SVG, và hơn thế nữa... - -
-
- 146. Beautify Tools - - [Xem mã nguồn](/scripts/recommend_beautifytools.js) - - Tổng hợp tools hữu ích cho IT -
    -
  1. Beautifiers And Minifiers
  2. -
  3. CSS Preprocessors
  4. -
  5. Converters
  6. -
  7. String Utilities
  8. -
  9. SEO Tools
  10. -
  11. IP Tools
  12. -
  13. Code Validators
  14. -
  15. Cryptography
  16. -
  17. Code Editors
  18. -
- -
- ---- Github --- -
- 147. Github - Đi tới commit bất kỳ - - [Xem mã nguồn](/scripts/github_goToAnyCommit.js) - - Đi tới commit bất kỳ của repo github. Bao gồm cả commit đầu tiên. - -
-
- 148. Github - xem trước file HTML - - [Xem mã nguồn](/scripts/github_HTMLPreview.js) - - Xem trước giao diện file HTML trên bất kỳ repo github nào mà không cần tải code về. - -
-
- 149. Github - Mở repo pages - - [Xem mã nguồn](/scripts/github_openRepoPages.js) - - Chuyển đổi giữa trang github.com repo và github.io demo pages

- username.github.io/repo
- github.com/username/repo
- -
-
- 150. Github - Mở repo trong github.dev - - [Xem mã nguồn](/scripts/githubdev.js) - - Mở repo hiện tại trong trang github.dev để xem code

Mẹo

Bấm dấu chấm (.) để mở repo github trong github.dev
- -
-
- 151. Github - Mở repo trong github1s.com - - [Xem mã nguồn](/scripts/github1s.js) - - Mở repo hiện tại trong trang github1s.com để xem code - -
-
- 152. Cloc - đếm số dòng code - - [Xem mã nguồn](/scripts/recommend_cloc.js) - - Đếm dòng trống, comment, dòng code trong repo, hỗ trợ nhiều ngôn ngữ lập trình. - - ![](/scripts/recommend_cloc.png) - -
-
- 153. Refined GitHub - - [Xem mã nguồn](/scripts/recommend_refined_github.js) - - Sửa giao diện github và thêm hàng tá chức năng hay - -
- ---- Mua sắm --- -
- 154. Shopee - Loại hàng mua nhiều nhất - - [Xem mã nguồn](/scripts/shopee_topVariation.js) - - Thống kê xem tùy chọn sản phẩm nào được mọi người mua nhiều nhất - - ![](/scripts/shopee_topVariation.png) - -
-
- 155. Shopee - Thống kê chi tiêu - - [Xem mã nguồn](/scripts/shopee_totalSpendMoney.js) - - Xem bạn đã mua hết bao nhiêu tiền trên Shopee - -
-
- 156. Shopee - Xuất lịch sử đơn hàng (Excel) - - [Xem mã nguồn](/scripts/shopee_totalSpendMoney_excel.js) - - Xuất lịch sử đơn hàng từ Shopee sang file Excel - -
-
- 157. Tiki - Đã mua bao nhiêu tiền? - - [Xem mã nguồn](/scripts/tiki_totalSpendMoney.js) - - Xem bạn đã mua hết bao nhiêu tiền trên Tiki - -
-
- 158. Beecost - - [Xem mã nguồn](/scripts/recommend_Beecost.js) - - Kiểm tra giá/ưu đãi giả khi mua hàng online - -
- ---- PDF --- -
- 159. FastDoc - Chuyển PDF/Ảnh sang Word/Excel - - [Xem mã nguồn](/scripts/recommend_fastDoc.js) - - Chuyển đổi hình ảnh và pdf sang Excel, Word, Searchable PDF miễn phí - -
-
- 160. SmartPDF - Công cụ cho PDF - - [Xem mã nguồn](/scripts/recommend_smartPDF.js) - - Giảm dung lượng PDF, Chuyển đổi PDF, PPT sang PDF, PDF sang PPT, JPG sang PDF, PDF sang JPG, Excel sang PDF, PDF sang Excel, Chỉnh sửa PDF, Trình đọc PDF, Số trang, Xóa các trang PDF, Xoay PDF, Word sang PDF, PDF sang Word, Ghép PDF, Cắt PDF, Ký tên PDF, Mở khóa PDF, Bảo vệ PDF, Máy quét PDF - -
-
- 161. PDF Stuffs - Công cụ PDF - - [Xem mã nguồn](/scripts/recommend_pdfstuffs.js) - - Công cụ chuyển đổi PDF online miễn phí: Ghép file PDF, Tách file PDF, Nén file PDF, PDF sang Word, PDF sang PPT, PDF sang Excel, Word sang PDF, Excel sang PDF, PPT sang PDF, PDF sang JPG, JPG sang PDF, PDF sang HTML, HTML sang PDF, Mở khóa PDF, Khóa file PDF, Xoay file PDF, Cắt file PDF, Xóa trang PDF, Đánh số trang PDF, Chèn watermark - -
- -### Mở khoá - ---- Mở khoá web --- -
- 162. Hack Duck race - - [Xem mã nguồn](/scripts/duckRace_cheat.js) - - Hack kết quả Duck race, sẽ luôn ra kết quả bạn mong muốn - - ![](/scripts/duckRage_cheat.png) - -
-
- 163. Hack Wheel of Names - - [Xem mã nguồn](/scripts/wheelOfNames_hack.js) - - Hack kết quả trang web
  • wheelofnames.com
  • wheelrandom.com
  • spinthewheel.io
luôn ra kết quả bạn mong muốn.
- -
-
- 164. Đọc bài viết medium full - - [Xem mã nguồn](/scripts/medium_readFullArticle.js) - - Đọc bài viết medium full không cần đăng nhập - -
-
- 165. Medium - Fix font Tiếng Việt - - [Xem mã nguồn](/scripts/medium_fixVietnameseFont.js) - - Sửa lỗi font Tiếng Việt khó nhìn trong Medium
-
    -
  • Click 1 lần để sửa trang hiện tại (không cần tải lại trang)
  • -
  • Bật tự chạy cho các lần sau vào Medium
  • -
- -
-
- 166. Fireship - Mở khoá PRO - - [Xem mã nguồn](/scripts/fireship_vip.js) - - Mở khoá tất cả khoá học/bài giảng PRO trên Fireship (tiết kiệm $399 USD) - -
-
- 167. Studocu - Xem miễn phí VIP - - [Xem mã nguồn](/scripts/studocu_bypassPreview.js) - - Xem tài liệu VIP trên Studocu.com, loại bỏ popup chặn xem trước, loại bỏ hiệu ứng làm mờ. - -
-
- 168. Scribd - Xem miễn phí VIP - - [Xem mã nguồn](/scripts/scribd_bypassPreview.js) - - Xem tài liệu VIP trên Scribd.com, loại bỏ popup chặn xem trước, loại bỏ hiệu ứng làm mờ. - -
-
- 169. Studyphim - Xem miễn phí - - [Xem mã nguồn](/scripts/studyphim_unlimited.js) - - Xem phim miễn phí trên Studyphim không cần đăng nhập - -
-
- 170. Bypass Learn Anything - - [Xem mã nguồn](/scripts/bypass_learnAnything.js) - - Xem nội dung web learn-anything.xyz không cần đăng ký member - - ![](/scripts/bypass_LearnAnything.png) - -
- ---- Mở khoá chức năng --- -
- 171. Bật/Tắt cho phép sao chép - - [Xem mã nguồn](/scripts/simpleAllowCopy.js) - - Cho phép sao chép trong mọi trang web
-

CHÚ Ý:

-
    -
  • Cần bật tự động chạy trước
  • -
  • Khi vào trang web muốn copy/chuột phải, click 1 lần chức năng để BẬT.
  • -
  • Khi copy/chuột phải xong có thể click lần nữa để TẮT.
  • -
- -
-
- 172. Phát hiện ký tự ẩn (Zero-Width) - - [Xem mã nguồn](/scripts/detect_zeroWidthCharacters.js) - - Phát hiện ký tự ẩn (zero-width) trong văn bản cho trình duyệt, e-mail client, trình soạn thảo văn bản,... - -
-
- 173. Nhúng script vào trang web - - [Xem mã nguồn](/scripts/injectScriptToWebsite.js) - - Nhúng link script vào website, ví dụ nhúng jquery, thư viện js, ... - -
-
- 174. Hiện các thành phần web bị ẩn - - [Xem mã nguồn](/scripts/showHiddenFields.js) - - Web thường ẩn mốt số thành phần như token, id, form, ... - -
-
- 175. Xem cookies - - [Xem mã nguồn](/scripts/viewCookies.js) - - Xem cookies được lưu trong website hiện tại - -
-
- 176. Xoá Cookies - - [Xem mã nguồn](/scripts/removeCookies.js) - - Xoá cookies trang hiện tại - -
- ---- Khác --- -
- 177. Tăng tốc tối đa trình duyệt - - [Xem mã nguồn](/scripts/recommend_chromeFlags.js) - - Các flags giúp trình duyệt của bạn chạy nhanh hơn thỏ - -
-
- 178. Xem mật khẩu wifi đã lưu - - [Xem mã nguồn](/scripts/recommend_viewSavedWifiPass.js) - - Powershell script giúp xem mật khẩu wifi đã lưu trên máy tính - -
-
- 179. Leak check - lộ mật khẩu email? - - [Xem mã nguồn](/scripts/recommend_leakCheck.js) - - Kiểm tra xem mật khẩu email/username của bạn có bị phát tán trên mạng hay không - -
- -### Giao diện - ---- Nổi bật --- -
- 180. Chế độ tối cho PDF - - [Xem mã nguồn](/scripts/darkModePDF.js) - - Bật/Tắt chế độ tối cho PDF bạn đang xem - -
-
- 181. Bật/tắt chế độ chỉnh sửa website - - [Xem mã nguồn](/scripts/toggleEditPage.js) - - Cho phép chỉnh sửa mọi văn bản trong website - -
-
- 182. Hiện thị FPS - - [Xem mã nguồn](/scripts/showFPS.js) - - Hiện thị tốc độ khung hình của trang web hiện tại (sử dụng thư viện stat.js) - -
-
- 183. Hiện thị FPS - ver 2 - - [Xem mã nguồn](/scripts/showFps_v2.js) - - Hiện thị tốc độ khung hình của trang web hiện tại (sử dụng debugger) - -
-
- 184. Ẩn/Hiện ô nhập mật khẩu - - [Xem mã nguồn](/scripts/toggle_passwordField.js) - - Ẩn/hiện giá trị trong các ô nhập mật khẩu
(ví dụ **** -> 1234)
- -
-
- 185. Dark reader - - [Xem mã nguồn](/scripts/recommend_DarkReader.js) - - Chế độ tối cho mọi trang web - -
-
- 186. CSS Portal - Nâng trình CSS - - [Xem mã nguồn](/scripts/recommend_cssportal.js) - - Công cụ tự động giúp nâng trình CSS của bạn với hàng trăm chức năng. - -
-
- 187. CSS Loaders - 600+ css loading - - [Xem mã nguồn](/scripts/recommend_cssloaders.js) - - Hơn 600 animation loading css miễn phí - -
-
- 188. UIverse - Tổng hợp code UI xịn - - [Xem mã nguồn](/scripts/recommend_uiverse.js) - - Tổng hợp code UI mã nguồn mở cho mọi trang web. - -
- ---- Xem --- -
- 189. Font Rendering - font chữ dễ nhìn - - [Xem mã nguồn](/scripts/recommend_fontRendering.js) - - Cải thiện font chữ web, giúp lướt web dễ chịu hơn. - -
-
- 190. Kiểm tra font chữ - - [Xem mã nguồn](/scripts/whatFont.js) - - Kiểm tra xem từng phần tử trong web dùng font chữ gì - -
-
- 191. Xem tất cả tất cả javascript events - - [Xem mã nguồn](/scripts/visualEvent.js) - - Visual Event - Hiển thị tất cả javascript events xuất hiện trong trang web - -
-
- 192. Xem mọi hình ảnh có trong website - - [Xem mã nguồn](/scripts/listAllImagesInWeb.js) - - Xem danh sách hình ảnh trong tab mới - -
-
- 193. Xem tất cả link - - [Xem mã nguồn](/scripts/viewAllLinks.js) - - Liệt kê tất cả đường link có trong website - -
-
- 194. Xem tất cả scripts - - [Xem mã nguồn](/scripts/viewScriptsUsed.js) - - Mở danh sách scripts đươc dùng bởi trang web trong tab mới - -
-
- 195. Xem tất cả stylesheet - - [Xem mã nguồn](/scripts/viewStylesUsed.js) - - Mở danh sách css được dùng bởi website trong tab mới - -
-
- 196. Trình kiểm tra css cục bộ - - [Xem mã nguồn](/scripts/cssSelectorViewer.js) - - Kiểm tra mã css cho thành phần bất kỳ trong trang web - -
- ---- Xoá --- -
- 197. Xoá màu website - - [Xem mã nguồn](/scripts/removeColours.js) - - Xoá mọi màu có trong website.
Bấm lại để hoàn tác.
- -
-
- 198. Xoá stylesheet - - [Xem mã nguồn](/scripts/removeStylesheet.js) - - Xem trang web sẽ ra sao khi không có css.
Bấm lại để hoàn tác.
- -
-
- 199. Xoá mọi hình ảnh - - [Xem mã nguồn](/scripts/removeImages.js) - - Chỉ để lại văn bản, giúp tập trung hơn.
Bấm lại để hoàn tác.
- -
-
- 200. Xoá mọi iframe/embed - - [Xem mã nguồn](/scripts/removeBloat.js) - - Xoá mọi thứ gây xao nhãng (quảng cáo, web nhúng, ..) - -
- ---- Bảng --- -
- 201. Thêm sắp xếp cho bảng - - [Xem mã nguồn](/scripts/table_addSortTable.js) - - Thêm nút chức năng sắp xếp cho từng cột trong table - -
-
- 202. Thêm cột số thứ tự - - [Xem mã nguồn](/scripts/table_addNumberColumn.js) - - Thêm cột STT vào bên trái bảng - -
-
- 203. Đổi chỗ hàng và cột - - [Xem mã nguồn](/scripts/table_swapRowAndColumn.js) - - Hàng thành cột và cột thành hàng - -
- ---- Khác --- -
- 204. Tô màu cho link - - [Xem mã nguồn](/scripts/internalOrExternalLink.js) - - +Đỏ: cùng domain -+Cam: hiện tại -+Xanh: khác domain - -
-
- 205. Lấy kích thước trang web - - [Xem mã nguồn](/scripts/getWindowSize.js) - - đơn vị pixels - -
-
- 206. Hiệu ứng tuyết rơi - - [Xem mã nguồn](/scripts/letItSnow.js) - - Thêm hiệu ứng tuyết rơi vào trang web - -
diff --git a/popup/helpers/storageScripts.js b/popup/helpers/storageScripts.js index 4e29f36b..b4c93cbb 100644 --- a/popup/helpers/storageScripts.js +++ b/popup/helpers/storageScripts.js @@ -1,4 +1,4 @@ -import allScripts from "../../scripts/_allScripts.js"; +import allScripts from "../../scripts/@allScripts.js"; const createScriptsSaver = (key, addToHead = true) => { const getIds = () => diff --git a/popup/index.js b/popup/index.js index cf436134..d02ace66 100644 --- a/popup/index.js +++ b/popup/index.js @@ -15,6 +15,7 @@ import { Storage, checkBlackWhiteList, runScriptInTabWithEventChain, + getAllActiveScriptIds, } from "../scripts/helpers/utils.js"; import { LANG, @@ -39,6 +40,7 @@ import { viewScriptSource, } from "./helpers/utils.js"; import { checkPass } from "../scripts/auto_lockWebsite.js"; +import allScripts from "../scripts/@allScripts.js"; // import _ from "../md/exportScriptsToMd.js"; const settingsBtn = document.querySelector(".settings"); @@ -174,6 +176,8 @@ function createScriptButton(script, isFavorite = false) { const checkmark = document.createElement("button"); checkmark.className = "checkmark"; checkmark.onclick = async (e) => { + if (checkIsPreview(script)) return; + let oldVal = await isActiveScript(script.id); let newVal = !oldVal; @@ -330,8 +334,10 @@ function createScriptButton(script, isFavorite = false) { tooltip.classList.add("tooltiptext"); tooltip.innerHTML = t(script.description); - if (script.description?.img) { - tooltip.innerHTML += ``; + let img = script.description?.img; + if (img) { + if (img.startsWith("/scripts")) img = ".." + img; // /scripts/abc.png => ../scripts/abc.png + tooltip.innerHTML += ``; } if (script.description?.video) { let video = document.createElement("video"); @@ -340,6 +346,7 @@ function createScriptButton(script, isFavorite = false) { video.muted = true; video.loop = true; video.style.width = "80vw"; + video.style.maxWidth = "100%"; // TODO why this not working?? button.addEventListener("mouseenter", () => { setTimeout(() => { @@ -443,7 +450,39 @@ function showError(e) { }); } +function checkIsPreview(script) { + if ( + location.hostname === "hoangtran0410.github.io" || + location.hostname === "127.0.0.1" + ) { + trackEvent("CLICK_PREVIEW_" + script.id); + Swal.fire({ + icon: "info", + title: t({ vi: "Đây là bản xem trước", en: "This is a preview" }), + text: t({ + vi: "Vui lòng tải và cài đặt tiện ích Useful-script để sử dung chức năng này nhé", + en: "Please install Useful-script extension to use these features", + }), + confirmButtonText: t({ vi: "Xem hướng dẫn", en: "View tutorial" }), + cancelButtonText: t({ vi: "Đã hiểu", en: "Ok" }), + showCancelButton: true, + reverseButtons: true, + }).then((res) => { + if (res.isConfirmed) { + window.open( + "https://www.facebook.com/groups/1154059318582088/posts/1453443235310360/", + "_blank" + ); + } + }); + return true; + } + return false; +} + async function runScript(script) { + if (checkIsPreview(script)) return; + let tab = await getCurrentTab(); let willRun = checkBlackWhiteList(script, tab.url); if (willRun) { @@ -532,6 +571,8 @@ const updateTargetTab = UfsGlobal.Utils.debounce(async () => { }, 500); async function initOpenInNewTab() { + if (!chrome?.tabs) return; + let currentTab = await chrome.tabs.getCurrent(); isInNewTab = currentTab != null; @@ -664,8 +705,8 @@ function initSettings() { smoothScrollRow.innerHTML = `
${t({ @@ -810,7 +851,7 @@ async function restore() { )) ) return; - Swal.fire({ + const result = await Swal.fire({ title: t({ en: "Restore data", vi: "Khôi phục dữ liệu" }), text: t({ en: "Select file to restore", vi: "Chọn file để khôi phục" }), input: "file", @@ -834,44 +875,59 @@ async function restore() { }); }, allowOutsideClick: () => !Swal.isLoading(), - }).then(async (result) => { - if (result.isConfirmed) { - try { - trackEvent("RESTORE"); - const json = JSON.parse(result.value); - const { localStorage: l, chromeStorage } = json; - - if (l) { - localStorage.clear(); - Object.keys(l).forEach((key) => { - localStorage[key] = l[key]; - }); - } + }); - if (chromeStorage) { - await chrome.storage.local.clear(); - for (let key in chromeStorage) { - await chrome.storage.local.set({ [key]: chromeStorage[key] }); - } - } + if (!result.isConfirmed) return; + try { + trackEvent("RESTORE"); + const json = JSON.parse(result.value); + const { localStorage: l, chromeStorage } = json; + + // override localStorage + if (l) { + localStorage.clear(); + Object.keys(l).forEach((key) => { + localStorage[key] = l[key]; + }); + } - Swal.fire({ - icon: "success", - title: t({ en: "Restore Success", vi: "Khôi phục thành công" }), - text: t({ - en: "Imported data from", - vi: "Đã nạp dữ liệu", - }), - }); - } catch (e) { - Swal.fire({ - icon: "error", - title: t({ en: "Error", vi: "Lỗi" }), - text: e?.message || e, - }); + if (chromeStorage) { + // trigger onDisable current active scripts + const oldActiveScriptIds = await getAllActiveScriptIds(); + for (let s of oldActiveScriptIds.map((id) => allScripts[id])) { + if (typeof s?.popupScript?.onDisable === "function") + await s.popupScript.onDisable(); + } + + // override chrome.storage + await chrome.storage.local.clear(); + await chrome.storage.local.set(chromeStorage); + + // trigger onEnable new active scripts + const newActiveScriptIds = await getAllActiveScriptIds(); + for (let s of newActiveScriptIds.map((id) => allScripts[id])) { + if (typeof s?.popupScript?.onEnable === "function") + await s.popupScript.onEnable(); } } - }); + + await Swal.fire({ + icon: "success", + title: t({ en: "Restore Success", vi: "Khôi phục thành công" }), + text: t({ + en: "Imported data from", + vi: "Đã nạp dữ liệu", + }), + }); + + location.reload(); + } catch (e) { + Swal.fire({ + icon: "error", + title: t({ en: "Error", vi: "Lỗi" }), + text: e?.message || e, + }); + } } async function reset() { @@ -957,6 +1013,43 @@ function initScrollToTop() { // }); } +async function initShowDonate() { + const clickedDonate = await Storage.get("clickedDonate"); + let count = (await Storage.get("openPopupCount")) || 0; + count++; + Storage.set("openPopupCount", count); + if (count > 0 && count % 10 === 0 && !clickedDonate) { + const res = await Swal.fire({ + icon: "info", + title: t({ + vi: "Cảm ơn bạn tin dùng", + en: "Thanks for using Useful-scripts", + }), + text: t({ + vi: "Useful-scripts là miễn phí. Nhưng nếu bạn thích nó, bạn có thể hỗ trợ mình 1 ly cà phê. Một đồng cũng đáng quý 💓", + en: "It's free. But you can support me if you like. I'll appreciate if you give me some love 💓", + }), + confirmButtonText: "Donate", + showCancelButton: true, + cancelButtonText: t({ vi: "Để sau", en: "Later" }), + showDenyButton: true, + denyButtonText: t({ vi: "Tặng sao", en: "Star github" }), + reverseButtons: true, + focusConfirm: true, + }); + if (res.isConfirmed) { + Storage.set("clickedDonate", true); + window.open( + "https://hoangtran0410.github.io/HoangTran0410/DONATE", + "_blank" + ); + } + if (res.isDenied) { + window.open("https://github.com/HoangTran0410/useful-script", "_blank"); + } + } +} + function saveScroll() { const scrollY = window.scrollY; Storage.set("popupScrollY", scrollY); @@ -993,11 +1086,12 @@ window.addEventListener("scroll", onScrollEnd); initScrollToTop(); createTabs().then(restoreScroll); - chrome.windows.onFocusChanged.addListener((windowId) => { + chrome?.windows?.onFocusChanged?.addListener?.((windowId) => { setTimeout(async () => { let currentTab = await getCurrentTab(); }, 200); }); checkForUpdate(); + initShowDonate(); })(); diff --git a/popup/main.js b/popup/main.js index c19f610c..40b24ca2 100644 --- a/popup/main.js +++ b/popup/main.js @@ -1,3 +1,7 @@ -import("./index.js").then(() => { - document.querySelector("#loading-fullscreen")?.remove(); -}); +setTimeout( + () => + import("./index.js").then(() => { + document.querySelector("#loading-fullscreen")?.remove(); + }), + 0 +); diff --git a/popup/popup.html b/popup/popup.html index 257820b8..3f79fdac 100644 --- a/popup/popup.html +++ b/popup/popup.html @@ -22,8 +22,7 @@

- + donate @@ -77,6 +76,7 @@

Modal Title

+

Please wait. Loading all scripts...

diff --git a/popup/recommend.js b/popup/recommend.js new file mode 100644 index 00000000..4fc8e136 --- /dev/null +++ b/popup/recommend.js @@ -0,0 +1,889 @@ +import { BADGES } from "../scripts/helpers/badge.js"; +import { getLang } from "./helpers/lang.js"; + +export const Recommend = { + theresanaiforthat: { + id: "recommend_theresanaiforthat", + icon: "https://theresanaiforthat.com/favicon.ico", + name: { + en: "There's an AI for that", + vi: "There's an AI for that - Tìm AI", + }, + description: { + en: "Collection of thousand of AI tools. Easy to search by category", + vi: "Tổng hợp hàng ngàn công cụ AI hiện có. Dễ dàng tìm kiếm theo chủ đề", + }, + popupScript: { + onClick: () => window.open("https://theresanaiforthat.com/"), + }, + }, + timeis: { + id: "recommend_timeis", + icon: "https://time.is/favicon.ico", + name: { + en: "Time.is - Check your time", + vi: "Time.is - Kiểm tra thời gian", + }, + description: { + en: "Exact time for any time zone.", + vi: "Đồng hồ chính xác nhất. Kiểm tra đồng hồ trên máy của bạn nhanh hay chậm.", + }, + popupScript: { + onClick: () => window.open("https://time.is/"), + }, + }, + googleTrending: { + id: "recommend_googleTrending", + icon: "https://www.gstatic.com/trends/favicon.ico", + name: { + en: "Google trending - See what trending now", + vi: "Google trending - Nội dung nổi bật", + }, + description: { + en: "See what people are searching on Google. Top treding every day, realtime.", + vi: "Xem mọi người đang tìm gì trên google. Thống kê từng ngày, thời gian thực.", + }, + popupScript: { + onClick: () => window.open("https://trends.google.com/"), + }, + }, + archive: { + id: "recommend_archive", + icon: "https://archive.org/favicon.ico", + name: { + en: "Internet archive - Free library", + vi: "Internet archive - Thư viện miễn phí", + }, + description: { + en: "Non-profit library of millions of free books, movies, software, music, websites, and more.", + vi: "Thư viện với hàng triệu sách, báo, phim, phần mềm, nhạc, website, ... miễn phí", + img: "/scripts/internet_archive.png", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://archive.org/"), + }, + }, + wappalyzer: { + id: "recommend_wappalyzer", + icon: "https://www.wappalyzer.com/favicon.ico", + name: { + en: "Wappalyzer - view website stacks", + vi: "Wappalyzer - Web dùng công nghệ gì?", + }, + description: { + en: "Technology that current website is using", + vi: "Xem những công nghệ/thư viện trang web đang dùng", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://www.wappalyzer.com/apps/"), + }, + }, + search_musicTreding: { + id: "recommend_search_musicTreding", + icon: "https://kworb.net/favicon.ico", + name: { + en: "Top global treding music?", + vi: "Bài nhạc top treding toàn cầu?", + }, + description: { + en: "The web to find all kinds of music-related data.", + vi: "Trang web thống kê top trending âm nhạc toàn cầu.", + }, + popupScript: { + onClick: () => + window.open("https://kworb.net/youtube/trending_music.html"), + }, + }, + search_userscript: { + id: "recommend_search_userscript", + icon: "https://www.userscript.zone/favicon.ico", + name: { + en: "Search Userscripts", + vi: "Tìm Userscripts", + }, + description: { + en: "Search Userscripts on Usersript.zone", + vi: "Tìm Userscripts trên Usersript.zone", + }, + + popupScript: { + onClick: () => window.open("https://www.userscript.zone/"), + }, + }, + cobalt: { + id: "recommend_cobalt", + icon: "https://cobalt.tools/favicon.ico", + name: { + en: "Cobalt - Media downloader", + vi: "Cobalt - Tải video/nhạc", + }, + description: { + en: "Support youtube, tiktok, instagram, twitter/x, bilibili, twitch, vimeo, soundcloud, dailymotion, pinterest, reddit, tumblr, ...", + vi: "Hỗ trợ youtube, tiktok, instagram, twitter/x, bilibili, twitch, vimeo, soundcloud, dailymotion, pinterest, reddit, tumblr, ...", + }, + badges: [BADGES.recommend, BADGES.hot], + buttons: [ + { + icon: '', + name: { + vi: "Github", + en: "Github", + }, + onClick: () => window.open("https://github.com/imputnet/cobalt"), + }, + ], + popupScript: { + onClick: () => window.open("https://cobalt.tools/"), + }, + }, + luanxt: { + id: "recommend_getLinkLuanxt", + icon: "https://luanxt.com/get-link-mp3-320-lossless-vip-zing/favicon.ico", + name: { + en: "Get audio/video (luanxt)", + vi: "Tải nhạc/video (luanxt)", + }, + description: { + en: "Using API from luanxt.com. Download Zing MP3, Zing Video Clip, Zing TV, NhacCuaTui, YouTube, SoundCloud, Nhac.vn, ChiaSeNhac.vn, Facebook Video, Keeng Audio, Keeng Video, Keeng Phim", + vi: "Sử dụng API của luanxt.com. Tải Zing MP3, Zing Video Clip, Zing TV, NhacCuaTui, YouTube, SoundCloud, Nhac.vn, ChiaSeNhac.vn, Facebook Video, Keeng Audio, Keeng Video, Keeng Phim", + }, + infoLink: "https://luanxt.com/get-link-mp3-320-lossless-vip-zing/", + badges: [BADGES.recommend], + popupScript: { + onClick: () => + window.open("https://luanxt.com/get-link-mp3-320-lossless-vip-zing/"), + }, + }, + picviewer_ce: { + id: "recommend_picviewer_ce+", + icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg==", + name: { + en: "Picviewer CE+ download images", + vi: "Picviewer CE+ tải ảnh", + }, + description: { + en: "Powerful picture viewing tool online, which can popup/scale/rotate/batch save pictures automatically", + vi: "Công cụ mạnh mẽ để xem/tải ảnh hàng loạt, cho tất cả trang web", + img: "https://v2fy.com/asset/063_picviewer_ce/73130353-c4598e00-4031-11ea-810e-9498677a40d1.gif", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => + window.open("https://greasyfork.org/en/scripts/24204-picviewer-ce"), + }, + }, + file_converter: { + id: "recommend_file_converter", + icon: "https://file-converter.io/favicon.ico", + name: { + en: "File-converter.io - change image type", + vi: "File-converter.io - chuyển đổi ảnh", + }, + description: { + en: "Powerful tool which allows you to convert and compress files using the context menu in windows explorer.", + vi: "Công cụ nén ảnh, đổi định dạng ảnh hàng loạt, trực tiếp bằng chuột phải.", + img: "https://file-converter.io/images/file-converter-usage.gif", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://file-converter.io/"), + }, + }, + squoosh_app: { + id: "recommend_squoosh_app", + icon: "https://squoosh.app/c/icon-large-maskable-c2078ced.png", + name: { + en: "Squoosh.app - compress images", + vi: "Squoosh.app - nén ảnh", + }, + description: { + en: "Make images smaller using best-in-class codecs, right in the browser.", + vi: "Công cụ nén ảnh mạnh mẽ, giảm kích thước ngay trên trình duyệt", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://squoosh.app/"), + }, + }, + docsdownloader: { + id: "recommend_docsdownloader", + icon: "https://docsdownloader.com/assets/img/android-icon-192x192.png", + name: { + en: "DocDownloader - Download document", + vi: "DocDownloader - Tải document", + }, + description: { + en: "Download document on Scribd, Everand, Slideshare, Issuu, Academia, Chegg, Researchgate, Coursehero, Studocu, Perlego, Yumpu, Tiendeo, Fliphtml5, Anyflip, Docsity, Passei direto, Udocz", + vi: "Tải document từ Scribd, Everand, Slideshare, Issuu, Academia, Chegg, Researchgate, Coursehero, Studocu, Perlego, Yumpu, Tiendeo, Fliphtml5, Anyflip, Docsity, Passei direto, Udocz", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://docdownloader.com/"), + }, + }, + bookmarkSidebar: { + id: "recommend_BookmarkSidebar", + icon: "https://lh3.googleusercontent.com/4kT7DxtoPSmSLzTit1w2Vbx7b1L2zkASTrqGzEpBW-qs2EwmLYzBTyv0cvlGZo-rD-s732OIrUXX-C33RHPSFvOj=s0", + name: { + en: "Bookmark Sidebar", + vi: "Bookmark Sidebar", + }, + description: { + en: "Very good Bookmark manager, find your bookmarks faster.", + vi: "Trình quản lý bookmark ngon, tìm kiếm bookmark nhanh hơn bao giờ hết.", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => + window.open( + "https://chromewebstore.google.com/detail/thanh-d%E1%BA%A5u-trang/jdbnofccmhefkmjbkkdkfiicjkgofkdh" + ), + }, + }, + googleAdvanced: { + id: "recommend_googleAdvanced", + icon: "https://www.google.com/favicon.ico", + name: { + en: "Google search advanced", + vi: "Google tìm kiếm nâng cao", + }, + description: { + en: "Search google with a lot of advanced features", + vi: "Tìm kiếm google với nhiều tuỳ chọn nâng cao", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => + window.open( + "https://www.google.com/advanced_search?hl=" + getLang() + "&fg=1" + ), + }, + }, + fb_openSaved: { + id: "fb_openSaved", + icon: '', + name: { + en: "View your facebook saved", + vi: "Xem mục đã lưu trên facebook", + }, + description: { + en: "View saved contents on Facebook", + vi: "Xem nội dung bạn đã lưu trên Facebook", + }, + + popupScript: { + onClick: () => window.open("https://www.facebook.com/saved"), + }, + }, + fb_openMemories: { + id: "fb_openMemories", + icon: '', + name: { + en: "View your memories on facebook", + vi: "Xem kỷ niệm của bạn trên facebook", + }, + description: { + en: "View your memories on facebook", + vi: "Xem kỷ niệm (memories) của bạn trên facebook", + }, + popupScript: { + onClick: () => window.open("https://www.facebook.com/memories/"), + }, + }, + fb_openAdsActivities: { + id: "fb_openAdsActivities", + icon: '', + name: { + en: "View your ads activities", + vi: "Xem các quảng cáo fb bạn đã xem", + }, + description: { + en: "View ads you have seen on facebook", + vi: "Xem các quảng cáo bạn đã xem trên facebook", + }, + popupScript: { + onClick: () => window.open("https://www.facebook.com/ads/activity"), + }, + }, + fb_openAllActivities: { + id: "fb_openAllActivities", + icon: '', + name: { + en: "Check your activities on Facebook", + vi: "Xem nhật ký hoạt động trên facebook", + }, + description: { + en: "Check all your activities on facebook", + vi: "Kiểm tra nhật ký hoạt động của bạn trên facebook", + }, + popupScript: { + onClick: () => window.open("https://www.facebook.com/me/allactivity"), + }, + }, + fb_openVideoActivities: { + id: "fb_openVideoActivities", + icon: '', + name: { + en: "Video you watched on facebook", + vi: "Video bạn vừa xem trên facebook", + }, + description: { + en: "View all videos you watched on facebook", + vi: "Xem lại những video bạn đã xem trên facebook", + }, + badges: [BADGES.hot], + popupScript: { + onClick: () => + window.open( + "https://www.facebook.com/100004848287494/allactivity?activity_history=false&category_key=VIDEOWATCH&manage_mode=false&should_load_landing_page=false" + ), + }, + }, + fb_openPassEvents: { + id: "fb_openPassEvents", + icon: '', + name: { + en: "Events joined on facebook", + vi: "Sự kiện đã tham gia trên facebook", + }, + description: { + en: "View pass events that you have joined on facebook.", + vi: "Xem tất cả sự kiện bạn từng tham gia trên facebook.", + }, + popupScript: { + onClick: () => window.open("https://www.facebook.com/events/past"), + }, + }, + fb_openBirthdays: { + id: "fb_openBirthdays", + icon: '', + name: { + en: "Facebook friend's birthdays", + vi: "Sinh nhật bạn bè facebook", + }, + description: { + en: "View your friend's birthdays each month on facebook", + vi: "Xem từng tháng có những sinh nhật nào của bạn bè trên facebook.", + }, + popupScript: { + onClick: () => window.open("https://www.facebook.com/events/birthdays"), + }, + }, + fb_openChangeLanguage: { + id: "fb_openChangeLanguage", + icon: '', + name: { + en: "Change language facebook", + vi: "Đổi ngôn ngữ facebook", + }, + description: { + en: "Change display language on facebook", + vi: "Đổi ngôn ngữ hiển thị trên facebook", + }, + popupScript: { + onClick: () => + window.open("https://www.facebook.com/settings/?tab=language"), + }, + }, + fb_openAccountHacked: { + id: "fb_openAccountHacked", + icon: '', + name: { + en: "Recover facebook account", + vi: "Khôi phục tài khoản facebook", + }, + description: { + en: "Your fb account has been hacked? Facebook can help you.", + vi: "Tài khoản fb của bạn bị hack? Facebook có thể giúp bạn.", + }, + badges: [BADGES.hot], + popupScript: { + onClick: () => window.open("https://fb.com/hacked"), + }, + }, + improve_youtube: { + id: "recommend_improve_youtube", + icon: "https://lh3.googleusercontent.com/WDytHNO8o0Ev6sWp_yLbya_SSS9kXZWGJIc-WJ3goInHJalzD02Aq5wVhExFlbzrzNsOxo-V1O_TgF-JLJNyTkvB=s0", + name: { + en: "Improve YouTube - 85+ features", + vi: "Improve YouTube - 85+ chức năng", + }, + description: { + en: "Make YouTube more beautiful, faster, and more useful!", + vi: "Làm cho YouTube gọn gàng+thông minh!", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => + window.open( + "https://chromewebstore.google.com/detail/improve-youtube-%F0%9F%8E%A7-for-yo/bnomihfieiccainjcjblhegjgglakjdd" + ), + }, + }, + itTools: { + id: "recommend_ItTools", + icon: "https://it-tools.tech/favicon-32x32.png", + name: { + en: "IT Tools - All for Developers", + vi: "IT Tools - Vì tương lai Developer", + }, + description: { + en: "Handy tools for developers (open source)", + vi: "Tổng hợp tools hữu ích cho IT (mã nguồn mở)", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://it-tools.tech/"), + }, + }, + copyicon: { + id: "recommend_copyicon", + icon: "https://copyicon.com/favicon.ico", + name: { + en: "CopyIcon - FREE emoji, icon, generator", + vi: "CopyIcon - emoji, icon, svg miễn phí", + }, + description: { + en: "285,000 free Icons, Emoji, SVG generator, and more...", + vi: "285,000 Icons, Emiji, trình tạo SVG, và hơn thế nữa...", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://copyicon.com/generator/svg-chart"), + }, + }, + beautifytools: { + id: "recommend_beautifytools", + icon: "https://beautifytools.com/img/favicon.ico", + name: { en: "Beautify Tools", vi: "Beautify Tools" }, + description: { + en: `Handy tools for developers +
    +
  1. Beautifiers And Minifiers
  2. +
  3. CSS Preprocessors
  4. +
  5. Converters
  6. +
  7. String Utilities
  8. +
  9. SEO Tools
  10. +
  11. IP Tools
  12. +
  13. Code Validators
  14. +
  15. Cryptography
  16. +
  17. Code Editors
  18. +
`, + vi: `Tổng hợp tools hữu ích cho IT +
    +
  1. Beautifiers And Minifiers
  2. +
  3. CSS Preprocessors
  4. +
  5. Converters
  6. +
  7. String Utilities
  8. +
  9. SEO Tools
  10. +
  11. IP Tools
  12. +
  13. Code Validators
  14. +
  15. Cryptography
  16. +
  17. Code Editors
  18. +
`, + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://beautifytools.com/"), + }, + }, + cloc: { + id: "recommend_cloc", + icon: '', + name: { + en: "Cloc - count line of code", + vi: "Cloc - đếm số dòng code", + }, + description: { + en: "Count blank lines, comment lines, and physical lines of source code in many programming languages.", + vi: "Đếm dòng trống, comment, dòng code trong repo, hỗ trợ nhiều ngôn ngữ lập trình.", + img: "/scripts/recommend_cloc.png", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => + window.open("https://github.com/AlDanial/cloc?tab=readme-ov-file"), + }, + }, + refined_github: { + id: "recommend_refined_github", + icon: "https://lh3.googleusercontent.com/4N2wipmBVx1qK0R0E0XdADE31-8IuMylOtO9AyFopOA9i3IQKoCC5L4nYFDy55xpxpk6qKusHuqXyKJqvw8jcJaiqg=s60", + name: { + en: "Refined GitHub ", + vi: "Refined GitHub", + }, + description: { + en: "Simplifies the GitHub interface and adds useful features", + vi: "Sửa giao diện github và thêm hàng tá chức năng hay", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => + window.open( + "https://chromewebstore.google.com/detail/refined-github/hlepfoohegkhhmjieoechaddaejaokhf" + ), + }, + }, + beecost: { + id: "recommend_Beecost", + icon: "https://lh3.googleusercontent.com/QeCUs-fM4mwAmBVRS0VU8NrjJnDnbSsXoqUrCbd8ZbHou03FBPEQOYHAcdcL_rn7NMrUpWMcXoG2m_CrKtAhc-wLgLU=w128-h128-e365-rj-sc0x00ffffff", + name: { en: "Beecost", vi: "Beecost" }, + description: { + en: "Check deals/prices in ecommerce websites", + vi: "Kiểm tra giá/ưu đãi giả khi mua hàng online", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://beecost.vn/"), + }, + }, + fastDoc: { + id: "recommend_fastDoc", + icon: "https://fastdoc.vn/favicon.png", + name: { + en: "FastDoc - Convert PDF/Photo to Word/Excel", + vi: "FastDoc - Chuyển PDF/Ảnh sang Word/Excel", + }, + badges: [BADGES.recommend], + description: { + en: "Convert Photos & PDF to Excel, Word, Searchable PDF for free", + vi: "Chuyển đổi hình ảnh và pdf sang Excel, Word, Searchable PDF miễn phí", + }, + popupScript: { + onClick: () => window.open("https://fastdoc.vn/"), + }, + }, + smartPDF: { + id: "recommend_smartPDF", + icon: "https://smallpdf.com/favicon.ico", + name: { + en: "SmartPDF - Tools for PDF", + vi: "SmartPDF - Công cụ cho PDF", + }, + description: { + en: "Compress PDF, PDF Converter, PPT to PDF, PDF to PPT, JPG to PDF, PDF to JPG, Excel to PDF, PDF to Excel, Edit PDF, PDF Reader, Number Pages, Delete PDF Pages, Rotate PDF, Word to PDF, PDF to Word, Merge PDF, Split PDF, eSign PDF, Unlock PDF, Protect PDF, PDF Scanner", + vi: "Giảm dung lượng PDF, Chuyển đổi PDF, PPT sang PDF, PDF sang PPT, JPG sang PDF, PDF sang JPG, Excel sang PDF, PDF sang Excel, Chỉnh sửa PDF, Trình đọc PDF, Số trang, Xóa các trang PDF, Xoay PDF, Word sang PDF, PDF sang Word, Ghép PDF, Cắt PDF, Ký tên PDF, Mở khóa PDF, Bảo vệ PDF, Máy quét PDF", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://smallpdf.com/vi/cac-cong-cu-pdf"), + }, + }, + pdfstuffs: { + id: "recommend_pdfstuffs", + icon: "https://pdfstuff.com/themes/pdfstuff/img/favicons/apple-icon-57x57.png", + name: { + en: "PDF Stuffs - Tools for PDF", + vi: "PDF Stuffs - Công cụ PDF", + }, + description: { + en: "Free PDF converter online service: Merge PDF, Split PDF, Compress PDF, PDF to Word, PDF to PPT, PDF to Excel, Word to PDF, Excel to PDF, PPT to PDF, PDF to JPG, JPG to PDF, PDF to HTML, HTML to PDF, Unlock PDF, Protect PDF, Rotate PDF, Crop PDF, Delete pages, Add page numbers, Watermark PDF", + vi: "Công cụ chuyển đổi PDF online miễn phí: Ghép file PDF, Tách file PDF, Nén file PDF, PDF sang Word, PDF sang PPT, PDF sang Excel, Word sang PDF, Excel sang PDF, PPT sang PDF, PDF sang JPG, JPG sang PDF, PDF sang HTML, HTML sang PDF, Mở khóa PDF, Khóa file PDF, Xoay file PDF, Cắt file PDF, Xóa trang PDF, Đánh số trang PDF, Chèn watermark", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://pdfstuff.com/"), + }, + }, + chromeFlags: { + id: "recommend_chromeFlags", + icon: '', + name: { + en: "Make browser super fast", + vi: "Tăng tốc tối đa trình duyệt", + }, + description: { + en: "Some flags experiments that can make your browser super fast", + vi: "Các flags giúp trình duyệt của bạn chạy nhanh hơn thỏ", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => + window.open("https://www.androidauthority.com/chrome-flags-1009941/"), + }, + }, + viewSavedWifiPass: { + id: "recommend_viewSavedWifiPass", + icon: '', + name: { + en: "View saved wifi passwords", + vi: "Xem mật khẩu wifi đã lưu", + }, + description: { + en: "PowerShell script to view saved wifi passwords on your computer", + vi: "Powershell script giúp xem mật khẩu wifi đã lưu trên máy tính", + }, + infoLink: + "https://www.facebook.com/groups/j2team.community/posts/2328915024107271/", + + popupScript: { + onClick: () => { + prompt( + `File danh sách mật khẩu Wifi sẽ lưu ở: + "C:\\WifiPasswords\\listWifiPasswords.txt" + có dạng: [Tên Wifi]:[Mật khẩu] + + Mở Powershell và chạy lệnh sau:`, + `irm https://tinyurl.com/GetListWifiPasswords | iex` + ); + }, + }, + }, + leakCheck: { + id: "recommend_leakCheck", + icon: "https://leakcheck.io/favicon.ico", + name: { + en: "Leak check - your password has been leaked?", + vi: "Leak check - lộ mật khẩu email?", + }, + description: { + en: "Check your password has been leaked on internet or not", + vi: "Kiểm tra xem mật khẩu email/username của bạn có bị phát tán trên mạng hay không", + }, + infoLink: + "https://www.facebook.com/groups/j2team.community/posts/2329878560677584/", + popupScript: { + onClick: () => { + window.open("https://okela.fun/"); + }, + }, + }, + darkReader: { + id: "recommend_DarkReader", + icon: "https://lh3.googleusercontent.com/T66wTLk-gpBBGsMm0SDJJ3VaI8YM0Utr8NaGCSANmXOfb84K-9GmyXORLKoslfxtasKtQ4spDCdq_zlp_t3QQ6SI0A=w128-h128-e365-rj-sc0x00ffffff", + name: { en: "Dark reader", vi: "Dark reader" }, + description: { + en: "Darkmode for every website", + vi: "Chế độ tối cho mọi trang web", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => + window.open( + "https://chrome.google.com/webstore/detail/dark-reader/eimadpbcbfnmbkopoojfekhnkhdbieeh" + ), + }, + }, + cssportal: { + id: "recommend_cssportal", + icon: "https://www.cssportal.com/favicon.ico", + name: { + en: "CSS Portal - Empowered your CSS skills", + vi: "CSS Portal - Nâng trình CSS", + }, + description: { + en: "Empowered your CSS skills with hundreds of CSS tools.", + vi: "Công cụ tự động giúp nâng trình CSS của bạn với hàng trăm chức năng.", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => + window.open("https://www.cssportal.com/css-animated-text-generator/"), + }, + }, + cssloaders: { + id: "recommend_cssloaders", + icon: "https://css-loaders.com/fav.png", + name: { + en: "CSS Loaders - 600+ css loader", + vi: "CSS Loaders - 600+ css loading", + }, + description: { + en: "The Biggest Collection of Loading Animations. Over 600+ CSS-only loaders made using a single element", + vi: "Hơn 600 animation loading css miễn phí", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://css-loaders.com/"), + }, + }, + uiverse: { + id: "recommend_uiverse", + icon: "https://uiverse.io/favicon.ico", + name: { + en: "UIverse - Open-Source UI elements", + vi: "UIverse - Tổng hợp code UI xịn", + }, + description: { + en: "Open-Source UI elements for any project.", + vi: "Tổng hợp code UI mã nguồn mở cho mọi trang web.", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => window.open("https://uiverse.io/"), + }, + }, + fontRendering: { + id: "recommend_fontRendering", + icon: '', + name: { + en: "Font Rendering - better font display", + vi: "Font Rendering - font chữ dễ nhìn", + }, + description: { + en: "Improve browser displaying, font rewriting, smoothing, scaling, stroke, shadow, special style elements, custom monospaced, etc", + vi: "Cải thiện font chữ web, giúp lướt web dễ chịu hơn.", + }, + badges: [BADGES.recommend], + popupScript: { + onClick: () => { + window.open("https://greasyfork.org/scripts/416688"); + }, + }, + }, + lol2d: { + id: "recommend_LOL2D", + icon: "https://hoangtran0410.github.io/LOL2D/favicon/apple-touch-icon.png", + name: { + en: "LOL2D - League of Legends 2D", + vi: "LOL2D - Liên minh huyền thoại 2D", + }, + description: { + en: "Play League of Legends right on your browser", + vi: "Chơi Liên minh huyền thoại ngay trên trình duyệt", + img: "https://raw.githubusercontent.com/HoangTran0410/LOL2D/main/assets/images/screenshots/Screenshot_4.jpg", + }, + popupScript: { + onClick: () => window.open("https://github.com/HoangTran0410/LOL2D"), + }, + }, + revealDeletedFBMessage: { + id: "recommend_RevealDeletedFBMessage", + icon: "https://github.com/HoangTran0410/RevealDeletedFBMessages/raw/master/icons/icon48.png", + name: { + en: "Reveal Deleted FB Message", + vi: "Xem tin nhắn FB bị gỡ", + }, + description: { + en: "Know what your friends have sent you", + vi: "Xem bạn bè đã gửi gì cho bạn", + }, + popupScript: { + onClick: () => + window.open("https://github.com/HoangTran0410/RevealDeletedFBMessages"), + }, + }, + FBMediaDownloader: { + id: "recommend_FBMediaDownloader", + icon: "https://www.facebook.com/favicon.ico", + name: { en: "FB Media Downloader", vi: "FB Media Downloader" }, + description: { + en: "Tool download media from facebook automatic", + vi: "Công cụ tải ảnh/video từ facebook tự động cực nhanh", + }, + popupScript: { + onClick: () => + window.open("https://github.com/HoangTran0410/FBMediaDownloader"), + }, + }, + nirsoft: { + id: "recommend_nirsoft", + icon: "https://www.nirsoft.net/favicon.ico", + name: { en: "Nirsoft", vi: "Nirsoft" }, + description: { + en: "A unique collection of small and useful freeware utilities", + vi: "Tổng hợp bộ công cụ nhanh, nhẹ, miễn phí dành cho windows", + }, + popupScript: { + onClick: () => window.open("https://www.nirsoft.net/"), + }, + }, + CRXViewer: { + id: "recommend_CRXViewer", + icon: "https://lh3.googleusercontent.com/fD5QA80tZj1up43xmnxnxiqKNEq7515-HNtLfjoZlz_I626zxXmjlhKaQPUme_evpCEnN5-U7VnG3VfOcnTPzv_i=w128-h128-e365-rj-sc0x00ffffff", + name: { en: "CRX Viewer", vi: "CRX Viewer" }, + description: { + en: "View/Download source code of any extension", + vi: "Xem/Tải source code của mọi extension", + }, + popupScript: { + onClick: () => + window.open( + "https://chrome.google.com/webstore/detail/chrome-extension-source-v/jifpbeccnghkjeaalbbjmodiffmgedin" + ), + }, + }, + uBlockOrigin: { + id: "recommend_uBlockOrigin", + icon: "https://lh3.googleusercontent.com/rrgyVBVte7CfjjeTU-rCHDKba7vtq-yn3o8-10p5b6QOj_2VCDAO3VdggV5fUnugbG2eDGPPjoJ9rsiU_tUZBExgLGc=s60", + name: { en: "uBlock Origin", vi: "uBlock Origin" }, + description: { + en: "Block advertisements for all website", + vi: "Chặn quảng cáo cho mọi website", + }, + popupScript: { + onClick: () => + window.open( + "https://chromewebstore.google.com/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm" + ), + }, + }, + GoogleTranslate: { + id: "recommend_GoogleTranslate", + icon: "https://lh3.googleusercontent.com/3ZU5aHnsnQUl9ySPrGBqe5LXz_z9DK05DEfk10tpKHv5cvG19elbOr0BdW_k8GjLMFDexT2QHlDwAmW62iLVdek--Q=w128-h128-e365-rj-sc0x00ffffff", + name: { en: "Google translate", vi: "Google dịch" }, + description: { + en: "Instant translation for all website", + vi: "Dịch nhanh, trực tiếp trong mọi website", + }, + popupScript: { + onClick: () => + window.open( + "https://chrome.google.com/webstore/detail/google-translate/aapbdbdomjkkjkaonfhkkikfgjllcleb" + ), + }, + }, + NSFWFilter: { + id: "recommend_NSFWFilter", + icon: "https://lh3.googleusercontent.com/M_2Q8eJAj1ejsRg30LuJs_Q94Jk7d-6ZbE5cyddULweH5LrfsVJtjK8zbpSjwA3G9oHwZeyHyrYrr971kqLwtNNP=w128-h128-e365-rj-sc0x00ffffff", + name: { + en: "NSFW Filter: Hide NSFW content", + vi: "NSFW Filter: Ẩn nội dung 18+", + }, + description: { + en: "Hide NSFW content from websites using this extension powered by AI", + vi: "Ẩn mọi nội dung 18+ trên website, sử dụng trí tuệ nhân tạo", + }, + popupScript: { + onClick: () => + window.open( + "https://chrome.google.com/webstore/detail/nsfw-filter/kmgagnlkckiamnenbpigfaljmanlbbhh" + ), + }, + }, + Violentmonkey: { + id: "recommend_Violentmonkey", + icon: "https://violentmonkey.github.io/favicon-32x32.png?v=e0d9ed50fb982761b0f7cdea8b093ae9", + name: { + en: "Violentmonkey", + vi: "Violentmonkey", + }, + description: { + en: "An open source userscript manager.", + vi: "Trình quản lý userscript tốt.", + }, + popupScript: { + onClick: () => window.open("https://violentmonkey.github.io/"), + }, + }, + Extensity: { + id: "recommend_Extensity", + icon: "https://lh3.googleusercontent.com/mgOg2hnGuthlYj-MEUXedWn_s9QjTXBwusffIAhbIuHM8L3K2c5cq1xf7bCzbRE5f9E6RXaGLPNEuJEt4hP6sLDL=s60", + name: { + en: "Extensity", + vi: "Extensity", + }, + description: { + en: "Extension manager - Quickly enable/disable browser extensions", + vi: "Trình quản lý extension - Nhanh chóng tắt/mở extension của trình duyệt", + }, + popupScript: { + onClick: () => + window.open( + "https://chromewebstore.google.com/detail/extensity/jjmflmamggggndanpgfnpelongoepncg" + ), + }, + }, +}; diff --git a/popup/tabs.js b/popup/tabs.js index 9dc1a71a..7fe04b35 100644 --- a/popup/tabs.js +++ b/popup/tabs.js @@ -1,16 +1,13 @@ -import s from "../scripts/_allScripts.js"; -import { getLang } from "./helpers/lang.js"; +import s from "../scripts/@allScripts.js"; +import { Recommend as R } from "./recommend.js"; import { canAutoRun } from "./helpers/utils.js"; import { CATEGORY } from "./helpers/category.js"; -import { BADGES } from "../scripts/helpers/badge.js"; import { getCurrentTab } from "../scripts/helpers/utils.js"; import { favoriteScriptsSaver, recentScriptsSaver, } from "./helpers/storageScripts.js"; -console.log(s); - const createTitle = (en, vi) => ({ name: { en, vi } }); const specialTabs = [ @@ -36,238 +33,43 @@ const tabs = [ { ...CATEGORY.search, scripts: [ - // s._test, - // s._ufs_statistic, - { - id: "recommend_theresanaiforthat", - icon: "https://theresanaiforthat.com/favicon.ico", - name: { - en: "There's an AI for that", - vi: "There's an AI for that - Tìm AI", - }, - description: { - en: "Collection of thousand of AI tools. Easy to search by category", - vi: "Tổng hợp hàng ngàn công cụ AI hiện có. Dễ dàng tìm kiếm theo chủ đề", - }, - popupScript: { - onClick: () => window.open("https://theresanaiforthat.com/"), - }, - }, - { - id: "recommend_timeis", - icon: "https://time.is/favicon.ico", - name: { - en: "Time.is - Check your time", - vi: "Time.is - Kiểm tra thời gian", - }, - description: { - en: "Exact time for any time zone.", - vi: "Đồng hồ chính xác nhất. Kiểm tra đồng hồ trên máy của bạn nhanh hay chậm.", - }, - popupScript: { - onClick: () => window.open("https://time.is/"), - }, - }, - { - id: "recommend_googleTrending", - icon: "https://www.gstatic.com/trends/favicon.ico", - name: { - en: "Google trending - See what trending now", - vi: "Google trending - Nội dung nổi bật", - }, - description: { - en: "See what people are searching on Google. Top treding every day, realtime.", - vi: "Xem mọi người đang tìm gì trên google. Thống kê từng ngày, thời gian thực.", - }, - popupScript: { - onClick: () => window.open("https://trends.google.com/"), - }, - }, + // s.test, + // s.ufs_statistic, + R.theresanaiforthat, + R.timeis, + R.googleTrending, s.similarWeb, s.similarWeb_bypassLimit, s.search_sharedAccount, - { - id: "recommend_archive", - icon: "https://archive.org/favicon.ico", - name: { - en: "Internet archive - Free library", - vi: "Internet archive - Thư viện miễn phí", - }, - description: { - en: "Non-profit library of millions of free books, movies, software, music, websites, and more.", - vi: "Thư viện với hàng triệu sách, báo, phim, phần mềm, nhạc, website, ... miễn phí", - img: "/scripts/internet_archive.png", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => window.open("https://archive.org/"), - }, - }, - { - id: "recommend_wappalyzer", - icon: "https://www.wappalyzer.com/favicon.ico", - name: { - en: "Wappalyzer - view website stacks", - vi: "Wappalyzer - Web dùng công nghệ gì?", - }, - description: { - en: "Technology that current website is using", - vi: "Xem những công nghệ/thư viện trang web đang dùng", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => window.open("https://www.wappalyzer.com/apps/"), - }, - }, + R.archive, + R.wappalyzer, s.whois, s.viewWebMetaInfo, - { - id: "recommend_search_musicTreding", - icon: "https://kworb.net/favicon.ico", - name: { - en: "Top global treding music?", - vi: "Bài nhạc top treding toàn cầu?", - }, - description: { - en: "The web to find all kinds of music-related data.", - vi: "Trang web thống kê top trending âm nhạc toàn cầu.", - }, - popupScript: { - onClick: () => - window.open("https://kworb.net/youtube/trending_music.html"), - }, - }, + R.search_musicTreding, s.search_paperWhere, s.search_hopamchuan, s.checkWebDie, s.downDetector, s.openWaybackUrl, s.archiveToday, - { - id: "recommend_search_userscript", - icon: "https://www.userscript.zone/favicon.ico", - name: { - en: "Search Userscripts", - vi: "Tìm Userscripts", - }, - description: { - en: "Search Userscripts on Usersript.zone", - vi: "Tìm Userscripts trên Usersript.zone", - }, - - popupScript: { - onClick: () => window.open("https://www.userscript.zone/"), - }, - }, + R.search_userscript, ], }, { ...CATEGORY.download, scripts: [ createTitle("--- All in one ---", "--- Tổng hợp ---"), - { - id: "recommend_cobalt", - icon: "https://cobalt.tools/favicon.ico", - name: { - en: "Cobalt - Media downloader", - vi: "Cobalt - Tải video/nhạc", - }, - description: { - en: "Support youtube, tiktok, instagram, twitter/x, bilibili, twitch, vimeo, soundcloud, dailymotion, pinterest, reddit, tumblr, ...", - vi: "Hỗ trợ youtube, tiktok, instagram, twitter/x, bilibili, twitch, vimeo, soundcloud, dailymotion, pinterest, reddit, tumblr, ...", - }, - badges: [BADGES.recommend, BADGES.new], - buttons: [ - { - icon: '', - name: { - vi: "Github", - en: "Github", - }, - onClick: () => window.open("https://github.com/imputnet/cobalt"), - }, - ], - popupScript: { - onClick: () => window.open("https://cobalt.tools/"), - }, - }, + R.cobalt, s.saveAllVideo, s.vuiz_getLink, s.savevideo_me, - { - id: "getLinkLuanxt_newtab", - icon: "https://luanxt.com/get-link-mp3-320-lossless-vip-zing/favicon.ico", - name: { - en: "Get audio/video (luanxt)", - vi: "Tải nhạc/video (luanxt)", - }, - description: { - en: "Using API from luanxt.com. Download Zing MP3, Zing Video Clip, Zing TV, NhacCuaTui, YouTube, SoundCloud, Nhac.vn, ChiaSeNhac.vn, Facebook Video, Keeng Audio, Keeng Video, Keeng Phim", - vi: "Sử dụng API của luanxt.com. Tải Zing MP3, Zing Video Clip, Zing TV, NhacCuaTui, YouTube, SoundCloud, Nhac.vn, ChiaSeNhac.vn, Facebook Video, Keeng Audio, Keeng Video, Keeng Phim", - }, - infoLink: "https://luanxt.com/get-link-mp3-320-lossless-vip-zing/", - badges: [BADGES.recommend], - popupScript: { - onClick: () => - window.open( - "https://luanxt.com/get-link-mp3-320-lossless-vip-zing/" - ), - }, - }, + R.luanxt, createTitle("--- Photos ---", "--- Ảnh ---"), s.twitter_downloadButton, s.getFavicon, - { - id: "recommend_picviewer_ce+", - icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////29vbKysoqKioiIiKysrKhoaGTk5N9fX3z8/Pv7+/r6+vk5OTb29vOzs6Ojo5UVFQzMzMZGRkREREMDAy4uLisrKylpaV4eHhkZGRPT08/Pz/IfxjQAAAAgklEQVQoz53RRw7DIBBAUb5pxr2m3/+ckfDImwyJlL9DDzQgDIUMRu1vWOxTBdeM+onApENF0qHjpkOk2VTwLVEF40Kbfj1wK8AVu2pQA1aBBYDHJ1wy9Cf4cXD5chzNAvsAnc8TjoLAhIzsBao9w1rlVTIvkOYMd9nm6xPi168t9AYkbANdajpjcwAAAABJRU5ErkJggg==", - name: { - en: "Picviewer CE+ download images", - vi: "Picviewer CE+ tải ảnh", - }, - description: { - en: "Powerful picture viewing tool online, which can popup/scale/rotate/batch save pictures automatically", - vi: "Công cụ mạnh mẽ để xem/tải ảnh hàng loạt, cho tất cả trang web", - img: "https://v2fy.com/asset/063_picviewer_ce/73130353-c4598e00-4031-11ea-810e-9498677a40d1.gif", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => - window.open("https://greasyfork.org/en/scripts/24204-picviewer-ce"), - }, - }, - { - id: "recommend_file_converter", - icon: "https://file-converter.io/favicon.ico", - name: { - en: "File-converter.io - change image type", - vi: "File-converter.io - chuyển đổi ảnh", - }, - description: { - en: "Powerful tool which allows you to convert and compress files using the context menu in windows explorer.", - vi: "Công cụ nén ảnh, đổi định dạng ảnh hàng loạt, trực tiếp bằng chuột phải.", - img: "https://file-converter.io/images/file-converter-usage.gif", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => window.open("https://file-converter.io/"), - }, - }, - { - id: "recommend_squoosh_app", - icon: "https://squoosh.app/c/icon-large-maskable-c2078ced.png", - name: { - en: "Squoosh.app - compress images", - vi: "Squoosh.app - nén ảnh", - }, - description: { - en: "Make images smaller using best-in-class codecs, right in the browser.", - vi: "Công cụ nén ảnh mạnh mẽ, giảm kích thước ngay trên trình duyệt", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => window.open("https://squoosh.app/"), - }, - }, + R.picviewer_ce, + R.file_converter, + R.squoosh_app, createTitle("--- Music ---", "--- Nhạc ---"), s.spotify_downloadButton, s.soundcloud_downloadMusic, @@ -282,42 +84,9 @@ const tabs = [ s.studocu_downs, s.scribd_downloadDocuments, s.tailieu_vn, - { - id: "recommend_docsdownloader", - icon: "https://docsdownloader.com/assets/img/android-icon-192x192.png", - name: { - en: "DocDownloader - Download document", - vi: "DocDownloader - Tải document", - }, - description: { - en: "Download document on Scribd, Everand, Slideshare, Issuu, Academia, Chegg, Researchgate, Coursehero, Studocu, Perlego, Yumpu, Tiendeo, Fliphtml5, Anyflip, Docsity, Passei direto, Udocz", - vi: "Tải document từ Scribd, Everand, Slideshare, Issuu, Academia, Chegg, Researchgate, Coursehero, Studocu, Perlego, Yumpu, Tiendeo, Fliphtml5, Anyflip, Docsity, Passei direto, Udocz", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => window.open("https://docdownloader.com/"), - }, - }, + R.docsdownloader, s.bookmark_exporter, - { - id: "recommend_BookmarkSidebar", - icon: "https://lh3.googleusercontent.com/4kT7DxtoPSmSLzTit1w2Vbx7b1L2zkASTrqGzEpBW-qs2EwmLYzBTyv0cvlGZo-rD-s732OIrUXX-C33RHPSFvOj=s0", - name: { - en: "Bookmark Sidebar", - vi: "Bookmark Sidebar", - }, - description: { - en: "Very good Bookmark manager, find your bookmarks faster.", - vi: "Trình quản lý bookmark ngon, tìm kiếm bookmark nhanh hơn bao giờ hết.", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => - window.open( - "https://chromewebstore.google.com/detail/thanh-d%E1%BA%A5u-trang/jdbnofccmhefkmjbkkdkfiicjkgofkdh" - ), - }, - }, + R.bookmarkSidebar, ], }, { @@ -334,25 +103,7 @@ const tabs = [ createTitle("--- Bulk Download ---", "--- Tải hàng loạt ---"), s.ggDrive_downloadAllVideosInFolder, createTitle("--- More ---", "--- Khác ---"), - { - id: "recommend_googleAdvanced", - icon: "https://www.google.com/favicon.ico", - name: { - en: "Google search advanced", - vi: "Google tìm kiếm nâng cao", - }, - description: { - en: "Search google with a lot of advanced features", - vi: "Tìm kiếm google với nhiều tuỳ chọn nâng cao", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => - window.open( - "https://www.google.com/advanced_search?hl=" + getLang() + "&fg=1" - ), - }, - }, + R.googleAdvanced, s.search_totalIndexedPages, s.search_googleSite, s.googleShortcuts, @@ -371,10 +122,6 @@ const tabs = [ s.fb_videoDownloader, s.fb_getAvatarFromUid, s.fb_exportSaved, - // createTitle("--- Bulk Download ---", "--- Tải hàng loạt ---"), - // s.fb_downloadAlbumMedia, - // s.fb_downloadWallMediaFromPosts, - // s.fb_getAllAlbumInformation, createTitle("--- Hot ---", "--- Nổi bật ---"), s.fb_autoLike, s.fb_revealDeletedMessages, @@ -395,7 +142,6 @@ const tabs = [ s.fb_getTokenFacebook, s.fb_getTokenMessage, s.fb_getTokenBussinessLocation, - // s.fb_getTokenBusinessStudio, s.fb_getTokenCampaigns, s.fb_getTokenFfb, createTitle("--- Get ID ---", "--- Lấy ID ---"), @@ -408,151 +154,15 @@ const tabs = [ s.fb_getAllUidFromFbSearch, s.fb_getAllUidOfGroupMembers, createTitle("--- Shortcut ---", "--- Phím tắt ---"), - { - id: "fb_openSaved", - icon: '', - name: { - en: "View your facebook saved", - vi: "Xem mục đã lưu trên facebook", - }, - description: { - en: "View saved contents on Facebook", - vi: "Xem nội dung bạn đã lưu trên Facebook", - }, - - popupScript: { - onClick: () => window.open("https://www.facebook.com/saved"), - }, - }, - { - id: "fb_openMemories", - icon: '', - name: { - en: "View your memories on facebook", - vi: "Xem kỷ niệm của bạn trên facebook", - }, - description: { - en: "View your memories on facebook", - vi: "Xem kỷ niệm (memories) của bạn trên facebook", - }, - popupScript: { - onClick: () => window.open("https://www.facebook.com/memories/"), - }, - }, - { - id: "fb_openAdsActivities", - icon: '', - name: { - en: "View your ads activities", - vi: "Xem các quảng cáo fb bạn đã xem", - }, - description: { - en: "View ads you have seen on facebook", - vi: "Xem các quảng cáo bạn đã xem trên facebook", - }, - popupScript: { - onClick: () => window.open("https://www.facebook.com/ads/activity"), - }, - }, - { - id: "fb_openAllActivities", - icon: '', - name: { - en: "Check your activities on Facebook", - vi: "Xem nhật ký hoạt động trên facebook", - }, - description: { - en: "Check all your activities on facebook", - vi: "Kiểm tra nhật ký hoạt động của bạn trên facebook", - }, - popupScript: { - onClick: () => window.open("https://www.facebook.com/me/allactivity"), - }, - }, - { - id: "fb_openVideoActivities", - icon: '', - name: { - en: "Video you watched on facebook", - vi: "Video bạn vừa xem trên facebook", - }, - description: { - en: "View all videos you watched on facebook", - vi: "Xem lại những video bạn đã xem trên facebook", - }, - badges: [BADGES.new], - popupScript: { - onClick: () => - window.open( - "https://www.facebook.com/100004848287494/allactivity?activity_history=false&category_key=VIDEOWATCH&manage_mode=false&should_load_landing_page=false" - ), - }, - }, - { - id: "fb_openPassEvents", - icon: '', - name: { - en: "Events joined on facebook", - vi: "Sự kiện đã tham gia trên facebook", - }, - description: { - en: "View pass events that you have joined on facebook.", - vi: "Xem tất cả sự kiện bạn từng tham gia trên facebook.", - }, - badges: [BADGES.new], - popupScript: { - onClick: () => window.open("https://www.facebook.com/events/past"), - }, - }, - { - id: "fb_openBirthdays", - icon: '', - name: { - en: "Facebook friend's birthdays", - vi: "Sinh nhật bạn bè facebook", - }, - description: { - en: "View your friend's birthdays each month on facebook", - vi: "Xem từng tháng có những sinh nhật nào của bạn bè trên facebook.", - }, - badges: [BADGES.new], - popupScript: { - onClick: () => - window.open("https://www.facebook.com/events/birthdays"), - }, - }, - { - id: "fb_openChangeLanguage", - icon: '', - name: { - en: "Change language facebook", - vi: "Đổi ngôn ngữ facebook", - }, - description: { - en: "Change display language on facebook", - vi: "Đổi ngôn ngữ hiển thị trên facebook", - }, - popupScript: { - onClick: () => - window.open("https://www.facebook.com/settings/?tab=language"), - }, - }, - { - id: "fb_openAccountHacked", - icon: '', - name: { - en: "Recover facebook account", - vi: "Khôi phục tài khoản facebook", - }, - description: { - en: "Your fb account has been hacked? Facebook can help you.", - vi: "Tài khoản fb của bạn bị hack? Facebook có thể giúp bạn.", - }, - badges: [BADGES.new], - popupScript: { - onClick: () => window.open("https://fb.com/hacked"), - }, - }, + R.fb_openSaved, + R.fb_openMemories, + R.fb_openAdsActivities, + R.fb_openAllActivities, + R.fb_openVideoActivities, + R.fb_openPassEvents, + R.fb_openBirthdays, + R.fb_openChangeLanguage, + R.fb_openAccountHacked, ], }, { @@ -577,27 +187,10 @@ const tabs = [ s.youtube_changeCountry, s.youtube_getVideoThumbnail, s.youtube_toggleLight, + s.pip_anything, s.pip_fullWebsite, s.pip_canvas, - { - id: "recommend_improve_youtube", - icon: "https://lh3.googleusercontent.com/WDytHNO8o0Ev6sWp_yLbya_SSS9kXZWGJIc-WJ3goInHJalzD02Aq5wVhExFlbzrzNsOxo-V1O_TgF-JLJNyTkvB=s0", - name: { - en: "Improve YouTube - 85+ features", - vi: "Improve YouTube - 85+ chức năng", - }, - description: { - en: "Make YouTube more beautiful, faster, and more useful!", - vi: "Làm cho YouTube gọn gàng+thông minh!", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => - window.open( - "https://chromewebstore.google.com/detail/improve-youtube-%F0%9F%8E%A7-for-yo/bnomihfieiccainjcjblhegjgglakjdd" - ), - }, - }, + R.improve_youtube, ], }, { @@ -643,185 +236,27 @@ const tabs = [ s.send_shareFiles, s.vuiz_createLogo, s.performanceAnalyzer, - { - id: "recommend_ItTools", - icon: "https://it-tools.tech/favicon-32x32.png", - name: { - en: "IT Tools - All for Developers", - vi: "IT Tools - Vì tương lai Developer", - }, - description: { - en: "Handy tools for developers (open source)", - vi: "Tổng hợp tools hữu ích cho IT (mã nguồn mở)", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => window.open("https://it-tools.tech/"), - }, - }, - // https://copyicon.com/generator/svg-chart - { - id: "recommend_copyicon", - icon: "https://copyicon.com/favicon.ico", - name: { - en: "CopyIcon - FREE emoji, icon, generator", - vi: "CopyIcon - emoji, icon, svg miễn phí", - }, - description: { - en: "285,000 free Icons, Emoji, SVG generator, and more...", - vi: "285,000 Icons, Emiji, trình tạo SVG, và hơn thế nữa...", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => - window.open("https://copyicon.com/generator/svg-chart"), - }, - }, - { - id: "recommend_beautifytools", - icon: "https://beautifytools.com/img/favicon.ico", - name: { en: "Beautify Tools", vi: "Beautify Tools" }, - description: { - en: `Handy tools for developers -
    -
  1. Beautifiers And Minifiers
  2. -
  3. CSS Preprocessors
  4. -
  5. Converters
  6. -
  7. String Utilities
  8. -
  9. SEO Tools
  10. -
  11. IP Tools
  12. -
  13. Code Validators
  14. -
  15. Cryptography
  16. -
  17. Code Editors
  18. -
`, - vi: `Tổng hợp tools hữu ích cho IT -
    -
  1. Beautifiers And Minifiers
  2. -
  3. CSS Preprocessors
  4. -
  5. Converters
  6. -
  7. String Utilities
  8. -
  9. SEO Tools
  10. -
  11. IP Tools
  12. -
  13. Code Validators
  14. -
  15. Cryptography
  16. -
  17. Code Editors
  18. -
`, - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => window.open("https://beautifytools.com/"), - }, - }, + R.itTools, + R.copyicon, + R.beautifytools, createTitle("--- Github ---", "--- Github ---"), s.github_goToAnyCommit, s.github_HTMLPreview, s.github_openRepoPages, s.githubdev, s.github1s, - { - id: "recommend_cloc", - icon: '', - name: { - en: "Cloc - count line of code", - vi: "Cloc - đếm số dòng code", - }, - description: { - en: "Count blank lines, comment lines, and physical lines of source code in many programming languages.", - vi: "Đếm dòng trống, comment, dòng code trong repo, hỗ trợ nhiều ngôn ngữ lập trình.", - img: "/scripts/recommend_cloc.png", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => - window.open("https://github.com/AlDanial/cloc?tab=readme-ov-file"), - }, - }, - { - id: "recommend_refined_github", - icon: "https://lh3.googleusercontent.com/4N2wipmBVx1qK0R0E0XdADE31-8IuMylOtO9AyFopOA9i3IQKoCC5L4nYFDy55xpxpk6qKusHuqXyKJqvw8jcJaiqg=s60", - name: { - en: "Refined GitHub ", - vi: "Refined GitHub", - }, - description: { - en: "Simplifies the GitHub interface and adds useful features", - vi: "Sửa giao diện github và thêm hàng tá chức năng hay", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => - window.open( - "https://chromewebstore.google.com/detail/refined-github/hlepfoohegkhhmjieoechaddaejaokhf" - ), - }, - }, + R.cloc, + R.refined_github, createTitle("--- Shopping ---", "--- Mua sắm ---"), s.shopee_topVariation, s.shopee_totalSpendMoney, s.shopee_totalSpendMoney_excel, s.tiki_totalSpendMoney, - { - id: "recommend_Beecost", - icon: "https://lh3.googleusercontent.com/QeCUs-fM4mwAmBVRS0VU8NrjJnDnbSsXoqUrCbd8ZbHou03FBPEQOYHAcdcL_rn7NMrUpWMcXoG2m_CrKtAhc-wLgLU=w128-h128-e365-rj-sc0x00ffffff", - name: { en: "Beecost", vi: "Beecost" }, - description: { - en: "Check deals/prices in ecommerce websites", - vi: "Kiểm tra giá/ưu đãi giả khi mua hàng online", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => window.open("https://beecost.vn/"), - }, - }, + R.beecost, createTitle("--- PDF ---", "--- PDF ---"), - { - id: "recommend_fastDoc", - icon: "https://fastdoc.vn/favicon.png", - name: { - en: "FastDoc - Convert PDF/Photo to Word/Excel", - vi: "FastDoc - Chuyển PDF/Ảnh sang Word/Excel", - }, - badges: [BADGES.recommend], - description: { - en: "Convert Photos & PDF to Excel, Word, Searchable PDF for free", - vi: "Chuyển đổi hình ảnh và pdf sang Excel, Word, Searchable PDF miễn phí", - }, - popupScript: { - onClick: () => window.open("https://fastdoc.vn/"), - }, - }, - { - id: "recommend_smartPDF", - icon: "https://smallpdf.com/favicon.ico", - name: { - en: "SmartPDF - Tools for PDF", - vi: "SmartPDF - Công cụ cho PDF", - }, - description: { - en: "Compress PDF, PDF Converter, PPT to PDF, PDF to PPT, JPG to PDF, PDF to JPG, Excel to PDF, PDF to Excel, Edit PDF, PDF Reader, Number Pages, Delete PDF Pages, Rotate PDF, Word to PDF, PDF to Word, Merge PDF, Split PDF, eSign PDF, Unlock PDF, Protect PDF, PDF Scanner", - vi: "Giảm dung lượng PDF, Chuyển đổi PDF, PPT sang PDF, PDF sang PPT, JPG sang PDF, PDF sang JPG, Excel sang PDF, PDF sang Excel, Chỉnh sửa PDF, Trình đọc PDF, Số trang, Xóa các trang PDF, Xoay PDF, Word sang PDF, PDF sang Word, Ghép PDF, Cắt PDF, Ký tên PDF, Mở khóa PDF, Bảo vệ PDF, Máy quét PDF", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => window.open("https://smallpdf.com/vi/cac-cong-cu-pdf"), - }, - }, - { - id: "recommend_pdfstuffs", - icon: "https://pdfstuff.com/themes/pdfstuff/img/favicons/apple-icon-57x57.png", - name: { - en: "PDF Stuffs - Tools for PDF", - vi: "PDF Stuffs - Công cụ PDF", - }, - description: { - en: "Free PDF converter online service: Merge PDF, Split PDF, Compress PDF, PDF to Word, PDF to PPT, PDF to Excel, Word to PDF, Excel to PDF, PPT to PDF, PDF to JPG, JPG to PDF, PDF to HTML, HTML to PDF, Unlock PDF, Protect PDF, Rotate PDF, Crop PDF, Delete pages, Add page numbers, Watermark PDF", - vi: "Công cụ chuyển đổi PDF online miễn phí: Ghép file PDF, Tách file PDF, Nén file PDF, PDF sang Word, PDF sang PPT, PDF sang Excel, Word sang PDF, Excel sang PDF, PPT sang PDF, PDF sang JPG, JPG sang PDF, PDF sang HTML, HTML sang PDF, Mở khóa PDF, Khóa file PDF, Xoay file PDF, Cắt file PDF, Xóa trang PDF, Đánh số trang PDF, Chèn watermark", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => window.open("https://pdfstuff.com/"), - }, - }, + R.fastDoc, + R.smartPDF, + R.pdfstuffs, ], }, { @@ -837,6 +272,7 @@ const tabs = [ s.scribd_bypassPreview, s.studyphim_unlimited, s.bypass_learnAnything, + s.guland_VIP, createTitle("--- Unlock function ---", "--- Mở khoá chức năng ---"), s.simpleAllowCopy, s.detect_zeroWidthCharacters, @@ -845,71 +281,9 @@ const tabs = [ s.viewCookies, s.removeCookies, createTitle("--- Other ---", "--- Khác ---"), - { - id: "recommend_chromeFlags", - icon: '', - name: { - en: "Make browser super fast", - vi: "Tăng tốc tối đa trình duyệt", - }, - description: { - en: "Some flags experiments that can make your browser super fast", - vi: "Các flags giúp trình duyệt của bạn chạy nhanh hơn thỏ", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => - window.open( - "https://www.androidauthority.com/chrome-flags-1009941/" - ), - }, - }, - { - id: "recommend_viewSavedWifiPass", - icon: '', - name: { - en: "View saved wifi passwords", - vi: "Xem mật khẩu wifi đã lưu", - }, - description: { - en: "PowerShell script to view saved wifi passwords on your computer", - vi: "Powershell script giúp xem mật khẩu wifi đã lưu trên máy tính", - }, - infoLink: - "https://www.facebook.com/groups/j2team.community/posts/2328915024107271/", - - popupScript: { - onClick: () => { - prompt( - `File danh sách mật khẩu Wifi sẽ lưu ở: - "C:\\WifiPasswords\\listWifiPasswords.txt" - có dạng: [Tên Wifi]:[Mật khẩu] - - Mở Powershell và chạy lệnh sau:`, - `irm https://tinyurl.com/GetListWifiPasswords | iex` - ); - }, - }, - }, - { - id: "recommend_leakCheck", - icon: "https://leakcheck.io/favicon.ico", - name: { - en: "Leak check - your password has been leaked?", - vi: "Leak check - lộ mật khẩu email?", - }, - description: { - en: "Check your password has been leaked on internet or not", - vi: "Kiểm tra xem mật khẩu email/username của bạn có bị phát tán trên mạng hay không", - }, - infoLink: - "https://www.facebook.com/groups/j2team.community/posts/2329878560677584/", - popupScript: { - onClick: () => { - window.open("https://okela.fun/"); - }, - }, - }, + R.chromeFlags, + R.viewSavedWifiPass, + R.leakCheck, ], }, { @@ -921,92 +295,12 @@ const tabs = [ s.showFPS, s.showFps_v2, s.toggle_passwordField, - { - id: "recommend_DarkReader", - icon: "https://lh3.googleusercontent.com/T66wTLk-gpBBGsMm0SDJJ3VaI8YM0Utr8NaGCSANmXOfb84K-9GmyXORLKoslfxtasKtQ4spDCdq_zlp_t3QQ6SI0A=w128-h128-e365-rj-sc0x00ffffff", - name: { en: "Dark reader", vi: "Dark reader" }, - description: { - en: "Darkmode for every website", - vi: "Chế độ tối cho mọi trang web", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => - window.open( - "https://chrome.google.com/webstore/detail/dark-reader/eimadpbcbfnmbkopoojfekhnkhdbieeh" - ), - }, - }, - { - id: "recommend_cssportal", - icon: "https://www.cssportal.com/favicon.ico", - name: { - en: "CSS Portal - Empowered your CSS skills", - vi: "CSS Portal - Nâng trình CSS", - }, - description: { - en: "Empowered your CSS skills with hundreds of CSS tools.", - vi: "Công cụ tự động giúp nâng trình CSS của bạn với hàng trăm chức năng.", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => - window.open( - "https://www.cssportal.com/css-animated-text-generator/" - ), - }, - }, - { - id: "recommend_cssloaders", - icon: "https://css-loaders.com/fav.png", - name: { - en: "CSS Loaders - 600+ css loader", - vi: "CSS Loaders - 600+ css loading", - }, - description: { - en: "The Biggest Collection of Loading Animations. Over 600+ CSS-only loaders made using a single element", - vi: "Hơn 600 animation loading css miễn phí", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => window.open("https://css-loaders.com/"), - }, - }, - { - id: "recommend_uiverse", - icon: "https://uiverse.io/favicon.ico", - name: { - en: "UIverse - Open-Source UI elements", - vi: "UIverse - Tổng hợp code UI xịn", - }, - description: { - en: "Open-Source UI elements for any project.", - vi: "Tổng hợp code UI mã nguồn mở cho mọi trang web.", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => window.open("https://uiverse.io/"), - }, - }, + R.darkReader, + R.cssportal, + R.cssloaders, + R.uiverse, createTitle("--- View ---", "--- Xem ---"), - { - id: "recommend_fontRendering", - icon: '', - name: { - en: "Font Rendering - better font display", - vi: "Font Rendering - font chữ dễ nhìn", - }, - description: { - en: "Improve browser displaying, font rewriting, smoothing, scaling, stroke, shadow, special style elements, custom monospaced, etc", - vi: "Cải thiện font chữ web, giúp lướt web dễ chịu hơn.", - }, - badges: [BADGES.recommend], - popupScript: { - onClick: () => { - window.open("https://greasyfork.org/scripts/416688"); - }, - }, - }, + R.fontRendering, s.whatFont, s.visualEvent, s.listAllImagesInWeb, @@ -1034,165 +328,19 @@ const tabs = [ const recommendTab = { ...CATEGORY.recommend, scripts: [ - { name: { en: "--- Same author ---", vi: "--- Cùng tác giả ---" } }, - { - id: "recommend_LOL2D", - icon: "https://hoangtran0410.github.io/LOL2D/favicon/apple-touch-icon.png", - name: { - en: "LOL2D - League of Legends 2D", - vi: "LOL2D - Liên minh huyền thoại 2D", - }, - description: { - en: "Play League of Legends right on your browser", - vi: "Chơi Liên minh huyền thoại ngay trên trình duyệt", - img: "https://raw.githubusercontent.com/HoangTran0410/LOL2D/main/assets/images/screenshots/Screenshot_4.jpg", - }, - popupScript: { - onClick: () => window.open("https://github.com/HoangTran0410/LOL2D"), - }, - }, - { - id: "recommend_RevealDeletedFBMessage", - icon: "https://github.com/HoangTran0410/RevealDeletedFBMessages/raw/master/icons/icon48.png", - name: { - en: "Reveal Deleted FB Message", - vi: "Xem tin nhắn FB bị gỡ", - }, - description: { - en: "Know what your friends have sent you", - vi: "Xem bạn bè đã gửi gì cho bạn", - }, - popupScript: { - onClick: () => - window.open( - "https://github.com/HoangTran0410/RevealDeletedFBMessages" - ), - }, - }, - { - id: "recommend_FBMediaDownloader", - icon: "https://www.facebook.com/favicon.ico", - name: { en: "FB Media Downloader", vi: "FB Media Downloader" }, - description: { - en: "Tool download media from facebook automatic", - vi: "Công cụ tải ảnh/video từ facebook tự động cực nhanh", - }, - popupScript: { - onClick: () => - window.open("https://github.com/HoangTran0410/FBMediaDownloader"), - }, - }, - // https://www.nirsoft.net/ - { name: { en: "--- Tools ---", vi: "--- Công cụ hay ---" } }, - { - id: "recommend_nirsoft", - icon: "https://www.nirsoft.net/favicon.ico", - name: { en: "Nirsoft", vi: "Nirsoft" }, - description: { - en: "A unique collection of small and useful freeware utilities", - vi: "Tổng hợp bộ công cụ nhanh, nhẹ, miễn phí dành cho windows", - }, - popupScript: { - onClick: () => window.open("https://www.nirsoft.net/"), - }, - }, - { name: { en: "--- Extensions ---", vi: "--- Extensions hay ---" } }, - { - id: "recommend_CRXViewer", - icon: "https://lh3.googleusercontent.com/fD5QA80tZj1up43xmnxnxiqKNEq7515-HNtLfjoZlz_I626zxXmjlhKaQPUme_evpCEnN5-U7VnG3VfOcnTPzv_i=w128-h128-e365-rj-sc0x00ffffff", - name: { en: "CRX Viewer", vi: "CRX Viewer" }, - description: { - en: "View/Download source code of any extension", - vi: "Xem/Tải source code của mọi extension", - }, - popupScript: { - onClick: () => - window.open( - "https://chrome.google.com/webstore/detail/chrome-extension-source-v/jifpbeccnghkjeaalbbjmodiffmgedin" - ), - }, - }, - { - id: "recommend_uBlockOrigin", - icon: "https://lh3.googleusercontent.com/rrgyVBVte7CfjjeTU-rCHDKba7vtq-yn3o8-10p5b6QOj_2VCDAO3VdggV5fUnugbG2eDGPPjoJ9rsiU_tUZBExgLGc=s60", - name: { en: "uBlock Origin", vi: "uBlock Origin" }, - description: { - en: "Block advertisements for all website", - vi: "Chặn quảng cáo cho mọi website", - }, - popupScript: { - onClick: () => - window.open( - "https://chromewebstore.google.com/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm" - ), - }, - }, - { - id: "recommend_GoogleTranslate", - icon: "https://lh3.googleusercontent.com/3ZU5aHnsnQUl9ySPrGBqe5LXz_z9DK05DEfk10tpKHv5cvG19elbOr0BdW_k8GjLMFDexT2QHlDwAmW62iLVdek--Q=w128-h128-e365-rj-sc0x00ffffff", - name: { en: "Google translate", vi: "Google dịch" }, - description: { - en: "Instant translation for all website", - vi: "Dịch nhanh, trực tiếp trong mọi website", - }, - popupScript: { - onClick: () => - window.open( - "https://chrome.google.com/webstore/detail/google-translate/aapbdbdomjkkjkaonfhkkikfgjllcleb" - ), - }, - }, - { - id: "recommend_NSFWFilter", - icon: "https://lh3.googleusercontent.com/M_2Q8eJAj1ejsRg30LuJs_Q94Jk7d-6ZbE5cyddULweH5LrfsVJtjK8zbpSjwA3G9oHwZeyHyrYrr971kqLwtNNP=w128-h128-e365-rj-sc0x00ffffff", - name: { - en: "NSFW Filter: Hide NSFW content", - vi: "NSFW Filter: Ẩn nội dung 18+", - }, - description: { - en: "Hide NSFW content from websites using this extension powered by AI", - vi: "Ẩn mọi nội dung 18+ trên website, sử dụng trí tuệ nhân tạo", - }, - popupScript: { - onClick: () => - window.open( - "https://chrome.google.com/webstore/detail/nsfw-filter/kmgagnlkckiamnenbpigfaljmanlbbhh" - ), - }, - }, - { - id: "recommend_Violentmonkey", - icon: "https://violentmonkey.github.io/favicon-32x32.png?v=e0d9ed50fb982761b0f7cdea8b093ae9", - name: { - en: "Violentmonkey", - vi: "Violentmonkey", - }, - description: { - en: "An open source userscript manager.", - vi: "Trình quản lý userscript tốt.", - }, - popupScript: { - onClick: () => window.open("https://violentmonkey.github.io/"), - }, - }, - { - id: "recommend_Extensity", - icon: "https://lh3.googleusercontent.com/mgOg2hnGuthlYj-MEUXedWn_s9QjTXBwusffIAhbIuHM8L3K2c5cq1xf7bCzbRE5f9E6RXaGLPNEuJEt4hP6sLDL=s60", - name: { - en: "Extensity", - vi: "Extensity", - }, - description: { - en: "Extension manager - Quickly enable/disable browser extensions", - vi: "Trình quản lý extension - Nhanh chóng tắt/mở extension của trình duyệt", - }, - popupScript: { - onClick: () => - window.open( - "https://chromewebstore.google.com/detail/extensity/jjmflmamggggndanpgfnpelongoepncg" - ), - }, - }, + createTitle("--- Same author ---", "--- Cùng tác giả ---"), + R.lol2d, + R.revealDeletedFBMessage, + R.FBMediaDownloader, + createTitle("--- Tools ---", "--- Công cụ hay ---"), + R.nirsoft, + createTitle("--- Extensions ---", "--- Extensions hay ---"), + R.CRXViewer, + R.uBlockOrigin, + R.GoogleTranslate, + R.NSFWFilter, + R.Violentmonkey, + R.Extensity, ], }; diff --git a/popup/themes/default.less b/popup/themes/default.less index 515e855f..32995b3c 100644 --- a/popup/themes/default.less +++ b/popup/themes/default.less @@ -577,6 +577,7 @@ option { display: flex; justify-content: center; align-items: center; + flex-direction: column; z-index: 5; -webkit-backdrop-filter: blur(.4rem); backdrop-filter: blur(.4rem); diff --git a/scripts/_allScripts.js b/scripts/@allScripts.js similarity index 78% rename from scripts/_allScripts.js rename to scripts/@allScripts.js index e119aa7c..1e7f3790 100644 --- a/scripts/_allScripts.js +++ b/scripts/@allScripts.js @@ -1,4 +1,4 @@ -import * as allScripts from "./_index.js"; +import * as allScripts from "./@index.js"; // inject id to all scripts Object.entries(allScripts).forEach(([variableName, script]) => { diff --git a/scripts/_index.js b/scripts/@index.js similarity index 96% rename from scripts/_index.js rename to scripts/@index.js index 492beea0..36e07c54 100644 --- a/scripts/_index.js +++ b/scripts/@index.js @@ -1,8 +1,7 @@ // https://stackoverflow.com/a/59002206 -export { default as _test } from "./_test.js"; +export { default as test } from "./test.js"; export { default as fb_toggleLight } from "./fb_toggleLight.js"; -export { default as fb_getTokenBusinessStudio } from "./fb_getTokenBusinessStudio.js"; export { default as fb_getTokenFacebook } from "./fb_getTokenFacebook.js"; export { default as fb_getUid } from "./fb_getUid.js"; export { default as fb_getPageId } from "./fb_getPageId.js"; @@ -13,7 +12,6 @@ export { default as fb_getUidFromUrl } from "./fb_getUidFromUrl.js"; export { default as fb_getAllUidFromFbSearch } from "./fb_getAllUidFromFbSearch.js"; export { default as fb_getAllUidOfGroupMembers } from "./fb_getAllUidOfGroupMembers.js"; export { default as fb_getAvatarFromUid } from "./fb_getAvatarFromUid.js"; -export { default as fb_downloadAlbumMedia } from "./fb_downloadAlbumMedia.js"; export { default as insta_getUserInfo } from "./insta_getUserInfo.js"; export { default as insta_getAllUserMedia } from "./insta_getAllUserMedia.js"; export { default as pictureInPicture } from "./pictureInPicture.js"; @@ -116,7 +114,6 @@ export { default as scribd_bypassPreview } from "./scribd_bypassPreview.js"; export { default as fb_searchGroupForOther } from "./fb_searchGroupForOther.js"; export { default as fb_searchPageForOther } from "./fb_searchPageForOther.js"; export { default as tailieu_vn } from "./tailieu_vn.js"; -export { default as fb_downloadWallMediaFromPosts } from "./fb_downloadWallMediaFromPosts.js"; export { default as fb_getAllAlbumInformation } from "./fb_getAllAlbumInformation.js"; export { default as textToSpeech } from "./textToSpeech.js"; export { default as shopee_totalSpendMoney_excel } from "./shopee_totalSpendMoney_excel.js"; @@ -140,7 +137,7 @@ export { default as magnify_image } from "./magnify_image.js"; export { default as auto_redirectLargestImageSrc } from "./auto_redirectLargestImageSrc.js"; export { default as textToQrCode } from "./textToQrCode.js"; export { default as insta_anonymousStoryViewer } from "./insta_anonymousStoryViewer.js"; -export { default as _ufs_statistic } from "./_ufs_statistic.js"; +export { default as ufs_statistic } from "./ufs_statistic.js"; export { default as pip_fullWebsite } from "./pip_fullWebsite.js"; export { default as similarWeb_bypassLimit } from "./similarWeb_bypassLimit.js"; export { default as pip_canvas } from "./pip_canvas.js"; @@ -172,3 +169,5 @@ export { default as youtube_getVideoThumbnail } from "./youtube_getVideoThumbnai export { default as youtube_getVideoCaption } from "./youtube_getVideoCaption.js"; 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"; diff --git a/scripts/auto_redirectLargestImageSrc.js b/scripts/auto_redirectLargestImageSrc.js index 32219ba4..4eb889f4 100644 --- a/scripts/auto_redirectLargestImageSrc.js +++ b/scripts/auto_redirectLargestImageSrc.js @@ -48,7 +48,7 @@ export default { UfsGlobal.DOM.onHrefChanged((oldHref, newHref) => check(newHref)); async function check(href) { - let url = await UfsGlobal.Utils.getLargestImageSrc(href, href); + let url = await getLargestImageSrc(href, href); if (url && url != href) { if ( confirm( @@ -63,3 +63,390 @@ export default { }, }, }; +const CACHED = {}; +export async function getLargestImageSrc(imgSrc, webUrl) { + if (/^data:/i.test(imgSrc)) { + return null; + } + + // bypass redirect + imgSrc = UfsGlobal.Utils.makeUrlValid(imgSrc); + let redirectedUrl = await UfsGlobal.Utils.getRedirectedUrl(imgSrc); + if (redirectedUrl) { + imgSrc = redirectedUrl; + } + + function try1() { + const url = new URL(imgSrc); + switch (url.hostname) { + // https://atlassiansuite.mservice.com.vn:8443/secure/useravatar?size=small&ownerId=JIRAUSER14656&avatarId=11605 + case "atlassiansuite.mservice.com.vn": + case "atlassiantool.mservice.com.vn": + if (url.href.includes("avatar")) { + if (url.searchParams.get("size")) { + url.searchParams.set("size", "256"); + } else { + url.searchParams.append("size", "256"); + } + } + if (url.href.includes("/thumbnail/")) { + return url.href.replace("/thumbnail/", "/attachments/"); + } + if (url.href.includes("/thumbnails/")) { + return url.href.replace("/thumbnails/", "/attachments/"); + } + return url.toString(); + } + return null; + } + + async function try2() { + if (!CACHED.largeImgSiteRules) { + let s = await import("./auto_redirectLargestImageSrc_rules.js"); + CACHED.largeImgSiteRules = s.default; + } + for (let rule of CACHED.largeImgSiteRules) { + if (rule.url && !testRegex(webUrl, rule.url)) continue; + if (rule.src && !testRegex(imgSrc, rule.src)) continue; + if (rule.exclude && testRegex(imgSrc, rule.exclude)) continue; + if (rule.r) { + let newSrc = replaceUsingRegex(imgSrc, rule.r, rule.s); + if (newSrc?.length) { + return newSrc; + } + } + } + return null; + } + + // https://greasyfork.org/en/scripts/2312-resize-image-on-open-image-in-new-tab + function try3() { + return new Promise((resolve) => { + let m = null; + //google + if ( + (m = imgSrc.match( + /^(https?:\/\/lh\d+\.googleusercontent\.com\/.+\/)([^\/]+)(\/[^\/]+(\.(jpg|jpeg|gif|png|bmp|webp))?)(?:\?.+)?$/i + )) + ) { + if (m[2] != "s0") { + resolve(m[1] + "s0" + m[3]); + } + } else if ( + (m = imgSrc.match( + /^(https?:\/\/lh\d+\.googleusercontent\.com\/.+=)(.+)(?:\?.+)?$/i + )) + ) { + if (m[2] != "s0") { + resolve(m[1] + "s0"); + } + } else if ( + (m = imgSrc.match( + /^(https?:\/\/\w+\.ggpht\.com\/.+\/)([^\/]+)(\/[^\/]+(\.(jpg|jpeg|gif|png|bmp|webp))?)(?:\?.+)?$/i + )) + ) { + if (m[2] != "s0") { + resolve(m[1] + "s0" + m[3]); + } + } + + //blogspot + else if ( + (m = imgSrc.match( + /^(https?:\/\/\w+\.bp\.blogspot\.com\/.+\/)([^\/]+)(\/[^\/]+(\.(jpg|jpeg|gif|png|bmp|webp))?)(?:\?.+)?$/i + )) + ) { + if (m[2] != "s0") { + resolve(m[1] + "s0" + m[3]); + } + } + + //tumblr + else if ( + (m = imgSrc.match( + /^(https?:\/\/\d+\.media\.tumblr\.com\/.*tumblr_\w+_)(\d+)(\.(jpg|jpeg|gif|png|bmp|webp))(?:\?.+)?$/i + )) + ) { + if (m[2] < 1280) { + let ajax = new XMLHttpRequest(); + ajax.onreadystatechange = function () { + if (ajax.status == 200) { + resolve(m[1] + "1280" + m[3]); + } + }; + ajax.open("HEAD", m[1] + "1280" + m[3], true); + ajax.send(); + } + } + + //twitter + else if ( + (m = imgSrc.match( + /^(https?:\/\/\w+\.twimg\.com\/media\/[^\/:]+)\.(jpg|jpeg|gif|png|bmp|webp)(:\w+)?$/i + )) + ) { + let format = m[2]; + if (m[2] == "jpeg") format = "jpg"; + resolve(m[1] + "?format=" + format + "&name=orig"); + } else if ( + (m = imgSrc.match(/^(https?:\/\/\w+\.twimg\.com\/.+)(\?.+)$/i)) + ) { + let url = new URL(webUrl); + let pars = url.searchParams; + if (!pars.format || !pars.name) return; + if (pars.name == "orig") return; + resolve(m[1] + "?format=" + pars.format + "&name=orig"); + } + + //Steam (Only user content) + else if ( + (m = imgSrc.match( + /^(https?:\/\/(images\.akamai\.steamusercontent\.com|steamuserimages-a\.akamaihd\.net)\/[^\?]+)\?.+$/i + )) + ) { + resolve(m[1]); + } + + //性浪微博 + else if ( + (m = imgSrc.match( + /^(https?:\/\/(?:(?:ww|wx|ws|tvax|tva)\d+|wxt|wt)\.sinaimg\.(?:cn|com)\/)([\w\.]+)(\/.+)(?:\?.+)?$/i + )) + ) { + if (m[2] != "large") { + resolve(m[1] + "large" + m[3]); + } + } + + //zhihu + else if ( + (m = imgSrc.match( + /^(https?:\/\/.+\.zhimg\.com\/)(?:\d+\/)?([\w\-]+_)(\w+)(\.(jpg|jpeg|gif|png|bmp|webp))(?:\?.+)?$/i + )) + ) { + if (m[3] != "r") { + resolve(m[1] + m[2] + "r" + m[4]); + } + } + + //pinimg + else if ( + (m = imgSrc.match(/^(https?:\/\/i\.pinimg\.com\/)(\w+)(\/.+)$/i)) + ) { + if (m[2] != "originals") { + resolve(m[1] + "originals" + m[3]); + } + } else if ( + (m = imgSrc.match( + /^(https?:\/\/s-media[\w-]+\.pinimg\.com\/)(\w+)(\/.+)$/i + )) + ) { + //need delete? + if (m[2] != "originals") { + resolve(m[1] + "originals" + m[3]); + } + } + + //bilibili + else if ( + (m = imgSrc.match( + /^(https?:\/\/\w+\.hdslb\.com\/.+\.(jpg|jpeg|gif|png|bmp|webp))(@|_).+$/i + )) + ) { + resolve(m[1]); + } + + //taobao(tmall) + else if ( + (m = imgSrc.match( + /^(https?:\/\/(?:.+?)\.alicdn\.com\/.+\.(jpg|jpeg|gif|png|bmp|webp))_.+$/i + )) + ) { + resolve(m[1]); + } + + //jd + else if ( + (m = imgSrc.match( + /^(https?:\/\/(?:img\d+)\.360buyimg\.com\/)((?:.+?)\/(?:.+?))(\/(?:.+?))(\!.+)?$/i + )) + ) { + if (m[2] != "sku/jfs") { + resolve(m[1] + "sku/jfs" + m[3]); + } + } + + // https://s01.riotpixels.net/data/2a/b2/2ab23684-6cec-41da-9bce-f72c5264353a.jpg.240p.jpg + else if ( + (m = imgSrc.match( + /^(https?:\/\/(?:.+?)\.riotpixels\.net\/.+\.(jpg|jpeg|gif|png|bmp|webp))\..+?$/i + )) + ) { + resolve(m[1]); + } + + // reddit NEED TEST + else if ( + (m = imgSrc.match( + /^https?:\/\/preview\.redd\.it\/(.+\.(jpg|jpeg|gif|png|bmp|webp))\?.+?$/i + )) + ) { + resolve("https://i.redd.it/" + m[1]); + } + + // akamaized.net/imagecache NEED TEST + else if ( + (m = imgSrc.match( + /^(https:\/\/.+\.akamaized\.net\/imagecache\/\d+\/\d+\/\d+\/\d+\/)(\d+)(\/.+)$/i + )) + ) { + if (m[2] < 1920) resolve(m[1] + "1920" + m[3]); + } + + // 微信公众号 by sbdx + else if ( + (m = imgSrc.match( + /^(https:\/\/mmbiz\.qpic\.cn\/mmbiz_jpg\/.+?\/)(\d+)(\?wx_fmt=jpeg)/i + )) + ) { + if (m[2] != 0) resolve(m[1] + "0" + m[3]); + } + + //百度贴吧(然而对于画质提升什么的并没有什么卵用...) + else if ( + (m = imgSrc.match( + /^https?:\/\/imgsrc\.baidu\.com\/forum\/pic\/item\/.+/i + )) + ) { + if ( + (m = imgSrc.match( + /^(https?):\/\/(?:imgsrc|imgsa|\w+\.hiphotos)\.(?:bdimg|baidu)\.com\/(?:forum|album)\/.+\/(\w+\.(?:jpg|jpeg|gif|png|bmp|webp))(?:\?.+)?$/i + )) + ) { + resolve(m[1] + "://imgsrc.baidu.com/forum/pic/item/" + m[2]); + } + //if( (m = imgSrc.match(/^(https?)(:\/\/(?:imgsrc|imgsa|\w+\.hiphotos|tiebapic)\.(?:bdimg|baidu)\.com\/)(?:forum|album)\/.+\/(\w+\.(?:jpg|jpeg|gif|png|bmp|webp))(?:\?.+)?$/i)) ){ + // resolve(m[1] + m[2] + "forum/pic/item/" + m[3]) + //} + } else { + resolve(null); + } + }); + } + + for (let fn of [try1, try2, try3]) { + try { + let res = await timeoutPromise(fn(), 5000); + if (res && res != imgSrc) { + if (!Array.isArray(res)) res = [res]; + if (res.length) { + let finalSrc = await findWorkingSrc(res, true); + if (finalSrc?.length) return finalSrc; + } + } + } catch (e) { + console.log("ERROR getLargestImageSrc: " + fn.name + " -> ", e); + } + } + + return null; +} +function findWorkingSrc(srcs, inOrder = true) { + return new Promise((resolve, reject) => { + if (!srcs || !Array.isArray(srcs) || srcs.length === 0) { + reject("srcs is falsy, not an array, or empty"); + } else { + const checkImage = (src) => + // prevent Error: Content Security Policy directive: "connect-src 'self' + isImageSrc(src).then((value) => { + if (inOrder) return value; + if (value) resolve(src); + return value; + }); + + const promises = srcs.map(checkImage); + Promise.all(promises).then((res) => { + let trueIndex = res.indexOf(true); + if (trueIndex > -1) { + resolve(srcs[trueIndex]); + } else { + reject("none of the URLs are valid images"); + } + }); + } + }); +} +async function isImageSrc(src) { + try { + const res = await UfsGlobal.Extension.fetchByPassOrigin(src, { + method: "HEAD", + }); + if (res?.ok) { + // const type = res.headers.get("content-type"); + const type = res.headers?.["content-type"]; + if (type?.startsWith?.("image/")) { + return true; + } + } + } catch (error) { + console.log("ERROR isImageSrc: " + src + " -> ", error); + } + return new Promise((resolve) => { + let img = new Image(); + img.src = src; + img.onload = () => resolve(true); + img.onerror = () => resolve(false); + }); +} + +function timeoutPromise(prom, time) { + return Promise.race([ + prom, + new Promise((_r, rej) => setTimeout(() => rej("time out " + time), time)), + ]); +} +function uniqueArray(array) { + return Array.from(new Set(array)); +} +function replaceUsingRegex(str, r, s) { + let results = []; + + if (!Array.isArray(r) && !Array.isArray(s)) { + if (r?.test?.(str)) { + results.push(str.replace(r, s)); + } + } else if (!Array.isArray(r) && Array.isArray(s)) { + if (r?.test?.(str)) { + for (const si of s) { + results.push(str.replace(r, si)); + } + } + } else if (Array.isArray(r) && !Array.isArray(s)) { + for (const ri of r) { + if (ri?.test?.(str)) { + results.push(str.replace(ri, s)); + } + } + } else if (Array.isArray(r) && Array.isArray(s)) { + for (let ri = 0; ri < r.length; ri++) { + let _r = r[ri]; + if (_r?.test?.(str)) { + let _s = Array.isArray(s[ri]) ? s[ri] : [s[ri]]; + for (const si of _s) { + results.push(str.replace(_r, si)); + } + } + } + } + + return uniqueArray(results); +} +function testRegex(str, regexs) { + if (!Array.isArray(regexs)) regexs = [regexs]; + for (let regex of regexs) { + if (regex?.test?.(str)) { + return true; + } + } + return false; +} diff --git a/scripts/background-scripts/background_script.js b/scripts/background-scripts/background_script.js index 5710e403..ff216fa4 100644 --- a/scripts/background-scripts/background_script.js +++ b/scripts/background-scripts/background_script.js @@ -1,5 +1,5 @@ import * as utils from "../helpers/utils.js"; -import allScripts from "../_allScripts.js"; +import allScripts from "../@allScripts.js"; import { UfsGlobal } from "../content-scripts/ufs_global.js"; // import "../content-scripts/ufs_global.js"; // https://stackoverflow.com/a/62806068/23648002 // importScripts() diff --git a/scripts/backup/auto-like-fb.js b/scripts/backup/auto-like-fb.js deleted file mode 100644 index e1277934..00000000 --- a/scripts/backup/auto-like-fb.js +++ /dev/null @@ -1,59 +0,0 @@ -javascript: (async function () { - function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - function focusTo(element) { - element.dispatchEvent( - new MouseEvent("pointerover", { - view: window, - bubbles: true, - cancelable: true, - }) - ); - } - - function scrollToBottom() { - window.scrollTo(0, document.body.scrollHeight, { - // behavior: "smooth", - }); - } - - const doneKey = "auto-like-done"; - const btns = []; - while (true) { - if (!btns.length) { - let curBtns = Array.from( - document.querySelectorAll("[aria-label='Bày tỏ cảm xúc']:not(li *)") - ); - let added = 0; - for (let btn of curBtns) { - if (btn.getAttribute(doneKey) === null) { - btns.push(btn); - btn.setAttribute(doneKey, true); - added++; - } - } - if (added === 0) break; - } - - for (let btn of btns) { - btn.scrollIntoView({ - block: "center", - // behavior: "smooth", - }); - btn.click(); - await sleep(500); - let loveBtn = document.querySelector("[aria-label='Yêu thích']"); - if (loveBtn) { - focusTo(loveBtn); - await sleep(500); - loveBtn.click(); - await sleep(500); - } - btns.splice(btns.indexOf(btn), 1); - } - scrollToBottom(); - await sleep(3000); - } - alert("xong"); -})(); diff --git a/scripts/backup/chongLuaDao_bg.js b/scripts/backup/chongLuaDao_bg.js deleted file mode 100644 index 1fc57d78..00000000 --- a/scripts/backup/chongLuaDao_bg.js +++ /dev/null @@ -1,246 +0,0 @@ -! function(e) { - var t = {}; - function n(i) { - if (t[i]) return t[i].exports; - var r = t[i] = { - i: i, - l: !1, - exports: {} - }; - return e[i].call(r.exports, r, r.exports, n), r.l = !0, r.exports - } - n.m = e, n.c = t, n.d = function(e, t, i) { - n.o(e, t) || Object.defineProperty(e, t, { - enumerable: !0, - get: i - }) - }, n.r = function(e) { - "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, { - value: "Module" - }), Object.defineProperty(e, "__esModule", { - value: !0 - }) - }, n.t = function(e, t) { - if (1 & t && (e = n(e)), 8 & t) return e; - if (4 & t && "object" == typeof e && e && e.__esModule) return e; - var i = Object.create(null); - if (n.r(i), Object.defineProperty(i, "default", { - enumerable: !0, - value: e - }), 2 & t && "string" != typeof e) - for (var r in e) n.d(i, r, function(t) { - return e[t] - }.bind(null, r)); - return i - }, n.n = function(e) { - var t = e && e.__esModule ? function() { - return e.default - } : function() { - return e - }; - return n.d(t, "a", t), t - }, n.o = function(e, t) { - return Object.prototype.hasOwnProperty.call(e, t) - }, n.p = "", n(n.s = 0) -}([function(e, t) { - window.isWhiteList = {}, window.isBlocked = {}, window.results = {}, window.isPhish = {}, window.legitimatePercents = {}; - let blackList = []; - const whiteList = []; - let r = !1; - const o = e => { - chrome.storage.local.get(["cache", "cacheTime"], t => { - if (t.cache && t.cacheTime) return e(t.cache); - (e => { - fetch("https://api.chongluadao.vn/classifier.json") - .then(e => e.json()) - .then(t => { - chrome.storage.local.set({ - cache: t, - cacheTime: Date.now() - }, () => e(t)) - }) - })(e) - }) - }, - s = (e, t, n) => { - if (window.isWhiteList[e] == n) return; - let i = 0, - r = 0, - s = 0; - for (const e in t) "1" == t[e] ? s++ : "0" == t[e] ? r++ : i++; - if (window.legitimatePercents[e] = i / (s + r + i) * 100, t.length) { - const n = [t.map(e => parseInt(e))]; - o((function(t) { - const i = (e => ({ - predict: t => { - let n = [e.estimators.map(e => (e => { - const t = t => { - let n = e; - for (; - "split" == n.type;) { - const e = n.threshold.split(" <= "); - n = t[e[0]] <= e[1] ? n.left : n.right - } - return n.value[0] - }; - return { - predict: e => e.map(e => t(e)), - predictOne: t - } - })(e) - .predict(t))]; - n = n[0].map((e, t) => n.map(e => e[t])); - const i = []; - for (const e in n) { - let t = 0, - r = 0; - for (const i in n[e]) t += n[e][i][1], r += n[e][i][0]; - i.push([t >= r, Math.max(t, r)]) - } - return i - } - }))(t); - window.isPhish[e] = i.predict(n)[0][0], window.isPhish[e] && window.legitimatePercents[e] > 60 && (window.isPhish[e] = !1), l(window.isPhish[e], window.legitimatePercents[e], e) - })) - } - }, - c = () => { - fetch("https://api.chongluadao.vn/v2/blacklist") - .then(e => e.json()) - .then(e => { - e.forEach(e => { - blackList.push(e.url) - }) - }) - .catch(() => {}), fetch("https://api.chongluadao.vn/v2/whitelist") - .then(e => e.json()) - .then(e => { - e.forEach(e => { - whiteList.push(e.url) - }) - }) - .catch(() => {}) - }, - redirectToBlocking = (e, t, n) => { - const i = { - site: e, - match: t, - title: e, - lenient: r, - favicon: "https://www.google.com/s2/favicons?domain=" + e - }; - window.isBlocked[n] = e, chrome.browserAction.setIcon({ - path: "../assets/cldvn_red.png", - tabId: n - }); - return { - redirectUrl: `${chrome.extension.getURL("blocking.html")}#${JSON.stringify(i)}` - } - }, - makeURL = e => { - try { - return new URL(e) - } catch (e) { - return - } - }, - l = (e, t, n) => { - if (chrome.browserAction.setTitle({ - title: `P:${e} per: ${t}` - }), e) return chrome.browserAction.setIcon({ - path: "../assets/cldvn_red.png", - tabId: n - }); - chrome.browserAction.setIcon({ - path: "../assets/cldvn128.png", - tabId: n - }) - }, - getHost = e => { - const t = e.match(/^https?:\/\/([^/?#]+)(?:[/?#]|$)/i); - return t && t[1] - }; - chrome.runtime.onStartup.addListener(c), chrome.runtime.onInstalled.addListener(() => { - c(), chrome.notifications.create({ - type: "basic", - iconUrl: chrome.extension.getURL("assets/logo.png"), - title: "Cài đặt thành công!", - message: "Khởi động lại trình duyệt của bạn để có thể bắt đầu sử dụng ChongLuaDao. Xin cảm ơn!" - }) - }), chrome.tabs.onActivated.addListener((e = null) => { - if (e && e.tabId) return l(window.isPhish[e.tabId], window.legitimatePercents[e.tabId], e.tabId); - chrome.tabs.query({ - active: !0, - currentWindow: !0 - }, ([e]) => { - l(window.isPhish[e.id], window.legitimatePercents[e.id], e.id) - }) - }), chrome.tabs.onUpdated.addListener((e, t, n) => { - "complete" == n.status && chrome.tabs.sendMessage(n.id, n) - }), chrome.runtime.onConnect.addListener(e => { - switch (e.name) { - case "REDIRECT_PORT_NAME": - e.onMessage.addListener(e => { - chrome.tabs.query({ - currentWindow: !0, - active: !0 - }, ([t]) => { - chrome.tabs.update(t.id, { - url: e.redirect - }) - }) - }); - break; - case "CLOSE_TAB_PORT_NAME": - e.onMessage.addListener(e => { - e.close_tab && chrome.tabs.query({ - currentWindow: !0, - active: !0 - }, ([e]) => { - chrome.tabs.remove(e.id) - }) - }); - break; - case "ML_PORT_NAME": - e.onMessage.addListener(e => { - const { - request: t - } = e; - void 0 !== t.input_block_list && (blackList = t.input_block_list, r = t.input_block_lenient), chrome.tabs.query({ - currentWindow: !0, - active: !0 - }, ([e]) => { - window.results[e.id] = t, s(e.id, t, e.url) - }) - }) - } - }), chrome.webRequest.onBeforeRequest.addListener(({ - url: url, - tabId: tabId, - initiator: initiator - }) => { - if (!url || 0 === url.indexOf("chrome://") || 0 === url.indexOf(chrome.extension.getURL("/"))) return; - if (!blackList || !blackList.length) return; - if (localStorage.getItem("whiteList")) return localStorage.removeItem("whiteList"); - const _blackList = blackList, - _url = makeURL(url), - psl_res = psl.parse(_url.host), - l = _url.href.replaceAll("/", ""); - for (let n = 0; n < _blackList.length; ++n) { - const black_url = makeURL(_blackList[n]); - if (!black_url) continue; - const firstPart = black_url.host.split(".")[0], - pathName = black_url.pathname; - if ("%2A" == firstPart) { - if (black_url.host.slice(4, black_url.host.length) == psl_res.domain) return redirectToBlocking(url, black_url.host, tabId) - } - if ("/*" == pathName && _url.host === black_url.host) return redirectToBlocking(url, black_url.host, tabId); - if (l && l == black_url.href.replaceAll("/", "")) return redirectToBlocking(url, black_url.host, tabId) - } - const hostName = getHost(initiator || url); - whiteList.find(e => e.includes(hostName)) && (window.isWhiteList[tabId] = hostName) - }, { - urls: ["*://*/*"], - types: ["main_frame", "sub_frame"] - }, ["blocking"]) -}]); \ No newline at end of file diff --git a/scripts/backup/fb-albums.min.js b/scripts/backup/fb-albums.min.js deleted file mode 100644 index afc07ea9..00000000 --- a/scripts/backup/fb-albums.min.js +++ /dev/null @@ -1,416 +0,0 @@ -javascript: (() => { - function _0x2e99(_0x1ba5b7, _0xdd8e24) { - const _0x52b858 = _0x56b6(); - return ( - (_0x2e99 = function (_0x1bc24e, _0x2376a4) { - _0x1bc24e = _0x1bc24e - (-0x1 * 0x131b + 0x14 * 0x58 + 0xd13); - let _0x293cc2 = _0x52b858[_0x1bc24e]; - return _0x293cc2; - }), - _0x2e99(_0x1ba5b7, _0xdd8e24) - ); - } - function _0x56b6() { - const _0x3aecb3 = [ - "revokeObjectURL", - "click", - "push", - "type", - "setRequestHeader", - "https://graph.facebook.com/v20.0/", - "POST", - "...", - "URL", - "concat", - "after", - "//www.facebook.com/v1.0/dialog/oauth/confirm", - "body", - "cursors", - "value", - "querySelector", - "exec", - "data", - "imgData", - "1207200zPKQJy", - "send", - "navigator", - "fb_dtsg", - "download", - "30ELnKbi", - "&after=", - "DTSGInitialData", - "Đang\x20tải\x20", - "status", - "from", - "paging", - "appendChild", - "cookie", - "count", - "473986yFsSiV", - "728720uPvVUd", - "863058mngagO", - "photos", - "open", - "3nLwfso", - "msSaveOrOpenBlob", - "Đang\x20tải\x20album\x20", - "href", - "Error:\x20", - "length", - "url", - "nextCursor", - "size", - "log", - "5exjSTG", - "/photos?fields=largest_image{source}&limit=100&access_token=", - "Failed\x20to\x20Get\x20Access\x20Token.", - "token", - "2024-07-15", - "https://www.facebook.com/dialog/oauth/business/cancel/?app_id=256002347743983&version=v19.0&logger_id=&user_scopes[0]=email&user_scopes[1]=read_insights&user_scopes[2]=read_page_mailboxes&user_scopes[3]=pages_show_list&redirect_uri=fbconnect%3A%2F%2Fsuccess&response_types[0]=token&response_types[1]=code&display=page&action=finish&return_scopes=false&return_format[0]=access_token&return_format[1]=code&tp=unspecified&sdk=&selected_business_id=&set_token_expires_in_60_days=false", - "Không\x20tìm\x20thấy\x20album\x20nào", - "append", - "largest_image", - "map", - "238512YXjwsZ", - "744723SlAuaS", - "readyState", - "access_token", - "23518tdVtxa", - "Nhập\x20user/page\x20id\x20muốn\x20tải:", - "json", - "Content-type", - "match", - "Không\x20tìm\x20thấy\x20uid\x20trong\x20cookie.\x20Bạn\x20đã\x20đăng\x20nhập\x20chưa?", - "onreadystatechange", - ]; - _0x56b6 = function () { - return _0x3aecb3; - }; - return _0x56b6(); - } - (function (_0x59c9d5, _0x9072f8) { - const _0x4bd475 = _0x2e99, - _0x2ebad9 = _0x59c9d5(); - while (!![]) { - try { - const _0x1742e3 = - parseInt(_0x4bd475(0xef)) / (-0x26e7 * -0x1 + 0xa9 * -0x7 + -0x2247) + - (parseInt(_0x4bd475(0x118)) / (0x1ed2 + 0x2 * 0x69c + 0xb02 * -0x4)) * - (parseInt(_0x4bd475(0x11d)) / - (-0x1524 + -0x5 * 0x613 + -0x2 * -0x19c3)) + - (-parseInt(_0x4bd475(0x119)) / (0xaf1 * -0x1 + 0x116b + -0x676)) * - (parseInt(_0x4bd475(0xe1)) / (-0x15d8 + 0x1241 + 0x39c)) + - parseInt(_0x4bd475(0x109)) / - (-0x19 * 0x106 + 0x1 * -0x23e1 + -0x21 * -0x1dd) + - parseInt(_0x4bd475(0x11a)) / (-0x169 + -0x1c11 + -0x7 * -0x437) + - parseInt(_0x4bd475(0xeb)) / (0x42 * 0x9 + -0x445 * 0x6 + 0x1754) + - (-parseInt(_0x4bd475(0xec)) / (0x3 * 0x935 + -0x1f1d + 0x387)) * - (parseInt(_0x4bd475(0x10e)) / - (0x78 * -0x3f + 0x1b3 * -0x16 + -0xd64 * -0x5)); - if (_0x1742e3 === _0x9072f8) break; - else _0x2ebad9["push"](_0x2ebad9["shift"]()); - } catch (_0x105d1a) { - _0x2ebad9["push"](_0x2ebad9["shift"]()); - } - } - })(_0x56b6, 0x5 * -0x25e5 + -0x4 * 0xbc56 + 0x67f1f), - (async () => { - const _0x50ae5a = _0x2e99; - function _0x5e3b54() { - return new Promise((_0x5b755f, _0x317202) => { - const _0x37d601 = _0x2e99; - let _0x50a8c8 = /(?<=c_user=)(\d+)/[_0x37d601(0x106)]( - document[_0x37d601(0x116)] - )?.[-0x1054 + 0x2 * -0x502 + 0x1a58]; - if (!_0x50a8c8) { - _0x317202(_0x37d601(0xf4)); - return; - } - let _0x42b070 = - require(_0x37d601(0x110))[_0x37d601(0xe4)] || - document[_0x37d601(0x105)]("[name=\x22fb_dtsg\x22]")["value"], - _0x17ad57 = new XMLHttpRequest(), - _0x2ba041 = _0x37d601(0x101), - _0x4b96fb = - "fb_dtsg=" + - _0x42b070 + - "&app_id=124024574287414&redirect_uri=fbconnect%3A%2F%2Fsuccess&display=page&access_token=&from_post=1&return_format=access_token&domain=&sso_device=ios&_CONFIRM=1&_user=" + - _0x50a8c8; - _0x17ad57["open"]( - _0x37d601(0xfc), - _0x2ba041, - !(-0x40d + 0x401 + 0xc) - ), - _0x17ad57[_0x37d601(0xfa)]( - _0x37d601(0xf2), - "application/x-www-form-urlencoded" - ), - (_0x17ad57[_0x37d601(0xf5)] = function () { - const _0xb5f30b = _0x37d601; - if ( - -0x3 * 0x937 + 0xab3 * -0x1 + 0x7ac * 0x5 == - _0x17ad57[_0xb5f30b(0xed)] && - -0x1541 + -0x67 * -0x61 + -0x10fe == _0x17ad57[_0xb5f30b(0x112)] - ) { - var _0xfe6416 = _0x17ad57["responseText"]["match"]( - /(?<=access_token=)(.*?)(?=\&)/ - ); - _0xfe6416 && _0xfe6416[0x4 * -0x646 + 0x1276 + 0x6a2] - ? _0x5b755f(_0xfe6416[-0x23e5 + 0x120d + 0x8 * 0x23b]) - : _0x317202(_0xb5f30b(0xe3)); - } - }), - (_0x17ad57["onerror"] = function () { - const _0x273597 = _0x37d601; - _0x317202(_0x273597(0xe3)); - }), - _0x17ad57[_0x37d601(0x10a)](_0x4b96fb); - }); - } - function _0x531c64() { - return new Promise((_0x19dad7, _0xf1ca1e) => { - const _0x5e979d = _0x2e99; - let _0x188a23 = /(?<=c_user=)(\d+)/[_0x5e979d(0x106)]( - document[_0x5e979d(0x116)] - )?.[0xc83 * -0x1 + -0x1 * 0xac9 + 0xba6 * 0x2]; - if (!_0x188a23) { - _0xf1ca1e(_0x5e979d(0xf4)); - return; - } - let _0x4cb9f2 = - require(_0x5e979d(0x110))[_0x5e979d(0xe4)] || - document[_0x5e979d(0x105)]("[name=\x22fb_dtsg\x22]")[ - _0x5e979d(0x104) - ], - _0x25b6e1 = new XMLHttpRequest(), - _0x4361e2 = new FormData(), - _0x560dc9 = _0x5e979d(0xe6); - _0x4361e2[_0x5e979d(0xe8)](_0x5e979d(0x10c), _0x4cb9f2), - _0x25b6e1[_0x5e979d(0x11c)]( - _0x5e979d(0xfc), - _0x560dc9, - !(-0x1a5c + 0x5 * 0x633 + -0x4a3 * 0x1) - ), - (_0x25b6e1["onreadystatechange"] = function () { - const _0x461d75 = _0x5e979d; - if ( - -0x9d0 + 0x18fb + -0xf27 == _0x25b6e1[_0x461d75(0xed)] && - -0x7f * 0x12 + -0x26dd + -0x3093 * -0x1 == - _0x25b6e1[_0x461d75(0x112)] - ) { - var _0xf5eaf1 = _0x25b6e1["responseText"][_0x461d75(0xf3)]( - /(?<=access_token=)(.*?)(?=\&)/ - ); - _0xf5eaf1 && _0xf5eaf1[-0x19bb + 0x11a8 + -0x27 * -0x35] - ? _0x19dad7(_0xf5eaf1[0x10c + 0x2c6 + -0x3d2]) - : _0xf1ca1e(_0x461d75(0xe3)); - } - }), - (_0x25b6e1["onerror"] = function () { - const _0x30cd9a = _0x5e979d; - _0xf1ca1e(_0x30cd9a(0xe3)); - }), - _0x25b6e1[_0x5e979d(0x10a)](_0x4361e2); - }); - } - function _0x58b659(_0x580cd7 = 0x32 * -0xa + -0x18e1 + 0x1ad6) { - return _0x580cd7 === 0x1373 * -0x1 + 0x1b7d + -0x79 * 0x11 - ? _0x531c64() - : _0x5e3b54(); - } - async function _0x11d7cb(_0x501108, _0x367a15) { - const _0x123cc0 = _0x2e99; - let _0x2da4dd = [], - _0x1dc208 = ""; - while (!![]) { - try { - const _0x41e58e = await fetch( - _0x123cc0(0xfb) + - _0x501108 + - "/albums?fields=type,name,count,link,created_time&limit=100&access_token=" + - _0x367a15 + - _0x123cc0(0x10f) + - _0x1dc208 - ), - _0x542834 = await _0x41e58e[_0x123cc0(0xf1)](); - if (_0x542834[_0x123cc0(0x107)]) - _0x2da4dd = _0x2da4dd[_0x123cc0(0xff)]( - _0x542834[_0x123cc0(0x107)] - ); - let _0x459968 = - _0x542834[_0x123cc0(0x114)]?.[_0x123cc0(0x103)]?.[ - _0x123cc0(0x100) - ]; - if (!_0x459968 || _0x459968 === _0x1dc208) break; - _0x1dc208 = _0x459968; - } catch (_0x478db3) { - break; - } - } - return _0x2da4dd; - } - async function _0xa2e09f({ - cursor: _0x4dcf80, - albumId: _0x2bd98a, - access_token: _0x2a011f, - }) { - const _0x4671fb = _0x2e99; - for (let _0x12b436 of [ - _0x4671fb(0xfb) + _0x2bd98a + _0x4671fb(0xe2) + _0x2a011f, - ]) { - let _0x17c470 = encodeURI(_0x12b436); - if (_0x4dcf80) _0x17c470 += _0x4671fb(0x10f) + _0x4dcf80; - const _0x41024f = await fetch(_0x17c470), - _0x5f0fd9 = await _0x41024f[_0x4671fb(0xf1)](), - _0x51c1e7 = _0x5f0fd9?.[_0x4671fb(0x11b)] || _0x5f0fd9; - if (!_0x5f0fd9 || !_0x51c1e7?.[_0x4671fb(0x107)]?.[_0x4671fb(0xdc)]) - continue; - return { - imgData: - _0x51c1e7?.[_0x4671fb(0x107)]?.[_0x4671fb(0xea)]((_0x475371) => ({ - id: _0x475371["id"], - url: _0x475371[_0x4671fb(0xe9)]["source"], - })) || [], - nextCursor: - _0x51c1e7?.[_0x4671fb(0x114)]?.[_0x4671fb(0x103)]?.[ - _0x4671fb(0x100) - ] || null, - }; - } - } - async function _0x774dd6({ - albumId: _0x42fc2d, - access_token: _0x3b190b, - pageLimit: pageLimit = Infinity, - fromPhotoId: fromPhotoId = null, - progress: progress = async () => {}, - }) { - const _0x2cb668 = _0x2e99; - let _0x6cae00 = -0x331 + -0x16bb + 0x19ed, - _0x5a200f = !![], - _0x4899e3 = fromPhotoId - ? Buffer[_0x2cb668(0x113)](fromPhotoId)["toString"]("base64") - : null, - _0x2da72c = new Set(), - _0x1f7d9a = []; - while (_0x5a200f && _0x6cae00 <= pageLimit) { - const _0x16e614 = {}; - (_0x16e614["albumId"] = _0x42fc2d), - (_0x16e614[_0x2cb668(0xee)] = _0x3b190b), - (_0x16e614["cursor"] = _0x4899e3); - const _0x5a67cd = await _0xa2e09f(_0x16e614); - if (!_0x5a67cd?.[_0x2cb668(0x108)]) break; - let _0x220591 = ![]; - for (let _0x36b261 of _0x5a67cd[_0x2cb668(0x108)]) { - !_0x2da72c["has"](_0x36b261["id"]) && - ((_0x220591 = !![]), - _0x2da72c["add"](_0x36b261["id"]), - _0x1f7d9a[_0x2cb668(0xf8)](_0x36b261[_0x2cb668(0xdd)])); - } - await progress(_0x2da72c[_0x2cb668(0xdf)]), - (_0x4899e3 = _0x5a67cd[_0x2cb668(0xde)]), - (_0x5a200f = _0x220591 && _0x4899e3 != null), - _0x6cae00++; - } - return _0x1f7d9a; - } - function _0x4ddfad(_0x1bcb3a, _0x4d04ac) { - const _0x516b29 = _0x2e99, - _0x3636c4 = {}; - _0x3636c4[_0x516b29(0xf9)] = "text/plain"; - let _0xf23e11 = new Blob([_0x1bcb3a], _0x3636c4); - if (window[_0x516b29(0x10b)][_0x516b29(0xd8)]) - window["navigator"]["msSaveOrOpenBlob"](_0xf23e11, _0x4d04ac); - else { - let _0x507bae = document["createElement"]("a"), - _0x3d1f45 = URL["createObjectURL"](_0xf23e11); - (_0x507bae[_0x516b29(0xda)] = _0x3d1f45), - (_0x507bae[_0x516b29(0x10d)] = _0x4d04ac), - document["body"][_0x516b29(0x115)](_0x507bae), - _0x507bae[_0x516b29(0xf7)](), - setTimeout(function () { - const _0x2d7b19 = _0x516b29; - document[_0x2d7b19(0x102)]["removeChild"](_0x507bae), - window[_0x2d7b19(0xfe)][_0x2d7b19(0xf6)](_0x3d1f45); - }, 0x4 * 0x2fe + -0xdb4 + 0x1bc); - } - } - if (Date["now"]() > new Date(_0x50ae5a(0xe5))["getTime"]()) { - alert( - "Script\x20đã\x20hết\x20hạn,\x20vui\x20lòng\x20liên\x20hệ\x20gia\x20hạn" - ); - return; - } - const _0xd61c0d = prompt(_0x50ae5a(0xf0)); - if (!_0xd61c0d) return; - alert( - "Thông\x20tin\x20sẽ\x20được\x20hiển\x20thị\x20trong\x20Console\x20(F12)" - ); - let _0x28de54 = [ - -0xca4 + -0x1 * 0x1a53 + 0x26f9, - 0x1feb + 0x1682 + 0x489 * -0xc, - ]; - for ( - let _0x2e7da0 = 0x1c4 * 0x6 + 0x43b * 0x1 + -0xed3; - _0x2e7da0 < _0x28de54[_0x50ae5a(0xdc)]; - _0x2e7da0++ - ) { - try { - let _0x613e5c = _0x28de54[_0x2e7da0], - _0x379726 = await _0x58b659(_0x613e5c); - console[_0x50ae5a(0xe0)](_0x379726); - const _0x4cdec9 = await _0x11d7cb(_0xd61c0d, _0x379726); - if (!_0x4cdec9?.[_0x50ae5a(0xdc)]) throw new Error(_0x50ae5a(0xe7)); - console[_0x50ae5a(0xe0)](_0x4cdec9); - const _0x390584 = []; - for ( - let _0x15f0e1 = 0x1 * -0x1f9c + -0x26d4 + 0x4670; - _0x15f0e1 < _0x4cdec9["length"]; - _0x15f0e1++ - ) { - let _0x2bcf10 = _0x4cdec9[_0x15f0e1]; - console[_0x50ae5a(0xe0)]( - _0x50ae5a(0xd9) + - _0x15f0e1 + - "/" + - _0x4cdec9[_0x50ae5a(0xdc)] + - _0x50ae5a(0xfd), - _0x2bcf10 - ); - const _0x58fb49 = await _0x774dd6({ - access_token: _0x379726, - albumId: _0x2bcf10["id"], - fromPhotoId: null, - progress: (_0x24f8c5) => { - const _0x376d3a = _0x50ae5a; - console[_0x376d3a(0xe0)]( - _0x376d3a(0x111) + - _0x24f8c5 + - "/" + - _0x2bcf10[_0x376d3a(0x117)] + - _0x376d3a(0xfd) - ); - }, - }); - console[_0x50ae5a(0xe0)](_0x58fb49); - if (_0x58fb49?.[_0x50ae5a(0xdc)]) _0x390584["push"](..._0x58fb49); - } - if (_0x390584?.[_0x50ae5a(0xdc)]) { - let _0x3a32e7 = Array["from"](new Set(_0x390584)); - console["log"](_0x3a32e7), - _0x4ddfad( - Array[_0x50ae5a(0x113)](_0x3a32e7)["join"]("\x0a"), - _0xd61c0d + ".txt" - ); - return; - } - } catch (_0x11cabf) { - let _0x13166b = - _0x2e7da0 === - _0x28de54[_0x50ae5a(0xdc)] - (0x2e * -0x60 + 0xb0 + 0x1 * 0x1091); - if (_0x13166b) alert(_0x50ae5a(0xdb) + _0x11cabf); - console[_0x50ae5a(0xe0)]("ERROR", _0x11cabf); - } - } - })(); -})(); diff --git a/scripts/backup/fb-down-all-album.js b/scripts/backup/fb-down-all-album.js deleted file mode 100644 index ad2ea9bc..00000000 --- a/scripts/backup/fb-down-all-album.js +++ /dev/null @@ -1,224 +0,0 @@ -javascript: (async () => { - function getToken2() { - return new Promise((resolve, reject) => { - let uid = /(?<=c_user=)(\d+)/.exec(document.cookie)?.[0]; - if (!uid) { - reject("Không tìm thấy uid trong cookie. Bạn đã đăng nhập chưa?"); - return; - } - let dtsg = - require("DTSGInitialData").token || - document.querySelector('[name="fb_dtsg"]').value, - xhr = new XMLHttpRequest(), - url = "//www.facebook.com/v1.0/dialog/oauth/confirm", - params = - "fb_dtsg=" + - dtsg + - "&app_id=124024574287414&redirect_uri=fbconnect%3A%2F%2Fsuccess&display=page&access_token=&from_post=1&return_format=access_token&domain=&sso_device=ios&_CONFIRM=1&_user=" + - uid; - xhr.open("POST", url, !0); - xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - xhr.onreadystatechange = function () { - if (4 == xhr.readyState && 200 == xhr.status) { - var a = xhr.responseText.match(/(?<=access_token=)(.*?)(?=\&)/); - if (a && a[0]) { - resolve(a[0]); - } else { - reject("Failed to Get Access Token."); - } - } - }; - xhr.onerror = function () { - reject("Failed to Get Access Token."); - }; - xhr.send(params); - }); - } - function getToken1() { - return new Promise((resolve, reject) => { - let uid = /(?<=c_user=)(\d+)/.exec(document.cookie)?.[0]; - if (!uid) { - reject("Không tìm thấy uid trong cookie. Bạn đã đăng nhập chưa?"); - return; - } - let dtsg = - require("DTSGInitialData").token || - document.querySelector('[name="fb_dtsg"]').value, - xhr = new XMLHttpRequest(), - data = new FormData(), - url = - "https://www.facebook.com/dialog/oauth/business/cancel/?app_id=256002347743983&version=v19.0&logger_id=&user_scopes[0]=email&user_scopes[1]=read_insights&user_scopes[2]=read_page_mailboxes&user_scopes[3]=pages_show_list&redirect_uri=fbconnect%3A%2F%2Fsuccess&response_types[0]=token&response_types[1]=code&display=page&action=finish&return_scopes=false&return_format[0]=access_token&return_format[1]=code&tp=unspecified&sdk=&selected_business_id=&set_token_expires_in_60_days=false"; - data.append("fb_dtsg", dtsg); - xhr.open("POST", url, !0); - xhr.onreadystatechange = function () { - if (4 == xhr.readyState && 200 == xhr.status) { - var a = xhr.responseText.match(/(?<=access_token=)(.*?)(?=\&)/); - if (a && a[0]) { - resolve(a[0]); - } else { - reject("Failed to Get Access Token."); - } - } - }; - xhr.onerror = function () { - reject("Failed to Get Access Token."); - }; - xhr.send(data); - }); - } - function getToken(option = 1) { - return option === 1 ? getToken1() : getToken2(); - } - async function getAllAlbums(id, access_token) { - let result = []; - let after = ""; - while (true) { - try { - const res = await fetch( - `https://graph.facebook.com/v20.0/${id}/albums?fields=type,name,count,link,created_time&limit=100&access_token=${access_token}&after=${after}` - ); - const json = await res.json(); - if (json.data) result = result.concat(json.data); - - let nextAfter = json.paging?.cursors?.after; - if (!nextAfter || nextAfter === after) break; - after = nextAfter; - } catch (e) { - break; - } - } - return result; - } - - async function fetchAlbumPhotosFromCursor({ cursor, albumId, access_token }) { - for (let _ of [ - `https://graph.facebook.com/v20.0/${albumId}/photos?fields=largest_image{source}&limit=100&access_token=${access_token}`, - ]) { - let url = encodeURI(_); - if (cursor) url += "&after=" + cursor; - const res = await fetch(url); - const json = await res.json(); - const root = json?.photos || json; - if (!json || !root?.data?.length) { - continue; - } - return { - imgData: - root?.data?.map((_) => ({ - id: _.id, - url: _.largest_image.source, - })) || [], - nextCursor: root?.paging?.cursors?.after || null, - }; - } - } - async function fetchAlbumPhotos({ - albumId, - access_token, - pageLimit = Infinity, - fromPhotoId = null, - progress = async () => {}, - }) { - let currentPage = 1; - let hasNextCursor = true; - let nextCursor = fromPhotoId - ? Buffer.from(fromPhotoId).toString("base64") - : null; - - let photIds = new Set(); - let allImgsData = []; - while (hasNextCursor && currentPage <= pageLimit) { - const data = await fetchAlbumPhotosFromCursor({ - albumId, - access_token, - cursor: nextCursor, - }); - if (!data?.imgData) break; - let added = false; - for (let img of data.imgData) { - if (!photIds.has(img.id)) { - added = true; - photIds.add(img.id); - allImgsData.push(img.url); - } - } - await progress(photIds.size); - - nextCursor = data.nextCursor; - hasNextCursor = added && nextCursor != null; - currentPage++; - } - return allImgsData; - } - - function downloadData(data, filename) { - let file = new Blob([data], { type: "text/plain" }); - if (window.navigator.msSaveOrOpenBlob) { - window.navigator.msSaveOrOpenBlob(file, filename); - } else { - let a = document.createElement("a"), - url = URL.createObjectURL(file); - a.href = url; - a.download = filename; - document.body.appendChild(a); - a.click(); - setTimeout(function () { - document.body.removeChild(a); - window.URL.revokeObjectURL(url); - }, 0); - } - } - - if (Date.now() > new Date("2024-07-15").getTime()) { - alert("Script đã hết hạn, vui lòng liên hệ gia hạn"); - return; - } - - const id = prompt("Nhập user/page id muốn tải:"); - if (!id) return; - - alert("Thông tin sẽ được hiển thị trong Console (F12)"); - - let options = [2, 1]; - for (let i = 0; i < options.length; i++) { - try { - let option = options[i]; - let access_token = await getToken(option); - console.log(access_token); - - const albums = await getAllAlbums(id, access_token); - if (!albums?.length) throw new Error("Không tìm thấy album nào"); - - console.log(albums); - - const allResult = []; - for (let i = 0; i < albums.length; i++) { - let album = albums[i]; - console.log(`Đang tải album ${i}/${albums.length}...`, album); - - const result = await fetchAlbumPhotos({ - access_token, - albumId: album.id, - fromPhotoId: null, - progress: (loaded) => { - console.log(`Đang tải ${loaded}/${album.count}...`); - }, - }); - - console.log(result); - if (result?.length) allResult.push(...result); - } - - if (allResult?.length) { - let uniqueResult = Array.from(new Set(allResult)); - console.log(uniqueResult); - downloadData(Array.from(uniqueResult).join("\n"), id + ".txt"); - return; - } - } catch (e) { - let outOfOption = i === options.length - 1; - if (outOfOption) alert("Error: " + e); - console.log("ERROR", e); - } - } -})(); diff --git a/scripts/backup/tool_get_link_all.js b/scripts/backup/tool_get_link_all.js deleted file mode 100644 index 08c0e60a..00000000 --- a/scripts/backup/tool_get_link_all.js +++ /dev/null @@ -1,156 +0,0 @@ -// https://openuserjs.org/scripts/we0019/Tool_Get_Link_Fshare,_4Share.vn,_Mp3zing.vn,_Nhaccuatui,_Tailieu.vn,_hoctot123.com/source - -// tool to decode: -// https://deobfuscate.relative.im/ -// + manual eval() + replace eval() with console.log() - -$(function () { - if (location.pathname.indexOf("/bai-hat/") === 0) { - var code = $("div.fb-like").data("href"); - $("#tabService").replaceWith( - '
Tải nhạc 128 kbps Tải nhạc 320 kbps Tải nhạc Lossless ' - ); - } - var linkbaihat = $("link[rel='canonical']").attr("href"); - if ( - $("#btnDownloadBox") === - 'Tải nhạc' - ) { - $("#btnDownloadBox").replaceWith( - ' Tải nhạc 128kbps Tải nhạc 320kbps Tải nhạc Lossless' - ); - } else { - $("#btnAddPlaylistNowPlaying").after( - ' Tải nhạc 128kbps Tải nhạc 320kbps Tải nhạc Lossless' - ); - } - if (location.pathname.indexOf("/file/") === 0) { - { - var link = window.location.href; - var link1 = link.replace("fshare.vn", "getlinkfshare.com"); - } - $(".policy_download").prepend( - ' ' - ); - } - if (window.location.hostname == "javhd.com") { - var link = window.location.href; - var linkget = "http://htstar.design/getjav.php?link=" + link; - $(".player-container").replaceWith( - '
' - ); - $(".downloads").replaceWith( - '' - ); - } - if (window.location.hostname == "tailieu.vn") { - var link = window.location.href; - var linkget = "https://linksvip.net/?link=" + link; - $(".btncam.marginright10").replaceWith( - 'Download qua linksvip' + - "" - ); - } - if (window.location.hostname == "hoctot123.com") { - var link = window.location.href; - var linkget = "http://bfeu.tk/getlinkhoctot123/xuly.php?url=" + link; - $("#login_pop").replaceWith( - 'DOWNLOAD TÀI LIỆU ĐÃ GET || Tool Get Link from J2TeaM' - ); - } - if (window.location.hostname == "4share.vn") { - var link = window.location.href; - var linkget = "https://linksvip.net/?link=" + link; - $("a[href='/payment/card/#FUNNY']").replaceWith( - '' - ); - } -}); -(function ($, window, document) { - "use strict"; - GM_addStyle( - ".bv-icon{background-image:url(http://static.mp3.zdn.vn/skins/zmp3-v4.1/images/icon.png)!important;background-repeat:no-repeat!important;background-position:-25px -2459px!important;}.bv-download{background-color:#70d4ff!important;border-color:#70d4ff!important;}.bv-download span{color:#fff!important;margin-left:8px!important;}.bv-disable,.bv-download:hover{background-color:#ff5e5e!important;border-color:#ff5e5e!important;}.bv-text{background-image:none!important;color:#fff!important;text-align:center!important;font-size:smaller!important;line-height:25px!important;}.bv-waiting{cursor:wait!important;background-color:#2980b9!important;border-color:#2980b9!important;}.bv-complete,.bv-complete:hover{background-color:#27ae60!important;border-color:#27ae60!important;}.bv-error,.bv-error:hover{background-color:#c0392b!important;border-color:#c0392b!important;}.bv-disable{cursor:not-allowed!important;opacity:0.4!important;}" - ); - function downloadSong(songId, progress, complete, error) { - GM_xmlhttpRequest({ - method: "GET", - url: linksVip(songId), - responseType: "blob", - onload: function (source) { - complete(source.response, source.finalUrl.split("filename=")[1]); - }, - onprogress: function (e) { - if (e.total) { - progress(Math.floor((e.loaded * 100) / e.total) + "%"); - } else { - progress(""); - } - }, - onerror: function (e) { - console.error(e); - error(); - }, - }); - } - window.URL = window.URL || window.webkitURL; - function multiDownloads() { - var $smallBtn = $(".fn-dlsong"); - if (!$smallBtn.length) return; - $smallBtn.replaceWith(function () { - var songId = $(this) - .closest("li, .item-song") - .attr("id") - .replace(/(chartitem)?song(rec)?/, ""); - return ( - '' - ); - }); - } - multiDownloads(); - $(document).on("ready", multiDownloads); - $(window).on("load", multiDownloads); -})(jQuery, window, document); diff --git a/scripts/backup/xem-tin-nhan-dau-tien b/scripts/backup/xem-tin-nhan-dau-tien deleted file mode 100644 index feb06d08..00000000 --- a/scripts/backup/xem-tin-nhan-dau-tien +++ /dev/null @@ -1,129 +0,0 @@ -#EndRegion -#include<_HttpRequest.au3> -#include -#include -#include -#include -#include -#include -#include -#include -#Region ### START Koda GUI section ### Form= -$Form1 = GUICreate("Tool Check Tin Nhắn FaceBook", 301, 156, 192, 124) -$Button1 = GUICtrlCreateButton("Check Nào !!", 72, 112, 139, 41) -GUICtrlSetFont(-1, 12, 800, 0, "MS Sans Serif") -$Label1 = GUICtrlCreateLabel("Tài Khoản : ", 0, 8, 86, 20) -GUICtrlSetFont(-1, 10, 800, 0, "MS Sans Serif") -$Input1 = GUICtrlCreateInput("", 112, 8, 185, 24) -GUICtrlSetFont(-1, 10, 800, 0, "MS Sans Serif") -$Label2 = GUICtrlCreateLabel("Mật Khẩu : ", 0, 40, 79, 20) -GUICtrlSetFont(-1, 10, 800, 0, "MS Sans Serif") -$Input2 = GUICtrlCreateInput("", 112, 40, 185, 24) -GUICtrlSetFont(-1, 10, 800, 0, "MS Sans Serif") -$Label3 = GUICtrlCreateLabel("Id Cần Check : ", 0, 72, 107, 20) -GUICtrlSetFont(-1, 10, 800, 0, "MS Sans Serif") -$Input3 = GUICtrlCreateInput("", 112, 72, 185, 24) -GUICtrlSetFont(-1, 10, 800, 0, "MS Sans Serif") -GUISetState(@SW_SHOW) -#EndRegion ### END Koda GUI section ### - -While 1 - $nMsg = GUIGetMsg() - Switch $nMsg - Case $GUI_EVENT_CLOSE - Exit - Case $Button1 - _main() - EndSwitch -WEnd - - -Func _main() - if GUICtrlRead($Input1) = '' Then - MsgBox(0,0,'Chưa Nhập Tài Khoản') - Return - elseif GUICtrlRead($Input2) = '' Then - MsgBox(0,0,'Chưa Nhập Mật Khẩu') - Return - elseif GUICtrlRead($Input3) = '' Then - MsgBox(0,0,'Chưa Nhập ID') - Return - Endif - MsgBox(0,0,'Quá Trình Check Đang Diễn Ra , Vui Lòng Bấm "OK" Và Đợi') -$tk = GUICtrlRead($Input1) -$mk = GUICtrlRead($Input2) -$id = GUICtrlRead($Input3) -_main1($tk,$mk,$id) -Endfunc - - -Func _main1($tk,$mk,$id) -$idd = $id -$a = fblogin($tk,$mk) -$cc = _HttpRequest(2, 'https://m.facebook.com/profile.php', "", $a, '', 'Connection: keep-alive') -$fb = StringRegExp($cc,'name="fb_dtsg" value="(.*?)"',3) -$idpr = StringRegExp($cc,'name="target" value="(.*?)"',3) -$i = 1 -if @error Then - MsgBox(0,0,'ERROR') - Exit -Endif -$post = '__user='&$idpr[0]&'&__a=1&__dyn=7AgNeS-aF398jgDxyIGzGomzEdpbGAdy8VdLFwgoqwWhE98nwgUaqG2yaBxebkwy6UnGi7VXDG4XzErDAxaFQ3ucDBxe6ohyUCqu58nUszaxbxm1tyrhVo9ohxGbwYUmC-UjDQ6ErKu7EgwLxqawDDgswVwjpUhCK6pESfyaBy8OcxO12zVolyoK7UyUhUKcyU4eQEx1DzXG&__af=jw&__req=u&__be=-1&__pc=EXP3%3Aholdout_pkg&__rev=3211951&fb_dtsg='&$fb[0]&'&jazoest=265817089856881797477105114586581701188790886782485370&queries=%7B%22o0%22%3A%7B%22doc_id%22%3A%221927845863895817%22%2C%22query_params%22%3A%7B%22id%22%3A%22'&$idd&'%22%2C%22message_limit%22%3A'&$i&'%2C%22load_messages%22%3A1%2C%22load_read_receipts%22%3Atrue%2C%22before%22%3A'&_TimeStampUNIX_ms()&'%7D%7D%7D' -$cc = _HttpRequest(2, 'https://www.facebook.com/api/graphqlbatch/', $post, $a, '', 'Connection: keep-alive') - -if StringInStr($cc,'"successful_results": 0') then - MsgBox(0,0,'Error') - Exit -Endif - -if Not StringInStr($cc,'messages_count') Then - MsgBox(0,0,'Error') - Exit -Endif - -$mess = _StringBetween($cc,'"messages_count":',',',3) - -if $mess[0] < 20 Then - MsgBox(0,0,'Chưa Nhắn Tới 20 Tin Nữa Check Làm Cm Gì 3') -Return -Endif - -$post1 = '__user='&$idpr[0]&'&__a=1&__dyn=7AgNeS-aF398jgDxyIGzGomzEdpbGAdy8VdLFwgoqwWhE98nwgUaqG2yaBxebkwy6UnGi7VXDG4XzErDAxaFQ3ucDBxe6ohyUCqu58nUszaxbxm1tyrhVo9ohxGbwYUmC-UjDQ6ErKu7EgwLxqawDDgswVwjpUhCK6pESfyaBy8OcxO12zVolyoK7UyUhUKcyU4eQEx1DzXG&__af=jw&__req=u&__be=-1&__pc=EXP3%3Aholdout_pkg&__rev=3211951&fb_dtsg='&$fb[0]&'&jazoest=265817089856881797477105114586581701188790886782485370&queries=%7B%22o0%22%3A%7B%22doc_id%22%3A%221927845863895817%22%2C%22query_params%22%3A%7B%22id%22%3A%22'&$idd&'%22%2C%22message_limit%22%3A'&$mess[0]&'%2C%22load_messages%22%3A1%2C%22load_read_receipts%22%3Atrue%2C%22before%22%3A'&_TimeStampUNIX_ms()&'%7D%7D%7D' -$cc1 = _HttpRequest(2, 'https://www.facebook.com/api/graphqlbatch/', $post1, $a, '', 'Connection: keep-alive') -$c1 = _StringBetween($cc1,'{"id":"','ge",',3) - -$path = @ScriptDir & '\'&$idd&'.html' - -For $a = 1 to 20 -$id = _StringBetween($c1[$a],'','",',3) -$tin1 = StringRegExp($c1[$a],'"snippet":"(.*?)"',3) -if IsArray($id) And IsArray($tin1) Then - FileWriteLine($path,$id[0] &"|"&_HTMLDecode($tin1[0]) & "
") -ENdif -Next -MsgBox(0,0,'OK') -ShellExecute(@ScriptDir & '\'&$idd&'.html') -Exit -ENdfunc - - -Func _TimeStampUNIX_ms($iYear = @YEAR, $iMonth = @MON, $iDay = @MDAY, $iHour = @HOUR, $iMin = @MIN, $iSec = @SEC) - Local $stSystemTime = DllStructCreate('ushort;ushort;ushort;ushort;ushort;ushort;ushort;ushort') - DllCall('kernel32.dll', 'none', 'GetSystemTime', 'ptr', DllStructGetPtr($stSystemTime)) - $iMSec = StringFormat('%03d', DllStructGetData($stSystemTime, 8)) - Local $nYear = $iYear - ($iMonth < 3 ? 1 : 0) - Return ((Int(Int($nYear / 100) / 4) - Int($nYear / 100) + $iDay + Int(365.25 * ($nYear + 4716)) + Int(30.6 * (($iMonth < 3 ? $iMonth + 12 : $iMonth) + 1)) - 2442110) * 86400 + ($iHour * 3600 + $iMin * 60 + $iSec)) * ($iMSec ? 1000 : 1) + $iMSec - EndFunc - - -Func fblogin($tk,$mk) -$kq1 = _HttpRequest(2, 'https://m.facebook.com/login.php?refsrc=https%3A%2F%2Fm.facebook.com%2F&lwv=101&login_try_number=1&ref=dbl', "", '', '', 'Connection: keep-alive') -$lsd = StringRegExp($kq1,'name="lsd" value="(.*?)"',3) -$mts = StringRegExp($kq1,'name="m_ts" value="(.*?)"',3) -$li = StringRegExp($kq1,'name="li" value="(.*?)"',3) -$post = 'lsd='&$lsd[0]&'&m_ts='&$mts[0]&'&li='&$li[0]&'&try_number=0&unrecognized_tries=0&email='&$tk&'&pass='&$mk&'&login=%C4%90%C4%83ng+nh%E1%BA%ADp' -$kq1 = _HttpRequest(1, 'https://m.facebook.com/login.php?refsrc=https%3A%2F%2Fm.facebook.com%2F&lwv=101&login_try_number=1&ref=dbl', $post, '', '', 'Connection: keep-alive') -$cookie = _GetCookie($kq1) -Return $cookie - -Endfunc \ No newline at end of file diff --git a/scripts/backup/yt-playlist-maker.js b/scripts/backup/yt-playlist-maker.js deleted file mode 100644 index 3cb1ec05..00000000 --- a/scripts/backup/yt-playlist-maker.js +++ /dev/null @@ -1,51 +0,0 @@ -// https://gist.github.com/J2TEAM/d8380866bb28dfb8a7f1ab72059658b0 - -/* Developed by Juno_okyo */ -(function (e, b) { - function f(a, b) { - var d = fetch, - e = - "sej=" + - encodeURIComponent( - JSON.stringify({ - playlistEditEndpoint: { - playlistId: a, - actions: [ - { - addedVideoId: b, - action: "ACTION_ADD_VIDEO", - }, - ], - }, - clickTrackingParams: "juno_okyo_j2team_community", - commandMetadata: { - webCommandMetadata: { - url: "/service_ajax", - sendPost: !0, - }, - }, - }) - ) + - "&session_token=", - f = encodeURIComponent; - var c = /"XSRF_TOKEN":"([^"]+)"/.exec(document.head.innerHTML); - c = null !== c ? c[1] : !1; - return d("https://www.youtube.com/service_ajax?name=playlistEditEndpoint", { - method: "POST", - credentials: "same-origin", - headers: { - "content-type": "application/x-www-form-urlencoded", - }, - body: e + f(c), - }); - } - - function d() { - var a = b.pop(); - a = new URL(a).searchParams.get("v"); - f(e, a).then(function (a) { - 0 < b.length && setTimeout(d, 500); - }); - } - d(); -})("YOUR_PLAYLIST_ID", "YOUR_VIDEO_URLS_IN_ARRAY"); diff --git a/scripts/bypass_learnAnything.js b/scripts/bypass_learnAnything.js index 31126eae..b75a5c27 100644 --- a/scripts/bypass_learnAnything.js +++ b/scripts/bypass_learnAnything.js @@ -1,5 +1,4 @@ import { UfsGlobal } from "./content-scripts/ufs_global.js"; -import { BADGES } from "./helpers/badge.js"; export default { icon: "https://learn-anything.xyz/favicon.ico", @@ -12,7 +11,6 @@ export default { vi: "Xem nội dung web learn-anything.xyz không cần đăng ký member", img: "/scripts/bypass_LearnAnything.png", }, - badges: [BADGES.new], changeLogs: { "2024-07-01": "init", }, diff --git a/scripts/content-scripts/backup.js b/scripts/content-scripts/backup.js new file mode 100644 index 00000000..974e27f6 --- /dev/null +++ b/scripts/content-scripts/backup.js @@ -0,0 +1,296 @@ +// Những hàm hay nhưng chưa được xài ở đâu sẽ được để ở đây +// Giúp giảm dung lượng cho UfsGlobal.js + +// https://stackoverflow.com/a/3381522 +function createFlashTitle(newMsg, howManyTimes) { + let original = document.title; + let timeout; + + function step() { + document.title = document.title == original ? newMsg : original; + if (--howManyTimes > 0) { + timeout = setTimeout(step, 1000); + } + } + howManyTimes = parseInt(howManyTimes); + if (isNaN(howManyTimes)) { + howManyTimes = 5; + } + clearTimeout(timeout); + step(); + + function cancel() { + clearTimeout(timeout); + document.title = original; + } + + return cancel; +} + +// click element as soon as possible +function clickASAP(selector, sleepTime = 0) { + const events = ["mouseover", "mousedown", "mouseup", "click"]; + const selectors = selector.split(", "); + if (selectors.length > 1) { + return selectors.forEach(clickASAP); + } + if (sleepTime > 0) { + return sleep(sleepTime * 1000).then(function () { + clickASAP(selector, 0); + }); + } + waitForElements(selector).then(function (element) { + element.removeAttribute("disabled"); + element.removeAttribute("target"); + events.forEach((eventName) => { + const eventObject = new MouseEvent(eventName, { bubbles: true }); + element.dispatchEvent(eventObject); + }); + }); +} + +function waitForElements(selector) { + return new Promise((resolve, reject) => { + onElementsAdded(selector, resolve, true); + }); +} +function onElementVisibilityChanged(el, option, callback) { + // use interaction observer + let observer = new IntersectionObserver( + (entries, observer) => { + entries.forEach((entry) => { + callback?.(entry.isIntersecting); + }); + }, + { + root: option?.root ?? null, + rootMargin: option?.rootMargin ?? "0px", + threshold: option?.threshold ?? 0, + } + ); + observer.observe(el); + return () => observer.unobserve(el); +} +/** + * Waits for a condition to be true within a specified timeout. + * + * @param {function} condition - The condition to be evaluated. + * @param {number} [timeout=1000] - The timeout in milliseconds. + * @return {Promise} A Promise that resolves when the condition is true. + */ +function waitFor(condition, timeout = 0) { + // return new Promise((resolve) => { + // let timer = setInterval(() => { + // if (condition()) { + // clearInterval(timer); + // resolve(); + // } + // }, timeout); + // }); + return new Promise(async (resolve) => { + while (true) { + if (condition()) { + resolve(); + break; + } + await new Promise((resolve) => setTimeout(resolve, timeout)); + } + }); +} +// https://stackoverflow.com/a/7616484/23648002 +function hashString(str) { + let hash = 0, + i, + chr; + if (str.length === 0) return hash; + for (i = 0; i < str.length; i++) { + chr = str.charCodeAt(i); + hash = (hash << 5) - hash + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; +} +/** + * Make a deep copy of an object + * @source - required (Object|Array) the object or array to be copied + */ +function deepClone(source) { + let result = {}; + + if (typeof source !== "object") { + return source; + } + if (Object.prototype.toString.call(source) === "[object Array]") { + result = []; + } + if (Object.prototype.toString.call(source) === "[object Null]") { + result = null; + } + for (let key in source) { + result[key] = + typeof source[key] === "object" ? deepClone(source[key]) : source[key]; + } + return result; +} +function domainCheck(domains) { + return new RegExp(domains).test(location.host); +} +function setCookie(name, value, days) { + if (days) { + let date = new Date(); + date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); + let expires = "; expires=" + date.toGMTString(); + } else { + let expires = ""; + document.cookie = name + "=" + value + expires + "; path=/"; + } +} +function delCookie(name) { + setCookie(name, "", -1); +} +function strBetween(s, front, back, trim = false) { + if (trim) { + s = s.replaceAll(" ", ""); + s = s.trim(); + s = s.replaceAll("\n", " "); + } + return s.slice( + s.indexOf(front) + front.length, + s.indexOf(back, s.indexOf(front) + front.length) + ); +} +async function json2xml(json) { + if (!window.json2xml) { + await import("../libs/xml-json/json2xml.js"); + } + return window.json2xml(json); +} +async function xml2json(xml) { + if (!window.xml2json) { + await import("../libs/xml-json/xml2json.js"); + } + return window.xml2json(xml); +} +function formatTimeToHHMMSSDD(date) { + const hours = ("0" + date.getHours()).slice(-2); + const minutes = ("0" + date.getMinutes()).slice(-2); + const seconds = ("0" + date.getSeconds()).slice(-2); + const milliseconds = ("00" + date.getMilliseconds()).slice(-3); + return `${hours}:${minutes}:${seconds}:${milliseconds}`; +} +// resolve relative URLs into canonical absolute URLs based on the current location. +function canonicalUri(src, location = window.location) { + if (src.charAt(0) == "#") return location.href + src; + if (src.charAt(0) == "?") + return location.href.replace(/^([^\?#]+).*/, "$1" + src); + let root_page = /^[^?#]*\//.exec(location.href)[0], + base_path = location.pathname.replace(/\/[^\/]+\.[^\/]+$/, "/"), + root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0], + absolute_regex = /^\w+\:\/\//; + src = src.replace("./", ""); + if (/^\/\/\/?/.test(src)) { + src = location.protocol + src; + } else if (!absolute_regex.test(src) && src.charAt(0) != "/") { + src = (base_path || "") + src; + } + return absolute_regex.test(src) + ? src + : (src.charAt(0) == "/" ? root_domain : root_page) + src; +} +// https://stackoverflow.com/a/7960435 +function isEmptyFunction(func) { + try { + let m = func.toString().match(/\{([\s\S]*)\}/m)[1]; + return !m.replace(/^\s*\/\/.*$/gm, ""); + } catch (e) { + console.log("Error isEmptyFunction", e); + return false; + } +} +// https://stackoverflow.com/a/9310752 +function escapeRegExp(text) { + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); +} +// https://stackoverflow.com/q/38849009 +function unescapeRegExp(text) { + return text.replace(/\\(.)/g, "$1"); +} +function encodeQueryString(obj) { + let str = []; + for (let p in obj) + if (obj.hasOwnProperty(p)) { + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + } + return str.join("&"); +} +async function zipAndDownloadBlobs( + blobList, + zipFileName, + progressCallback, + successCallback +) { + if (!window.JSZip) { + await import("../libs/jzip/index.js"); + } + const zip = new window.JSZip(); + console.log(zip); + + // Add each Blob to the ZIP archive with a unique name + blobList.forEach(({ blob, fileName }, index) => { + console.log(fileName); + zip.file(fileName, blob); + }); + + // Generate the ZIP content with progress callback + zip + .generateAsync({ type: "blob" }, (metadata) => { + if (progressCallback) { + // Calculate progress as a percentage + const progress = metadata.percent | 0; + progressCallback(progress); + } + }) + .then((content) => { + successCallback?.(); + saveAs(content, zipFileName); + }); +} +async function getBlobFromUrl(url) { + try { + const response = await fetch(url); + const blob = await response.blob(); + return blob; + } catch (error) { + alert("Error: " + error); + } +} +// https://stackoverflow.com/a/15832662/11898496 +// TODO: chrome.downloads: https://developer.chrome.com/docs/extensions/reference/downloads/#method-download +function downloadURL(url, name) { + let link = document.createElement("a"); + link.target = "_blank"; + link.download = name; + link.href = url; + link.click(); +} + +// store cache for all functions in UfsGlobal +const CACHED = { + mouse: { + x: 0, + y: 0, + }, +}; + +if (typeof window !== "undefined") { + window.UfsGlobal = UfsGlobal; + if (typeof window?.addEventListener === "function") { + window.addEventListener("mousemove", (e) => { + CACHED.mouse.x = e.clientX; + CACHED.mouse.y = e.clientY; + }); + } +} +function getMousePos() { + return CACHED.mouse; +} diff --git a/scripts/content-scripts/ufs_global.js b/scripts/content-scripts/ufs_global.js index 463e518e..07b65868 100644 --- a/scripts/content-scripts/ufs_global.js +++ b/scripts/content-scripts/ufs_global.js @@ -1,3 +1,5 @@ +// TODO: split this file into multiple helper files + export const UfsGlobal = { Extension: { sendToContentScript, @@ -10,30 +12,15 @@ export const UfsGlobal = { waitForTabToLoad, }, DOM: { - getMousePos, - isInCrossOriginFrame, - isInIframe, - checkElementvisibility, closest, - addLoadingAnimation, - addLoadingAnimationAtPos, - addEventListener, - enableDragAndZoom, - getContentClientRect, - dataURLToCanvas, notifyStack, notify, - onDoublePress, - createFlashTitle, - clickASAP, deleteElements, - waitForElements, onElementsAdded, onElementRemoved, onElementAttributeChanged, onElementTextContentChanged, onHrefChanged, - onElementVisibilityChanged, injectCssCode, injectCssFile, getTrustedPolicy, @@ -44,77 +31,30 @@ export const UfsGlobal = { injectScriptSrcAsync, isElementInViewport, getOverlapScore, - makeUrlValid, getWatchingVideoSrc, }, Utils: { - waitFor, - hashString, - lerp, - getNumberFormatter, - deepClone, + getRedirectedUrl, + sanitizeName, debounce, - throttle, - domainCheck, + makeUrlValid, + getNumberFormatter, sleep, - setCookie, - delCookie, - strBetween, - json2xml, - xml2json, - getLargestSrcset, - svgBase64ToUrl, - svgToBlobUrl, - svgToBase64, - getResolutionCategory, saveAs, - isEmoji, - formatTimeToHHMMSSDD, - timeoutPromise, - getRedirectedUrl, - getLargestImageSrc, - isImageSrc, - findWorkingSrc, - canonicalUri, formatSize, promiseAllStepN, - parseJwt, copyToClipboard, - isEmptyFunction, - escapeRegExp, - unescapeRegExp, - encodeQueryString, moneyFormat, - zipAndDownloadBlobs, - getBlobFromUrl, - getBlobFromUrlWithProgress, + getOriginalWindowFunction, + chooseFolderToDownload, + downloadToFolder, downloadBlobUrl, downloadBlob, - downloadURL, downloadData, }, }; -// store cache for all functions in UfsGlobal -const CACHED = { - mouse: { - x: 0, - y: 0, - }, -}; - -if (typeof window !== "undefined") { - window.UfsGlobal = UfsGlobal; - if (typeof window?.addEventListener === "function") { - window.addEventListener("mousemove", (e) => { - CACHED.mouse.x = e.clientX; - CACHED.mouse.y = e.clientY; - }); - } -} -function getMousePos() { - return CACHED.mouse; -} +if (typeof window !== "undefined") window.UfsGlobal = UfsGlobal; // #region Extension @@ -199,26 +139,6 @@ function download(options) { // #endregion // #region DOM -function isInCrossOriginFrame() { - let result = true; - try { - if (window.top.localStorage || window.top.location.href) { - result = false; - } - } catch (e) { - result = true; - } - return result; -} -function isInIframe() { - return window !== window.top; -} -function checkElementvisibility(elem) { - if (!elem.offsetHeight && !elem.offsetWidth) { - return false; - } - return !(getComputedStyle(elem).visibility === "hidden"); -} function closest(element, selector) { let el = element; while (el !== null) { @@ -231,254 +151,6 @@ function closest(element, selector) { } return el; } -function addLoadingAnimationAtPos( - x, - y, - size = 40, - containerStyle = "", - loadingStyle = "" -) { - let ele = document.createElement("div"); - ele.style.cssText = ` - position: fixed; - left: ${x - size / 2}px; - top: ${y - size / 2}px; - width: ${size}px; - height: ${size}px; - z-index: 2147483647; - pointer-events: none; - user-select: none; - ${containerStyle} - `; - addLoadingAnimation(ele, size, loadingStyle); - document.body.appendChild(ele); - return () => ele.remove(); -} -function addLoadingAnimation( - element, - size = Math.min(element?.clientWidth, element?.clientHeight) || 0, - customStyle = "" -) { - let id = Math.random().toString(36).substr(2, 9); - element.classList.add("ufs-loading-" + id); - - let borderSize = 4; - - // inject css code - let style = document.createElement("style"); - style.id = "ufs-loading-style-" + id; - style.textContent = ` - .ufs-loading-${id}::after { - content: ""; - display: block; - position: absolute; - top: 50%; - left: 50%; - width: ${size}px; - height: ${size}px; - margin-top: -${size / 2}px; - margin-left: -${size / 2}px; - border-radius: 50%; - border: ${borderSize}px solid #555 !important; - border-top-color: #eee !important; - animation: ufs-spin 1s ease-in-out infinite; - box-sizing: border-box !important; - ${customStyle} - } - @keyframes ufs-spin { - to { - transform: rotate(360deg); - } - } - `; - (document.head || document.documentElement).appendChild(style); - - return () => { - if (element) element.classList.remove("ufs-loading-" + id); - }; -} -function addEventListener(target, event, callback, options) { - target.addEventListener(event, callback, options); - return () => target.removeEventListener(event, callback, options); -} -function enableDragAndZoom(element, container, onUpdateCallback) { - // set style - const className = "ufs-drag-and-zoom"; - element.classList.add(className); - - let style = document.createElement("style"); - style.textContent = ` - .${className} { - cursor: grab; - position: relative !important; - -webkit-user-select: none !important; - -moz-user-select: none !important; - -ms-user-select: none !important; - user-select: none !important; - -khtml-user-select: none; - max-width: unset !important; - max-height: unset !important; - min-width: unset !important; - min-height: unset !important; - -webkit-user-drag: none !important; - }`; - (container || element).appendChild(style); - - // config - const lerpSpeed = 0.3; - const last = { x: 0, y: 0 }; - const mouse = { x: 0, y: 0 }; - const animTarget = { - left: parseFloat(element.style.left), - top: parseFloat(element.style.top), - width: parseFloat(element.style.width), - height: parseFloat(element.style.height), - }; - - let run = true; - function animate() { - let updated = false; - let updatedValue = {}; - for (let prop in animTarget) { - const currentValue = parseFloat(element.style[prop]); - const targetValue = animTarget[prop]; - let del = Math.abs(targetValue - currentValue); - - if (del > 0.1) { - const newValue = - del < 1 ? targetValue : lerp(currentValue, targetValue, lerpSpeed); - element.style[prop] = newValue + "px"; - updatedValue[prop] = newValue; - updated = true; - } - } - if (updated) onUpdateCallback?.(updatedValue); - if (run) requestAnimationFrame(animate); - } - - animate(); - - // Mouse down event listener - let dragging = false; - let _down = addEventListener(container || element, "mousedown", function (e) { - e.preventDefault(); - dragging = true; - last.x = e.clientX; - last.y = e.clientY; - element.style.cursor = "grabbing"; - }); - - // Mouse move event listener - let _move = addEventListener(document, "mousemove", function (e) { - mouse.x = e.clientX; - mouse.y = e.clientY; - if (dragging) { - let delX = e.clientX - last.x; - let delY = e.clientY - last.y; - - animTarget.left += delX; - animTarget.top += delY; - - last.x = e.clientX; - last.y = e.clientY; - } - }); - - // Mouse up event listener - let _up = addEventListener(document, "mouseup", function () { - dragging = false; - element.style.cursor = "grab"; - }); - - // Mouse leave event listener - let _leave = addEventListener(document, "mouseleave", function () { - dragging = false; - element.style.cursor = "grab"; - }); - - // Mouse wheel event listener for zooming - let _wheel = addEventListener(container || element, "wheel", function (e) { - e.preventDefault(); - - const curScale = parseFloat(element.style.width) / element.width; - const delta = -e.wheelDeltaY || -e.wheelDelta; - const factor = Math.abs((0.3 * delta) / 120); - const newScale = - delta > 0 ? curScale * (1 - factor) : curScale * (1 + factor); - - const newW = element.width * newScale; - const newH = element.height * newScale; - - if (newW < 10 || newH < 10) { - return; - } - - const left = parseFloat(element.style.left); - const top = parseFloat(element.style.top); - const offsetX = mouse.x - left; - const offsetY = mouse.y - top; - const newLeft = left - (newW - element.width) * (offsetX / element.width); - const newTop = top - (newH - element.height) * (offsetY / element.height); - - animTarget.left = newLeft; - animTarget.top = newTop; - animTarget.width = newW; - animTarget.height = newH; - }); - - let listeners = [_down, _move, _up, _leave, _wheel]; - - return { - animateTo: (x, y, w, h) => { - animTarget.left = x; - animTarget.top = y; - animTarget.width = w; - animTarget.height = h; - }, - destroy: () => { - run = false; - style.remove(); - element.classList.remove(className); - listeners.forEach((l) => l?.()); - }, - }; -} -// prettier-ignore -function getContentClientRect(target, win = window) { - let rect = target.getBoundingClientRect(); - let compStyle = win.getComputedStyle(target); - let pFloat = parseFloat; - let top = rect.top + pFloat(compStyle.paddingTop) + pFloat(compStyle.borderTopWidth); - let right = rect.right - pFloat(compStyle.paddingRight) - pFloat(compStyle.borderRightWidth); - let bottom = rect.bottom - pFloat(compStyle.paddingBottom) - pFloat(compStyle.borderBottomWidth); - let left = rect.left + pFloat(compStyle.paddingLeft) + pFloat(compStyle.borderLeftWidth); - return { - top : top, - right : right, - bottom : bottom, - left : left, - width : right-left, - height : bottom-top, - }; -} -function dataURLToCanvas(dataurl, cb) { - if (!dataurl) return cb(null); - let canvas = document.createElement("canvas"); - let ctx = canvas.getContext("2d"); - let img = new Image(); - img.setAttribute("crossOrigin", "anonymous"); - img.onload = function () { - canvas.width = img.width; - canvas.height = img.height; - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(img, 0, 0); - cb(canvas); - }; - img.onerror = function () { - cb(null); - }; - img.src = dataurl; -} // TODO: finish this function notifyStack(msg) { let id = "ufs_notify_stack"; @@ -601,81 +273,6 @@ function notify({ }, }; } -function onDoublePress(key, callback, timeout = 500) { - let timer = null; - let clickCount = 0; - - const keyup = (event) => { - if (event.key !== key) { - clickCount = 0; - return; - } - - clickCount++; - if (clickCount === 2) { - callback?.(); - clickCount = 0; - return; - } - - clearTimeout(timer); - timer = setTimeout(() => { - clickCount = 0; - }, timeout); - }; - - document.addEventListener("keyup", keyup); - - return () => { - clearTimeout(timer); - document.removeEventListener("keyup", keyup); - }; -} // https://stackoverflow.com/a/3381522 -function createFlashTitle(newMsg, howManyTimes) { - let original = document.title; - let timeout; - - function step() { - document.title = document.title == original ? newMsg : original; - if (--howManyTimes > 0) { - timeout = setTimeout(step, 1000); - } - } - howManyTimes = parseInt(howManyTimes); - if (isNaN(howManyTimes)) { - howManyTimes = 5; - } - clearTimeout(timeout); - step(); - - function cancel() { - clearTimeout(timeout); - document.title = original; - } - - return cancel; -} -// click element as soon as possible -function clickASAP(selector, sleepTime = 0) { - const events = ["mouseover", "mousedown", "mouseup", "click"]; - const selectors = selector.split(", "); - if (selectors.length > 1) { - return selectors.forEach(clickASAP); - } - if (sleepTime > 0) { - return sleep(sleepTime * 1000).then(function () { - clickASAP(selector, 0); - }); - } - waitForElements(selector).then(function (element) { - element.removeAttribute("disabled"); - element.removeAttribute("target"); - events.forEach((eventName) => { - const eventObject = new MouseEvent(eventName, { bubbles: true }); - element.dispatchEvent(eventObject); - }); - }); -} function deleteElements(selector, once) { onElementsAdded( selector, @@ -688,11 +285,6 @@ function deleteElements(selector, once) { once ); } -function waitForElements(selector) { - return new Promise((resolve, reject) => { - onElementsAdded(selector, resolve, true); - }); -} // https://stackoverflow.com/a/46428962 function onHrefChanged(callback, once) { let oldHref = document.location.href; @@ -707,24 +299,6 @@ function onHrefChanged(callback, once) { }); observer.observe(body, { childList: true, subtree: true }); } -function onElementVisibilityChanged(el, option, callback) { - // use interaction observer - let observer = new IntersectionObserver( - (entries, observer) => { - entries.forEach((entry) => { - callback?.(entry.isIntersecting); - }); - }, - { - root: option?.root ?? null, - rootMargin: option?.rootMargin ?? "0px", - threshold: option?.threshold ?? 0, - } - ); - observer.observe(el); - return () => observer.unobserve(el); -} - // Idea from https://github.com/gys-dev/Unlimited-Stdphim // https://stackoverflow.com/a/61511955/11898496 function onElementsAdded(selector, callback, once) { @@ -951,49 +525,62 @@ function getWatchingVideoSrc() { // #region Utils -/** - * Waits for a condition to be true within a specified timeout. - * - * @param {function} condition - The condition to be evaluated. - * @param {number} [timeout=1000] - The timeout in milliseconds. - * @return {Promise} A Promise that resolves when the condition is true. - */ -function waitFor(condition, timeout = 0) { - // return new Promise((resolve) => { - // let timer = setInterval(() => { - // if (condition()) { - // clearInterval(timer); - // resolve(); - // } - // }, timeout); - // }); - return new Promise(async (resolve) => { +async function getRedirectedUrl(url) { + try { while (true) { - if (condition()) { - resolve(); - break; + let res = await UfsGlobal.Extension.fetchByPassOrigin(url, { + method: "HEAD", + }); + if (res?.redirected) { + console.log("redirected:", url, "->", res.url); + url = res.url; + } else { + return url; } - await new Promise((resolve) => setTimeout(resolve, timeout)); } - }); -} - -// https://stackoverflow.com/a/7616484/23648002 -function hashString(str) { - let hash = 0, - i, - chr; - if (str.length === 0) return hash; - for (i = 0; i < str.length; i++) { - chr = str.charCodeAt(i); - hash = (hash << 5) - hash + chr; - hash |= 0; // Convert to 32bit integer + } catch (e) { + console.log("ERROR:", e); + return url; } - return hash; } -function lerp(from, to, speed) { - return from + (to - from) * speed; +// https://github.com/parshap/node-sanitize-filename/blob/master/index.js +// https://github.com/Dinoosauro/tiktok-to-ytdlp/blob/main/script.js +function sanitizeName(name, modifyIfPosible = true) { + if (typeof name !== "string") { + throw new Error("Input must be string"); + } + const replacement = ""; + const illegalRe = /[\/\?<>\\:\*\|"’#]/g; + const controlRe = /[\x00-\x1f\x80-\x9f]/g; + const reservedRe = /^\.+$/; + const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i; + const windowsTrailingRe = /[\. ]+$/; + if (modifyIfPosible) { + name = name + .replaceAll("<", "‹") + .replaceAll(">", "›") + .replaceAll(":", "∶") + .replaceAll('"', "″") + .replaceAll("/", "∕") + .replaceAll("\\", "∖") + .replaceAll("|", "¦") + .replaceAll("?", "¿"); + } + const sanitized = name + .replace(illegalRe, replacement) + .replace(controlRe, replacement) + .replace(reservedRe, replacement) + .replace(windowsReservedRe, replacement) + .replace(windowsTrailingRe, replacement); + return sanitized; // TODO truncates to length of 255 +} +function debounce(func, delay) { + let timeout; + return (...args) => { + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, args), delay); + }; } const numberFormatCached = {}; @@ -1051,158 +638,9 @@ function getNumberFormatter(optionSelect, locale) { } return numberFormatCached[key]; } - -/** - * Make a deep copy of an object - * @source - required (Object|Array) the object or array to be copied - */ -function deepClone(source) { - let result = {}; - - if (typeof source !== "object") { - return source; - } - if (Object.prototype.toString.call(source) === "[object Array]") { - result = []; - } - if (Object.prototype.toString.call(source) === "[object Null]") { - result = null; - } - for (let key in source) { - result[key] = - typeof source[key] === "object" ? deepClone(source[key]) : source[key]; - } - return result; -} - -function debounce(func, delay) { - let timeout; - return (...args) => { - clearTimeout(timeout); - timeout = setTimeout(() => func.apply(this, args), delay); - }; -} - -// https://dev.to/jeetvora331/throttling-in-javascript-easiest-explanation-1081 -function throttle(mainFunction, delay) { - let timerFlag = null; // Variable to keep track of the timer - - // Returning a throttled version - return (...args) => { - if (timerFlag === null) { - // If there is no timer currently running - mainFunction(...args); // Execute the main function - timerFlag = setTimeout(() => { - // Set a timer to clear the timerFlag after the specified delay - timerFlag = null; // Clear the timerFlag to allow the main function to be executed again - }, delay); - } - }; -} function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } -function domainCheck(domains) { - return new RegExp(domains).test(location.host); -} -function setCookie(name, value, days) { - if (days) { - let date = new Date(); - date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); - let expires = "; expires=" + date.toGMTString(); - } else { - let expires = ""; - document.cookie = name + "=" + value + expires + "; path=/"; - } -} -function delCookie(name) { - setCookie(name, "", -1); -} -function strBetween(s, front, back, trim = false) { - if (trim) { - s = s.replaceAll(" ", ""); - s = s.trim(); - s = s.replaceAll("\n", " "); - } - return s.slice( - s.indexOf(front) + front.length, - s.indexOf(back, s.indexOf(front) + front.length) - ); -} -async function json2xml(json) { - if (!window.json2xml) { - await import("../libs/xml-json/json2xml.js"); - } - return window.json2xml(json); -} -async function xml2json(xml) { - if (!window.xml2json) { - await import("../libs/xml-json/xml2json.js"); - } - return window.xml2json(xml); -} -function getLargestSrcset(srcset) { - let srcs = srcset.split(/[xw],/i), - largeSize = -1, - largeSrc = null; - if (!srcs.length) return null; - srcs.forEach((srci) => { - let srcInfo = srci.trim().split(/(\s+|%20)/), - curSize = parseInt(srcInfo[2] || 0); - if (srcInfo[0] && curSize > largeSize) { - largeSize = curSize; - largeSrc = srcInfo[0]; - } - }); - return largeSrc; -} -function svgBase64ToUrl(sgvBase64) { - try { - if (!/^data:image\/svg/.test(sgvBase64)) throw new Error("Invalid SVG"); - const svgContent = atob(sgvBase64.split(",")[1]); - const blob = new Blob([svgContent], { type: "image/svg+xml" }); - const url = URL.createObjectURL(blob); - setTimeout(() => URL.revokeObjectURL(url), 6e4); - return url; - } catch (e) { - console.log("ERROR: ", e); - return null; - } -} -function svgToBlobUrl(svg) { - let url = URL.createObjectURL(new Blob([svg], { type: "image/svg+xml" })); - return url; -} -function svgToBase64(svg) { - try { - return ( - "data:image/svg+xml;charset=utf-8;base64," + - btoa(new XMLSerializer().serializeToString(svg)) - ); - } catch (e) { - console.log("ERROR: ", e); - return null; - } -} -function getResolutionCategory(width, height) { - let min = Math.min(width, height); - let max = Math.max(width, height); - - if (max <= 256 && min <= 144) return "144p"; - if (max <= 320 && min <= 180) return "240p"; - if (max <= 640 && min <= 360) return "360p"; - if (max <= 640 && min <= 480) return "SD (480p)"; - if (max <= 1280 && min <= 720) return "HD (720p)"; - if (max <= 1600 && min <= 900) return "HD+ (900p)"; - if (max <= 1920 && min <= 1080) return "FHD (1080p)"; - if (max <= 2560 && min <= 1440) return "QHD (1440p)"; - if (max <= 3840 && min <= 2160) return "4K (2160p)"; - if (max <= 5120 && min <= 2880) return "5K (2880p)"; - if (max <= 7680 && min <= 4320) return "8K (4320p)"; - if (max <= 10240 && min <= 4320) return "10K (4320p)"; - if (max <= 15360 && min <= 8640) return "16K (8640p)"; - return "> 16K"; -} async function saveAs(url_blob_file, title = "download", options = {}) { try { if (!window.saveAs) { @@ -1214,442 +652,6 @@ async function saveAs(url_blob_file, title = "download", options = {}) { alert("Error: " + e); } } -// https://stackoverflow.com/a/67705964/23648002 -function isEmoji(text) { - return text?.match( - /^(\u00a9|\u00ae|[\u25a0-\u27bf]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+$/ - ); -} -function formatTimeToHHMMSSDD(date) { - const hours = ("0" + date.getHours()).slice(-2); - const minutes = ("0" + date.getMinutes()).slice(-2); - const seconds = ("0" + date.getSeconds()).slice(-2); - const milliseconds = ("00" + date.getMilliseconds()).slice(-3); - return `${hours}:${minutes}:${seconds}:${milliseconds}`; -} -function timeoutPromise(prom, time) { - return Promise.race([ - prom, - new Promise((_r, rej) => setTimeout(() => rej("time out " + time), time)), - ]); -} -async function getRedirectedUrl(url) { - try { - while (true) { - let res = await fetchByPassOrigin(url, { - method: "HEAD", - }); - if (res?.redirected) { - console.log("redirected:", url, "->", res.url); - url = res.url; - } else { - return url; - } - } - } catch (e) { - console.log("ERROR:", e); - return url; - } -} -function uniqueArray(array) { - return Array.from(new Set(array)); -} -function replaceUsingRegex(str, r, s) { - let results = []; - - if (!Array.isArray(r) && !Array.isArray(s)) { - if (r?.test?.(str)) { - results.push(str.replace(r, s)); - } - } else if (!Array.isArray(r) && Array.isArray(s)) { - if (r?.test?.(str)) { - for (const si of s) { - results.push(str.replace(r, si)); - } - } - } else if (Array.isArray(r) && !Array.isArray(s)) { - for (const ri of r) { - if (ri?.test?.(str)) { - results.push(str.replace(ri, s)); - } - } - } else if (Array.isArray(r) && Array.isArray(s)) { - for (let ri = 0; ri < r.length; ri++) { - let _r = r[ri]; - if (_r?.test?.(str)) { - let _s = Array.isArray(s[ri]) ? s[ri] : [s[ri]]; - for (const si of _s) { - results.push(str.replace(_r, si)); - } - } - } - } - - return uniqueArray(results); -} -function testRegex(str, regexs) { - if (!Array.isArray(regexs)) regexs = [regexs]; - for (let regex of regexs) { - if (regex?.test?.(str)) { - return true; - } - } - return false; -} -async function getLargestImageSrc(imgSrc, webUrl) { - if (/^data:/i.test(imgSrc)) { - return null; - } - - // bypass redirect - imgSrc = makeUrlValid(imgSrc); - let redirectedUrl = await getRedirectedUrl(imgSrc); - if (redirectedUrl) { - imgSrc = redirectedUrl; - } - - function try1() { - const url = new URL(imgSrc); - switch (url.hostname) { - // https://atlassiansuite.mservice.com.vn:8443/secure/useravatar?size=small&ownerId=JIRAUSER14656&avatarId=11605 - case "atlassiansuite.mservice.com.vn": - case "atlassiantool.mservice.com.vn": - if (url.href.includes("avatar")) { - if (url.searchParams.get("size")) { - url.searchParams.set("size", "256"); - } else { - url.searchParams.append("size", "256"); - } - } - if (url.href.includes("/thumbnail/")) { - return url.href.replace("/thumbnail/", "/attachments/"); - } - if (url.href.includes("/thumbnails/")) { - return url.href.replace("/thumbnails/", "/attachments/"); - } - return url.toString(); - } - return null; - } - - async function try2() { - if (!CACHED.largeImgSiteRules) { - let s = await import("../auto_redirectLargestImageSrc_rules.js"); - CACHED.largeImgSiteRules = s.default; - } - for (let rule of CACHED.largeImgSiteRules) { - if (rule.url && !testRegex(webUrl, rule.url)) continue; - if (rule.src && !testRegex(imgSrc, rule.src)) continue; - if (rule.exclude && testRegex(imgSrc, rule.exclude)) continue; - if (rule.r) { - let newSrc = replaceUsingRegex(imgSrc, rule.r, rule.s); - if (newSrc?.length) { - return newSrc; - } - } - } - return null; - } - - // https://greasyfork.org/en/scripts/2312-resize-image-on-open-image-in-new-tab - function try3() { - return new Promise((resolve) => { - let m = null; - //google - if ( - (m = imgSrc.match( - /^(https?:\/\/lh\d+\.googleusercontent\.com\/.+\/)([^\/]+)(\/[^\/]+(\.(jpg|jpeg|gif|png|bmp|webp))?)(?:\?.+)?$/i - )) - ) { - if (m[2] != "s0") { - resolve(m[1] + "s0" + m[3]); - } - } else if ( - (m = imgSrc.match( - /^(https?:\/\/lh\d+\.googleusercontent\.com\/.+=)(.+)(?:\?.+)?$/i - )) - ) { - if (m[2] != "s0") { - resolve(m[1] + "s0"); - } - } else if ( - (m = imgSrc.match( - /^(https?:\/\/\w+\.ggpht\.com\/.+\/)([^\/]+)(\/[^\/]+(\.(jpg|jpeg|gif|png|bmp|webp))?)(?:\?.+)?$/i - )) - ) { - if (m[2] != "s0") { - resolve(m[1] + "s0" + m[3]); - } - } - - //blogspot - else if ( - (m = imgSrc.match( - /^(https?:\/\/\w+\.bp\.blogspot\.com\/.+\/)([^\/]+)(\/[^\/]+(\.(jpg|jpeg|gif|png|bmp|webp))?)(?:\?.+)?$/i - )) - ) { - if (m[2] != "s0") { - resolve(m[1] + "s0" + m[3]); - } - } - - //tumblr - else if ( - (m = imgSrc.match( - /^(https?:\/\/\d+\.media\.tumblr\.com\/.*tumblr_\w+_)(\d+)(\.(jpg|jpeg|gif|png|bmp|webp))(?:\?.+)?$/i - )) - ) { - if (m[2] < 1280) { - let ajax = new XMLHttpRequest(); - ajax.onreadystatechange = function () { - if (ajax.status == 200) { - resolve(m[1] + "1280" + m[3]); - } - }; - ajax.open("HEAD", m[1] + "1280" + m[3], true); - ajax.send(); - } - } - - //twitter - else if ( - (m = imgSrc.match( - /^(https?:\/\/\w+\.twimg\.com\/media\/[^\/:]+)\.(jpg|jpeg|gif|png|bmp|webp)(:\w+)?$/i - )) - ) { - let format = m[2]; - if (m[2] == "jpeg") format = "jpg"; - resolve(m[1] + "?format=" + format + "&name=orig"); - } else if ( - (m = imgSrc.match(/^(https?:\/\/\w+\.twimg\.com\/.+)(\?.+)$/i)) - ) { - let url = new URL(webUrl); - let pars = url.searchParams; - if (!pars.format || !pars.name) return; - if (pars.name == "orig") return; - resolve(m[1] + "?format=" + pars.format + "&name=orig"); - } - - //Steam (Only user content) - else if ( - (m = imgSrc.match( - /^(https?:\/\/(images\.akamai\.steamusercontent\.com|steamuserimages-a\.akamaihd\.net)\/[^\?]+)\?.+$/i - )) - ) { - resolve(m[1]); - } - - //性浪微博 - else if ( - (m = imgSrc.match( - /^(https?:\/\/(?:(?:ww|wx|ws|tvax|tva)\d+|wxt|wt)\.sinaimg\.(?:cn|com)\/)([\w\.]+)(\/.+)(?:\?.+)?$/i - )) - ) { - if (m[2] != "large") { - resolve(m[1] + "large" + m[3]); - } - } - - //zhihu - else if ( - (m = imgSrc.match( - /^(https?:\/\/.+\.zhimg\.com\/)(?:\d+\/)?([\w\-]+_)(\w+)(\.(jpg|jpeg|gif|png|bmp|webp))(?:\?.+)?$/i - )) - ) { - if (m[3] != "r") { - resolve(m[1] + m[2] + "r" + m[4]); - } - } - - //pinimg - else if ( - (m = imgSrc.match(/^(https?:\/\/i\.pinimg\.com\/)(\w+)(\/.+)$/i)) - ) { - if (m[2] != "originals") { - resolve(m[1] + "originals" + m[3]); - } - } else if ( - (m = imgSrc.match( - /^(https?:\/\/s-media[\w-]+\.pinimg\.com\/)(\w+)(\/.+)$/i - )) - ) { - //need delete? - if (m[2] != "originals") { - resolve(m[1] + "originals" + m[3]); - } - } - - //bilibili - else if ( - (m = imgSrc.match( - /^(https?:\/\/\w+\.hdslb\.com\/.+\.(jpg|jpeg|gif|png|bmp|webp))(@|_).+$/i - )) - ) { - resolve(m[1]); - } - - //taobao(tmall) - else if ( - (m = imgSrc.match( - /^(https?:\/\/(?:.+?)\.alicdn\.com\/.+\.(jpg|jpeg|gif|png|bmp|webp))_.+$/i - )) - ) { - resolve(m[1]); - } - - //jd - else if ( - (m = imgSrc.match( - /^(https?:\/\/(?:img\d+)\.360buyimg\.com\/)((?:.+?)\/(?:.+?))(\/(?:.+?))(\!.+)?$/i - )) - ) { - if (m[2] != "sku/jfs") { - resolve(m[1] + "sku/jfs" + m[3]); - } - } - - // https://s01.riotpixels.net/data/2a/b2/2ab23684-6cec-41da-9bce-f72c5264353a.jpg.240p.jpg - else if ( - (m = imgSrc.match( - /^(https?:\/\/(?:.+?)\.riotpixels\.net\/.+\.(jpg|jpeg|gif|png|bmp|webp))\..+?$/i - )) - ) { - resolve(m[1]); - } - - // reddit NEED TEST - else if ( - (m = imgSrc.match( - /^https?:\/\/preview\.redd\.it\/(.+\.(jpg|jpeg|gif|png|bmp|webp))\?.+?$/i - )) - ) { - resolve("https://i.redd.it/" + m[1]); - } - - // akamaized.net/imagecache NEED TEST - else if ( - (m = imgSrc.match( - /^(https:\/\/.+\.akamaized\.net\/imagecache\/\d+\/\d+\/\d+\/\d+\/)(\d+)(\/.+)$/i - )) - ) { - if (m[2] < 1920) resolve(m[1] + "1920" + m[3]); - } - - // 微信公众号 by sbdx - else if ( - (m = imgSrc.match( - /^(https:\/\/mmbiz\.qpic\.cn\/mmbiz_jpg\/.+?\/)(\d+)(\?wx_fmt=jpeg)/i - )) - ) { - if (m[2] != 0) resolve(m[1] + "0" + m[3]); - } - - //百度贴吧(然而对于画质提升什么的并没有什么卵用...) - else if ( - (m = imgSrc.match( - /^https?:\/\/imgsrc\.baidu\.com\/forum\/pic\/item\/.+/i - )) - ) { - if ( - (m = imgSrc.match( - /^(https?):\/\/(?:imgsrc|imgsa|\w+\.hiphotos)\.(?:bdimg|baidu)\.com\/(?:forum|album)\/.+\/(\w+\.(?:jpg|jpeg|gif|png|bmp|webp))(?:\?.+)?$/i - )) - ) { - resolve(m[1] + "://imgsrc.baidu.com/forum/pic/item/" + m[2]); - } - //if( (m = imgSrc.match(/^(https?)(:\/\/(?:imgsrc|imgsa|\w+\.hiphotos|tiebapic)\.(?:bdimg|baidu)\.com\/)(?:forum|album)\/.+\/(\w+\.(?:jpg|jpeg|gif|png|bmp|webp))(?:\?.+)?$/i)) ){ - // resolve(m[1] + m[2] + "forum/pic/item/" + m[3]) - //} - } else { - resolve(null); - } - }); - } - - for (let fn of [try1, try2, try3]) { - try { - let res = await timeoutPromise(fn(), 5000); - if (res && res != imgSrc) { - if (!Array.isArray(res)) res = [res]; - if (res.length) { - let finalSrc = await findWorkingSrc(res, true); - if (finalSrc?.length) return finalSrc; - } - } - } catch (e) { - console.log("ERROR getLargestImageSrc: " + fn.name + " -> ", e); - } - } - - return null; -} -async function isImageSrc(src) { - try { - const res = await fetchByPassOrigin(src, { - method: "HEAD", - }); - if (res?.ok) { - // const type = res.headers.get("content-type"); - const type = res.headers?.["content-type"]; - if (type?.startsWith?.("image/")) { - return true; - } - } - } catch (error) { - console.log("ERROR isImageSrc: " + src + " -> ", error); - } - - return new Promise((resolve) => { - let img = new Image(); - img.src = src; - img.onload = () => resolve(true); - img.onerror = () => resolve(false); - }); -} -function findWorkingSrc(srcs, inOrder = true) { - return new Promise((resolve, reject) => { - if (!srcs || !Array.isArray(srcs) || srcs.length === 0) { - reject("srcs is falsy, not an array, or empty"); - } else { - const checkImage = (src) => - // prevent Error: Content Security Policy directive: "connect-src 'self' - isImageSrc(src).then((value) => { - if (inOrder) return value; - if (value) resolve(src); - return value; - }); - - const promises = srcs.map(checkImage); - Promise.all(promises).then((res) => { - let trueIndex = res.indexOf(true); - if (trueIndex > -1) { - resolve(srcs[trueIndex]); - } else { - reject("none of the URLs are valid images"); - } - }); - } - }); -} -// resolve relative URLs into canonical absolute URLs based on the current location. -function canonicalUri(src, location = window.location) { - if (src.charAt(0) == "#") return location.href + src; - if (src.charAt(0) == "?") - return location.href.replace(/^([^\?#]+).*/, "$1" + src); - let root_page = /^[^?#]*\//.exec(location.href)[0], - base_path = location.pathname.replace(/\/[^\/]+\.[^\/]+$/, "/"), - root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0], - absolute_regex = /^\w+\:\/\//; - src = src.replace("./", ""); - if (/^\/\/\/?/.test(src)) { - src = location.protocol + src; - } else if (!absolute_regex.test(src) && src.charAt(0) != "/") { - src = (base_path || "") + src; - } - return absolute_regex.test(src) - ? src - : (src.charAt(0) == "/" ? root_domain : root_page) + src; -} function formatSize(size, fixed = 0) { size = Number(size); if (!size) return "?"; @@ -1694,20 +696,6 @@ function promiseAllStepN(n, list) { }); }); } -// https://stackoverflow.com/a/38552302/11898496 -function parseJwt(token) { - let base64Url = token.split(".")[1]; - let base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); - let jsonPayload = decodeURIComponent( - atob(base64) - .split("") - .map(function (c) { - return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); - }) - .join("") - ); - return JSON.parse(jsonPayload); -} function copyToClipboard(text) { // Check if Clipboard API is supported if (!navigator.clipboard) { @@ -1734,32 +722,6 @@ function copyToClipboard(text) { console.error("Failed to copy text to clipboard:", err); }); } -// https://stackoverflow.com/a/7960435 -function isEmptyFunction(func) { - try { - let m = func.toString().match(/\{([\s\S]*)\}/m)[1]; - return !m.replace(/^\s*\/\/.*$/gm, ""); - } catch (e) { - console.log("Error isEmptyFunction", e); - return false; - } -} -// https://stackoverflow.com/a/9310752 -function escapeRegExp(text) { - return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); -} -// https://stackoverflow.com/q/38849009 -function unescapeRegExp(text) { - return text.replace(/\\(.)/g, "$1"); -} -function encodeQueryString(obj) { - let str = []; - for (let p in obj) - if (obj.hasOwnProperty(p)) { - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - } - return str.join("&"); -} function moneyFormat(number, fixed = 0) { if (isNaN(number)) return 0; number = number.toFixed(fixed); @@ -1771,77 +733,80 @@ function moneyFormat(number, fixed = 0) { } return number; } -async function zipAndDownloadBlobs( - blobList, - zipFileName, - progressCallback, - successCallback -) { - if (!window.JSZip) { - await import("../libs/jzip/index.js"); +// https://stackoverflow.com/a/69543476/11898496 +function getOriginalWindowFunction(fnName) { + const key = "ufs_original_windown_fn"; + if (!window[key]) window[key] = {}; + + if (!window[key][fnName]) { + let iframe = window[key]["iframe"]; + + if (!iframe) { + iframe = document.createElement("iframe"); + iframe.style.display = "none"; + document.body.appendChild(iframe); + window[key]["iframe"] = iframe; + } + + window[key][fnName] = iframe.contentWindow[fnName]; } - const zip = new window.JSZip(); - console.log(zip); - // Add each Blob to the ZIP archive with a unique name - blobList.forEach(({ blob, fileName }, index) => { - console.log(fileName); - zip.file(fileName, blob); + return window[key][fnName]; +} +async function chooseFolderToDownload(subDirName = "") { + const dirHandler = await window.showDirectoryPicker({ + mode: "readwrite", + // startIn: 'downloads', }); + await dirHandler.requestPermission({ writable: true }); + if (!subDirName) return dirHandler; - // Generate the ZIP content with progress callback - zip - .generateAsync({ type: "blob" }, (metadata) => { - if (progressCallback) { - // Calculate progress as a percentage - const progress = metadata.percent | 0; - progressCallback(progress); - } - }) - .then((content) => { - successCallback?.(); - saveAs(content, zipFileName); - }); -} -async function getBlobFromUrl(url) { + const subDir = await dirHandler.getDirectoryHandle(subDirName, { + create: true, + }); + return subDir; +} +async function downloadToFolder({ + url, + fileName, + dirHandler, + expectBlobTypes, + subFolderName = "", +}) { + if (!url) return false; try { - const response = await fetch(url); - const blob = await response.blob(); - return blob; - } catch (error) { - alert("Error: " + error); - } -} -async function getBlobFromUrlWithProgress(url, progressCallback) { - const response = await fetch(url, {}); - if (!response.ok) { - throw new Error(`Error: ${response.status} - ${response.statusText}`); - } - const contentLength = response.headers.get("content-length"); - const total = parseInt(contentLength, 10); - let loaded = 0; - const reader = response.body.getReader(); - const chunks = []; + // const f = getOriginalWindowFunction("fetch"); + const res = await fetch(url); + const blob = await res.blob(); + // blobtype canbe regex + if ( + expectBlobTypes?.length && + !expectBlobTypes.find((t) => + t?.test ? t.test(blob.type) : t === blob.type + ) + ) { + throw new Error( + `Blob type ${blob.type} doesn't match expected type ${expectBlobTypes}` + ); + } + const fileHandler = await dirHandler.getFileHandle(fileName, { + create: true, + }); + const writable = await fileHandler.createWritable(); + await writable.write(blob); + await writable.close(); + return true; + } catch (e) { + console.error(e, arguments); - const startTime = Date.now(); - while (true) { - const { done, value } = await reader.read(); - if (done) break; - loaded += value.byteLength; - const ds = (Date.now() - startTime + 1) / 1000; - progressCallback?.({ - loaded, - total, - speed: loaded / ds, + // backup download: using extension api + await download({ + url: url, + conflictAction: "overwrite", + filename: (subFolderName ? subFolderName + "/" : "") + fileName, }); - chunks.push(value); + return false; } - - const blob = new Blob(chunks, { - type: response.headers.get("content-type"), - }); - - return blob; } // TODO use saveAs instead all of these download functions @@ -1863,15 +828,6 @@ function downloadBlob(blob, filename) { a.click(); URL.revokeObjectURL(blobUrl); } -// https://stackoverflow.com/a/15832662/11898496 -// TODO: chrome.downloads: https://developer.chrome.com/docs/extensions/reference/downloads/#method-download -function downloadURL(url, name) { - let link = document.createElement("a"); - link.target = "_blank"; - link.download = name; - link.href = url; - link.click(); -} function downloadData(data, filename, type = "text/plain") { let file = new Blob([data], { type: type }); if (window.navigator.msSaveOrOpenBlob) diff --git a/scripts/fb_blockSeenStory.js b/scripts/fb_blockSeenStory.js index 2351dbdd..c00c91e4 100644 --- a/scripts/fb_blockSeenStory.js +++ b/scripts/fb_blockSeenStory.js @@ -12,7 +12,7 @@ export default { en: "Block 'Seen' story in facebook. Your friend will not know that you have seen his/her stories.", vi: "Chặn 'Đã xem' cho story facebook. Bạn bè sẽ không biết được bạn đã xem story của họ.", }, - badges: [BADGES.new], + badges: [BADGES.hot], changeLogs: { "2024-05-31": "init", }, diff --git a/scripts/fb_downloadAlbumMedia.js b/scripts/fb_downloadAlbumMedia.js deleted file mode 100644 index 215daa9d..00000000 --- a/scripts/fb_downloadAlbumMedia.js +++ /dev/null @@ -1,121 +0,0 @@ -import { UfsGlobal } from "./content-scripts/ufs_global.js"; -import { showLoading } from "./helpers/utils.js"; - -export default { - icon: '', - name: { - en: "Download album facebook", - vi: "Tải album facebook", - }, - description: { - en: "Download photo/video links from facebook album", - vi: "Tải về danh sách link ảnh/video từ album facebook", - }, - // whiteList: ["https://graph.facebook.com/*"], - - popupScript: { - onClick: async function () { - const accessToken = prompt("Nhập access token:", ""); - if (!accessToken) return; - const albumId = prompt("Nhập album id: ", ""); - if (!albumId) return; - - async function fetchAlbumPhotosFromCursor({ albumId, cursor }) { - let url = `https://graph.facebook.com/v13.0/${albumId}/photos?fields=largest_image&limit=100&access_token=${accessToken}`; - if (cursor) url += `&after=${cursor}`; - const data = await fetch(url); - const json = await data.json(); - if (!json) return null; - return { - imgData: json.data?.map((_) => ({ - id: _.id, - url: _.largest_image.source, - })), - nextCursor: json.paging?.cursors?.after || null, - }; - } - async function fetchAlbumPhotos({ - albumId, - pageLimit = Infinity, - fromPhotoId = null, - pageFetchedCallback = async () => {}, - }) { - let currentPage = 1; - let hasNextCursor = true; - let nextCursor = fromPhotoId - ? Buffer.from(fromPhotoId).toString("base64") - : null; - let allImgsData = []; - while (hasNextCursor && currentPage <= pageLimit) { - console.log( - `ĐANG TẢI TRANG: ${currentPage}, Kích thước trang: 100 ảnh...` - ); - const data = await fetchAlbumPhotosFromCursor({ - albumId, - cursor: nextCursor, - }); - if (data?.imgData) { - allImgsData.push(...data.imgData); - console.log( - `> TÌM THẤY ${data.imgData.length} ẢNH. (TỔNG: ${allImgsData.length})` - ); - await pageFetchedCallback(data.imgData); - nextCursor = data.nextCursor; - hasNextCursor = nextCursor != null; - currentPage++; - } else { - console.log("[!] ERROR."); - break; - } - } - return allImgsData; - } - async function fetchAllPhotoLinksInAlbum({ - albumId, - fromPhotoId, - progress, - }) { - const from_text = fromPhotoId - ? "vị trí photo_id=" + fromPhotoId - : "đầu album"; - console.log(`ĐANG TẢI DỮ LIỆU ALBUM ${albumId} TỪ ${from_text}...`); - const result = []; - await fetchAlbumPhotos({ - albumId, - fromPhotoId, - pageFetchedCallback: (pageImgsData) => { - result.push(...pageImgsData.map((_) => _.url)); - progress?.(result.length); - }, - }); - return result; - } - - const { closeLoading, setLoadingText } = showLoading( - "Đang thu thập link ảnh/video trong album..." - ); - fetchAllPhotoLinksInAlbum({ - albumId, - progress: (total) => { - setLoadingText("Đang thu thập " + total + " links..."); - }, - }) - .then((links) => { - if ( - confirm( - "Tìm được " + - links.length + - " links ảnh/video.\nBấm OK để tải xuống." - ) - ) - UfsGlobal.Utils.downloadData(links.join("\n"), albumId, ".txt"); - }) - .catch((e) => { - alert("ERROR: " + e); - }) - .finally(() => { - closeLoading(); - }); - }, - }, -}; diff --git a/scripts/fb_downloadWallMediaFromPosts.js b/scripts/fb_downloadWallMediaFromPosts.js deleted file mode 100644 index c1929294..00000000 --- a/scripts/fb_downloadWallMediaFromPosts.js +++ /dev/null @@ -1,221 +0,0 @@ -import { UfsGlobal } from "./content-scripts/ufs_global.js"; -import { showLoading } from "./helpers/utils.js"; - -export default { - icon: '', - name: { - en: "Download all photos from posts", - vi: "Tải tất cả hình trên bài viết", - }, - description: { - en: "Get photos from all posts of group/page/user", - vi: "Lấy tất cả hình trên các bài viết (post) của group/page/user", - }, - - popupScript: { - onClick: async () => { - const WAIT_BEFORE_NEXT_FETCH = 500; - const FB_API_HOST = "https://graph.facebook.com/v12.0"; - const MEDIA_TYPE = { - PHOTO: "photo", - VIDEO: "video", - }; - let ACCESS_TOKEN = prompt("Nhập access token của bạn vào đây"); - if (!ACCESS_TOKEN) return; - - const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - - const myFetch = async (_url) => { - try { - const response = await fetch(_url); - const json = await response.json(); - if (json.error) { - console.log("[!] ERROR", JSON.stringify(json, null, 4)); - return null; - } - return json; - } catch (e) { - console.log("[!] ERROR", e.toString()); - return null; - } - }; - - const getMediaFromAttachment = (attachment) => { - const filtered_media = []; - - let id = attachment?.target?.id; - let type = attachment?.type; - - if (!id || !type) return filtered_media; - - /* - Attachment LOẠI PHOTO có cấu trúc như sau - { - "media": { - "image": { - "height": 720, - "src": "https://scontent.fhan2-4.fna.fbcdn.net/v/t39.30808-6/p480x480/233193975_582887376210934_3917501890611553539_n.jpg?_nc_cat=103&ccb=1-5&_nc_sid=07e735&_nc_ohc=b2Z1BxAj3PwAX_a0j-F&_nc_ht=scontent.fhan2-4.fna&oh=1100b63609d1d331a0a17721b002ae78&oe=614A6EAD", - "width": 480 - } - }, - "target": { - "id": "582887366210935", - "url": "https://www.facebook.com/photo.php?fbid=582887366210935&set=gm.1020873538672374&type=3" - }, - "type": "photo", - "url": "https://www.facebook.com/photo.php?fbid=582887366210935&set=gm.1020873538672374&type=3" - }*/ - if (type === "photo") { - filtered_media.push({ - type: MEDIA_TYPE.PHOTO, - id: id, - url: attachment.media.image.src, - }); - } - - /* - Attachment LOẠI VIDEO_AUTOPLAY, VIDEO_INLINE, VIDEO có định dạng như sau - { - "media": { - "image": { - "height": 720, - "src": "https://scontent.fsgn2-4.fna.fbcdn.net/v/t15.5256-10/s720x720/241870607_843209866352821_4272847571535179706_n.jpg?_nc_cat=101&ccb=1-5&_nc_sid=ad6a45&_nc_ohc=Ap2YChXA4fUAX_RgBT7&_nc_ht=scontent.fsgn2-4.fna&oh=f9fcc65d6c8a53207c1d03b19d036503&oe=614B4EE9", - "width": 405 - }, - "source": "https://video.fsgn2-6.fna.fbcdn.net/v/t42.1790-2/241979905_562868464766358_5763545655575200708_n.mp4?_nc_cat=110&ccb=1-5&_nc_sid=985c63&efg=eyJybHIiOjM5MiwicmxhIjo1MTIsInZlbmNvZGVfdGFnIjoic3ZlX3NkIn0%3D&_nc_ohc=1vx2K2s8m1IAX8TzDPs&rl=392&vabr=218&_nc_ht=video.fsgn2-6.fna&oh=32df5af4a31f119a16ca4fb8d30b48f0&oe=61477791" - }, - "target": { - "id": "843209423019532", - "url": "https://www.facebook.com/groups/j2team.community.girls/permalink/1045907852835609/" - }, - "type": "video_autoplay", - "url": "https://www.facebook.com/groups/j2team.community.girls/permalink/1045907852835609/" - } */ - if ( - type === "video_autoplay" || - type === "video_inline" || - type === "video" - ) { - filtered_media.push({ - type: MEDIA_TYPE.VIDEO, - id: id, - url: attachment.media.source, - }); - } - - /* - Attachment LOẠI ALBUM có định dạng như sau - { - "media": { - "image": { - "height": 720, - "src": "https://scontent.fhan2-4.fna.fbcdn.net/v/t39.30808-6/p480x480/233193975_582887376210934_3917501890611553539_n.jpg?_nc_cat=103&ccb=1-5&_nc_sid=07e735&_nc_ohc=b2Z1BxAj3PwAX_a0j-F&_nc_ht=scontent.fhan2-4.fna&oh=1100b63609d1d331a0a17721b002ae78&oe=614A6EAD", - "width": 480 - } - }, - "subattachments": { - "data": [ - {sub_attachment_1}, // Các sub attachment này có cấu trúc giống attachment PHOTO hoặc VIDEO_AUTOPLAY - {sub_attachment_2}, - ... - ] - }, - "target": { - "id": "1020873538672374", - "url": "https://www.facebook.com/media/set/?set=pcb.1020873538672374&type=1" - }, - "title": "Photos from Lê Tài's post", - "type": "album", - "url": "https://www.facebook.com/media/set/?set=pcb.1020873538672374&type=1" - } */ - if (type === "album") { - // GỌI ĐỆ QUY VỚI TỪNG SUB_ATTACHMENT - attachment?.subattachments?.data?.forEach((sub) => { - filtered_media.push(...getMediaFromAttachment(sub)); - }); - } - - return filtered_media; - }; - - // fetch tất cả bài post (feed) trong wall của 1 target (user, group, page), 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 - const fetchWallMedia = async ({ - targetId, - 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}/${targetId}/feed?fields=attachments{media,type,subattachments,target}&access_token=${ACCESS_TOKEN}`; - - while (url && page <= pageLimit) { - console.log("fetching", url); - const fetchData = await myFetch(url); - page++; - - if (fetchData?.data) { - // Get all media from each attachment - const media = []; - fetchData.data.forEach((feedData) => { - feedData.attachments?.data.forEach((at) => { - media.push(...getMediaFromAttachment(at)); - }); - }); - - all_media.push(...media); - console.log("media: " + all_media.length, all_media); - - // callback when each page fetched - await pageFetchedCallback(media, all_media); - - // get next paging - url = fetchData?.paging?.next; - - // wait for next fetch - if needed - if (WAIT_BEFORE_NEXT_FETCH) { - await sleep(WAIT_BEFORE_NEXT_FETCH); - } - } else { - break; - } - } - - return all_media; - }; - - let id = prompt("Nhập ID của user, group, page cần lấy media", ""); - if (id) { - alert( - "Quá trình lấy link ảnh sắp diễn ra.\nVui lòng không tắt popup extension trong quá trình này.\nThông tin chi tiết sẽ được hiển thị ở console của extension." - ); - - let { setLoadingText, closeLoading } = showLoading("Prepairing..."); - fetchWallMedia({ - targetId: id, - pageFetchedCallback: (media, all_media) => { - setLoadingText(`Tải được ${all_media.length} ảnh/video...`); - }, - }) - .then((all_media) => { - let urls = Array.from(new Set(all_media.map((_) => _.url))).join( - "\n" - ); - console.log(urls); - setLoadingText(`Đang lưu ${urls.length} links vào file...`); - alert( - "Các link ảnh sẽ được lưu vào file\nBạn có thể bỏ file vào IDM để tải tất cả hình ảnh" - ); - UfsGlobal.Utils.downloadData(urls, "urls.txt"); - }) - .catch((err) => { - console.log(err); - alert("Có lỗi xảy ra: " + err); - }) - .finally(() => { - closeLoading(); - }); - } - }, - }, -}; diff --git a/scripts/fb_getAllUidOfGroupMembers.js b/scripts/fb_getAllUidOfGroupMembers.js index e2b1fb3f..abd0a92c 100644 --- a/scripts/fb_getAllUidOfGroupMembers.js +++ b/scripts/fb_getAllUidOfGroupMembers.js @@ -21,10 +21,6 @@ export default { pageScript: { onClick: async function () { - function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - function getGroupId() { try { return require("CometRouteStore").getRoute(location.pathname) diff --git a/scripts/fb_getPostReactionCount.js b/scripts/fb_getPostReactionCount.js index 8171a36a..4095b072 100644 --- a/scripts/fb_getPostReactionCount.js +++ b/scripts/fb_getPostReactionCount.js @@ -1,7 +1,6 @@ import { UfsGlobal } from "./content-scripts/ufs_global.js"; import { fetchGraphQl, getFbdtsg } from "./fb_GLOBAL.js"; import { hookXHR } from "./libs/ajax-hook/index.js"; -import { BADGES } from "./helpers/badge.js"; export default { icon: '', @@ -14,7 +13,6 @@ export default { vi: "Hiện tổng lượt thích bài viết khi đưa chuột vào xem lượt thích.", img: "/scripts/fb_getPostReactionCount.jpg", }, - badges: [BADGES.new], changeLogs: { "2024-06-25": "init", }, diff --git a/scripts/fb_getTokenBusinessStudio.js b/scripts/fb_getTokenBusinessStudio.js deleted file mode 100644 index 9e5a7870..00000000 --- a/scripts/fb_getTokenBusinessStudio.js +++ /dev/null @@ -1,51 +0,0 @@ -export default { - icon: ``, - name: { - en: "Get fb token EAAc (studio)", - vi: "Lấy fb token EAAc (studio)", - }, - description: { - en: "Get facebook access token EAAc from business.facebook.com", - vi: "Lấy facebook access token EAAc từ trang business.facebook.com", - }, - - popupScript: { - onClick: async function () { - const { showLoading } = await import("./helpers/utils.js"); - // old - FAILED - // try { - // const accessToken = - // "EAA" + /(?<=EAA)(.*?)(?=\")/.exec(document.body.textContent)[0]; - // prompt("Access Token của bạn:", accessToken); - // } catch (e) { - // alert("LỖI: " + e.message); - // } - - const { closeLoading, setLoadingText } = showLoading( - "Đang tìm access token..." - ); - fetch("https://business.facebook.com/creatorstudio/home", { - method: "GET", - credentials: "include", - }) - .then((res) => res.text()) - .then(function (htmlText) { - let regex = htmlText.match( - /MediaManagerStatics",\[\],{"accessToken":"(.+?)"/ - ); - if (null !== regex) { - let accesstoken = regex[1]; - prompt("Access Token: ", accesstoken); - } else { - alert("Token not found"); - } - }) - .catch(function (e) { - alert("ERROR:" + JSON.stringify(e)); - }) - .finally(() => { - closeLoading(); - }); - }, - }, -}; diff --git a/scripts/fb_moreReactionStory.js b/scripts/fb_moreReactionStory.js index a1d004e6..5096a2c9 100644 --- a/scripts/fb_moreReactionStory.js +++ b/scripts/fb_moreReactionStory.js @@ -37,7 +37,7 @@ export default { window.ufs_reactStory = async (text) => { const storyId = getStoryId(); - if (!UfsGlobal.Utils.isEmoji(text)) { + if (!isEmoji(text)) { alert("Must be emoji"); return; } @@ -197,6 +197,12 @@ export default { } }); } + // https://stackoverflow.com/a/67705964/23648002 + function isEmoji(text) { + return text?.match( + /^(\u00a9|\u00ae|[\u25a0-\u27bf]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+$/ + ); + } }, onDocumentIdle: async () => { diff --git a/scripts/fb_stopNewFeed.js b/scripts/fb_stopNewFeed.js index 4a58211c..d696f7d6 100644 --- a/scripts/fb_stopNewFeed.js +++ b/scripts/fb_stopNewFeed.js @@ -21,7 +21,7 @@ export default { changeLogs: { "2024-06-12": "init", }, - badges: [BADGES.new], + badges: [BADGES.hot], whiteList: ["https://www.facebook.com/*"], diff --git a/scripts/fb_toggleNewFeed.js b/scripts/fb_toggleNewFeed.js index 1d4b1684..c0300177 100644 --- a/scripts/fb_toggleNewFeed.js +++ b/scripts/fb_toggleNewFeed.js @@ -1,5 +1,4 @@ import { UfsGlobal } from "./content-scripts/ufs_global.js"; -import { BADGES } from "./helpers/badge.js"; export default { icon: '', @@ -11,7 +10,6 @@ export default { en: "Hide facebook new feed (home page) for better focus to work", vi: "Ẩn dòng thời gian facebook (trang chủ) để tập trung làm việc", }, - badges: [BADGES.hot], infoLink: "https://www.facebook.com/groups/j2team.community/posts/1919935575005220/", whiteList: ["https://*.facebook.com/*"], diff --git a/scripts/ggdrive_downloadVideo.js b/scripts/ggdrive_downloadVideo.js index 9ff38190..7e5e86dd 100644 --- a/scripts/ggdrive_downloadVideo.js +++ b/scripts/ggdrive_downloadVideo.js @@ -14,6 +14,10 @@ export default { infoLink: "https://www.facebook.com/groups/j2team.community/posts/974953859503401/", + changeLogs: { + "2024-07-25": "add backup plan", + }, + popupScript: { onClick: async function () { const { getCurrentTab, openPopupWithHtml, showLoading } = await import( @@ -57,6 +61,31 @@ export default { } }, }, + contentScript: { + onClick_: async () => { + 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 + .map((_) => { + return /*html*/ `
+

${_.quality}

+
`; + }) + .join("
")}`, + 700, + 700 + ); + } + }, + }, }; export const shared = { @@ -75,14 +104,15 @@ export const shared = { // Post: https://www.facebook.com/groups/j2team.community/posts/974953859503401/ getLinkVideoGDriveFromDocId: async function (docid) { function parse(e) { - var result = {}; - return ( - e.split("&").forEach(function (e) { - result[decodeURIComponent(e.substring(0, e.indexOf("=")))] = - decodeURIComponent(e.substring(e.indexOf("=") + 1)); - }), - result - ); + let result = {}; + let a = new URLSearchParams(e); + a.entries().forEach((e) => { + result[e[0]] = e[1]; + }); + return result; + + // Array.from(new URLSearchParams("").entries()) + // .reduce((total, cur) => ({ ...total, [cur[0]]: cur[1] }), {}); } function parseStream(e) { @@ -103,6 +133,10 @@ export const shared = { let text = await res.text(); let json = parse(text); + if (json?.status === "fail") { + throw Error("FAILED: " + json.reason); + } + json.url_encoded_fmt_stream_map = parseStream( json.url_encoded_fmt_stream_map ); diff --git a/scripts/github_HTMLPreview.js b/scripts/github_HTMLPreview.js index f5dd9d21..b84bccb6 100644 --- a/scripts/github_HTMLPreview.js +++ b/scripts/github_HTMLPreview.js @@ -1,5 +1,3 @@ -import { BADGES } from "./helpers/badge.js"; - export default { icon: '', name: { @@ -10,7 +8,6 @@ export default { en: "Preview github's HTML file in any repo without download code.", vi: "Xem trước giao diện file HTML trên bất kỳ repo github nào mà không cần tải code về.", }, - badges: [BADGES.new], changeLogs: { "2024-06-14": "init", }, diff --git a/scripts/github_openRepoPages.js b/scripts/github_openRepoPages.js index 328e1ae7..ee0b892b 100644 --- a/scripts/github_openRepoPages.js +++ b/scripts/github_openRepoPages.js @@ -1,5 +1,4 @@ import { getRepoNameFromUrl } from "./github_goToAnyCommit.js"; -import { BADGES } from "./helpers/badge.js"; export default { icon: '', @@ -15,7 +14,6 @@ export default { username.github.io/repo
github.com/username/repo
`, }, - badges: [BADGES.new], changeLogs: { "2024-06-03": "init", }, diff --git a/scripts/guland_VIP.js b/scripts/guland_VIP.js new file mode 100644 index 00000000..5a71ed65 --- /dev/null +++ b/scripts/guland_VIP.js @@ -0,0 +1,28 @@ +import { UfsGlobal } from "./content-scripts/ufs_global.js"; +import { BADGES } from "./helpers/badge.js"; + +export default { + icon: "https://guland.vn/bds/img/apple-touch-icon.png", + name: { + en: "Guland VIP", + vi: "Guland VIP - Xem quy hoạch đất", + }, + description: { + en: "VIP for Guland.vn, view map without restriction", + vi: "Xem quy hoạch đất không bị làm phiền bởi popup vip tại Guland.vn", + img: "/scripts/guland_VIP.png", + }, + badges: [BADGES.new], + infoLink: "https://guland.vn", + changeLogs: { + "2024-07-14": "init", + }, + whiteList: ["https://guland.vn/*"], + + contentScript: { + onDocumentStart: (details) => { + UfsGlobal.DOM.deleteElements(".modal-backdrop"); + UfsGlobal.DOM.deleteElements("#Modal-NotificationWithButton"); + }, + }, +}; diff --git a/scripts/guland_VIP.png b/scripts/guland_VIP.png new file mode 100644 index 00000000..31c7b350 Binary files /dev/null and b/scripts/guland_VIP.png differ diff --git a/scripts/helpers/utils.js b/scripts/helpers/utils.js index 883de28f..97459bd8 100644 --- a/scripts/helpers/utils.js +++ b/scripts/helpers/utils.js @@ -255,7 +255,7 @@ export const getAllTabs = async () => { }; export function getPopupURL() { - return chrome.runtime.getURL("/popup/popup.html"); + return chrome?.runtime?.getURL?.("/popup/popup.html"); } export const getLastFocusedWindowIds = () => { @@ -288,14 +288,19 @@ export const getLastFocusedTab = async () => { }; export const getCurrentTab = async () => { - // case normal popup - let tabs = await chrome.tabs.query({ - active: true, - currentWindow: true, - }); - if (tabs?.[0]?.url !== getPopupURL()) return tabs[0]; + try { + // case normal popup + let tabs = await chrome?.tabs?.query?.({ + active: true, + currentWindow: true, + }); + if (tabs?.[0]?.url !== getPopupURL()) return tabs[0]; - return await getLastFocusedTab(); + return await getLastFocusedTab(); + } catch (err) { + console.log(err); + return null; + } }; export const getCurrentTabId = async () => { diff --git a/scripts/insta_getFollowForOther.js b/scripts/insta_getFollowForOther.js index 17d5372c..25bb46b7 100644 --- a/scripts/insta_getFollowForOther.js +++ b/scripts/insta_getFollowForOther.js @@ -1,5 +1,4 @@ import { UfsGlobal } from "./content-scripts/ufs_global.js"; -import { BADGES } from "./helpers/badge.js"; export default { icon: '', @@ -11,7 +10,6 @@ export default { en: "Know about your (or your friends's) following / followers on instagram. Export to json file", vi: "Biết bạn bè của bạn (hoặc chính bạn) đang follow những ai / được ai follow trên instagram. Tải về file json", }, - badges: [BADGES.new], whiteList: ["https://www.instagram.com/*"], popupScript: { diff --git a/scripts/libs/ajax-hook/index.js b/scripts/libs/ajax-hook/index.js index 0a72e061..cc371639 100644 --- a/scripts/libs/ajax-hook/index.js +++ b/scripts/libs/ajax-hook/index.js @@ -26,17 +26,47 @@ let readyFetch = false; function initFetch() { const originalFetch = window.fetch; - window.fetch = async function (url, options) { + window.fetch = async function (urlOrRequest, options) { + let url = urlOrRequest; + if (urlOrRequest instanceof Request) { + url = urlOrRequest?.url; + options = options || {}; + for (const key in urlOrRequest) { + const type = typeof urlOrRequest[key]; + if (type === "string" || type === "number" || type === "boolean") { + options[key] = urlOrRequest[key]; + } + } + } + let request = { url, options }; for (const { fn } of onBeforeFetchFn) { - const res = await fn?.(request.url, request.options); + const res = await fn?.(request.url, request.options)?.catch( + console.error + ); if (res) request = res; if (res === null) return null; } - let response = await originalFetch(...request); + if (urlOrRequest instanceof Request) { + try { + // TODO modify options + // for (const key in request.options) { + // urlOrRequest[key] = request.options[key]; + // } + if (urlOrRequest.url !== request.url) { + urlOrRequest = new Request(request.url, urlOrRequest); + } + } catch (e) { + debugger; + } + } + + let response = await originalFetch(urlOrRequest); for (const { fn } of onAfterFetchFn) { - const res = await fn?.(request.url, request.options, response); + const res = await fn?.(request.url, request.options, response)?.catch( + console.error + ); if (res) response = res; if (res === null) return null; } diff --git a/scripts/libs/vue/index.js b/scripts/libs/vue/index.js new file mode 100644 index 00000000..e983b1ab --- /dev/null +++ b/scripts/libs/vue/index.js @@ -0,0 +1,6 @@ +/*! + * Vue.js v2.6.10 + * (c) 2014-2019 Evan You + * Released under the MIT License. + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).Vue=t()}(this,function(){"use strict";var e=Object.freeze({});function t(e){return null==e}function n(e){return null!=e}function r(e){return!0===e}function i(e){return"string"==typeof e||"number"==typeof e||"symbol"==typeof e||"boolean"==typeof e}function o(e){return null!==e&&"object"==typeof e}var a=Object.prototype.toString;function s(e){return"[object Object]"===a.call(e)}function c(e){var t=parseFloat(String(e));return t>=0&&Math.floor(t)===t&&isFinite(e)}function u(e){return n(e)&&"function"==typeof e.then&&"function"==typeof e.catch}function l(e){return null==e?"":Array.isArray(e)||s(e)&&e.toString===a?JSON.stringify(e,null,2):String(e)}function f(e){var t=parseFloat(e);return isNaN(t)?e:t}function p(e,t){for(var n=Object.create(null),r=e.split(","),i=0;i-1)return e.splice(n,1)}}var m=Object.prototype.hasOwnProperty;function y(e,t){return m.call(e,t)}function g(e){var t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}var _=/-(\w)/g,b=g(function(e){return e.replace(_,function(e,t){return t?t.toUpperCase():""})}),$=g(function(e){return e.charAt(0).toUpperCase()+e.slice(1)}),w=/\B([A-Z])/g,C=g(function(e){return e.replace(w,"-$1").toLowerCase()});var x=Function.prototype.bind?function(e,t){return e.bind(t)}:function(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n};function k(e,t){t=t||0;for(var n=e.length-t,r=new Array(n);n--;)r[n]=e[n+t];return r}function A(e,t){for(var n in t)e[n]=t[n];return e}function O(e){for(var t={},n=0;n0,Z=J&&J.indexOf("edge/")>0,G=(J&&J.indexOf("android"),J&&/iphone|ipad|ipod|ios/.test(J)||"ios"===K),X=(J&&/chrome\/\d+/.test(J),J&&/phantomjs/.test(J),J&&J.match(/firefox\/(\d+)/)),Y={}.watch,Q=!1;if(z)try{var ee={};Object.defineProperty(ee,"passive",{get:function(){Q=!0}}),window.addEventListener("test-passive",null,ee)}catch(e){}var te=function(){return void 0===B&&(B=!z&&!V&&"undefined"!=typeof global&&(global.process&&"server"===global.process.env.VUE_ENV)),B},ne=z&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function re(e){return"function"==typeof e&&/native code/.test(e.toString())}var ie,oe="undefined"!=typeof Symbol&&re(Symbol)&&"undefined"!=typeof Reflect&&re(Reflect.ownKeys);ie="undefined"!=typeof Set&&re(Set)?Set:function(){function e(){this.set=Object.create(null)}return e.prototype.has=function(e){return!0===this.set[e]},e.prototype.add=function(e){this.set[e]=!0},e.prototype.clear=function(){this.set=Object.create(null)},e}();var ae=S,se=0,ce=function(){this.id=se++,this.subs=[]};ce.prototype.addSub=function(e){this.subs.push(e)},ce.prototype.removeSub=function(e){h(this.subs,e)},ce.prototype.depend=function(){ce.target&&ce.target.addDep(this)},ce.prototype.notify=function(){for(var e=this.subs.slice(),t=0,n=e.length;t-1)if(o&&!y(i,"default"))a=!1;else if(""===a||a===C(e)){var c=Pe(String,i.type);(c<0||s0&&(st((u=e(u,(a||"")+"_"+c))[0])&&st(f)&&(s[l]=he(f.text+u[0].text),u.shift()),s.push.apply(s,u)):i(u)?st(f)?s[l]=he(f.text+u):""!==u&&s.push(he(u)):st(u)&&st(f)?s[l]=he(f.text+u.text):(r(o._isVList)&&n(u.tag)&&t(u.key)&&n(a)&&(u.key="__vlist"+a+"_"+c+"__"),s.push(u)));return s}(e):void 0}function st(e){return n(e)&&n(e.text)&&!1===e.isComment}function ct(e,t){if(e){for(var n=Object.create(null),r=oe?Reflect.ownKeys(e):Object.keys(e),i=0;i0,a=t?!!t.$stable:!o,s=t&&t.$key;if(t){if(t._normalized)return t._normalized;if(a&&r&&r!==e&&s===r.$key&&!o&&!r.$hasNormal)return r;for(var c in i={},t)t[c]&&"$"!==c[0]&&(i[c]=pt(n,c,t[c]))}else i={};for(var u in n)u in i||(i[u]=dt(n,u));return t&&Object.isExtensible(t)&&(t._normalized=i),R(i,"$stable",a),R(i,"$key",s),R(i,"$hasNormal",o),i}function pt(e,t,n){var r=function(){var e=arguments.length?n.apply(null,arguments):n({});return(e=e&&"object"==typeof e&&!Array.isArray(e)?[e]:at(e))&&(0===e.length||1===e.length&&e[0].isComment)?void 0:e};return n.proxy&&Object.defineProperty(e,t,{get:r,enumerable:!0,configurable:!0}),r}function dt(e,t){return function(){return e[t]}}function vt(e,t){var r,i,a,s,c;if(Array.isArray(e)||"string"==typeof e)for(r=new Array(e.length),i=0,a=e.length;idocument.createEvent("Event").timeStamp&&(sn=function(){return cn.now()})}function un(){var e,t;for(an=sn(),rn=!0,Qt.sort(function(e,t){return e.id-t.id}),on=0;onon&&Qt[n].id>e.id;)n--;Qt.splice(n+1,0,e)}else Qt.push(e);nn||(nn=!0,Ye(un))}}(this)},fn.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||o(e)||this.deep){var t=this.value;if(this.value=e,this.user)try{this.cb.call(this.vm,e,t)}catch(e){Re(e,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,e,t)}}},fn.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},fn.prototype.depend=function(){for(var e=this.deps.length;e--;)this.deps[e].depend()},fn.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||h(this.vm._watchers,this);for(var e=this.deps.length;e--;)this.deps[e].removeSub(this);this.active=!1}};var pn={enumerable:!0,configurable:!0,get:S,set:S};function dn(e,t,n){pn.get=function(){return this[t][n]},pn.set=function(e){this[t][n]=e},Object.defineProperty(e,n,pn)}function vn(e){e._watchers=[];var t=e.$options;t.props&&function(e,t){var n=e.$options.propsData||{},r=e._props={},i=e.$options._propKeys=[];e.$parent&&$e(!1);var o=function(o){i.push(o);var a=Me(o,t,n,e);xe(r,o,a),o in e||dn(e,"_props",o)};for(var a in t)o(a);$e(!0)}(e,t.props),t.methods&&function(e,t){e.$options.props;for(var n in t)e[n]="function"!=typeof t[n]?S:x(t[n],e)}(e,t.methods),t.data?function(e){var t=e.$options.data;s(t=e._data="function"==typeof t?function(e,t){le();try{return e.call(t,t)}catch(e){return Re(e,t,"data()"),{}}finally{fe()}}(t,e):t||{})||(t={});var n=Object.keys(t),r=e.$options.props,i=(e.$options.methods,n.length);for(;i--;){var o=n[i];r&&y(r,o)||(a=void 0,36!==(a=(o+"").charCodeAt(0))&&95!==a&&dn(e,"_data",o))}var a;Ce(t,!0)}(e):Ce(e._data={},!0),t.computed&&function(e,t){var n=e._computedWatchers=Object.create(null),r=te();for(var i in t){var o=t[i],a="function"==typeof o?o:o.get;r||(n[i]=new fn(e,a||S,S,hn)),i in e||mn(e,i,o)}}(e,t.computed),t.watch&&t.watch!==Y&&function(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var i=0;i-1:"string"==typeof e?e.split(",").indexOf(t)>-1:(n=e,"[object RegExp]"===a.call(n)&&e.test(t));var n}function An(e,t){var n=e.cache,r=e.keys,i=e._vnode;for(var o in n){var a=n[o];if(a){var s=xn(a.componentOptions);s&&!t(s)&&On(n,o,r,i)}}}function On(e,t,n,r){var i=e[t];!i||r&&i.tag===r.tag||i.componentInstance.$destroy(),e[t]=null,h(n,t)}!function(t){t.prototype._init=function(t){var n=this;n._uid=bn++,n._isVue=!0,t&&t._isComponent?function(e,t){var n=e.$options=Object.create(e.constructor.options),r=t._parentVnode;n.parent=t.parent,n._parentVnode=r;var i=r.componentOptions;n.propsData=i.propsData,n._parentListeners=i.listeners,n._renderChildren=i.children,n._componentTag=i.tag,t.render&&(n.render=t.render,n.staticRenderFns=t.staticRenderFns)}(n,t):n.$options=De($n(n.constructor),t||{},n),n._renderProxy=n,n._self=n,function(e){var t=e.$options,n=t.parent;if(n&&!t.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(e)}e.$parent=n,e.$root=n?n.$root:e,e.$children=[],e.$refs={},e._watcher=null,e._inactive=null,e._directInactive=!1,e._isMounted=!1,e._isDestroyed=!1,e._isBeingDestroyed=!1}(n),function(e){e._events=Object.create(null),e._hasHookEvent=!1;var t=e.$options._parentListeners;t&&qt(e,t)}(n),function(t){t._vnode=null,t._staticTrees=null;var n=t.$options,r=t.$vnode=n._parentVnode,i=r&&r.context;t.$slots=ut(n._renderChildren,i),t.$scopedSlots=e,t._c=function(e,n,r,i){return Pt(t,e,n,r,i,!1)},t.$createElement=function(e,n,r,i){return Pt(t,e,n,r,i,!0)};var o=r&&r.data;xe(t,"$attrs",o&&o.attrs||e,null,!0),xe(t,"$listeners",n._parentListeners||e,null,!0)}(n),Yt(n,"beforeCreate"),function(e){var t=ct(e.$options.inject,e);t&&($e(!1),Object.keys(t).forEach(function(n){xe(e,n,t[n])}),$e(!0))}(n),vn(n),function(e){var t=e.$options.provide;t&&(e._provided="function"==typeof t?t.call(e):t)}(n),Yt(n,"created"),n.$options.el&&n.$mount(n.$options.el)}}(wn),function(e){var t={get:function(){return this._data}},n={get:function(){return this._props}};Object.defineProperty(e.prototype,"$data",t),Object.defineProperty(e.prototype,"$props",n),e.prototype.$set=ke,e.prototype.$delete=Ae,e.prototype.$watch=function(e,t,n){if(s(t))return _n(this,e,t,n);(n=n||{}).user=!0;var r=new fn(this,e,t,n);if(n.immediate)try{t.call(this,r.value)}catch(e){Re(e,this,'callback for immediate watcher "'+r.expression+'"')}return function(){r.teardown()}}}(wn),function(e){var t=/^hook:/;e.prototype.$on=function(e,n){var r=this;if(Array.isArray(e))for(var i=0,o=e.length;i1?k(t):t;for(var n=k(arguments,1),r='event handler for "'+e+'"',i=0,o=t.length;iparseInt(this.max)&&On(a,s[0],s,this._vnode)),t.data.keepAlive=!0}return t||e&&e[0]}}};!function(e){var t={get:function(){return F}};Object.defineProperty(e,"config",t),e.util={warn:ae,extend:A,mergeOptions:De,defineReactive:xe},e.set=ke,e.delete=Ae,e.nextTick=Ye,e.observable=function(e){return Ce(e),e},e.options=Object.create(null),M.forEach(function(t){e.options[t+"s"]=Object.create(null)}),e.options._base=e,A(e.options.components,Tn),function(e){e.use=function(e){var t=this._installedPlugins||(this._installedPlugins=[]);if(t.indexOf(e)>-1)return this;var n=k(arguments,1);return n.unshift(this),"function"==typeof e.install?e.install.apply(e,n):"function"==typeof e&&e.apply(null,n),t.push(e),this}}(e),function(e){e.mixin=function(e){return this.options=De(this.options,e),this}}(e),Cn(e),function(e){M.forEach(function(t){e[t]=function(e,n){return n?("component"===t&&s(n)&&(n.name=n.name||e,n=this.options._base.extend(n)),"directive"===t&&"function"==typeof n&&(n={bind:n,update:n}),this.options[t+"s"][e]=n,n):this.options[t+"s"][e]}})}(e)}(wn),Object.defineProperty(wn.prototype,"$isServer",{get:te}),Object.defineProperty(wn.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(wn,"FunctionalRenderContext",{value:Tt}),wn.version="2.6.10";var En=p("style,class"),Nn=p("input,textarea,option,select,progress"),jn=function(e,t,n){return"value"===n&&Nn(e)&&"button"!==t||"selected"===n&&"option"===e||"checked"===n&&"input"===e||"muted"===n&&"video"===e},Dn=p("contenteditable,draggable,spellcheck"),Ln=p("events,caret,typing,plaintext-only"),Mn=function(e,t){return Hn(t)||"false"===t?"false":"contenteditable"===e&&Ln(t)?t:"true"},In=p("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),Fn="http://www.w3.org/1999/xlink",Pn=function(e){return":"===e.charAt(5)&&"xlink"===e.slice(0,5)},Rn=function(e){return Pn(e)?e.slice(6,e.length):""},Hn=function(e){return null==e||!1===e};function Bn(e){for(var t=e.data,r=e,i=e;n(i.componentInstance);)(i=i.componentInstance._vnode)&&i.data&&(t=Un(i.data,t));for(;n(r=r.parent);)r&&r.data&&(t=Un(t,r.data));return function(e,t){if(n(e)||n(t))return zn(e,Vn(t));return""}(t.staticClass,t.class)}function Un(e,t){return{staticClass:zn(e.staticClass,t.staticClass),class:n(e.class)?[e.class,t.class]:t.class}}function zn(e,t){return e?t?e+" "+t:e:t||""}function Vn(e){return Array.isArray(e)?function(e){for(var t,r="",i=0,o=e.length;i-1?hr(e,t,n):In(t)?Hn(n)?e.removeAttribute(t):(n="allowfullscreen"===t&&"EMBED"===e.tagName?"true":t,e.setAttribute(t,n)):Dn(t)?e.setAttribute(t,Mn(t,n)):Pn(t)?Hn(n)?e.removeAttributeNS(Fn,Rn(t)):e.setAttributeNS(Fn,t,n):hr(e,t,n)}function hr(e,t,n){if(Hn(n))e.removeAttribute(t);else{if(q&&!W&&"TEXTAREA"===e.tagName&&"placeholder"===t&&""!==n&&!e.__ieph){var r=function(t){t.stopImmediatePropagation(),e.removeEventListener("input",r)};e.addEventListener("input",r),e.__ieph=!0}e.setAttribute(t,n)}}var mr={create:dr,update:dr};function yr(e,r){var i=r.elm,o=r.data,a=e.data;if(!(t(o.staticClass)&&t(o.class)&&(t(a)||t(a.staticClass)&&t(a.class)))){var s=Bn(r),c=i._transitionClasses;n(c)&&(s=zn(s,Vn(c))),s!==i._prevClass&&(i.setAttribute("class",s),i._prevClass=s)}}var gr,_r,br,$r,wr,Cr,xr={create:yr,update:yr},kr=/[\w).+\-_$\]]/;function Ar(e){var t,n,r,i,o,a=!1,s=!1,c=!1,u=!1,l=0,f=0,p=0,d=0;for(r=0;r=0&&" "===(h=e.charAt(v));v--);h&&kr.test(h)||(u=!0)}}else void 0===i?(d=r+1,i=e.slice(0,r).trim()):m();function m(){(o||(o=[])).push(e.slice(d,r).trim()),d=r+1}if(void 0===i?i=e.slice(0,r).trim():0!==d&&m(),o)for(r=0;r-1?{exp:e.slice(0,$r),key:'"'+e.slice($r+1)+'"'}:{exp:e,key:null};_r=e,$r=wr=Cr=0;for(;!zr();)Vr(br=Ur())?Jr(br):91===br&&Kr(br);return{exp:e.slice(0,wr),key:e.slice(wr+1,Cr)}}(e);return null===n.key?e+"="+t:"$set("+n.exp+", "+n.key+", "+t+")"}function Ur(){return _r.charCodeAt(++$r)}function zr(){return $r>=gr}function Vr(e){return 34===e||39===e}function Kr(e){var t=1;for(wr=$r;!zr();)if(Vr(e=Ur()))Jr(e);else if(91===e&&t++,93===e&&t--,0===t){Cr=$r;break}}function Jr(e){for(var t=e;!zr()&&(e=Ur())!==t;);}var qr,Wr="__r",Zr="__c";function Gr(e,t,n){var r=qr;return function i(){null!==t.apply(null,arguments)&&Qr(e,i,n,r)}}var Xr=Ve&&!(X&&Number(X[1])<=53);function Yr(e,t,n,r){if(Xr){var i=an,o=t;t=o._wrapper=function(e){if(e.target===e.currentTarget||e.timeStamp>=i||e.timeStamp<=0||e.target.ownerDocument!==document)return o.apply(this,arguments)}}qr.addEventListener(e,t,Q?{capture:n,passive:r}:n)}function Qr(e,t,n,r){(r||qr).removeEventListener(e,t._wrapper||t,n)}function ei(e,r){if(!t(e.data.on)||!t(r.data.on)){var i=r.data.on||{},o=e.data.on||{};qr=r.elm,function(e){if(n(e[Wr])){var t=q?"change":"input";e[t]=[].concat(e[Wr],e[t]||[]),delete e[Wr]}n(e[Zr])&&(e.change=[].concat(e[Zr],e.change||[]),delete e[Zr])}(i),rt(i,o,Yr,Qr,Gr,r.context),qr=void 0}}var ti,ni={create:ei,update:ei};function ri(e,r){if(!t(e.data.domProps)||!t(r.data.domProps)){var i,o,a=r.elm,s=e.data.domProps||{},c=r.data.domProps||{};for(i in n(c.__ob__)&&(c=r.data.domProps=A({},c)),s)i in c||(a[i]="");for(i in c){if(o=c[i],"textContent"===i||"innerHTML"===i){if(r.children&&(r.children.length=0),o===s[i])continue;1===a.childNodes.length&&a.removeChild(a.childNodes[0])}if("value"===i&&"PROGRESS"!==a.tagName){a._value=o;var u=t(o)?"":String(o);ii(a,u)&&(a.value=u)}else if("innerHTML"===i&&qn(a.tagName)&&t(a.innerHTML)){(ti=ti||document.createElement("div")).innerHTML=""+o+"";for(var l=ti.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;l.firstChild;)a.appendChild(l.firstChild)}else if(o!==s[i])try{a[i]=o}catch(e){}}}}function ii(e,t){return!e.composing&&("OPTION"===e.tagName||function(e,t){var n=!0;try{n=document.activeElement!==e}catch(e){}return n&&e.value!==t}(e,t)||function(e,t){var r=e.value,i=e._vModifiers;if(n(i)){if(i.number)return f(r)!==f(t);if(i.trim)return r.trim()!==t.trim()}return r!==t}(e,t))}var oi={create:ri,update:ri},ai=g(function(e){var t={},n=/:(.+)/;return e.split(/;(?![^(]*\))/g).forEach(function(e){if(e){var r=e.split(n);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t});function si(e){var t=ci(e.style);return e.staticStyle?A(e.staticStyle,t):t}function ci(e){return Array.isArray(e)?O(e):"string"==typeof e?ai(e):e}var ui,li=/^--/,fi=/\s*!important$/,pi=function(e,t,n){if(li.test(t))e.style.setProperty(t,n);else if(fi.test(n))e.style.setProperty(C(t),n.replace(fi,""),"important");else{var r=vi(t);if(Array.isArray(n))for(var i=0,o=n.length;i-1?t.split(yi).forEach(function(t){return e.classList.add(t)}):e.classList.add(t);else{var n=" "+(e.getAttribute("class")||"")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function _i(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(yi).forEach(function(t){return e.classList.remove(t)}):e.classList.remove(t),e.classList.length||e.removeAttribute("class");else{for(var n=" "+(e.getAttribute("class")||"")+" ",r=" "+t+" ";n.indexOf(r)>=0;)n=n.replace(r," ");(n=n.trim())?e.setAttribute("class",n):e.removeAttribute("class")}}function bi(e){if(e){if("object"==typeof e){var t={};return!1!==e.css&&A(t,$i(e.name||"v")),A(t,e),t}return"string"==typeof e?$i(e):void 0}}var $i=g(function(e){return{enterClass:e+"-enter",enterToClass:e+"-enter-to",enterActiveClass:e+"-enter-active",leaveClass:e+"-leave",leaveToClass:e+"-leave-to",leaveActiveClass:e+"-leave-active"}}),wi=z&&!W,Ci="transition",xi="animation",ki="transition",Ai="transitionend",Oi="animation",Si="animationend";wi&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(ki="WebkitTransition",Ai="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Oi="WebkitAnimation",Si="webkitAnimationEnd"));var Ti=z?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(e){return e()};function Ei(e){Ti(function(){Ti(e)})}function Ni(e,t){var n=e._transitionClasses||(e._transitionClasses=[]);n.indexOf(t)<0&&(n.push(t),gi(e,t))}function ji(e,t){e._transitionClasses&&h(e._transitionClasses,t),_i(e,t)}function Di(e,t,n){var r=Mi(e,t),i=r.type,o=r.timeout,a=r.propCount;if(!i)return n();var s=i===Ci?Ai:Si,c=0,u=function(){e.removeEventListener(s,l),n()},l=function(t){t.target===e&&++c>=a&&u()};setTimeout(function(){c0&&(n=Ci,l=a,f=o.length):t===xi?u>0&&(n=xi,l=u,f=c.length):f=(n=(l=Math.max(a,u))>0?a>u?Ci:xi:null)?n===Ci?o.length:c.length:0,{type:n,timeout:l,propCount:f,hasTransform:n===Ci&&Li.test(r[ki+"Property"])}}function Ii(e,t){for(;e.length1}function Ui(e,t){!0!==t.data.show&&Pi(t)}var zi=function(e){var o,a,s={},c=e.modules,u=e.nodeOps;for(o=0;ov?_(e,t(i[y+1])?null:i[y+1].elm,i,d,y,o):d>y&&$(0,r,p,v)}(p,h,y,o,l):n(y)?(n(e.text)&&u.setTextContent(p,""),_(p,null,y,0,y.length-1,o)):n(h)?$(0,h,0,h.length-1):n(e.text)&&u.setTextContent(p,""):e.text!==i.text&&u.setTextContent(p,i.text),n(v)&&n(d=v.hook)&&n(d=d.postpatch)&&d(e,i)}}}function k(e,t,i){if(r(i)&&n(e.parent))e.parent.data.pendingInsert=t;else for(var o=0;o-1,a.selected!==o&&(a.selected=o);else if(N(Wi(a),r))return void(e.selectedIndex!==s&&(e.selectedIndex=s));i||(e.selectedIndex=-1)}}function qi(e,t){return t.every(function(t){return!N(t,e)})}function Wi(e){return"_value"in e?e._value:e.value}function Zi(e){e.target.composing=!0}function Gi(e){e.target.composing&&(e.target.composing=!1,Xi(e.target,"input"))}function Xi(e,t){var n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}function Yi(e){return!e.componentInstance||e.data&&e.data.transition?e:Yi(e.componentInstance._vnode)}var Qi={model:Vi,show:{bind:function(e,t,n){var r=t.value,i=(n=Yi(n)).data&&n.data.transition,o=e.__vOriginalDisplay="none"===e.style.display?"":e.style.display;r&&i?(n.data.show=!0,Pi(n,function(){e.style.display=o})):e.style.display=r?o:"none"},update:function(e,t,n){var r=t.value;!r!=!t.oldValue&&((n=Yi(n)).data&&n.data.transition?(n.data.show=!0,r?Pi(n,function(){e.style.display=e.__vOriginalDisplay}):Ri(n,function(){e.style.display="none"})):e.style.display=r?e.__vOriginalDisplay:"none")},unbind:function(e,t,n,r,i){i||(e.style.display=e.__vOriginalDisplay)}}},eo={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function to(e){var t=e&&e.componentOptions;return t&&t.Ctor.options.abstract?to(zt(t.children)):e}function no(e){var t={},n=e.$options;for(var r in n.propsData)t[r]=e[r];var i=n._parentListeners;for(var o in i)t[b(o)]=i[o];return t}function ro(e,t){if(/\d-keep-alive$/.test(t.tag))return e("keep-alive",{props:t.componentOptions.propsData})}var io=function(e){return e.tag||Ut(e)},oo=function(e){return"show"===e.name},ao={name:"transition",props:eo,abstract:!0,render:function(e){var t=this,n=this.$slots.default;if(n&&(n=n.filter(io)).length){var r=this.mode,o=n[0];if(function(e){for(;e=e.parent;)if(e.data.transition)return!0}(this.$vnode))return o;var a=to(o);if(!a)return o;if(this._leaving)return ro(e,o);var s="__transition-"+this._uid+"-";a.key=null==a.key?a.isComment?s+"comment":s+a.tag:i(a.key)?0===String(a.key).indexOf(s)?a.key:s+a.key:a.key;var c=(a.data||(a.data={})).transition=no(this),u=this._vnode,l=to(u);if(a.data.directives&&a.data.directives.some(oo)&&(a.data.show=!0),l&&l.data&&!function(e,t){return t.key===e.key&&t.tag===e.tag}(a,l)&&!Ut(l)&&(!l.componentInstance||!l.componentInstance._vnode.isComment)){var f=l.data.transition=A({},c);if("out-in"===r)return this._leaving=!0,it(f,"afterLeave",function(){t._leaving=!1,t.$forceUpdate()}),ro(e,o);if("in-out"===r){if(Ut(a))return u;var p,d=function(){p()};it(c,"afterEnter",d),it(c,"enterCancelled",d),it(f,"delayLeave",function(e){p=e})}}return o}}},so=A({tag:String,moveClass:String},eo);function co(e){e.elm._moveCb&&e.elm._moveCb(),e.elm._enterCb&&e.elm._enterCb()}function uo(e){e.data.newPos=e.elm.getBoundingClientRect()}function lo(e){var t=e.data.pos,n=e.data.newPos,r=t.left-n.left,i=t.top-n.top;if(r||i){e.data.moved=!0;var o=e.elm.style;o.transform=o.WebkitTransform="translate("+r+"px,"+i+"px)",o.transitionDuration="0s"}}delete so.mode;var fo={Transition:ao,TransitionGroup:{props:so,beforeMount:function(){var e=this,t=this._update;this._update=function(n,r){var i=Zt(e);e.__patch__(e._vnode,e.kept,!1,!0),e._vnode=e.kept,i(),t.call(e,n,r)}},render:function(e){for(var t=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,i=this.$slots.default||[],o=this.children=[],a=no(this),s=0;s-1?Gn[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:Gn[e]=/HTMLUnknownElement/.test(t.toString())},A(wn.options.directives,Qi),A(wn.options.components,fo),wn.prototype.__patch__=z?zi:S,wn.prototype.$mount=function(e,t){return function(e,t,n){var r;return e.$el=t,e.$options.render||(e.$options.render=ve),Yt(e,"beforeMount"),r=function(){e._update(e._render(),n)},new fn(e,r,S,{before:function(){e._isMounted&&!e._isDestroyed&&Yt(e,"beforeUpdate")}},!0),n=!1,null==e.$vnode&&(e._isMounted=!0,Yt(e,"mounted")),e}(this,e=e&&z?Yn(e):void 0,t)},z&&setTimeout(function(){F.devtools&&ne&&ne.emit("init",wn)},0);var po=/\{\{((?:.|\r?\n)+?)\}\}/g,vo=/[-.*+?^${}()|[\]\/\\]/g,ho=g(function(e){var t=e[0].replace(vo,"\\$&"),n=e[1].replace(vo,"\\$&");return new RegExp(t+"((?:.|\\n)+?)"+n,"g")});var mo={staticKeys:["staticClass"],transformNode:function(e,t){t.warn;var n=Fr(e,"class");n&&(e.staticClass=JSON.stringify(n));var r=Ir(e,"class",!1);r&&(e.classBinding=r)},genData:function(e){var t="";return e.staticClass&&(t+="staticClass:"+e.staticClass+","),e.classBinding&&(t+="class:"+e.classBinding+","),t}};var yo,go={staticKeys:["staticStyle"],transformNode:function(e,t){t.warn;var n=Fr(e,"style");n&&(e.staticStyle=JSON.stringify(ai(n)));var r=Ir(e,"style",!1);r&&(e.styleBinding=r)},genData:function(e){var t="";return e.staticStyle&&(t+="staticStyle:"+e.staticStyle+","),e.styleBinding&&(t+="style:("+e.styleBinding+"),"),t}},_o=function(e){return(yo=yo||document.createElement("div")).innerHTML=e,yo.textContent},bo=p("area,base,br,col,embed,frame,hr,img,input,isindex,keygen,link,meta,param,source,track,wbr"),$o=p("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source"),wo=p("address,article,aside,base,blockquote,body,caption,col,colgroup,dd,details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,title,tr,track"),Co=/^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,xo=/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,ko="[a-zA-Z_][\\-\\.0-9_a-zA-Z"+P.source+"]*",Ao="((?:"+ko+"\\:)?"+ko+")",Oo=new RegExp("^<"+Ao),So=/^\s*(\/?)>/,To=new RegExp("^<\\/"+Ao+"[^>]*>"),Eo=/^]+>/i,No=/^",""":'"',"&":"&"," ":"\n"," ":"\t","'":"'"},Io=/&(?:lt|gt|quot|amp|#39);/g,Fo=/&(?:lt|gt|quot|amp|#39|#10|#9);/g,Po=p("pre,textarea",!0),Ro=function(e,t){return e&&Po(e)&&"\n"===t[0]};function Ho(e,t){var n=t?Fo:Io;return e.replace(n,function(e){return Mo[e]})}var Bo,Uo,zo,Vo,Ko,Jo,qo,Wo,Zo=/^@|^v-on:/,Go=/^v-|^@|^:/,Xo=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,Yo=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,Qo=/^\(|\)$/g,ea=/^\[.*\]$/,ta=/:(.*)$/,na=/^:|^\.|^v-bind:/,ra=/\.[^.\]]+(?=[^\]]*$)/g,ia=/^v-slot(:|$)|^#/,oa=/[\r\n]/,aa=/\s+/g,sa=g(_o),ca="_empty_";function ua(e,t,n){return{type:1,tag:e,attrsList:t,attrsMap:ma(t),rawAttrsMap:{},parent:n,children:[]}}function la(e,t){Bo=t.warn||Sr,Jo=t.isPreTag||T,qo=t.mustUseProp||T,Wo=t.getTagNamespace||T;t.isReservedTag;zo=Tr(t.modules,"transformNode"),Vo=Tr(t.modules,"preTransformNode"),Ko=Tr(t.modules,"postTransformNode"),Uo=t.delimiters;var n,r,i=[],o=!1!==t.preserveWhitespace,a=t.whitespace,s=!1,c=!1;function u(e){if(l(e),s||e.processed||(e=fa(e,t)),i.length||e===n||n.if&&(e.elseif||e.else)&&da(n,{exp:e.elseif,block:e}),r&&!e.forbidden)if(e.elseif||e.else)a=e,(u=function(e){var t=e.length;for(;t--;){if(1===e[t].type)return e[t];e.pop()}}(r.children))&&u.if&&da(u,{exp:a.elseif,block:a});else{if(e.slotScope){var o=e.slotTarget||'"default"';(r.scopedSlots||(r.scopedSlots={}))[o]=e}r.children.push(e),e.parent=r}var a,u;e.children=e.children.filter(function(e){return!e.slotScope}),l(e),e.pre&&(s=!1),Jo(e.tag)&&(c=!1);for(var f=0;f]*>)","i")),p=e.replace(f,function(e,n,r){return u=r.length,Do(l)||"noscript"===l||(n=n.replace(//g,"$1").replace(//g,"$1")),Ro(l,n)&&(n=n.slice(1)),t.chars&&t.chars(n),""});c+=e.length-p.length,e=p,A(l,c-u,c)}else{var d=e.indexOf("<");if(0===d){if(No.test(e)){var v=e.indexOf("--\x3e");if(v>=0){t.shouldKeepComment&&t.comment(e.substring(4,v),c,c+v+3),C(v+3);continue}}if(jo.test(e)){var h=e.indexOf("]>");if(h>=0){C(h+2);continue}}var m=e.match(Eo);if(m){C(m[0].length);continue}var y=e.match(To);if(y){var g=c;C(y[0].length),A(y[1],g,c);continue}var _=x();if(_){k(_),Ro(_.tagName,e)&&C(1);continue}}var b=void 0,$=void 0,w=void 0;if(d>=0){for($=e.slice(d);!(To.test($)||Oo.test($)||No.test($)||jo.test($)||(w=$.indexOf("<",1))<0);)d+=w,$=e.slice(d);b=e.substring(0,d)}d<0&&(b=e),b&&C(b.length),t.chars&&b&&t.chars(b,c-b.length,c)}if(e===n){t.chars&&t.chars(e);break}}function C(t){c+=t,e=e.substring(t)}function x(){var t=e.match(Oo);if(t){var n,r,i={tagName:t[1],attrs:[],start:c};for(C(t[0].length);!(n=e.match(So))&&(r=e.match(xo)||e.match(Co));)r.start=c,C(r[0].length),r.end=c,i.attrs.push(r);if(n)return i.unarySlash=n[1],C(n[0].length),i.end=c,i}}function k(e){var n=e.tagName,c=e.unarySlash;o&&("p"===r&&wo(n)&&A(r),s(n)&&r===n&&A(n));for(var u=a(n)||!!c,l=e.attrs.length,f=new Array(l),p=0;p=0&&i[a].lowerCasedTag!==s;a--);else a=0;if(a>=0){for(var u=i.length-1;u>=a;u--)t.end&&t.end(i[u].tag,n,o);i.length=a,r=a&&i[a-1].tag}else"br"===s?t.start&&t.start(e,[],!0,n,o):"p"===s&&(t.start&&t.start(e,[],!1,n,o),t.end&&t.end(e,n,o))}A()}(e,{warn:Bo,expectHTML:t.expectHTML,isUnaryTag:t.isUnaryTag,canBeLeftOpenTag:t.canBeLeftOpenTag,shouldDecodeNewlines:t.shouldDecodeNewlines,shouldDecodeNewlinesForHref:t.shouldDecodeNewlinesForHref,shouldKeepComment:t.comments,outputSourceRange:t.outputSourceRange,start:function(e,o,a,l,f){var p=r&&r.ns||Wo(e);q&&"svg"===p&&(o=function(e){for(var t=[],n=0;nc&&(s.push(o=e.slice(c,i)),a.push(JSON.stringify(o)));var u=Ar(r[1].trim());a.push("_s("+u+")"),s.push({"@binding":u}),c=i+r[0].length}return c-1"+("true"===o?":("+t+")":":_q("+t+","+o+")")),Mr(e,"change","var $$a="+t+",$$el=$event.target,$$c=$$el.checked?("+o+"):("+a+");if(Array.isArray($$a)){var $$v="+(r?"_n("+i+")":i)+",$$i=_i($$a,$$v);if($$el.checked){$$i<0&&("+Br(t,"$$a.concat([$$v])")+")}else{$$i>-1&&("+Br(t,"$$a.slice(0,$$i).concat($$a.slice($$i+1))")+")}}else{"+Br(t,"$$c")+"}",null,!0)}(e,r,i);else if("input"===o&&"radio"===a)!function(e,t,n){var r=n&&n.number,i=Ir(e,"value")||"null";Er(e,"checked","_q("+t+","+(i=r?"_n("+i+")":i)+")"),Mr(e,"change",Br(t,i),null,!0)}(e,r,i);else if("input"===o||"textarea"===o)!function(e,t,n){var r=e.attrsMap.type,i=n||{},o=i.lazy,a=i.number,s=i.trim,c=!o&&"range"!==r,u=o?"change":"range"===r?Wr:"input",l="$event.target.value";s&&(l="$event.target.value.trim()"),a&&(l="_n("+l+")");var f=Br(t,l);c&&(f="if($event.target.composing)return;"+f),Er(e,"value","("+t+")"),Mr(e,u,f,null,!0),(s||a)&&Mr(e,"blur","$forceUpdate()")}(e,r,i);else if(!F.isReservedTag(o))return Hr(e,r,i),!1;return!0},text:function(e,t){t.value&&Er(e,"textContent","_s("+t.value+")",t)},html:function(e,t){t.value&&Er(e,"innerHTML","_s("+t.value+")",t)}},isPreTag:function(e){return"pre"===e},isUnaryTag:bo,mustUseProp:jn,canBeLeftOpenTag:$o,isReservedTag:Wn,getTagNamespace:Zn,staticKeys:function(e){return e.reduce(function(e,t){return e.concat(t.staticKeys||[])},[]).join(",")}(ba)},xa=g(function(e){return p("type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap"+(e?","+e:""))});function ka(e,t){e&&($a=xa(t.staticKeys||""),wa=t.isReservedTag||T,function e(t){t.static=function(e){if(2===e.type)return!1;if(3===e.type)return!0;return!(!e.pre&&(e.hasBindings||e.if||e.for||d(e.tag)||!wa(e.tag)||function(e){for(;e.parent;){if("template"!==(e=e.parent).tag)return!1;if(e.for)return!0}return!1}(e)||!Object.keys(e).every($a)))}(t);if(1===t.type){if(!wa(t.tag)&&"slot"!==t.tag&&null==t.attrsMap["inline-template"])return;for(var n=0,r=t.children.length;n|^function\s*(?:[\w$]+)?\s*\(/,Oa=/\([^)]*?\);*$/,Sa=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,Ta={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},Ea={esc:["Esc","Escape"],tab:"Tab",enter:"Enter",space:[" ","Spacebar"],up:["Up","ArrowUp"],left:["Left","ArrowLeft"],right:["Right","ArrowRight"],down:["Down","ArrowDown"],delete:["Backspace","Delete","Del"]},Na=function(e){return"if("+e+")return null;"},ja={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:Na("$event.target !== $event.currentTarget"),ctrl:Na("!$event.ctrlKey"),shift:Na("!$event.shiftKey"),alt:Na("!$event.altKey"),meta:Na("!$event.metaKey"),left:Na("'button' in $event && $event.button !== 0"),middle:Na("'button' in $event && $event.button !== 1"),right:Na("'button' in $event && $event.button !== 2")};function Da(e,t){var n=t?"nativeOn:":"on:",r="",i="";for(var o in e){var a=La(e[o]);e[o]&&e[o].dynamic?i+=o+","+a+",":r+='"'+o+'":'+a+","}return r="{"+r.slice(0,-1)+"}",i?n+"_d("+r+",["+i.slice(0,-1)+"])":n+r}function La(e){if(!e)return"function(){}";if(Array.isArray(e))return"["+e.map(function(e){return La(e)}).join(",")+"]";var t=Sa.test(e.value),n=Aa.test(e.value),r=Sa.test(e.value.replace(Oa,""));if(e.modifiers){var i="",o="",a=[];for(var s in e.modifiers)if(ja[s])o+=ja[s],Ta[s]&&a.push(s);else if("exact"===s){var c=e.modifiers;o+=Na(["ctrl","shift","alt","meta"].filter(function(e){return!c[e]}).map(function(e){return"$event."+e+"Key"}).join("||"))}else a.push(s);return a.length&&(i+=function(e){return"if(!$event.type.indexOf('key')&&"+e.map(Ma).join("&&")+")return null;"}(a)),o&&(i+=o),"function($event){"+i+(t?"return "+e.value+"($event)":n?"return ("+e.value+")($event)":r?"return "+e.value:e.value)+"}"}return t||n?e.value:"function($event){"+(r?"return "+e.value:e.value)+"}"}function Ma(e){var t=parseInt(e,10);if(t)return"$event.keyCode!=="+t;var n=Ta[e],r=Ea[e];return"_k($event.keyCode,"+JSON.stringify(e)+","+JSON.stringify(n)+",$event.key,"+JSON.stringify(r)+")"}var Ia={on:function(e,t){e.wrapListeners=function(e){return"_g("+e+","+t.value+")"}},bind:function(e,t){e.wrapData=function(n){return"_b("+n+",'"+e.tag+"',"+t.value+","+(t.modifiers&&t.modifiers.prop?"true":"false")+(t.modifiers&&t.modifiers.sync?",true":"")+")"}},cloak:S},Fa=function(e){this.options=e,this.warn=e.warn||Sr,this.transforms=Tr(e.modules,"transformCode"),this.dataGenFns=Tr(e.modules,"genData"),this.directives=A(A({},Ia),e.directives);var t=e.isReservedTag||T;this.maybeComponent=function(e){return!!e.component||!t(e.tag)},this.onceId=0,this.staticRenderFns=[],this.pre=!1};function Pa(e,t){var n=new Fa(t);return{render:"with(this){return "+(e?Ra(e,n):'_c("div")')+"}",staticRenderFns:n.staticRenderFns}}function Ra(e,t){if(e.parent&&(e.pre=e.pre||e.parent.pre),e.staticRoot&&!e.staticProcessed)return Ha(e,t);if(e.once&&!e.onceProcessed)return Ba(e,t);if(e.for&&!e.forProcessed)return za(e,t);if(e.if&&!e.ifProcessed)return Ua(e,t);if("template"!==e.tag||e.slotTarget||t.pre){if("slot"===e.tag)return function(e,t){var n=e.slotName||'"default"',r=qa(e,t),i="_t("+n+(r?","+r:""),o=e.attrs||e.dynamicAttrs?Ga((e.attrs||[]).concat(e.dynamicAttrs||[]).map(function(e){return{name:b(e.name),value:e.value,dynamic:e.dynamic}})):null,a=e.attrsMap["v-bind"];!o&&!a||r||(i+=",null");o&&(i+=","+o);a&&(i+=(o?"":",null")+","+a);return i+")"}(e,t);var n;if(e.component)n=function(e,t,n){var r=t.inlineTemplate?null:qa(t,n,!0);return"_c("+e+","+Va(t,n)+(r?","+r:"")+")"}(e.component,e,t);else{var r;(!e.plain||e.pre&&t.maybeComponent(e))&&(r=Va(e,t));var i=e.inlineTemplate?null:qa(e,t,!0);n="_c('"+e.tag+"'"+(r?","+r:"")+(i?","+i:"")+")"}for(var o=0;o>>0}(a):"")+")"}(e,e.scopedSlots,t)+","),e.model&&(n+="model:{value:"+e.model.value+",callback:"+e.model.callback+",expression:"+e.model.expression+"},"),e.inlineTemplate){var o=function(e,t){var n=e.children[0];if(n&&1===n.type){var r=Pa(n,t.options);return"inlineTemplate:{render:function(){"+r.render+"},staticRenderFns:["+r.staticRenderFns.map(function(e){return"function(){"+e+"}"}).join(",")+"]}"}}(e,t);o&&(n+=o+",")}return n=n.replace(/,$/,"")+"}",e.dynamicAttrs&&(n="_b("+n+',"'+e.tag+'",'+Ga(e.dynamicAttrs)+")"),e.wrapData&&(n=e.wrapData(n)),e.wrapListeners&&(n=e.wrapListeners(n)),n}function Ka(e){return 1===e.type&&("slot"===e.tag||e.children.some(Ka))}function Ja(e,t){var n=e.attrsMap["slot-scope"];if(e.if&&!e.ifProcessed&&!n)return Ua(e,t,Ja,"null");if(e.for&&!e.forProcessed)return za(e,t,Ja);var r=e.slotScope===ca?"":String(e.slotScope),i="function("+r+"){return "+("template"===e.tag?e.if&&n?"("+e.if+")?"+(qa(e,t)||"undefined")+":undefined":qa(e,t)||"undefined":Ra(e,t))+"}",o=r?"":",proxy:true";return"{key:"+(e.slotTarget||'"default"')+",fn:"+i+o+"}"}function qa(e,t,n,r,i){var o=e.children;if(o.length){var a=o[0];if(1===o.length&&a.for&&"template"!==a.tag&&"slot"!==a.tag){var s=n?t.maybeComponent(a)?",1":",0":"";return""+(r||Ra)(a,t)+s}var c=n?function(e,t){for(var n=0,r=0;r':'
',ts.innerHTML.indexOf(" ")>0}var os=!!z&&is(!1),as=!!z&&is(!0),ss=g(function(e){var t=Yn(e);return t&&t.innerHTML}),cs=wn.prototype.$mount;return wn.prototype.$mount=function(e,t){if((e=e&&Yn(e))===document.body||e===document.documentElement)return this;var n=this.$options;if(!n.render){var r=n.template;if(r)if("string"==typeof r)"#"===r.charAt(0)&&(r=ss(r));else{if(!r.nodeType)return this;r=r.innerHTML}else e&&(r=function(e){if(e.outerHTML)return e.outerHTML;var t=document.createElement("div");return t.appendChild(e.cloneNode(!0)),t.innerHTML}(e));if(r){var i=rs(r,{outputSourceRange:!1,shouldDecodeNewlines:os,shouldDecodeNewlinesForHref:as,delimiters:n.delimiters,comments:n.comments},this),o=i.render,a=i.staticRenderFns;n.render=o,n.staticRenderFns=a}}return cs.call(this,e,t)},wn.compile=rs,wn}); diff --git a/scripts/magnify_image.js b/scripts/magnify_image.js index 4689542a..9c960197 100644 --- a/scripts/magnify_image.js +++ b/scripts/magnify_image.js @@ -1,8 +1,29 @@ +import { getLargestImageSrc } from "./auto_redirectLargestImageSrc.js"; import { UfsGlobal } from "./content-scripts/ufs_global.js"; import { BADGES } from "./helpers/badge.js"; const contextMenuId = "magnify-image"; +const CACHED = { + mouse: { x: 0, y: 0 }, + configSize: null, +}; + +const MagnifySizeKey = "ufs_magnify_image_size"; +const getConfigSize = async () => { + if (!CACHED.configSize) { + const { Storage } = await import("./helpers/utils.js"); + const data = await Storage.get(MagnifySizeKey); + CACHED.configSize = data?.split("x") || [20, 20]; + } + return CACHED.configSize; +}; +const setConfigSize = async (width, height) => { + const { Storage } = await import("./helpers/utils.js"); + CACHED.configSize = [width, height]; + return Storage.set(MagnifySizeKey, width + "x" + height); +}; + export default { icon: '', name: { @@ -50,7 +71,64 @@ export default { "2024-05-21": "not require autorun", "2024-06-07": "support video frame + right click anywhere + magnify on hover + search by images", + "2024-07-28": "config img size for hover", }, + buttons: [ + { + icon: '', + name: { + vi: "Cài đặt", + en: "Setting", + }, + onClick: async () => { + const { t } = await import("../popup/helpers/lang.js"); + const [width, height] = await getConfigSize(); + + const result = await Swal.fire({ + title: t({ vi: "Cài đặt phóng to", en: "Magnify Setting" }), + html: ` +

${t({ + vi: "Chỉ hiện nút phóng to cho hình ảnh có kích thước lớn hơn:", + en: "Only show the magnify button when the image's size is larger than:", + })}

+ X + + `, + preConfirm: () => { + return [ + document.getElementById("swal-input1").value, + document.getElementById("swal-input2").value, + ]; + }, + }); + + if (result.isConfirmed) { + const [width, height] = result.value; + await setConfigSize(width, height); + Swal.fire({ + icon: "success", + title: t({ vi: "Thành công", en: "Success" }), + text: t({ + vi: "Cài đặt phóng to đã được cập nhật.", + en: "Magnify setting has been updated.", + }), + }); + } + }, + }, + ], popupScript: { onClick: () => { @@ -100,6 +178,11 @@ export default { }, onDocumentStart_: async () => { + window.addEventListener("mousemove", (e) => { + CACHED.mouse.x = e.clientX; + CACHED.mouse.y = e.clientY; + }); + injectCss(); let hovering = null; @@ -128,14 +211,18 @@ export default { addToDom(); UfsGlobal.DOM.onElementRemoved(div, addToDom); - window.addEventListener("mouseover", (e) => { + window.addEventListener("mouseover", async (e) => { + const [width, height] = await getConfigSize(); + if (e.target.clientWidth < width || e.target.clientHeight < height) + return; + let srcs = getImgSrcsFromElement(e.target); if (!srcs?.length) { div.classList.toggle("hide", e.target !== div); return; } - let rect = UfsGlobal.DOM.getContentClientRect(e.target); + let rect = getContentClientRect(e.target); if (rect.width < 30 || rect.height < 30) { rect.top -= rect.width / 2; rect.left -= rect.height / 2; @@ -152,9 +239,9 @@ export default { // only main frame if (window === window.top) { // ctrl - let unsub = UfsGlobal.DOM.onDoublePress("Control", () => { + let unsub = onDoublePress("Control", () => { UfsGlobal.Extension.trackEvent("magnify-image-CTRL"); - let mouse = UfsGlobal.DOM.getMousePos(); + let mouse = getMousePos(); magnifyImage(mouse.x, mouse.y); }); @@ -234,10 +321,13 @@ function injectCss( }); } } +function getMousePos() { + return CACHED.mouse; +} function validateMouse(x, y) { if (x == null || y == null) { - let mouse = UfsGlobal.DOM.getMousePos(); + let mouse = getMousePos(); return { x: mouse.x ?? x ?? 0, y: mouse.y ?? y ?? 0, @@ -253,7 +343,7 @@ function magnifyImage(x, y) { setTimeout(() => { if (!loaded) - removeLoading = UfsGlobal.DOM.addLoadingAnimationAtPos( + removeLoading = addLoadingAnimationAtPos( mouse.x, mouse.y, 40, @@ -374,7 +464,7 @@ function getImgSrcsFromElement(ele) { } } if (!srcset) return; - return UfsGlobal.Utils.getLargestSrcset(srcset); + return getLargestSrcset(srcset); }, () => { // if (/img|picture|source|image|a/i.test(ele.tagName)) @@ -393,10 +483,7 @@ function getImgSrcsFromElement(ele) { return ele.getAttribute("href"); // reddit } if (/svg/i.test(ele.tagName)) { - return [ - UfsGlobal.Utils.svgToBase64(ele), - UfsGlobal.Utils.svgToBlobUrl(ele), - ]; + return [svgToBase64(ele), svgToBlobUrl(ele)]; } if (/canvas/i.test(ele.tagName)) { return ele.toDataURL(); @@ -471,6 +558,22 @@ async function filterImageSrcs(eleSrcs) { } } +function getLargestSrcset(srcset) { + let srcs = srcset.split(/[xw],/i), + largeSize = -1, + largeSrc = null; + if (!srcs.length) return null; + srcs.forEach((srci) => { + let srcInfo = srci.trim().split(/(\s+|%20)/), + curSize = parseInt(srcInfo[2] || 0); + if (srcInfo[0] && curSize > largeSize) { + largeSize = curSize; + largeSrc = srcInfo[0]; + } + }); + return largeSrc; +} + async function getImagesAtPos(x, y) { let eles = Array.from(document.querySelectorAll("*")); @@ -707,7 +810,7 @@ function chooseImg(srcs, _x, _y) { }; img.onclick = () => { // overlay.remove(); - let mouse = UfsGlobal.DOM.getMousePos(); + let mouse = getMousePos(); createPreview( src, mouse.x, @@ -800,7 +903,7 @@ function createPreview( function updateSize() { if (img.naturalWidth && img.naturalHeight) { - let resolution = UfsGlobal.Utils.getResolutionCategory( + let resolution = getResolutionCategory( img.naturalWidth, img.naturalHeight ); @@ -818,7 +921,7 @@ function createPreview( } } - const { destroy, animateTo } = UfsGlobal.DOM.enableDragAndZoom( + const { destroy, animateTo } = enableDragAndZoom( img, overlay, (updatedValue) => { @@ -827,7 +930,7 @@ function createPreview( ); let removeAnimLoading; setTimeout(() => { - removeAnimLoading = UfsGlobal.DOM.addLoadingAnimation(animDiv, 40); + removeAnimLoading = addLoadingAnimation(animDiv, 40); }, 500); // close on click outside @@ -983,7 +1086,7 @@ function createPreview( }; openNewTab.onclick = () => { if (/^data:image\/svg/.test(img.src)) { - const url = UfsGlobal.Utils.svgBase64ToUrl(img.src); + const url = svgBase64ToUrl(img.src); window.open(url, "_blank"); } else { window.open(img.src, "_blank"); @@ -1027,7 +1130,7 @@ function createPreview( }); setTimeout(() => { if (!loaded) { - loadingRef = UfsGlobal.DOM.addLoadingAnimationAtPos( + loadingRef = addLoadingAnimationAtPos( window.innerWidth / 2, window.innerHeight - 130 ); @@ -1042,7 +1145,7 @@ function createPreview( }, 100); }, 300); // show loading after 300ms - UfsGlobal.Utils.getLargestImageSrc(src, location.href).then((_src) => { + getLargestImageSrc(src, location.href).then((_src) => { if (!_src || _src == src) { loaded = true; UfsGlobal.DOM.notify({ @@ -1080,3 +1183,313 @@ function createPreview( } // #endregion + +// #region utils + +function addLoadingAnimationAtPos( + x, + y, + size = 40, + containerStyle = "", + loadingStyle = "" +) { + let ele = document.createElement("div"); + ele.style.cssText = ` + position: fixed; + left: ${x - size / 2}px; + top: ${y - size / 2}px; + width: ${size}px; + height: ${size}px; + z-index: 2147483647; + pointer-events: none; + user-select: none; + ${containerStyle} + `; + addLoadingAnimation(ele, size, loadingStyle); + document.body.appendChild(ele); + return () => ele.remove(); +} +function addLoadingAnimation( + element, + size = Math.min(element?.clientWidth, element?.clientHeight) || 0, + customStyle = "" +) { + let id = Math.random().toString(36).substr(2, 9); + element.classList.add("ufs-loading-" + id); + + let borderSize = 4; + + // inject css code + let style = document.createElement("style"); + style.id = "ufs-loading-style-" + id; + style.textContent = ` + .ufs-loading-${id}::after { + content: ""; + display: block; + position: absolute; + top: 50%; + left: 50%; + width: ${size}px; + height: ${size}px; + margin-top: -${size / 2}px; + margin-left: -${size / 2}px; + border-radius: 50%; + border: ${borderSize}px solid #555 !important; + border-top-color: #eee !important; + animation: ufs-spin 1s ease-in-out infinite; + box-sizing: border-box !important; + ${customStyle} + } + @keyframes ufs-spin { + to { + transform: rotate(360deg); + } + } + `; + (document.head || document.documentElement).appendChild(style); + + return () => { + if (element) element.classList.remove("ufs-loading-" + id); + }; +} +function addEventListener(target, event, callback, options) { + target.addEventListener(event, callback, options); + return () => target.removeEventListener(event, callback, options); +} +function enableDragAndZoom(element, container, onUpdateCallback) { + // set style + const className = "ufs-drag-and-zoom"; + element.classList.add(className); + + let style = document.createElement("style"); + style.textContent = ` + .${className} { + cursor: grab; + position: relative !important; + -webkit-user-select: none !important; + -moz-user-select: none !important; + -ms-user-select: none !important; + user-select: none !important; + -khtml-user-select: none; + max-width: unset !important; + max-height: unset !important; + min-width: unset !important; + min-height: unset !important; + -webkit-user-drag: none !important; + }`; + (container || element).appendChild(style); + + // config + const lerpSpeed = 0.3; + const last = { x: 0, y: 0 }; + const mouse = { x: 0, y: 0 }; + const animTarget = { + left: parseFloat(element.style.left), + top: parseFloat(element.style.top), + width: parseFloat(element.style.width), + height: parseFloat(element.style.height), + }; + + let run = true; + function animate() { + let updated = false; + let updatedValue = {}; + for (let prop in animTarget) { + const currentValue = parseFloat(element.style[prop]); + const targetValue = animTarget[prop]; + let del = Math.abs(targetValue - currentValue); + + if (del > 0.1) { + const newValue = + del < 1 ? targetValue : lerp(currentValue, targetValue, lerpSpeed); + element.style[prop] = newValue + "px"; + updatedValue[prop] = newValue; + updated = true; + } + } + if (updated) onUpdateCallback?.(updatedValue); + if (run) requestAnimationFrame(animate); + } + + animate(); + + // Mouse down event listener + let dragging = false; + let _down = addEventListener(container || element, "mousedown", function (e) { + e.preventDefault(); + dragging = true; + last.x = e.clientX; + last.y = e.clientY; + element.style.cursor = "grabbing"; + }); + + // Mouse move event listener + let _move = addEventListener(document, "mousemove", function (e) { + mouse.x = e.clientX; + mouse.y = e.clientY; + if (dragging) { + let delX = e.clientX - last.x; + let delY = e.clientY - last.y; + + animTarget.left += delX; + animTarget.top += delY; + + last.x = e.clientX; + last.y = e.clientY; + } + }); + + // Mouse up event listener + let _up = addEventListener(document, "mouseup", function () { + dragging = false; + element.style.cursor = "grab"; + }); + + // Mouse leave event listener + let _leave = addEventListener(document, "mouseleave", function () { + dragging = false; + element.style.cursor = "grab"; + }); + + // Mouse wheel event listener for zooming + let _wheel = addEventListener(container || element, "wheel", function (e) { + e.preventDefault(); + + const curScale = parseFloat(element.style.width) / element.width; + const delta = -e.wheelDeltaY || -e.wheelDelta; + const factor = Math.abs((0.3 * delta) / 120); + const newScale = + delta > 0 ? curScale * (1 - factor) : curScale * (1 + factor); + + const newW = element.width * newScale; + const newH = element.height * newScale; + + if (newW < 10 || newH < 10) { + return; + } + + const left = parseFloat(element.style.left); + const top = parseFloat(element.style.top); + const offsetX = mouse.x - left; + const offsetY = mouse.y - top; + const newLeft = left - (newW - element.width) * (offsetX / element.width); + const newTop = top - (newH - element.height) * (offsetY / element.height); + + animTarget.left = newLeft; + animTarget.top = newTop; + animTarget.width = newW; + animTarget.height = newH; + }); + + let listeners = [_down, _move, _up, _leave, _wheel]; + + return { + animateTo: (x, y, w, h) => { + animTarget.left = x; + animTarget.top = y; + animTarget.width = w; + animTarget.height = h; + }, + destroy: () => { + run = false; + style.remove(); + element.classList.remove(className); + listeners.forEach((l) => l?.()); + }, + }; +} +// prettier-ignore +function getContentClientRect(target, win = window) { + let rect = target.getBoundingClientRect(); + let compStyle = win.getComputedStyle(target); + let pFloat = parseFloat; + let top = rect.top + pFloat(compStyle.paddingTop) + pFloat(compStyle.borderTopWidth); + let right = rect.right - pFloat(compStyle.paddingRight) - pFloat(compStyle.borderRightWidth); + let bottom = rect.bottom - pFloat(compStyle.paddingBottom) - pFloat(compStyle.borderBottomWidth); + let left = rect.left + pFloat(compStyle.paddingLeft) + pFloat(compStyle.borderLeftWidth); + return { + top : top, + right : right, + bottom : bottom, + left : left, + width : right-left, + height : bottom-top, + }; +} +function onDoublePress(key, callback, timeout = 500) { + let timer = null; + let clickCount = 0; + const keyup = (event) => { + if (event.key !== key) { + clickCount = 0; + return; + } + clickCount++; + if (clickCount === 2) { + callback?.(); + clickCount = 0; + return; + } + clearTimeout(timer); + timer = setTimeout(() => { + clickCount = 0; + }, timeout); + }; + + document.addEventListener("keyup", keyup); + return () => { + clearTimeout(timer); + document.removeEventListener("keyup", keyup); + }; +} +function lerp(from, to, speed) { + return from + (to - from) * speed; +} +function svgBase64ToUrl(sgvBase64) { + try { + if (!/^data:image\/svg/.test(sgvBase64)) throw new Error("Invalid SVG"); + const svgContent = atob(sgvBase64.split(",")[1]); + const blob = new Blob([svgContent], { type: "image/svg+xml" }); + const url = URL.createObjectURL(blob); + setTimeout(() => URL.revokeObjectURL(url), 6e4); + return url; + } catch (e) { + console.log("ERROR: ", e); + return null; + } +} +function svgToBlobUrl(svg) { + let url = URL.createObjectURL(new Blob([svg], { type: "image/svg+xml" })); + return url; +} +function svgToBase64(svg) { + try { + return ( + "data:image/svg+xml;charset=utf-8;base64," + + btoa(new XMLSerializer().serializeToString(svg)) + ); + } catch (e) { + console.log("ERROR: ", e); + return null; + } +} +function getResolutionCategory(width, height) { + let min = Math.min(width, height); + let max = Math.max(width, height); + + if (max <= 256 && min <= 144) return "144p"; + if (max <= 320 && min <= 180) return "240p"; + if (max <= 640 && min <= 360) return "360p"; + if (max <= 640 && min <= 480) return "SD (480p)"; + if (max <= 1280 && min <= 720) return "HD (720p)"; + if (max <= 1600 && min <= 900) return "HD+ (900p)"; + if (max <= 1920 && min <= 1080) return "FHD (1080p)"; + if (max <= 2560 && min <= 1440) return "QHD (1440p)"; + if (max <= 3840 && min <= 2160) return "4K (2160p)"; + if (max <= 5120 && min <= 2880) return "5K (2880p)"; + if (max <= 7680 && min <= 4320) return "8K (4320p)"; + if (max <= 10240 && min <= 4320) return "10K (4320p)"; + if (max <= 15360 && min <= 8640) return "16K (8640p)"; + return "> 16K"; +} +// #endregion diff --git a/scripts/medium_readFullArticle.js b/scripts/medium_readFullArticle.js index e7e2b0d7..73ed6677 100644 --- a/scripts/medium_readFullArticle.js +++ b/scripts/medium_readFullArticle.js @@ -1,5 +1,3 @@ -// javascript:window.open("https://freedium.cfd/"+encodeURIComponent(window.location)) - export default { icon: "https://cdn-icons-png.flaticon.com/512/5968/5968906.png", name: { @@ -20,7 +18,8 @@ export default { url = prompt("Nhập link medium:", url); if (url) { - window.open("https://freedium.cfd/" + url); + window.open("https://readmedium.com/" + url); + // window.open("https://freedium.cfd/" + url); } }, }, diff --git a/scripts/pictureInPicture.js b/scripts/pictureInPicture.js index cb6e8599..a6af26e7 100644 --- a/scripts/pictureInPicture.js +++ b/scripts/pictureInPicture.js @@ -13,16 +13,18 @@ export default { badges: [BADGES.hot], changeLogs: { "2024-06-05": "fix video in iframes", + "2024-07-19": "fix video that disable pip", }, contentScript: { onClick_: async function () { const { UfsGlobal } = await import("./content-scripts/ufs_global.js"); + const Mark = "__pip__"; + function findLargestPlayingVideoInViewport() { const videos = Array.from(document.querySelectorAll("video")) .filter((video) => video.readyState != 0) - .filter((video) => video.disablePictureInPicture == false) .sort( (v1, v2) => UfsGlobal.DOM.getOverlapScore(v2) - @@ -35,11 +37,11 @@ export default { } async function requestPictureInPicture(video) { await video.requestPictureInPicture(); - video.setAttribute("__pip__", true); + video.setAttribute(Mark, true); video.addEventListener( "leavepictureinpicture", (event) => { - video.removeAttribute("__pip__"); + video.removeAttribute(Mark); }, { once: true, @@ -49,12 +51,12 @@ export default { } function maybeUpdatePictureInPictureVideo(entries, observer) { const observedVideo = entries[0].target; - if (!document.querySelector("[__pip__]")) { + if (!document.querySelector("[" + Mark + "]")) { observer.unobserve(observedVideo); return; } const video = findLargestPlayingVideoInViewport(); - if (video && !video.hasAttribute("__pip__")) { + if (video && !video.hasAttribute(Mark)) { observer.unobserve(observedVideo); requestPictureInPicture(video); } @@ -62,7 +64,9 @@ export default { (async () => { const video = findLargestPlayingVideoInViewport(); if (!video) return; - if (video.hasAttribute("__pip__")) { + if (video.disablePictureInPicture) + video.disablePictureInPicture = false; + if (video.hasAttribute(Mark)) { document.exitPictureInPicture(); return; } diff --git a/scripts/pip_anything.js b/scripts/pip_anything.js new file mode 100644 index 00000000..a18c1b5e --- /dev/null +++ b/scripts/pip_anything.js @@ -0,0 +1,204 @@ +import { BADGES } from "./helpers/badge.js"; + +export default { + icon: '', + name: { + en: "PIP anything", + vi: "PIP mọi thứ", + }, + description: { + en: "Picture in picture mode for anything, not just video, choose website content to show in PIP mode", + vi: "Xem bất kỳ giao diện nào trong cửa sổ nổi (Picture in picture), không chỉ mỗi video, click chọn phần tử từ website để xem trong cửa sổ nổi.", + img: "", + }, + badges: [BADGES.beta], + changeLogs: { + "2024-07-20": "init", + }, + + popupScript: { + onClick: () => { + window.close(); + }, + }, + + contentScript: { + // original from https://chromewebstore.google.com/detail/gepffghbolhjojibgohkdecdibdpbali + // document: https://developer.chrome.com/docs/web-platform/document-picture-in-picture + onClick: () => { + pickElement().then((element) => { + openInPip(element); + }); + }, + }, +}; + +const copyStyleSheets = (pipWindow) => { + [...document.styleSheets].forEach((styleSheet) => { + try { + const cssRules = [...styleSheet.cssRules] + .map((rule) => rule.cssText) + .join(""); + const style = document.createElement("style"); + style.textContent = cssRules; + pipWindow.document.head.appendChild(style); + } catch (e) { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.type = styleSheet.type; + link.media = styleSheet.media; + link.href = styleSheet.href; + pipWindow.document.head.appendChild(link); + } + }); +}; +const replaceWithPlaceHolder = (element) => { + const placeHolder = document.createElement("div"); + placeHolder.id = "PIPPlaceHolder"; + placeHolder.style.display = "none"; + element.replaceWith(placeHolder); +}; +const restorePIPElement = (event) => { + const pipElement = event.target.body.firstChild; + const placeHolder = document.querySelector("#PIPPlaceHolder"); + placeHolder.replaceWith(pipElement); +}; +export const openInPip = async (element) => { + const pipWindow = await documentPictureInPicture.requestWindow(); + copyStyleSheets(pipWindow); + replaceWithPlaceHolder(element); + pipWindow.document.body.append(element); + pipWindow.addEventListener("pagehide", restorePIPElement); +}; + +function pickElement() { + return new Promise((resolve) => { + // #region init elements + const host = document.createElement("div"); + const shadow = host.attachShadow({ mode: "closed" }); + + const style = document.createElement("style"); + style.textContent = /*css*/ ` + :host { + position: fixed; + display: block; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 99999999; + pointer-events: none; + } + #highlighter { + position: absolute; + top: 0; + left: 0; + width: 0; + height: 0; + background: #ff3f3f22; + border: 1px solid #F00; + box-shadow: 0 0 0 99999px rgba(0, 0, 0, .5); + } + #confirmer { + position: absolute; + bottom: 0; + right: 0; + width: 200px; + height: 200px; + } + #confirmer input { + width: 100%; + } + `; + shadow.append(style); + + const highlighter = document.createElement("div"); + highlighter.id = "highlighter"; + shadow.append(highlighter); + + // const confirmer = document.createElement("div"); + // confirmer.id = "confirmer"; + // confirmer.innerHTML = ` + // + // Pick + // + // `; + // shadow.append(confirmer); + + document.documentElement.append(host); + // #endregion + + // #region pick element + const STATES = { + picking: "picking", + confirming: "confirming", + }; + let state = STATES.picking; + let currentTarget = null; + function onMouseOver(e) { + e.preventDefault(); + const target = e.target; + if (state !== STATES.picking || !target || target === highlighter) return; + + highlightElement(target); + } + + function highlightElement(elem) { + const rect = getElementBoundingClientRect(elem); + highlighter.style.cssText = ` + top: ${rect.top}px; + left: ${rect.left}px; + width: ${rect.width}px; + height: ${rect.height}px; + `; + currentTarget = elem; + } + + window.addEventListener("mouseover", onMouseOver, { capture: true }); + window.addEventListener( + "click", + (e) => { + e.preventDefault(); + // confirmPick(currentTarget); + + host.remove(); + resolve(currentTarget); + window.removeEventListener("mouseover", onMouseOver); + }, + { once: true, capture: true } + ); + // #endregion + }); +} + +function getElementBoundingClientRect(elem) { + let rect = + typeof elem.getBoundingClientRect === "function" + ? elem.getBoundingClientRect() + : { height: 0, left: 0, top: 0, width: 0 }; + + // https://github.com/gorhill/uBlock/issues/1024 + // Try not returning an empty bounding rect. + if (rect.width !== 0 && rect.height !== 0) return rect; + if (elem.shadowRoot instanceof DocumentFragment) + return getElementBoundingClientRect(elem.shadowRoot); + + let left = rect.left, + right = left + rect.width, + top = rect.top, + bottom = top + rect.height; + + for (const child of elem.children) { + rect = getElementBoundingClientRect(child); + if (rect.width === 0 || rect.height === 0) continue; + if (rect.left < left) left = rect.left; + if (rect.right > right) right = rect.right; + if (rect.top < top) top = rect.top; + if (rect.bottom > bottom) bottom = rect.bottom; + } + + let height = bottom - top, + width = right - left; + + return { bottom, height, left, right, top, width }; +} diff --git a/scripts/screenshotVisiblePage.js b/scripts/screenshotVisiblePage.js index 55788a42..8ec9400d 100644 --- a/scripts/screenshotVisiblePage.js +++ b/scripts/screenshotVisiblePage.js @@ -1,5 +1,4 @@ import { UfsGlobal } from "./content-scripts/ufs_global.js"; -import { BADGES } from "./helpers/badge.js"; import { attachDebugger, detachDebugger, @@ -47,9 +46,6 @@ export default { }, }, ], - - badges: [BADGES.new], - changeLogs: { "2024-06-10": "init", }, diff --git a/scripts/scrollToVeryEnd.js b/scripts/scrollToVeryEnd.js index ded8b684..40b62240 100644 --- a/scripts/scrollToVeryEnd.js +++ b/scripts/scrollToVeryEnd.js @@ -24,7 +24,7 @@ export default { }, }; -export function scrollToVeryEnd() { +export function scrollToVeryEnd(animation = true) { return new Promise(async (resolve, reject) => { const notify = UfsGlobal.DOM.notify({ msg: "Useful-script: Scrolling to very end...", @@ -49,8 +49,6 @@ export function scrollToVeryEnd() { } } - console.log(scrollableElements); - // If only one scrollable element is found, return it if (scrollableElements.length === 1) { return scrollableElements[0]; @@ -72,7 +70,12 @@ export function scrollToVeryEnd() { } let height = (ele) => (ele || document.body).scrollHeight; - let down = (ele = document) => ele.scrollTo({ left: 0, top: height(ele) }); + let down = (ele = document) => + ele.scrollTo({ + left: 0, + top: height(ele), + behavior: animation ? "smooth" : "instant", + }); let sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); let lastScroll = { @@ -91,8 +94,6 @@ export function scrollToVeryEnd() { let scrollEle = findMainScrollableElement(); - console.log(scrollEle); - while (running) { down(scrollEle); let currentHeight = height(scrollEle); diff --git a/scripts/showImageOnHoverLink.js b/scripts/showImageOnHoverLink.js index 9f8d7482..01208651 100644 --- a/scripts/showImageOnHoverLink.js +++ b/scripts/showImageOnHoverLink.js @@ -1,5 +1,3 @@ -import { BADGES } from "./helpers/badge.js"; - export default { icon: '', name: { @@ -10,7 +8,6 @@ export default { en: "Show preview image when you hover mouse over an image link", vi: "Xem trước hình ảnh khi bạn đưa chuột qua link hình ảnh", }, - badges: [BADGES.new], changeLogs: { "2024-06-19": "init", }, diff --git a/scripts/simpleAllowCopy.js b/scripts/simpleAllowCopy.js index 82cab80c..4ae630b2 100644 --- a/scripts/simpleAllowCopy.js +++ b/scripts/simpleAllowCopy.js @@ -1,4 +1,3 @@ -import { UfsGlobal } from "./content-scripts/ufs_global.js"; import { BADGES } from "./helpers/badge.js"; export default { @@ -24,6 +23,9 @@ export default { `, }, badges: [BADGES.hot], + changeLogs: { + "2024-07-24": "hotfix", + }, contentScript: { onDocumentStart_: function () { @@ -32,7 +34,7 @@ export default { }, onClick_: function () { - let isMainFrame = !UfsGlobal.DOM.isInIframe(); + let isMainFrame = window === window.top; if (!window.ufs_simpleAllowCopy) { if (isMainFrame) alert("Vui lòng mở chức năng trước, rồi tải lại trang web."); @@ -95,7 +97,7 @@ const unlocker = (() => { const addCss = () => { try { const doc = window.document; - removeCss(wnd); + removeCss(); const style = doc.createElement("STYLE"); style.id = CSS_ELEM_ID; style.innerHTML = diff --git a/scripts/smoothScroll.js b/scripts/smoothScroll.js index 8583f78f..712948c1 100644 --- a/scripts/smoothScroll.js +++ b/scripts/smoothScroll.js @@ -92,7 +92,7 @@ async function setEnableForAllTab(enable) { : { vi: "Tắt", en: "Disabled" }; if (count) - Swal.fire({ + await Swal.fire({ icon: "success", title: t({ vi: "Đã " + text.vi + " Cuộn chuột Siêu mượt", @@ -182,12 +182,13 @@ export function enableSmoothScroll() { /*********************************************** * SETTINGS ***********************************************/ - chrome.storage.sync.get(defaultOptions, function (syncedOptions) { - options = syncedOptions; - // it seems that sometimes settings come late - // and we need to test again for excluded pages - initTest(); - }); + // chrome.storage.sync.get(defaultOptions, function (syncedOptions) { + // options = syncedOptions; + // // it seems that sometimes settings come late + // // and we need to test again for excluded pages + // initTest(); + // }); + initTest(); /*********************************************** * INITIALIZE ***********************************************/ @@ -735,11 +736,11 @@ export function enableSmoothScroll() { if (deltaY < 50) tp = true; clearTimeout(deltaBufferTimer); deltaBufferTimer = setTimeout(function () { - chrome.storage.local.set({ + chrome?.storage?.local?.set?.({ deltaBuffer: deltaBuffer, }); if (!tp) - chrome.storage.local.set({ + chrome?.storage?.local?.set?.({ lastDiscreetWheel: dateNow(), }); }, 1000); @@ -755,7 +756,7 @@ export function enableSmoothScroll() { isDivisible(deltaBuffer[2], divisor) ); } - chrome.storage.local.get("deltaBuffer", function (stored) { + chrome?.storage?.local?.get?.("deltaBuffer", function (stored) { if (stored.deltaBuffer) { deltaBuffer = stored.deltaBuffer; } @@ -838,21 +839,21 @@ export function enableSmoothScroll() { // we check the OS for default middle mouse behavior only! let isLinux = navigator.platform.indexOf("Linux") != -1; // get global settings - chrome.storage.sync.get(defaultOptions, function (syncedOptions) { - options = syncedOptions; - // leave time for the main script to check excluded pages - setTimeout(function () { - // if we shouldn't run, stop listening to events - if (isExcluded && !options.middleMouse) { - cleanup(); - } - }, 10); - }); + // chrome.storage.sync.get(defaultOptions, function (syncedOptions) { + // options = syncedOptions; + // // leave time for the main script to check excluded pages + // setTimeout(function () { + // // if we shouldn't run, stop listening to events + // if (isExcluded && !options.middleMouse) { + // cleanup(); + // } + // }, 10); + // }); /** * Initializes the image at the reference point. */ function init() { - let url = chrome.runtime.getURL("/scripts/smoothscroll_cursor.png"); + let url = chrome?.runtime?.getURL?.("/scripts/smoothscroll_cursor.png"); img.style.background = "url(" + url + ") no-repeat"; img.style.position = "fixed"; img.style.zIndex = "999999999"; diff --git a/scripts/studyphim_unlimited.js b/scripts/studyphim_unlimited.js index aaf8dc71..24830838 100644 --- a/scripts/studyphim_unlimited.js +++ b/scripts/studyphim_unlimited.js @@ -1,7 +1,7 @@ import { UfsGlobal } from "./content-scripts/ufs_global.js"; export default { - icon: "https://www.studyphim.vn/assets/ico/favicon.ico", + icon: "https://s2.googleusercontent.com/s2/favicons?domain=www.studyphim.vn", name: { en: "Studyphim - Watch free movies", vi: "Studyphim - Xem miễn phí", diff --git a/scripts/_test.js b/scripts/test.js similarity index 100% rename from scripts/_test.js rename to scripts/test.js diff --git a/scripts/tiktok_GLOBAL.js b/scripts/tiktok_GLOBAL.js index 0d6c35e9..b775cf33 100644 --- a/scripts/tiktok_GLOBAL.js +++ b/scripts/tiktok_GLOBAL.js @@ -171,7 +171,22 @@ export const SnapTik = { let result = c(...params); let jwt = result.match(/d\?token=(.*?)\&dl=1/)?.[1]; if (!jwt) return null; - let payload = UfsGlobal.Utils.parseJwt(jwt); + let payload = parseJwt(jwt); return payload?.url; }, }; + +// https://stackoverflow.com/a/38552302/11898496 +function parseJwt(token) { + let base64Url = token.split(".")[1]; + let base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); + let jsonPayload = decodeURIComponent( + atob(base64) + .split("") + .map(function (c) { + return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); + }) + .join("") + ); + return JSON.parse(jsonPayload); +} diff --git a/scripts/tiktok_batchDownload.css b/scripts/tiktok_batchDownload.css new file mode 100644 index 00000000..b9575d00 --- /dev/null +++ b/scripts/tiktok_batchDownload.css @@ -0,0 +1,179 @@ +#ufs_tiktok_batchDownload { + position: fixed; + z-index: 16777269; +} + +#ufs_tiktok_batchDownload a:hover { + text-decoration: underline; +} + +#ufs_tiktok_batchDownload .ufs_floating_btn { + border-radius: 25px; + background: #444; + color: white; + padding: 15px; + position: fixed; + bottom: 25px; + right: 25px; + border: 1px solid #777; + cursor: pointer; +} + +#ufs_tiktok_batchDownload .ufs_floating_btn:hover { + background: #666; +} + +#ufs_tiktok_batchDownload .ufs_container { + display: flex; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.5); + align-items: center; + justify-content: center; +} + +.ufs_popup { + position: relative; + background: #444; + color: #eee; + padding: 20px; + border-radius: 10px; + max-width: 90vw; + height: 90vh; + overflow-y: auto; + overflow-x: hidden; + display: flex; + flex-direction: column; + transition: all 0.3s ease; +} + +.ufs_popup .ufs_popup_header { + padding: 10px; + width: 100%; + display: flex; + justify-content: flex-end; +} + +.ufs_popup .table_wrap { + overflow: auto; +} + +.ufs_popup table { + width: 100%; +} + +.ufs_popup table, +.ufs_popup th, +.ufs_popup td { + border: 1px solid #aaa; + border-collapse: collapse; +} + +.ufs_popup table td { + padding: 10px; +} + +.ufs_popup table thead { + position: sticky; + top: -2px; + background: #333; +} + +.ufs_popup input { + padding: 5px; +} + +.ufs_popup button { + padding: 5px 10px; + background: #333; + color: #eee; + border: 1px solid #777; + cursor: pointer; +} + +.ufs_popup button:hover { + background: #666; +} + +.ufs_popup .ufs_avatar { + width: 50px; + height: 50px; + cursor: pointer +} + +.ufs_popup .clickable { + cursor: pointer; + background: #333; + user-select: none; +} + +.ufs_popup .clickable:hover { + background: #666; +} + +.ufs_popup .ufs_scroll_top { + position: absolute; + bottom: 10px; + right: 10px; + border-radius: 10px; + cursor: pointer; + background: #444; + color: white; + padding: 10px; +} + +/* check boxes */ +.ufs_is_private { + position: absolute; + top: 0; + right: 0; + color: red; + background: black; + padding: 8px; + font-weight: bold; +} + +.ufs_tiktok_checkbox { + z-index: 2; + position: absolute; + top: 0; + right: 0; + width: 50px; + height: 50px; + transition: all 0.1s ease; +} + +.ufs_tiktok_checkbox:hover { + transform: scale(1.3); +} + +.ufs_video_checkbox { + width: 30px; + height: 30px; +} + +.ufs_dropdown { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; +} + +.ufs_dropdown .ufs_dropdown_content { + visibility: hidden; + overflow: hidden; + position: absolute; + width: max-content; + top: 100%; + z-index: 2; + display: flex; + flex-direction: column; +} + +.ufs_dropdown .ufs_dropdown_trigger:hover~.ufs_dropdown_content, +.ufs_dropdown .ufs_dropdown_content:hover { + visibility: visible; +} diff --git a/scripts/tiktok_batchDownload.jpg b/scripts/tiktok_batchDownload.jpg deleted file mode 100644 index 26e32362..00000000 Binary files a/scripts/tiktok_batchDownload.jpg and /dev/null differ diff --git a/scripts/tiktok_batchDownload.js b/scripts/tiktok_batchDownload.js index 2e6c4aa3..bfb6dcdb 100644 --- a/scripts/tiktok_batchDownload.js +++ b/scripts/tiktok_batchDownload.js @@ -1,8 +1,7 @@ import { UfsGlobal } from "./content-scripts/ufs_global.js"; -import { - downloadTiktokVideoFromUrl, - downloadTiktokVideoFromId, -} from "./tiktok_GLOBAL.js"; +import { BADGES } from "./helpers/badge.js"; +import { hookFetch } from "./libs/ajax-hook/index.js"; +import { scrollToVeryEnd } from "./scrollToVeryEnd.js"; export default { icon: "https://www.tiktok.com/favicon.ico", @@ -13,222 +12,453 @@ export default { description: { en: "Select and download all tiktok video (user profile, tiktok explore).", vi: "Tải hàng loạt video tiktok (trang người dùng, trang tìm kiếm), có giao diện chọn video muốn tải.", - img: "/scripts/tiktok_batchDownload.jpg", + img: "/scripts/tiktok_batchDownload.png", }, - + badges: [BADGES.new, BADGES.hot], changeLogs: { "2024-04-27": "fix bug - use snaptik", "2024-05-16": "fix style", + "2024-07-28": "re-build hook fetch", }, whiteList: ["https://www.tiktok.com/*"], - popupScript: { - onClick: () => { - alert(`Làm các bước sau: - 1: Tích chọn nút bên trái để mở chức năng. - 2: Tải lại trang web tiktok. - 3: Sẽ hiện giao diện giúp tải hàng loạt ngay trong trang web.`); - }, - }, - pageScript: { - onDocumentIdle: async () => { - let checkboxes = []; - - // Setup DOM - let id = "ufs-tiktok-batch-download"; - let container = document.createElement("div"); - container.id = id; - - const style = document.createElement("style"); - style.textContent = ` - #${id} { - position: fixed; - bottom: 20px; - left: 50%; - transform: translateX(-50%); - background: #333e; - color: white; - min-height: 50px; - padding: 15px; - z-index: 6; - border-radius: 5px; - border: 1px solid #eee; - } - #${id} button { - padding: 5px 10px; - background: #444; - color: white !important; - border: none; - } - #${id} button:hover { - background: #666; - } - `; - container.appendChild(style); - - (document.body || document.documentElement).appendChild(container); - - let progressDiv = document.createElement("p"); - progressDiv.innerText = "Useful script: Tiktok tải hàng loạt"; - progressDiv.style = "margin-bottom: 5px; font-family: monospace;"; - container.appendChild(progressDiv); - - // scroll button - let scrolling = false; - const scrollBtn = document.createElement("button"); - scrollBtn.innerText = "Scroll xuống ⏬"; - scrollBtn.onclick = async () => { - scrolling = !scrolling; - - scrollBtn.innerText = scrolling - ? "Đang scroll... ⏳" - : "Scroll xuống ⏬"; + onDocumentStart: async () => { + const CACHED = { + hasNew: true, + videoById: new Map(), + }; - let doubleCheck = 0; - while (scrolling) { - let previousHeight = document.body.scrollHeight; - window.scrollTo(0, document.body.scrollHeight); - await new Promise((resolve) => setTimeout(resolve, 1000)); - if (document.body.scrollHeight <= previousHeight) { - doubleCheck++; - console.log(doubleCheck); - if (doubleCheck > 5) { - scrolling = false; - scrollBtn.innerText = "Scroll xuống ⏬"; + // reference to Cached + window.ufs_tiktok_batchDownload = CACHED; + + hookFetch({ + onAfter: async (url, options, response) => { + if (url.includes("item_list/")) { + const res = response.clone(); + const json = await res.json(); + console.log(json); + + if (json?.itemList) { + json.itemList.forEach((_) => { + if (_.video.playAddr || _.imagePost?.images?.length) { + CACHED.videoById.set(_.video.id, _); + CACHED.hasNew = true; + } + + if (_.imagePost?.images?.length) console.log(_); + }); } } - } - }; - container.appendChild(scrollBtn); - - // Select all button - const selectAllBtn = document.createElement("button"); - selectAllBtn.innerText = "Chọn/Huỷ chọn ✅"; - selectAllBtn.onclick = function () { - let value = checkboxes[0].checked; - for (let checkbox of checkboxes) { - checkbox.checked = !value; - } - }; - container.appendChild(selectAllBtn); - // Revert all Button - const revertAllBtn = document.createElement("button"); - revertAllBtn.innerText = "Đảo ngược 🔁"; - revertAllBtn.onclick = function () { - for (let checkbox of checkboxes) { - checkbox.checked = !checkbox.checked; - } - }; - container.appendChild(revertAllBtn); - - // Download button - const downloadBtn = document.createElement("button"); - downloadBtn.innerText = "GET LINK 🔗"; - downloadBtn.onclick = function () { - let videoUrls = []; - for (let checkbox of checkboxes) { - if (checkbox.checked) { - videoUrls.push(checkbox["data-url"]); + if (url.includes("api/search")) { + const res = response.clone(); + const json = await res.json(); + console.log(json); + + if (json.data?.length) { + json.data.forEach((_) => { + if (_.type === 1) { + CACHED.videoById.set(_.item.video.id, _.item); + CACHED.hasNew = true; + } + }); + } } - } - console.log(videoUrls); - getLinkVideos(videoUrls); - }; - container.appendChild(downloadBtn); - - // result div - let resultDiv = document.createElement("div"); - resultDiv.style = "margin-top: 10px"; - container.appendChild(resultDiv); - - let resultLabel = document.createElement("label"); - resultDiv.appendChild(resultLabel); - - let resultTxt = document.createElement("textarea"); - resultTxt.style = "width: 100%; height: 50px"; - resultTxt.hidden = true; - resultDiv.appendChild(resultTxt); - - // click listener - window.onclick = function (e) { - if ( - e.target.type === "checkbox" || - e.target == selectAllBtn || - e.target == revertAllBtn - ) { - let selected = checkboxes.filter( - (checkbox) => checkbox.checked - ).length; - progressDiv.innerText = `Đã chọn ${selected}/${checkboxes.length} video. Bấm nút Get link khi chọn xong nhé.`; - } - }; - - function createCheckBox(url) { - let checkbox = document.createElement("input"); - checkbox.type = "checkbox"; - checkbox.name = "video"; - checkbox.checked = false; - checkbox["data-url"] = url; - checkbox.style = - "z-index: 0; position: absolute; top: 0; right: 0; width: 60px; height: 60px;"; - return checkbox; - } + }, + }); - async function sleep(time) { - await new Promise((resolve) => setTimeout(resolve, time)); + 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("-") + ); } - async function getLinkVideos(videoUrls) { - if (!videoUrls.length) return; - const getId = (url) => url.split("/").at(-1); - const queue = [...videoUrls]; - const links = []; - downloadBtn.disabled = true; - - while (queue.length) { - let progress = `[${videoUrls.length - queue.length + 1}/${ - videoUrls.length - }]`; - try { - console.log(`${progress} Đang tìm link cho video ${queue[0]}`); - progressDiv.innerText = `${progress} Đang tìm link video ${queue[0]}...`; - downloadBtn.innerText = `Đang get link ${progress}...`; - let link = await downloadTiktokVideoFromUrl(queue[0], true); - - if (!link) { - link = await downloadTiktokVideoFromId(getId(queue[0])); + const app = new Vue({ + template: /*html*/ ` +
+
📥 {{totalCount}}
+
+
+

Tiktok - Useful Scripts

+

Found {{totalCount}} videos

+ +
+ +
+ +
+ +
+
+
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
#🎬 VideoTitle👤 UserViewLengthDownload

No video

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

{{v.desc}}

+ + {{v.author.nickname}}
+ {{v.author.uniqueId}}
+ {{v.author.id}} +
{{format(v.stats.playCount)}}{{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; } - - if (link) { - resultTxt.hidden = false; - resultTxt.value += link + "\n"; - let count = resultTxt.value.split("\n").filter((i) => i).length; - resultLabel.innerText = `Link tại đây, ${count} video, copy bỏ vào IDM tải hàng loạt nhé:`; - - links.push(link); - } else { - progressDiv.innerText = `[LỖI] Không thể tải video ${queue[0]}.`; - await sleep(1000); + }, 1000); + }, + data() { + return { + showModal: false, + videos: [], + search: "", + sortBy: "index", + sortDir: "asc", + downloading: {}, + 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.id]) + : this.videosToShow; + }, + audioToDownload() { + const list = this.hasSelected + ? this.videosToShow.filter((v) => this.selected[v.id]) + : this.videosToShow; + + // get unique + const result = new Map(); + for (const item of list) { + if (!result.has(item.music.id)) result.set(item.music.id, item); + } + return Array.from(result.values()); + }, + videoTitle() { + if (this.downloading.video) { + return ( + "Downloading " + + this.downloading.video + + "/" + + this.videoToDownload.length + + " video" + ); } - queue.shift(); + return ( + "Download " + + this.videoToDownload.length + + (this.hasSelected ? " selected" : "") + + " video" + ); + }, + audioTitle() { + if (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.id, + v.author.nickname, + v.author.uniqueId, + ].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 "view": + return this.sortDir === "asc" + ? a.stats.playCount - b.stats.playCount + : b.stats.playCount - a.stats.playCount; + 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: "tiktok_videos_" + getNow(), + expectBlobTypes: ["video/mp4", "image/jpeg"], + data: this.videoToDownload + .map((_, i) => { + // 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", + })); + } + + // video + const urlList = + _.video?.bitrateInfo?.find?.( + (b) => b.Bitrate === _.video.bitrate + )?.PlayAddr?.UrlList || []; + const bestUrl = urlList[urlList.length - 1]; + return { + url: bestUrl || _.video.playAddr, + filename: + i + + 1 + + "_" + + UfsGlobal.Utils.sanitizeName(_.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: "tiktok_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_tiktok.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.tiktok.com/@" + id, "_blank"); + }, + format(v) { + return formatter.format(v); + }, + onClickContainer(e) { + if (e.target === this.$el) this.showModal = false; + }, + }, + }).$mount(div); + + async function download({ + folderName = "tiktok", + expectBlobTypes, + data, + onProgressItem, + onFinishItem, + }) { + const dir = await UfsGlobal.Utils.chooseFolderToDownload(folderName); + onProgressItem?.(0, data.length); + + UfsGlobal.Extension.trackEvent("tiktok_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.log(`${progress} Lỗi tải, thử lại sau 1s...`); - let failId = queue.shift(); - queue.push(failId); - await sleep(1000); + console.error(e); } } - - progressDiv.innerText = "Bạn vẫn có thể chọn thêm video để get link."; - downloadBtn.disabled = false; - downloadBtn.innerText = "GET LINK 🔗"; - - if (links?.length) UfsGlobal.Utils.copyToClipboard(links.join("\n")); - console.log(links); } + return; + // checkbox on videos + let checkboxes = []; // Listen for videos UfsGlobal.DOM.onElementsAdded('a[href*="/video/"]', (nodes) => { @@ -247,15 +477,7 @@ export default { if (isPrivate) { let p = document.createElement("p"); p.innerText = "Riêng tư"; - p.style = [ - "position: absolute;", - "top: 0;", - "right: 0;", - "color: red", - "background: black", - "padding: 8px", - "font-weight: bold", - ].join(";"); + p.className = "ufs_is_private"; node.parentElement.appendChild(p); } else { let url = node.getAttribute("href"); @@ -264,10 +486,17 @@ export default { checkboxes.push(checkbox); } } - - let selected = checkboxes.filter((checkbox) => checkbox.checked).length; - progressDiv.innerText = `Đã chọn ${selected}/${checkboxes.length} video. Bấm nút Get link khi chọn xong nhé.`; }); + + function createCheckBox(url) { + let checkbox = document.createElement("input"); + checkbox.className = "ufs_tiktok_checkbox"; + checkbox.type = "checkbox"; + checkbox.name = "video"; + checkbox.checked = false; + checkbox["data-url"] = url; + return checkbox; + } }, }, }; diff --git a/scripts/tiktok_batchDownload.png b/scripts/tiktok_batchDownload.png new file mode 100644 index 00000000..b29983e6 Binary files /dev/null and b/scripts/tiktok_batchDownload.png differ diff --git a/scripts/tiktok_downloadWatchingVideo.js b/scripts/tiktok_downloadWatchingVideo.js index 5d804a9c..cb51036f 100644 --- a/scripts/tiktok_downloadWatchingVideo.js +++ b/scripts/tiktok_downloadWatchingVideo.js @@ -85,32 +85,31 @@ export default { t({ vi: `Đang tải video ${title}...`, en: `Downloading ${title}...` }) ); - // UfsGlobal.Extension.download({ - // url: link, - // filename: title + ".mp4", - // }); - - const { formatSize, downloadBlob, getBlobFromUrlWithProgress } = - UfsGlobal.Utils; - const blob = await getBlobFromUrlWithProgress( - link, - ({ loaded, total, speed }) => { - let desc = - formatSize(loaded, 1) + - " / " + - formatSize(total, 1) + - " (" + - formatSize(speed, 1) + - "/s)"; - setLoadingText( - t({ - vi: `Đang tải ${desc}...
${title}`, - en: `Downloading ${desc}...
${title}`, - }) - ); - } - ); - downloadBlob(blob, title + ".mp4"); + UfsGlobal.Extension.download({ + url: link, + filename: title + ".mp4", + }); + + // const { formatSize, downloadBlob } = UfsGlobal.Utils; + // const blob = await getBlobFromUrlWithProgress( + // link, + // ({ loaded, total, speed }) => { + // let desc = + // formatSize(loaded, 1) + + // " / " + + // formatSize(total, 1) + + // " (" + + // formatSize(speed, 1) + + // "/s)"; + // setLoadingText( + // t({ + // vi: `Đang tải ${desc}...
${title}`, + // en: `Downloading ${desc}...
${title}`, + // }) + // ); + // } + // ); + // downloadBlob(blob, title + ".mp4"); } closeLoading(); @@ -149,3 +148,35 @@ export const shared = { }); }, }; + +async function getBlobFromUrlWithProgress(url, progressCallback) { + const response = await fetch(url, {}); + if (!response.ok) { + throw new Error(`Error: ${response.status} - ${response.statusText}`); + } + const contentLength = response.headers.get("content-length"); + const total = parseInt(contentLength, 10); + let loaded = 0; + const reader = response.body.getReader(); + const chunks = []; + + const startTime = Date.now(); + while (true) { + const { done, value } = await reader.read(); + if (done) break; + loaded += value.byteLength; + const ds = (Date.now() - startTime + 1) / 1000; + progressCallback?.({ + loaded, + total, + speed: loaded / ds, + }); + chunks.push(value); + } + + const blob = new Blob(chunks, { + type: response.headers.get("content-type"), + }); + + return blob; +} diff --git a/scripts/_ufs_statistic.css b/scripts/ufs_statistic.css similarity index 100% rename from scripts/_ufs_statistic.css rename to scripts/ufs_statistic.css diff --git a/scripts/_ufs_statistic.js b/scripts/ufs_statistic.js similarity index 98% rename from scripts/_ufs_statistic.js rename to scripts/ufs_statistic.js index 33f703f0..376594ba 100644 --- a/scripts/_ufs_statistic.js +++ b/scripts/ufs_statistic.js @@ -49,7 +49,7 @@ async function onDocumentEnd() { const container = document.createElement("div"); if (hasLog) { - UfsGlobal.Extension.getURL("/scripts/_ufs_statistic.css").then( + UfsGlobal.Extension.getURL("/scripts/ufs_statistic.css").then( UfsGlobal.DOM.injectCssFile ); @@ -311,6 +311,9 @@ async function onDocumentEnd() { const logByUidSorted = new Map( [...logByUid.entries()].sort((a, b) => b[1] - a[1]) ); + const fbUsers = [...logByUid.entries()].filter(([key, value]) => { + return isFbUid(key); + }); const canvas_uid = document.createElement("canvas"); const ctx4 = canvas_uid.getContext("2d"); @@ -398,7 +401,9 @@ async function onDocumentEnd() { h1.innerHTML = `${allLogs.length} logs (~${_logsPerHour} logs/hour)
${eventNameCount.size} unique events

${scriptUsedTotalCount} scripts used (~${_scriptsPerHour} scripts/hour)
- ${scriptsUsed.size} unique scripts`; + ${scriptsUsed.size} unique scripts

+ ${logByUid.size} unique users
+ ${fbUsers.length} facebook users`; // ======================== Append Charts ======================== container.prepend( diff --git a/scripts/web_timer.js b/scripts/web_timer.js index 9abd4bae..eafafbd0 100644 --- a/scripts/web_timer.js +++ b/scripts/web_timer.js @@ -68,7 +68,7 @@ export default { } } if (count) - Swal.fire({ + await Swal.fire({ icon: "success", title: t({ vi: "Đã bật", @@ -107,7 +107,7 @@ export default { } } if (count) - Swal.fire({ + await Swal.fire({ icon: "success", title: t({ vi: "Đã tắt", @@ -147,7 +147,7 @@ function run() { // track user events: mouse, keyboard, touch, ... let needUpdateLastActive = true; - let updateLastActive = UfsGlobal.Utils.throttle(function (e, mainframe) { + let updateLastActive = throttle(function (e, mainframe) { needUpdateLastActive = true; }, 1000 / 24); @@ -506,6 +506,22 @@ function run() { document.title = newTitle; } } +// https://dev.to/jeetvora331/throttling-in-javascript-easiest-explanation-1081 +function throttle(mainFunction, delay) { + let timerFlag = null; // Variable to keep track of the timer + + // Returning a throttled version + return (...args) => { + if (timerFlag === null) { + // If there is no timer currently running + mainFunction(...args); // Execute the main function + timerFlag = setTimeout(() => { + // Set a timer to clear the timerFlag after the specified delay + timerFlag = null; // Clear the timerFlag to allow the main function to be executed again + }, delay); + } + }; +} const backup = () => { // export data from https://chrome.google.com/webstore/detail/ppaojnbmmaigjmlpjaldnkgnklhicppk (async () => { diff --git a/scripts/youtube_changeCountry.js b/scripts/youtube_changeCountry.js index a1cdfdf5..ce126cd4 100644 --- a/scripts/youtube_changeCountry.js +++ b/scripts/youtube_changeCountry.js @@ -1,5 +1,3 @@ -import { BADGES } from "./helpers/badge.js"; - export default { icon: '', name: { @@ -10,7 +8,6 @@ export default { en: "Change youtube country to view content in other country", vi: "Đổi quốc gia youtube để xem nội dung youtube bên các nước khác", }, - badges: [BADGES.new], changeLogs: { "2024-07-07": "init", }, diff --git a/scripts/youtube_getVideoCaption.js b/scripts/youtube_getVideoCaption.js index cac5d971..0b6cb8e2 100644 --- a/scripts/youtube_getVideoCaption.js +++ b/scripts/youtube_getVideoCaption.js @@ -1,6 +1,5 @@ import { UfsGlobal } from "./content-scripts/ufs_global.js"; import { runScriptInCurrentTab } from "./helpers/utils.js"; -import { BADGES } from "./helpers/badge.js"; export default { icon: '', @@ -13,7 +12,6 @@ export default { vi: "- Bấm để tải về tất cả phụ đề của video youtube đang xem
- Bật tự chạy để hiển thị phụ đề thời gian thực", img: "/scripts/youtube_getVideoCaption.png", }, - badges: [BADGES.new], changeLogs: { "2024-07-04": "init", }, diff --git a/scripts/youtube_getVideoThumbnail.js b/scripts/youtube_getVideoThumbnail.js index 65b63df6..38393b67 100644 --- a/scripts/youtube_getVideoThumbnail.js +++ b/scripts/youtube_getVideoThumbnail.js @@ -1,4 +1,3 @@ -import { BADGES } from "./helpers/badge.js"; import { getIdFromYoutubeURL } from "./youtube_downloadVideo.js"; export default { @@ -11,7 +10,6 @@ export default { en: "Get largest thumbnail of playing youtube video", vi: "Tải về hình thumbnail độ phân giải lớn nhất của video youtube đang xem", }, - badges: [BADGES.new], changeLogs: { "2024-07-04": "init", }, diff --git a/working_note.md b/working_note.md index 3a4891ac..3f7a3f85 100644 --- a/working_note.md +++ b/working_note.md @@ -1,6 +1,16 @@ # WORKING NOTES -## 01/07/2024 - ? +## 01/07/2024 - 28/7/2024 + +- [x] Tải video bằng cách record??? => cannot record full video + +- [ ] + +- [ ] good feature - global video speed + +- [x] good feature - medium unlock + +- [ ] good feature - facebook adblocker - [ ] take a look at @@ -10,7 +20,7 @@ - [x] add this beautiful video downloader tool -- [ ] savefile using new api +- [x] savefile using new api - [x] hotfix - use chrome.downloads instead of library => can not apply to all scripts