├── .gitignore ├── favicon-16x16.png ├── README.md ├── get_fixture.py ├── .github └── workflows │ └── build.yaml ├── ladder.py ├── gen_html.py └── thttp.py /.gitignore: -------------------------------------------------------------------------------- 1 | matches.json 2 | __pycache__ 3 | *.pyc 4 | _build -------------------------------------------------------------------------------- /favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sesh/footyfixture/main/favicon-16x16.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Fetch the AFL fixture from the official AFL website, then generate a version that only takes a couple of KBs of bandwidth, doesn't include any tracking or betting ads, and includes the whole fixture on one page. 2 | 3 | Scores for games in the past are included if they are available. This is _absolutely not_ intended to provide live scores. 4 | -------------------------------------------------------------------------------- /get_fixture.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | import sys 4 | from thttp import request 5 | 6 | headers = { 7 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:97.0) Gecko/20100101 Firefox/97.0", 8 | "Accept": "*/*", 9 | "Accept-Language": "en-US,en;q=0.5", 10 | "Accept-Encoding": "gzip, deflate, br", 11 | "Account": "afl", 12 | } 13 | 14 | matches = [] 15 | 16 | for r in range(1, 24): 17 | print(f"Fetching Round {r}") 18 | response = request( 19 | f"https://aflapi.afl.com.au/afl/v2/matches?competitionId=1&compSeasonId=43&pageSize=50&roundNumber={r}" 20 | ) 21 | 22 | try: 23 | matches.extend(response.json["matches"]) 24 | except: 25 | print(response) 26 | sys.exit(1) 27 | 28 | print(f"{len(matches)} matches retrieved so far") 29 | time.sleep(5) 30 | 31 | with open("matches.json", "w") as f: 32 | f.write(json.dumps(matches)) 33 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build Footy Calendar 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | schedule: 7 | - cron: "0 */12 * * *" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - uses: actions/setup-python@v3 18 | with: 19 | python-version: '3.10' 20 | 21 | - name: Create the build directory 22 | run: mkdir _build 23 | 24 | - name: Get the latest fixture 25 | run: python3 get_fixture.py 26 | 27 | - name: Generate the calendar html file 28 | run: python3 gen_html.py 29 | 30 | - name: Copy the favicon 31 | run: cp favicon-16x16.png ./_build/ 32 | 33 | - name: Deploy to Github Pages 34 | uses: peaceiris/actions-gh-pages@v3 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | publish_dir: ./_build 38 | -------------------------------------------------------------------------------- /ladder.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def get_ladder(): 5 | teams = {} 6 | 7 | matches = json.load(open("matches.json")) 8 | 9 | for m in matches: 10 | if m["status"] == "CONCLUDED": 11 | home_score = m["home"]["score"]["totalScore"] 12 | away_score = m["away"]["score"]["totalScore"] 13 | 14 | print( 15 | m["home"]["team"]["abbreviation"], 16 | m["away"]["team"]["abbreviation"], 17 | home_score, 18 | away_score, 19 | ) 20 | 21 | for t in ["home", "away"]: 22 | if m[t]["team"]["abbreviation"] not in teams: 23 | teams[m[t]["team"]["abbreviation"]] = { 24 | "wins": 0, 25 | "losses": 0, 26 | "draws": 0, 27 | "for": 0, 28 | "against": 0, 29 | "results": [], 30 | } 31 | 32 | teams[m["home"]["team"]["abbreviation"]]["for"] += home_score 33 | teams[m["home"]["team"]["abbreviation"]]["against"] += away_score 34 | teams[m["away"]["team"]["abbreviation"]]["for"] += away_score 35 | teams[m["away"]["team"]["abbreviation"]]["against"] += home_score 36 | 37 | if home_score > away_score: 38 | teams[m["home"]["team"]["abbreviation"]]["wins"] += 1 39 | teams[m["home"]["team"]["abbreviation"]]["results"].append("W") 40 | 41 | teams[m["away"]["team"]["abbreviation"]]["losses"] += 1 42 | teams[m["away"]["team"]["abbreviation"]]["results"].append("L") 43 | elif away_score > home_score: 44 | teams[m["home"]["team"]["abbreviation"]]["losses"] += 1 45 | teams[m["home"]["team"]["abbreviation"]]["results"].append("L") 46 | 47 | teams[m["away"]["team"]["abbreviation"]]["wins"] += 1 48 | teams[m["away"]["team"]["abbreviation"]]["results"].append("W") 49 | else: 50 | teams[m["home"]["team"]["abbreviation"]]["draws"] += 1 51 | teams[m["home"]["team"]["abbreviation"]]["results"].append("D") 52 | 53 | teams[m["away"]["team"]["abbreviation"]]["draws"] += 1 54 | teams[m["away"]["team"]["abbreviation"]]["results"].append("D") 55 | 56 | teams = { 57 | k: teams[k] 58 | for k in sorted( 59 | teams, key=lambda x: teams[x]["for"] / teams[x]["against"], reverse=True 60 | ) 61 | } 62 | teams = teams = { 63 | k: teams[k] 64 | for k in sorted( 65 | teams, 66 | key=lambda x: teams[x]["wins"] * 4 + teams[x]["draws"] * 2, 67 | reverse=True, 68 | ) 69 | } 70 | 71 | html = "" 72 | html += "

Ladder

    " 73 | html += '
  1. WLDLast 5
  2. ' 74 | 75 | for team in teams: 76 | html += "
  3. " 77 | html += f'{team}' 78 | html += f'{teams[team]["wins"]}' 79 | html += f'{teams[team]["losses"]}' 80 | html += f'{teams[team]["draws"]}' 81 | html += f'{teams[team]["for"] / teams[team]["against"] * 100:.1f}%' 82 | html += f'{"".join(teams[team]["results"][-5:])}' 83 | html += "
  4. " 84 | 85 | html += "
" 86 | return html 87 | -------------------------------------------------------------------------------- /gen_html.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import json 3 | from thttp import request 4 | from datetime import datetime 5 | from zoneinfo import ZoneInfo 6 | 7 | from ladder import get_ladder 8 | 9 | 10 | matches = json.loads(open("matches.json").read()) 11 | 12 | html = "" 13 | roundNumber = 0 14 | 15 | for m in matches: 16 | if m["round"]["roundNumber"] != roundNumber: 17 | # is new round 18 | roundNumber = m["round"]["roundNumber"] 19 | if html: 20 | html += "" 21 | 22 | html += f"

Round {roundNumber}

" 60 | 61 | template = ( 62 | request("https://basehtml.xyz") 63 | .content.decode() 64 | .split("")[0] 65 | ) 66 | template = template.replace( 67 | "Minimal base.html", "Footy Fixture - 2022 Edition" 68 | ) 69 | 70 | template = template.replace("width=device-width", "width=440") 71 | template = template.replace("initial-scale=1.0", "maximum-scale=1.2") 72 | 73 | template = template.replace( 74 | "", 75 | """ 76 | 77 | 134 |

footyfixture.txt

""", 135 | ) 136 | 137 | template += get_ladder() 138 | template += html 139 | template += '