Skip to content

Instantly share code, notes, and snippets.

Last active April 18, 2021 22:27
Show Gist options
  • Save obskyr/c99a89d404e32d7eacaf271b21ab272d to your computer and use it in GitHub Desktop.
Save obskyr/c99a89d404e32d7eacaf271b21ab272d to your computer and use it in GitHub Desktop.
A collection of tools to make Project Egg a bit friendlier.

Project Egg

More info coming here later! Hopefully.


Or, at the present moment, one tool.

To use:

  1. Download this repository as a zip file (button at the top right of the page!) and extract it somewhere.
  2. Install Python 3 (and make sure it's added to your PATH, if you're on Windows!).
  3. Follow the instructions for the specific tool you want to run.

“Purchases” all of the free titles available on Project Egg on your account, so you don't have to add over a hundred games to your cart manually!

To run:

  1. Run pip install -r requirements.txt (pip3 instead of pip on many Linux distributions).
  2. Run and follow the instructions.

And ba-bam! All the games are yours!

#!/usr/bin/env python3
""""Purchase" all the free titles from Project Egg without having to manually
add them all to your cart.
Written July 27th, 2020 by Obskyr (!
import sys
import requests
from functools import lru_cache
from getpass import getpass
from urllib.parse import parse_qs, urljoin, urlparse
from bs4 import BeautifulSoup
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0'
ADD_TO_CART_URL = '{contcode}&cartinid={product_id}'
# Some entries in the "free" list point to old, invalid URLs.
# DISC SAGA 依頼者はモンスター? (PC-9801)
'': '',
# 魔王ゴルベリアス (MSX)
'': '',
# トップルジップ (PC-8801)
'': ''
# Project Egg specifies <meta charset="utf-8"> but sometimes uses EUC-JP...
# to a degree. Pages contain characters that are invalid in EUC-JP as well.
def get_soup(*args, session=None, from_encoding='euc-jp', **kwargs):
if session is None:
session = requests.Session()
return BeautifulSoup(session.get(*args, **kwargs).content.decode(from_encoding, 'replace'), 'html.parser')
class AlreadyPurchasedError(ValueError):
class AlreadyInCartError(ValueError):
class NotFreeError(ValueError):
class NotAvailableError(ValueError):
class LoginError(ValueError):
class Buyer:
def __init__(self):
self._session = requests.Session()
self._session.headers['User-Agent'] = USER_AGENT
def log_in(self, username, password):
r =, data={
'destination': '/project/egg/memberpage/',
'credential_0': username,
'credential_1': password
if r.status_code != 200:
raise LoginError("Could not log in with those credentials.")
def populate_games(self):
self._games = []
soup = get_soup(FREE_LIST_URL, session=self._session, from_encoding='utf-8')
first_game_element = soup.find(class_='new_freegame')
first_game_url = urljoin(FREE_LIST_URL, first_game_element.find('a')['href'])
first_game_url = GAME_URL_CORRECTIONS.get(first_game_url, first_game_url)
first_game_list_title = first_game_element.find('h4').get_text(strip=True)
first_game_list_title = first_game_list_title[:first_game_list_title.index('(')]
self._games.append(Game(self._session, first_game_url, first_game_list_title))
list_element = soup.find(class_='freebox')
for item in list_element(recursive=False):
cur_url = urljoin(FREE_LIST_URL, item.find('a')['href'])
cur_url = GAME_URL_CORRECTIONS.get(cur_url, cur_url)
cur_list_title = str(item.find('span').contents[-1])
self._games.append(Game(self._session, cur_url, cur_list_title))
def check_out(self):
assert self._session.get(CHECKOUT_URL).status_code == 200
def execute(self, username, password):
print("Logging in...")
self.log_in(username, password)
print("Getting list of games...")
for game in self._games:
except AlreadyPurchasedError:
print(f"Already purchased: {game.title}", file=sys.stderr)
except AlreadyInCartError:
print(f"Already in cart: {game.title}", file=sys.stderr)
except NotFreeError:
print(f"Not free: {game.title}", file=sys.stderr)
except NotAvailableError:
print(f"Not available: {game.list_title} ({game.url})", file=sys.stderr)
print(f"Added to cart: {game.title}")
print("Checking out...")
class Game:
def __init__(self, session, url, list_title):
self._session = session
self.url = url
self.list_title = list_title
def _soup(self):
r = self._session.get(self.url, allow_redirects=False)
if r.status_code == 302:
raise NotAvailableError("Game not available.")
assert r.status_code == 200
return BeautifulSoup(r.content.decode('euc-jp', 'replace'), 'html.parser')
def title(self):
return self._soup.find(class_='game_title').get_text(strip=True)
def add_to_cart(self):
status = self._soup.find(class_='gamestatus')
if 'bought' in status['class']:
raise AlreadyPurchasedError("Game already purchased.")
elif status.get_text(strip=True) == "レジへ":
raise AlreadyInCartError("Game already in cart.")
elif status.get_text(strip=True) != "無料":
raise NotFreeError("Game isn't free.")
assert 'buynow' in status['class'] # Sanity check; why not!
query = parse_qs(urlparse(self.url).query)
assert self._session.get(ADD_TO_CART_URL.format(contcode=query['contcode'][0], product_id=query['product_id'][0]), allow_redirects=False).status_code == 302
def main(*argv):
print("Please enter your Project Egg...")
username = input("Username: ")
password = getpass("Password: ")
buyer = Buyer()
buyer.execute(username, password)
except LoginError:
print("Could not log in with those credentials.", file=sys.stderr)
return 1
print(f"Successfully purchased all free games for the account \"{username}\"!")
return 0
if __name__ == '__main__':
requests >= 2.22.0, < 3.0.0
beautifulsoup4 >= 4.9.1, < 5.0.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment