Guest User

Untitled

a guest
Sep 16th, 2023
1,614
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // Change this variable to 'true' to bring the Shopping Game to the top of the page.
  2. var switchAreaSlot = true;
  3.  
  4. // Change this variable to 'true' to show the custom game counter.
  5. var showCustomGameCounter = true;
  6.  
  7. // Change this variable to 'true' to show the custom points counter.
  8. var showCustomPointsCounter = true;
  9.  
  10. // Change this variable to 'true' to automatically click 'Play Again' on game complete.
  11. var autoReplay = true;
  12.  
  13.  
  14. // Basic query selectors to find 'msft-feed-layout', 'msn-shopping-game-pane' and 'ms-rewards' elements.
  15. var msftFeedLayout = document.querySelector("shopping-page-base")
  16.     ?.shadowRoot.querySelector("shopping-homepage")
  17.     ?.shadowRoot.querySelector("div > cs-feed-layout");
  18.  
  19. var msnShoppingGamePane = msftFeedLayout?.shadowRoot.querySelector("msn-shopping-game-pane");
  20.  
  21. var fluentDesignSystemProvider = document.querySelector("shopping-page-base")
  22.     ?.shadowRoot.querySelector("common-header")
  23.     ?.shadowRoot.querySelector("msn-verticals-header")
  24.     ?.shadowRoot.querySelector("fluent-design-system-provider");
  25.  
  26. // Function to make sure we're on MSN shopping page.
  27. function validUrlCheck(){
  28.     var isValidUrl = document.location.href.includes("https://www.msn.com/") && document.location.href.includes("/shopping");
  29.     return isValidUrl ? true : alert("Invalid site detected. You need to be on https://www.msn.com/shopping");
  30. }
  31.  
  32. // Function to try get user accessToken if 1s-tokens not present.
  33. function tryGetUserAccessToken(){
  34.     for(var i = 0; i <= 100000; i ++){
  35.         var val = localStorage.key(i);
  36.         if(!val) break;
  37.         if(val.includes("accesstoken") && val.includes("oneservice")){
  38.             return JSON.parse(localStorage.getItem(val)).secret;
  39.         }
  40.     }
  41.     return null;
  42. }
  43.  
  44. // Function to try get 'common.js' url which is fetched by 'tryGetOneServiceApiKey' to extract 'OneServiceApiKey'.
  45. function tryGetCommonJsUrl(){
  46.     var scriptTags = document.getElementsByTagName("script");
  47.     var jsFiles = [];
  48.     for (var i = 0; i < scriptTags.length; i++) {
  49.     const src = scriptTags[i].getAttribute("src");
  50.         if (src && src.endsWith(".js")) {
  51.             jsFiles.push(src);
  52.         }
  53.     }
  54.     return jsFiles.findLast((item) => item.includes("/common."));
  55. }
  56.  
  57. // Function to try get 'OneServiceApiKey' used in 'reportActivity' url. ( Must be a better way of retrieving this... )
  58. async function tryGetOneServiceApiKey(){
  59.     var commonJsUrl = tryGetCommonJsUrl();
  60.     if(!commonJsUrl) return null;
  61.     var serviceWorker = await fetch(commonJsUrl);
  62.     var body = serviceWorker.ok ? await serviceWorker.text() : null;
  63.     window.oneServiceApiKey = body ? body.includes("apiKey") ? body.split('apiKey:"')[1].split('"')[0] : null : null;
  64.     return window.oneServiceApiKey;
  65. }
  66.  
  67. // Function to get 'ActivityId' used in 'reportActivity' url.
  68. function tryGetActivityId(){
  69.     var dataClientSettings = document.head.getAttribute("data-client-settings");
  70.     window.activityId = dataClientSettings ? JSON.parse(dataClientSettings).aid.replace(/^(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})$/, "$1-$2-$3-$4-$5") : MeControl.WebInline.guid();
  71.     return window.activityId;
  72. }
  73.  
  74. // Function to get 'MUID' used in 'reportActivity' url.
  75. async function tryGetMuid(){
  76.     var muid = await cookieStore.get("MUID");
  77.     window.muid = muid ? `m-${muid.value}` : null;
  78.     return window.muid;
  79. }
  80.  
  81. // Function to check OneServiceApiKey, ActivityId, MUID.
  82. async function setupReportActivityUrl(){
  83.     if(!await tryGetMuid()) return alert("Unable to retrieve 'MUID'");
  84.     if(!await tryGetActivityId()) return alert("Unable to retrieve 'ActivityId'");
  85.     if(!await tryGetOneServiceApiKey()) return alert("Unable to retrieve 'OneServiceApiKey'");
  86.     return true;
  87. }
  88.  
  89. // Function to create button elements at the top left of the shopping game.
  90. function createButtonElement(){
  91.     if(!window.elementsCreated)
  92.         window.elementsCreated = 0;
  93.     var divElem = document.createElement("div");
  94.     divElem.className = "view-leaderboard stats-game-counter";
  95.     divElem.style = `right: unset; left: ${25+(window.elementsCreated++ * 100)}px; font-size: 13px;background: linear-gradient(100.25deg, rgba(7, 158, 130, 0.9) 0%, rgba(2, 100, 188, 0.9) 100%);color: white;font-weight: 700;`;
  96.     var parentElem = msnShoppingGamePane.gameContainerRef.getElementsByClassName("game-panel-container")[0];
  97.     parentElem.appendChild(divElem);
  98.     return divElem;
  99. }
  100.  
  101. // Function to create and increment the game counter.
  102. function incrementGameCounter(){
  103.     if(!window.gameCounterElem){
  104.         window.gameCounter = 0;
  105.         window.gameCounterElem = createButtonElement();
  106.     }
  107.     window.gameCounter++;
  108.     window.gameCounterElem.textContent = `Game: ${window.gameCounter}`;
  109. }
  110.  
  111. // Function to create and increment the points counter, Also increments the default BingFlyout points counter.
  112. function incrementPointsCounter(balance = 0){
  113.     if(!window.rewardsBalanceElement)
  114.         window.rewardsBalanceElement = fluentDesignSystemProvider?.querySelector("ms-rewards")?.shadowRoot?.querySelector("fluent-button")?.getElementsByClassName("reward-points")[0];
  115.    
  116.     if(!window.pointsCounterElem){
  117.         window.pointsBalancePrev = balance-1;
  118.         window.pointsBalance = balance;
  119.         window.pointsCounterElem = createButtonElement();
  120.        
  121.         window.pointsIncrementTimer = setInterval(() => {
  122.             if(window.pointsBalance > window.pointsBalancePrev){
  123.                 // Update our custom balance points text.
  124.                 window.pointsCounterElem.textContent = `Points: ${++window.pointsBalancePrev}`;
  125.                
  126.                 // Update BingFlyout balance points text.
  127.                 if(window.rewardsBalanceElement)
  128.                     window.rewardsBalanceElement.textContent = `\n${window.pointsBalancePrev}\n\n`;
  129.             }
  130.         }, 50);
  131.     }
  132.     window.pointsBalance = balance;
  133. }
  134.  
  135. // Function to update user balance, Which then updates the points counters.
  136. async function updateUserPointsBalance(){
  137.     await fetch("https://assets.msn.com/service/News/Users/me/Rewards?apikey=0QfOX3Vn51YCzitbLaRkTTBadtWpgTN8NZLW0C1SEM&ocid=rewards-peregrine&cm=en-gb&it=web&user=0&scn=ANON",
  138.     {
  139.         headers: { "Authorization": `Bearer ${window.userAccessToken}` }
  140.     }).then(async (response) => {
  141.         var userInfo = await response.json();
  142.         incrementPointsCounter(userInfo.profile.rewardsPoints);
  143.     });
  144. }
  145.  
  146. // Function to report the 'guessinggame' activity.
  147. async function reportActivity(){
  148.     return await fetch(`https://assets.msn.com/service/news/feed/segments/shopping?ocid=shopping-shophp-Peregrine&apikey=${window.oneServiceApiKey}&timeOut=10000&cm=${MeControl.Config.mkt.toLowerCase()}&scn=MSNRPSAuth&user=${window.muid}&$select=rewards|reportactivity|guessinggame|0|${window.gameHash}&$filter=~5000&activityid=${window.activityId}`,{
  149.         method: "GET",
  150.         cache: "no-store",
  151.         headers: {'Authorization': `Bearer ${window.userAccessToken}`}
  152.     });
  153. }
  154.  
  155. // Function to see if we are authenticated.
  156. async function rewardsConnectorAuthCheck(){
  157.     var accessToken = tryGetUserAccessToken();
  158.     var tokenStorage = localStorage.getItem("1s-tokens");
  159.     if(tokenStorage || accessToken){
  160.         window.userAccessToken = (accessToken ? accessToken : JSON.parse(tokenStorage).accessToken);
  161.         msnShoppingGamePane.signInState = 0;
  162.         return true;
  163.     }
  164.     else
  165.     {
  166.         alert("Unable to find '1s-tokens', The page will now reload.\nYou will need to re-run the script when the page has reloaded.");
  167.         return document.location.reload();
  168.     }
  169. }
  170.  
  171. // Function that modifies the game products.
  172. function modifyGameProducts(){
  173.     msnShoppingGamePane.displayedShoppingEntities = [msnShoppingGamePane.displayedShoppingEntities[0]];
  174. }
  175.  
  176. // Function to remove the 10 daily game limit. ( still limited to 100 points daily )
  177. function removeDailyGameLimit(){
  178.     if(msnShoppingGamePane.displayedShoppingEntities.length > 1)
  179.         modifyGameProducts();
  180.  
  181.     localStorage.removeItem("gamesPerDay");
  182.     msnShoppingGamePane.dailyLimitReached = false;
  183.     if(msnShoppingGamePane.leaderboardRecord)
  184.         msnShoppingGamePane.leaderboardRecord.dailyGuessingGamesPlayed = 0;
  185.    
  186.     msnShoppingGamePane.gameState = (msnShoppingGamePane.gameState == "idle" ? "active" : msnShoppingGamePane.gameState);
  187. }
  188.  
  189. // Function that modifies the game.
  190. async function modifyGame(){
  191.     // Get the game hash.
  192.     window.gameHash = msnShoppingGamePane.displayedShoppingEntities[0].gameHash;
  193.    
  194.     // Check if the shopping game was found.
  195.     if(msnShoppingGamePane != null)
  196.     {      
  197.         // Switches msnShoppingGamePane slot with slot2, bringing it to the top of the page.
  198.         if(switchAreaSlot){
  199.             if(msnShoppingGamePane.style.gridArea != "slot2"){
  200.                 msftFeedLayout.shadowRoot.children[1].style.gridArea = msnShoppingGamePane.style.gridArea;
  201.                 msnShoppingGamePane.style.gridArea = "slot2";
  202.  
  203.                 // Scroll to the top of the page, For people who scroll down before running the script.
  204.                 window.scrollTo(0,0);
  205.             }
  206.            
  207.             // Keep the game at the top when layout changes.
  208.             if(!window.layoutColumnsChangedOG){
  209.                 window.layoutColumnsChangedOG = msnShoppingGamePane.layoutColumnsChanged;
  210.                 msnShoppingGamePane.layoutColumnsChanged = function(e, t){
  211.                     layoutColumnsChangedOG.call(msnShoppingGamePane, [e, t]);
  212.                     msnShoppingGamePane.style.gridArea = "slot2";
  213.                 }
  214.             }
  215.         }
  216.        
  217.         // Override their 'startCountdown' so we can increment the game count.
  218.         if(showCustomGameCounter && !window.startCountdownOG){
  219.             window.startCountdownOG = msnShoppingGamePane.startCountdown;
  220.             msnShoppingGamePane.startCountdown = function(){
  221.                 window.startCountdownOG.call(msnShoppingGamePane);
  222.                 setTimeout(() => {
  223.                     incrementGameCounter();
  224.                     modifyGameProducts();
  225.                 }, (msnShoppingGamePane.gameSettings.newGameCountdown * 1000) + 1200);
  226.             }
  227.         }
  228.        
  229.         // Get initial user balance.
  230.         if(showCustomPointsCounter)
  231.             updateUserPointsBalance();
  232.        
  233.         // Override their gSCS to always return green.
  234.         msnShoppingGamePane.gSCS = function (e) {
  235.             return msnShoppingGamePane.isGameFinished ? "--price-color:#00AE56;--price-color-dark:#00AE56" : "";
  236.         }
  237.        
  238.         // Override their 'getGameResult' function with our own to execute 'autoReplay' and 'updateUserPointsBalance' on game complete, Also removes the 10 game limit.
  239.         msnShoppingGamePane.getGameResult = async function(e)
  240.         {
  241.             // Make sure a product card is selected.
  242.             if (msnShoppingGamePane.isGameFinished)
  243.             {
  244.                 // Change current gameState to 'win'.
  245.                 msnShoppingGamePane.gameState = 'win';
  246.  
  247.                 // Remove daily game limit.
  248.                 removeDailyGameLimit();
  249.  
  250.                 // Report 'guessinggame' activity, Only calling when the answer was wrong.
  251.                 if(msnShoppingGamePane.selectedCardIndex != msnShoppingGamePane.c_ai && msnShoppingGamePane.selectedCardIndex > -1){
  252.                     msnShoppingGamePane.gameContainerRef.querySelector("fluent-card").parentElement.style = "border:4px solid rgb(0, 174, 86)";
  253.                     msnShoppingGamePane.selectedCardIndex = -1;
  254.                     msnShoppingGamePane.confettiAnimate.play();
  255.                     await reportActivity();            
  256.                 }              
  257.                 // Update user points balance.
  258.                 if(msnShoppingGamePane.gameState === "win" && showCustomPointsCounter)
  259.                     setTimeout(updateUserPointsBalance, 1200);
  260.                
  261.                 // Automatically click 'Play Again'.
  262.                 if(autoReplay && msnShoppingGamePane.selectedCardIndex > -1){
  263.                     msnShoppingGamePane.selectedCardIndex = -1;
  264.                     setTimeout(()=>Array.from(msnShoppingGamePane.gameContainerRef.querySelectorAll("button")).find(e=>e.textContent.toLowerCase().includes("play again"))?.click(), 25);
  265.                 }
  266.                 return "win";
  267.             }
  268.         };
  269.         setInterval(removeDailyGameLimit, 100);
  270.         incrementGameCounter();
  271.         msnShoppingGamePane.gameState = "active";
  272.     }
  273.     else alert("Unable to locate the shopping game!\nRefresh the page and try again.");
  274. }
  275.  
  276. // This is the start...
  277. if(validUrlCheck()){
  278.     setTimeout(async () => { await rewardsConnectorAuthCheck() && await setupReportActivityUrl() && modifyGame(); }, 500);
  279. }
  280.  
Advertisement
Add Comment
Please, Sign In to add comment