charlesbel / Microsoft-Rewards-Farmer

A simple bot that uses selenium to farm Microsoft Rewards written in Python
MIT License
937 stars 249 forks source link

Bing Shopping #305

Open EastArctica opened 10 months ago

EastArctica commented 10 months ago

Bing shopping has a game where you pick what you think costs the least, this game can be visited at https://www.bing.com/shop however a more minimal version of it can be accessed at https://www.msn.com/en-us/shopping?isembedded=1&disableheader=1

This game only gives you points when you get it correct, which for a human is very easy as you can simply click on see more to open the price in a new page. However we don't want to have to do that in the bot.

One method I attempted was to navigate the dom to get to the msn-shopping-game-pane and then searching the displayedShoppingEntities field.

let gamePane = document.querySelector('shopping-page-base').shadowRoot.querySelector('shopping-homepage').shadowRoot.querySelector('msft-feed-layout').shadowRoot.querySelector('msn-shopping-game-pane');
let cheapest = gamePane.displayedShoppingEntities.sort((a, b) => parseFloat(a.priceInfo.price) < parseFloat(b.priceInfo.price))[0];

However, if you attempt to use that yourself. It does not show the cheapest item. If we look into this we notice that the price field is an empty string. It seems that Microsoft has an "anti-cheat" of sort that they have added. (I thought I had found a function but it actually doesn't seem to be called...

Either way, the price is an empty string; So that won't work. However I did notice we have a couple things stating the discount percentage... Now this is nowhere near perfect. But I think it'll do for now (I went through my 10 per day so I can't keep testing)

let gamePane = document.querySelector('shopping-page-base').shadowRoot.querySelector('shopping-homepage').shadowRoot.querySelector('msft-feed-layout').shadowRoot.querySelector('msn-shopping-game-pane');
/*
// You can do this for faster ending games, however we won't use this in production
gamePane.gameSettings = { "newGameCountdown": 0 }
*/
for (const ent of gamePane.displayedShoppingEntities) {
    let originalPrice = parseFloat(ent.priceInfo.originalPrice.slice(ent.priceInfo.priceCurrencySymbol.length))
    let discountedPrice = originalPrice * (1 - parseFloat(ent.dealPercentage) / 100)
    console.log(discountedPrice)
}

So one thing I also noticed, the game must still be storing the original prices somewhere as it shows you them at the end of the game. That should be looked into to see how it's being done.

EastArctica commented 10 months ago

After more research, I found the function responsible for clearing the price.

clearPrice & utility function ```js // If you see d._Q used, this is what that is. // To make your life much easier, in VSCode, replace // \(0, *d\.Q_\)\(([a-zA-Z_0-9]+), *([a-zA-Z_0-9]+), *"f"\) with $2.get($1) function queryPrivateMember(object, instance, accessorType, accessor) { if ("a" === accessorType && !accessor) { throw new TypeError("Private accessor was defined without a getter"); } if ("function" == typeof instance ? object !== instance || !accessor : !instance.has(object)) { throw new TypeError("Cannot read private member from an object whose class did not declare it"); } if (accessorType === "m") { return accessor; } else if (accessorType === "a") { return accessor.call(object); } else { return accessor ? accessor.value : instance.get(object); } } // If you see d.YH used, this is what that is. // To make your life much easier, in VSCode, replace // (0, *d\.YH)\(([a-zA-Z_0-9]+), *([a-zA-Z_0-9]+), *([a-zA-Z_0-9]+), *"f"\) // with $2.set($1, $3) function setPrivateMember(object, instance, value, type, setter) { if (type === "m") { throw new TypeError("Private method is not writable"); } if (type === "a" && !setter) { throw new TypeError("Private accessor was defined without a setter"); } if (typeof instance === "function" ? object !== instance || !setter : !instance.has(object)) { throw new TypeError("Cannot write private member to an object whose class did not declare it"); } if (type === "a") { return setter.call(object, value); } else if (setter) { setter.value = value; } else { instance.set(object, value); } return value; } // I don't remember the original name tbh function clearPrice() { Ut.set(this, new Map()); this.displayedShoppingEntities.forEach((e) => { if (!e.priceInfo) { return; } // Store the original `priceInfo` in the `Ut` map kinda Ut.get(this)[e.id] = e.priceInfo; // Clear the `price` field from the `priceInfo` e.priceInfo = Object.assign({}, e.priceInfo, { price: "" }); }); // idk who cares this.displayAttributes.map((e) => { e.type === lt.G.StrikedOutPrice && (e.type = lt.G.Price); }); } ```

I have also included the d._Q and d.YH functions, they're aweful in the code. You'll see them used like this a lot (0, d.YH)(this, Ut, new Map, "f"), think of that as just Ut.set(this, new Map).

ANYWAY, none of this matters if we can't get an instance of Ut which uh... we can't... BUT! We might still be able to get the price...

If we look at when Ut is used, we can find the ii function.

ii function ```js function ii() { const fixedEntities = []; for (let t = 0; t < this.displayedShoppingEntities.length; t++) { const originalPriceInfo = Ut.get(this)[this.displayedShoppingEntities[t].id]; fixedEntities.push(Object.assign({}, this.displayedShoppingEntities[t], { priceInfo: originalPriceInfo })); } this.displayedShoppingEntities = fixedEntities, this.displayAttributes.map((e => { e.type == lt.G.Price && (e.type = lt.G.StrikedOutPrice) })) } ```

This looks promising! If we just call this, then it should reset the prices back to their originals. So! How do we call it?

We can't.

It's only in the qt function which seems to be a selectCard(idx) function of sorts

qt function ```js function qt(selectedCardIdx) { var t; // Correct index this.c_ai = Bt.get(this); // This is -1 until it's set here. const isCorrect = selectedCardIdx === this.c_ai; // idk yet ei.call(this); // Add back price to entitites ii.call(this); const cardHash = Gt.get(this); // I think if (isCorrect) { pt.get(this).callLeaderboardIncrementPointsV2(2, cardHash).then((e=>{ _t.set(this, e); pi.call(this); })); } else { pt.get(this).callLeaderboardIncrementPointsV2(1, cardHash).then((e=>{ _t.set(this, e); pi.call(this); })); } if (isCorrect) { this.gameState = mt.win; this.confettiAnimate?.play() } else { this.gameState = mt.lose this.startCountdown() } if (!At) { this.answerValidationDisplayed = true; } // Stopped reversing here let e, t, i, o = (0, bt.$o)().getItem("gamesPerDay"); if (o && ([e,t,i] = o.split("-").map(Number), Ct(t, i) && e++), void 0 === o || !Ct(t, i)) { const o = new Date; e = 1, t = o.getUTCMonth(), i = o.getUTCDate() } o = e + "-" + t + "-" + i, (0, bt.$o)().setItem("gamesPerDay", o); (wt(this.gameSettings.dailyGamesLimit) || (0, d.Q_)(this, Nt, "f") || (0, d.Q_)(this, Mt, "m", oi).call(this)) && (this.dailyLimitReached = !0) } ```

However, in this reversal, we see that Bt.get(this) contains the correct index. Let's look into that!

I reversed most of Wt but got nowhere, it just sets a max number of entities.

Okay, what about Jt? This is where Bt is set after all. This function is called every time a game starts (including page load)

Jt function ```js function Jt() { var e; if ( (Ht.get(this) < 3 && d.Q_(this, Mt, "m", Wt).call(this), clearTimeout(Vt.get(this)), null === (e = this.confettiAnimate) || void 0 === e || e.stop(), (this.selectedCardIndex = -1), (this.c_ai = -1), d.YH(this, Zt, !1, "f"), (this.failedToReportRewardsActivity = !1), this.rewardsEnabled && d.Q_(this, Mt, "m", si).call(this), this.strings) ) { const e = _t.get(this) ? _t.get(this).dailyGuessingGamesPlayed : (function () { const e = (0, bt.$o)().getItem("gamesPerDay"); let t, i, o; return e && (([t, i, o] = e.split("-").map(Number)), Ct(i, o)) ? t : 0; })(); this.strings.gameCountText = (0, G.WU)( this.strings.gamesCountFormat, (e + 1).toString(), this.gameSettings.dailyGamesLimit.toString() ); } (this.displayedShoppingEntities = Rt.get(this).slice(0, Ht.get(this))), zt.get(this) || d.YH(this, zt, this.fetchGameDataFunc, "f"), zt .get(this) .call(this, _t.get(this).dailyGuessingGamesPlayed) .then(d.Q_(this, Mt, "m", ni).bind(this)) .catch(() => d.YH(this, Nt, !0, "f")); // Everything above this was not reversed let correctIdx = 0; if (this.displayedShoppingEntities?.length > 1) { let lowestPrice = $t(this.displayedShoppingEntities[0]?.priceInfo?.price); for (let idx = 1; idx < this.displayedShoppingEntities.length; idx++) { const price = $t(this.displayedShoppingEntities[idx]?.priceInfo?.price); if (price < lowestPrice) { correctIdx = idx; lowestPrice = price } } } Bt.set(this, correctIdx); d.Q_(this, Mt, "m", ti).call(this); if (wt(this.gameSettings.dailyGamesLimit)) { this.dailyLimitReached = true; this.stopCardsAnimation = true; } else { this.gameState = mt.active; } }; ```

Nothing... So we have no way to get either the price of the items or the correct index. We more or less have no way to get the correct answer here.

What I propose is that since we're already hooking the resource loads, we patch out the e.priceInfo = Object.assign({}, e.priceInfo, { price: "" }); which should be relatively easy because we can just patch all instances of {price:""}

Edit: I also wrote this quickie script to click the right one. Keep in mind you need to be here

Script ```js let gamePane = document.querySelector('shopping-page-base').shadowRoot.querySelector('shopping-homepage').shadowRoot.querySelector('msft-feed-layout').shadowRoot.querySelector('msn-shopping-game-pane'); let cheapestIdx = 0; let cheapestCost = 999999; for (let i = 1; i < gamePane.displayedShoppingEntities.length; i++) { let ent = gamePane.displayedShoppingEntities[i] let originalPrice = parseFloat(ent.priceInfo.originalPrice.slice(ent.priceInfo.priceCurrencySymbol.length)) let discountedPrice = originalPrice * (1 - parseFloat(ent.dealPercentage) / 100) if (discountedPrice < cheapestCost) { cheapestIdx = i; cheapestCost = discountedPrice; } } gamePane.shadowRoot.querySelectorAll('msn-shopping-card')[cheapestIdx].querySelector('.shopping-select-overlay-button').click() ```
Chappie18 commented 10 months ago

That's fantastic, bro. Can you do a pull request for this?

theskid31 commented 10 months ago

getting error:

2023-08-28 00:49:30,827 [INFO] [SHOPPING GAME] Trying to complete the Shopping Game... 2023-08-28 00:50:33,619 [ERROR] JavascriptException: Message: javascript error: Cannot read properties of null (reading 'click') JavaScript stack: TypeError: Cannot read properties of null (reading 'click') at eval (eval at executeAsyncScript (:1:1), :50:72) (Session info: chrome=116.0.5845.111) Stacktrace: GetHandleVerifier [0x00A837C3+48947] (No symbol) [0x00A18551] (No symbol) [0x0091C92D] (No symbol) [0x0092170B] (No symbol) [0x009212D2] (No symbol) [0x009781A0] (No symbol) [0x0096508C] (No symbol) [0x009775DA] (No symbol) [0x00964E86] (No symbol) [0x009416C7] (No symbol) [0x0094284D] GetHandleVerifier [0x00CCFDF9+2458985] GetHandleVerifier [0x00D1744F+2751423] GetHandleVerifier [0x00D11361+2726609] GetHandleVerifier [0x00B00680+560624] (No symbol) [0x00A2238C] (No symbol) [0x00A1E268] (No symbol) [0x00A1E392] (No symbol) [0x00A110B7] BaseThreadInitThunk [0x75847BA9+25] RtlInitializeExceptionChain [0x779FB79B+107] RtlClearBits [0x779FB71F+191] Traceback (most recent call last): File "F:\udu3324 Microsoft-Rewards-Farmer-master\main.py", line 33, in main executeBot(currentAccount, notifier, args) File "F:\udu3324 Microsoft-Rewards-Farmer-master\main.py", line 151, in executeBot ShoppingGame(desktopBrowser).completeShoppingGame() File "F:\udu3324 Microsoft-Rewards-Farmer-master\src\shoppingGame.py", line 22, in completeShoppingGame res = self.webdriver.execute_async_script( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\site-packages\selenium\webdriver\remote\webdriver.py", line 424, in execute_async_script return self.execute(command, {"script": script, "args": converted_args})["value"] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\site-packages\selenium\webdriver\remote\webdriver.py", line 345, in execute self.error_handler.check_response(response) File "C:\Program Files\Python311\Lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 229, in check_response raise exception_class(message, screen, stacktrace) selenium.common.exceptions.JavascriptException: Message: javascript error: Cannot read properties of null (reading 'click') JavaScript stack: TypeError: Cannot read properties of null (reading 'click') at eval (eval at executeAsyncScript (:1:1), :50:72) (Session info: chrome=116.0.5845.111) Stacktrace: GetHandleVerifier [0x00A837C3+48947] (No symbol) [0x00A18551] (No symbol) [0x0091C92D] (No symbol) [0x0092170B] (No symbol) [0x009212D2] (No symbol) [0x009781A0] (No symbol) [0x0096508C] (No symbol) [0x009775DA] (No symbol) [0x00964E86] (No symbol) [0x009416C7] (No symbol) [0x0094284D] GetHandleVerifier [0x00CCFDF9+2458985] GetHandleVerifier [0x00D1744F+2751423] GetHandleVerifier [0x00D11361+2726609] GetHandleVerifier [0x00B00680+560624] (No symbol) [0x00A2238C] (No symbol) [0x00A1E268] (No symbol) [0x00A1E392] (No symbol) [0x00A110B7] BaseThreadInitThunk [0x75847BA9+25] RtlInitializeExceptionChain [0x779FB79B+107] RtlClearBits [0x779FB71F+191]

im running it in windows can you help?

EastArctica commented 10 months ago

getting error:

2023-08-28 00:49:30,827 [INFO] [SHOPPING GAME] Trying to complete the Shopping Game... 2023-08-28 00:50:33,619 [ERROR] JavascriptException: Message: javascript error: Cannot read properties of null (reading 'click') JavaScript stack: TypeError: Cannot read properties of null (reading 'click') at eval (eval at executeAsyncScript (:1:1), :50:72) (Session info: chrome=116.0.5845.111) Stacktrace: GetHandleVerifier [0x00A837C3+48947] (No symbol) [0x00A18551] (No symbol) [0x0091C92D] (No symbol) [0x0092170B] (No symbol) [0x009212D2] (No symbol) [0x009781A0] (No symbol) [0x0096508C] (No symbol) [0x009775DA] (No symbol) [0x00964E86] (No symbol) [0x009416C7] (No symbol) [0x0094284D] GetHandleVerifier [0x00CCFDF9+2458985] GetHandleVerifier [0x00D1744F+2751423] GetHandleVerifier [0x00D11361+2726609] GetHandleVerifier [0x00B00680+560624] (No symbol) [0x00A2238C] (No symbol) [0x00A1E268] (No symbol) [0x00A1E392] (No symbol) [0x00A110B7] BaseThreadInitThunk [0x75847BA9+25] RtlInitializeExceptionChain [0x779FB79B+107] RtlClearBits [0x779FB71F+191] Traceback (most recent call last): File "F:\udu3324 Microsoft-Rewards-Farmer-master\main.py", line 33, in main executeBot(currentAccount, notifier, args) File "F:\udu3324 Microsoft-Rewards-Farmer-master\main.py", line 151, in executeBot ShoppingGame(desktopBrowser).completeShoppingGame() File "F:\udu3324 Microsoft-Rewards-Farmer-master\src\shoppingGame.py", line 22, in completeShoppingGame res = self.webdriver.execute_async_script( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\site-packages\selenium\webdriver\remote\webdriver.py", line 424, in execute_async_script return self.execute(command, {"script": script, "args": converted_args})["value"] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Program Files\Python311\Lib\site-packages\selenium\webdriver\remote\webdriver.py", line 345, in execute self.error_handler.check_response(response) File "C:\Program Files\Python311\Lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 229, in check_response raise exception_class(message, screen, stacktrace) selenium.common.exceptions.JavascriptException: Message: javascript error: Cannot read properties of null (reading 'click') JavaScript stack: TypeError: Cannot read properties of null (reading 'click') at eval (eval at executeAsyncScript (:1:1), :50:72) (Session info: chrome=116.0.5845.111) Stacktrace: GetHandleVerifier [0x00A837C3+48947] (No symbol) [0x00A18551] (No symbol) [0x0091C92D] (No symbol) [0x0092170B] (No symbol) [0x009212D2] (No symbol) [0x009781A0] (No symbol) [0x0096508C] (No symbol) [0x009775DA] (No symbol) [0x00964E86] (No symbol) [0x009416C7] (No symbol) [0x0094284D] GetHandleVerifier [0x00CCFDF9+2458985] GetHandleVerifier [0x00D1744F+2751423] GetHandleVerifier [0x00D11361+2726609] GetHandleVerifier [0x00B00680+560624] (No symbol) [0x00A2238C] (No symbol) [0x00A1E268] (No symbol) [0x00A1E392] (No symbol) [0x00A110B7] BaseThreadInitThunk [0x75847BA9+25] RtlInitializeExceptionChain [0x779FB79B+107] RtlClearBits [0x779FB71F+191]

im running it in windows can you help?

This PR is a work in progress, and therefore should not be used. I'll post in the PR when it's in a state where testing should be done.

theskid31 commented 10 months ago

This PR is a work in progress, and therefore should not be used. I'll post in the PR when it's in a state where testing should be done.

okay thanks for your reply,best of luck looking great if you require an update ,got the script running but now just getting:

←[38;5;39m2023-08-28 20:56:24,345 [INFO] [SHOPPING GAME] Trying to complete the Shopping Game...←[0m Hooked ←[38;5;196m2023-08-28 20:56:51,929 [ERROR] [SHOPPING GAME] Failed to get game element.←[0m

aerithanimon commented 10 months ago

Hello, I tried the quickie script, and the quickie script did not work.

I really do not want to use linux, just to complete the shopping game.

No one has figured out to create a brand new script, for the shopping game.

I really do not want to use the rewards farmer tool, so if anyone can figure out a script for the shopping game, to be used on a windows brower, that would be appreciated.

Thanks for your time.

EastArctica commented 10 months ago

Hello, I tried the quickie script, and the quickie script did not work.

I really do not want to use linux, just to complete the shopping game.

No one has figured out to create a brand new script, for the shopping game.

I really do not want to use the rewards farmer tool, so if anyone can figure out a script for the shopping game, to be used on a windows brower, that would be appreciated.

Thanks for your time.

No script used in the browser will ever be 100% perfect without overwriting the sites js.