Remove inventory lock to fix a bug;
with campaign not refreshing once all drops are mined
This commit is contained in:
1 parent
e921295613
commit
b0007eaab2
1 file changed
+88
-88
@@ -55,7 +55,6 @@ class Twitch:
|
|||||||
self._state_change = asyncio.Event()
|
self._state_change = asyncio.Event()
|
||||||
self.game: Optional[Game] = None
|
self.game: Optional[Game] = None
|
||||||
self.inventory: Dict[Game, List[DropsCampaign]] = {}
|
self.inventory: Dict[Game, List[DropsCampaign]] = {}
|
||||||
self._inv_lock = asyncio.Lock()
|
|
||||||
# GUI
|
# GUI
|
||||||
self.gui = GUIManager(self)
|
self.gui = GUIManager(self)
|
||||||
# Cookies, session and auth
|
# Cookies, session and auth
|
||||||
@@ -490,58 +489,60 @@ class Twitch:
|
|||||||
if msg_type not in ("drop-progress", "drop-claim"):
|
if msg_type not in ("drop-progress", "drop-claim"):
|
||||||
return
|
return
|
||||||
drop_id: str = message["data"]["drop_id"]
|
drop_id: str = message["data"]["drop_id"]
|
||||||
async with self._inv_lock:
|
drop: Optional[TimedDrop] = self.get_drop(drop_id)
|
||||||
drop: Optional[TimedDrop] = self.get_drop(drop_id)
|
if msg_type == "drop-claim":
|
||||||
if msg_type == "drop-claim":
|
if drop is None:
|
||||||
if drop is None:
|
logger.error(
|
||||||
logger.error(
|
f"Received a drop claim ID for a non-existing drop: {drop_id}\n"
|
||||||
f"Received a drop claim ID for a non-existing drop: {drop_id}\n"
|
f"Drop claim ID: {message['data']['drop_instance_id']}"
|
||||||
f"Drop claim ID: {message['data']['drop_instance_id']}"
|
)
|
||||||
)
|
|
||||||
return
|
|
||||||
drop.update_claim(message["data"]["drop_instance_id"])
|
|
||||||
campaign = drop.campaign
|
|
||||||
mined = await drop.claim()
|
|
||||||
if mined:
|
|
||||||
claim_text = (
|
|
||||||
f"{drop.rewards_text()} "
|
|
||||||
f"({campaign.claimed_drops}/{campaign.total_drops})"
|
|
||||||
)
|
|
||||||
self.gui.print(f"Claimed drop: {claim_text}")
|
|
||||||
self.gui.tray.notify(claim_text, "Mined Drop")
|
|
||||||
else:
|
|
||||||
logger.error(f"Drop claim failed! Drop ID: {drop_id}")
|
|
||||||
# About 4-20s after claiming the drop, next drop can be started
|
|
||||||
# by re-sending the watch payload. We can test for it by fetching the current drop
|
|
||||||
# via GQL, and then comparing drop IDs.
|
|
||||||
await asyncio.sleep(4)
|
|
||||||
for attempt in range(8):
|
|
||||||
context = await self.gql_request(GQL_OPERATIONS["CurrentDrop"])
|
|
||||||
drop_data: Optional[JsonType] = (
|
|
||||||
context["data"]["currentUser"]["dropCurrentSession"]
|
|
||||||
)
|
|
||||||
if drop_data is None or drop_data["dropID"] != drop.id:
|
|
||||||
break
|
|
||||||
await asyncio.sleep(2)
|
|
||||||
self.restart_watching()
|
|
||||||
return
|
return
|
||||||
assert msg_type == "drop-progress"
|
drop.update_claim(message["data"]["drop_instance_id"])
|
||||||
watching_channel = self.watching_channel.get_with_default(None)
|
campaign = drop.campaign
|
||||||
if self._drop_update is None:
|
mined = await drop.claim()
|
||||||
# we aren't actually waiting for a progress update right now, so we can just
|
if mined:
|
||||||
# ignore the event this time
|
claim_text = (
|
||||||
return
|
f"{drop.rewards_text()} "
|
||||||
elif drop is not None and drop.can_earn(watching_channel):
|
f"({campaign.claimed_drops}/{campaign.total_drops})"
|
||||||
drop.update_minutes(message["data"]["current_progress_min"])
|
)
|
||||||
drop.display()
|
self.gui.print(f"Claimed drop: {claim_text}")
|
||||||
# Let the watch loop know we've handled it here
|
self.gui.tray.notify(claim_text, "Mined Drop")
|
||||||
self._drop_update.set_result(True)
|
|
||||||
else:
|
else:
|
||||||
# Sometimes, the drop update we receive doesn't actually match what we're mining.
|
logger.error(f"Drop claim failed! Drop ID: {drop_id}")
|
||||||
# This is a Twitch bug workaround: signal the watch loop to use GQL
|
# About 4-20s after claiming the drop, next drop can be started
|
||||||
# to get the current drop progress instead.
|
# by re-sending the watch payload. We can test for it by fetching the current drop
|
||||||
self._drop_update.set_result(False)
|
# via GQL, and then comparing drop IDs.
|
||||||
self._drop_update = None
|
await asyncio.sleep(4)
|
||||||
|
for attempt in range(8):
|
||||||
|
context = await self.gql_request(GQL_OPERATIONS["CurrentDrop"])
|
||||||
|
drop_data: Optional[JsonType] = (
|
||||||
|
context["data"]["currentUser"]["dropCurrentSession"]
|
||||||
|
)
|
||||||
|
if drop_data is None or drop_data["dropID"] != drop.id:
|
||||||
|
break
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
if campaign.remaining_drops:
|
||||||
|
self.restart_watching()
|
||||||
|
else:
|
||||||
|
self.change_state(State.INVENTORY_FETCH)
|
||||||
|
return
|
||||||
|
assert msg_type == "drop-progress"
|
||||||
|
watching_channel = self.watching_channel.get_with_default(None)
|
||||||
|
if self._drop_update is None:
|
||||||
|
# we aren't actually waiting for a progress update right now, so we can just
|
||||||
|
# ignore the event this time
|
||||||
|
return
|
||||||
|
elif drop is not None and drop.can_earn(watching_channel):
|
||||||
|
drop.update_minutes(message["data"]["current_progress_min"])
|
||||||
|
drop.display()
|
||||||
|
# Let the watch loop know we've handled it here
|
||||||
|
self._drop_update.set_result(True)
|
||||||
|
else:
|
||||||
|
# Sometimes, the drop update we receive doesn't actually match what we're mining.
|
||||||
|
# This is a Twitch bug workaround: signal the watch loop to use GQL
|
||||||
|
# to get the current drop progress instead.
|
||||||
|
self._drop_update.set_result(False)
|
||||||
|
self._drop_update = None
|
||||||
|
|
||||||
@task_wrapper
|
@task_wrapper
|
||||||
async def process_points(self, user_id: int, message: JsonType):
|
async def process_points(self, user_id: int, message: JsonType):
|
||||||
@@ -777,43 +778,42 @@ class Twitch:
|
|||||||
return DropsCampaign(self, response["data"]["user"]["dropCampaign"], claimed_benefits)
|
return DropsCampaign(self, response["data"]["user"]["dropCampaign"], claimed_benefits)
|
||||||
|
|
||||||
async def fetch_inventory(self) -> None:
|
async def fetch_inventory(self) -> None:
|
||||||
async with self._inv_lock:
|
# fetch all available campaign IDs, that are currently ACTIVE and account is connected
|
||||||
# fetch all available campaign IDs, that are currently ACTIVE and account is connected
|
response = await self.gql_request(GQL_OPERATIONS["Campaigns"])
|
||||||
response = await self.gql_request(GQL_OPERATIONS["Campaigns"])
|
data = response["data"]["currentUser"]["dropCampaigns"] or []
|
||||||
data = response["data"]["currentUser"]["dropCampaigns"] or []
|
applicable_statuses = ("ACTIVE", "UPCOMING")
|
||||||
applicable_statuses = ("ACTIVE", "UPCOMING")
|
available_campaigns: Set[str] = set(
|
||||||
available_campaigns: Set[str] = set(
|
c["id"] for c in data
|
||||||
c["id"] for c in data
|
if c["status"] in applicable_statuses and c["self"]["isAccountConnected"]
|
||||||
if c["status"] in applicable_statuses and c["self"]["isAccountConnected"]
|
)
|
||||||
)
|
# fetch in-progress campaigns (inventory)
|
||||||
# fetch in-progress campaigns (inventory)
|
response = await self.gql_request(GQL_OPERATIONS["Inventory"])
|
||||||
response = await self.gql_request(GQL_OPERATIONS["Inventory"])
|
inventory = response["data"]["currentUser"]["inventory"]
|
||||||
inventory = response["data"]["currentUser"]["inventory"]
|
ongoing_campaigns = inventory["dropCampaignsInProgress"] or []
|
||||||
ongoing_campaigns = inventory["dropCampaignsInProgress"] or []
|
# this contains claimed benefit edge IDs, not drop IDs
|
||||||
# this contains claimed benefit edge IDs, not drop IDs
|
claimed_benefits: Dict[str, datetime] = {
|
||||||
claimed_benefits: Dict[str, datetime] = {
|
b["id"]: timestamp(b["lastAwardedAt"]) for b in inventory["gameEventDrops"]
|
||||||
b["id"]: timestamp(b["lastAwardedAt"]) for b in inventory["gameEventDrops"]
|
}
|
||||||
}
|
campaigns: List[DropsCampaign] = [
|
||||||
campaigns: List[DropsCampaign] = [
|
DropsCampaign(self, campaign_data, claimed_benefits)
|
||||||
DropsCampaign(self, campaign_data, claimed_benefits)
|
for campaign_data in ongoing_campaigns
|
||||||
for campaign_data in ongoing_campaigns
|
]
|
||||||
]
|
# filter out in-progress campaigns from all available campaigns,
|
||||||
# filter out in-progress campaigns from all available campaigns,
|
# since we already have all information needed for them
|
||||||
# since we already have all information needed for them
|
for campaign in campaigns:
|
||||||
for campaign in campaigns:
|
available_campaigns.discard(campaign.id)
|
||||||
available_campaigns.discard(campaign.id)
|
# add campaigns that remained, that can be earned but are not in-progress yet
|
||||||
# add campaigns that remained, that can be earned but are not in-progress yet
|
for campaign_id in available_campaigns:
|
||||||
for campaign_id in available_campaigns:
|
campaign = await self.fetch_campaign(campaign_id, claimed_benefits)
|
||||||
campaign = await self.fetch_campaign(campaign_id, claimed_benefits)
|
if any(drop.can_earn() for drop in campaign.drops):
|
||||||
if any(drop.can_earn() for drop in campaign.drops):
|
campaigns.append(campaign)
|
||||||
campaigns.append(campaign)
|
campaigns.sort(key=lambda c: c.ends_at)
|
||||||
campaigns.sort(key=lambda c: c.ends_at)
|
self.inventory.clear()
|
||||||
self.inventory.clear()
|
for campaign in campaigns:
|
||||||
for campaign in campaigns:
|
game = campaign.game
|
||||||
game = campaign.game
|
if game not in self.inventory:
|
||||||
if game not in self.inventory:
|
self.inventory[game] = []
|
||||||
self.inventory[game] = []
|
self.inventory[game].append(campaign)
|
||||||
self.inventory[game].append(campaign)
|
|
||||||
|
|
||||||
def get_drop(self, drop_id: str) -> Optional[TimedDrop]:
|
def get_drop(self, drop_id: str) -> Optional[TimedDrop]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in new issue
Block a user