Compare commits

...

17 Commits

Author SHA1 Message Date
rufei a5e622b950 improve delete log line
Periodic refresh of events / Run-Scraper (push) Successful in 1m22s
2025-04-16 00:27:30 -07:00
rufei 9cd7505517 adjust stdout
Periodic refresh of events / Run-Scraper (push) Successful in 1m6s
2025-04-10 23:54:44 -07:00
rufei c0ed56d9e0 new description in ical 2025-04-10 23:51:00 -07:00
rufei 75e0fd8a47 comments
Periodic refresh of events / Run-Scraper (push) Successful in 1m49s
2025-04-10 23:11:02 -07:00
rufei f41c0b5806 add icaluid to description 2025-04-10 23:09:42 -07:00
rufei da8a7afb56 update str
Periodic refresh of events / Run-Scraper (push) Successful in 46s
2025-04-10 15:50:42 -07:00
rufei 647c20955c mkdir to fix tee 2025-04-10 15:48:45 -07:00
rufei 2faabee6c4 fix naming 2025-04-10 15:47:15 -07:00
rufei efbd8f5122 add stdout to release notes 2025-04-10 15:41:11 -07:00
rufei 4f9c4716ef specify active event count
Periodic refresh of events / Run-Scraper (push) Successful in 1m48s
2025-04-10 15:19:03 -07:00
rufei a5ea31fa6f yay 2025-04-10 15:16:28 -07:00
rufei 8a9fa7a954 summary outcome output 2025-04-10 15:14:32 -07:00
rufei 2573093694 use different ical prefix, better print diffs
Periodic refresh of events / Run-Scraper (push) Waiting to run
2025-04-10 14:30:21 -07:00
rufei 0de41e3522 fix indent
Periodic refresh of events / Run-Scraper (push) Successful in 12m6s
2025-04-10 05:20:08 -07:00
rufei c897eb8265 add secret to pipeline 2025-04-10 05:18:29 -07:00
rufei 1821d86a1e put some temp sleeps in there 2025-04-10 05:15:34 -07:00
rufei 1dfaac75d2 there's a lot to unpack here 2025-04-10 05:09:57 -07:00
7 changed files with 678 additions and 19 deletions
+5 -2
View File
@@ -57,7 +57,9 @@ jobs:
- name: Install package - name: Install package
run: poetry install --no-interaction run: poetry install --no-interaction
- name: Generate new calendar Files - name: Generate new calendar Files
run: poetry run generate-ics run: mkdir output && poetry run generate-ics | tee output/run_logs.txt
env:
GOOGLE_SERVICE_ACCOUNT_CREDS: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_CREDS }}
#---------------------------------------------- #----------------------------------------------
# load cached venv if cache exists # load cached venv if cache exists
#---------------------------------------------- #----------------------------------------------
@@ -66,7 +68,7 @@ jobs:
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: output path: output
key: calendar-files-${{ hashFiles('output/**') }} key: calendar-files-${{ hashFiles('output/**/*.ics') }}
#---------------------------------------------- #----------------------------------------------
# publish artifacts and release # publish artifacts and release
#---------------------------------------------- #----------------------------------------------
@@ -90,5 +92,6 @@ jobs:
with: with:
name: ${{ steps.time.outputs.time }} name: ${{ steps.time.outputs.time }}
tag_name: ${{ steps.time_tag.outputs.tag }} tag_name: ${{ steps.time_tag.outputs.tag }}
body_path: output/run_logs.txt
files: |- files: |-
calendars.zip calendars.zip
+4 -4
View File
@@ -29,10 +29,10 @@ poetry run generate-ics
``` ```
## TODO ## TODO
* sync events to gcal by icaluid on every release via gcal api * ~~sync events to gcal by icaluid on every release via gcal api~~
* ~~compare event ids from last sync to current and highlight changes to previously selected events in release~~
* modify runner scripts to stop running on events that have already elapsed
* refactor event scrapers to use a config containing start/stop and other common metadata
* create multi-select webui * create multi-select webui
* save selected event uids in cookie * save selected event uids in cookie
* compare event ids from last sync to current and highlight changes to previously selected events
* refactor event scrapers to use a config containing start/stop and other common metadata
* modify runner scripts to stop running on events that have already elapsed
* add support for other events like lbx * add support for other events like lbx
+18
View File
@@ -0,0 +1,18 @@
{
"start": 1744948800,
"end": 1745207999,
"calendars": {
"Sakura-Con Presents": "f2802717d1727828bf1bfe9f6cc35844a36ffff6daad7c3fa293ab35bf51c495@group.calendar.google.com",
"Fan Panel": "8062c73edfb8db11c385b255531d97ba7a30672a5e6ded4e24cd589b25282d65@group.calendar.google.com",
"Gaming": "46c95e247f9e449f2968b44edfe659560a232efbf57576efc9f15ba8668ee0ff@group.calendar.google.com",
"Main Stage": "ae20459e6d4c41b46496663b04ec87279fb1a0ed858351ea796d82c217e20579@group.calendar.google.com",
"Cultural Panel": "633adb5a3a96b984092c8e641dda4011b80a8a314d03fc0c6e941895caabf2d2@group.calendar.google.com",
"Guest of Honor": "da7b9a6845eb7868457147046aa388101ef2601cffbaf4d29f1a1c8d00a8bea9@group.calendar.google.com",
"Charity Auction": "a51250c24fd2c4fd2d4bbefc94c055eb1f392ccf89d93799d4a92ff2c319f436@group.calendar.google.com",
"Industry Panel": "fe3a9a7e1e839dd41ac934bb0b79287d4ff705f577f726d06b0f4249bae1ac40@group.calendar.google.com",
"Autographs": "1959b9a550cbdf73d7997c070009ea836c1af93d8330353bbfdbbc2f82aff27a@group.calendar.google.com",
"Summit Stage": "106d7620bbe3a8e14ee593758e7db9c71a311615e3a17ada938f50974b0b9ccf@group.calendar.google.com"
},
"tzSuffix": "-07:00",
"uidPrefix": "sakuracon2025-"
}
Generated
+392 -1
View File
@@ -164,6 +164,18 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi
tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"]
tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""]
[[package]]
name = "cachetools"
version = "5.5.2"
description = "Extensible memoizing collections and decorators"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"},
{file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"},
]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2025.1.31" version = "2025.1.31"
@@ -396,6 +408,146 @@ files = [
{file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"},
] ]
[[package]]
name = "google-api-core"
version = "2.24.2"
description = "Google API client core library"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "google_api_core-2.24.2-py3-none-any.whl", hash = "sha256:810a63ac95f3c441b7c0e43d344e372887f62ce9071ba972eacf32672e072de9"},
{file = "google_api_core-2.24.2.tar.gz", hash = "sha256:81718493daf06d96d6bc76a91c23874dbf2fac0adbbf542831b805ee6e974696"},
]
[package.dependencies]
google-auth = ">=2.14.1,<3.0.0"
googleapis-common-protos = ">=1.56.2,<2.0.0"
proto-plus = [
{version = ">=1.25.0,<2.0.0", markers = "python_version >= \"3.13\""},
{version = ">=1.22.3,<2.0.0", markers = "python_version < \"3.13\""},
]
protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0"
requests = ">=2.18.0,<3.0.0"
[package.extras]
async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"]
grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""]
grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
[[package]]
name = "google-api-python-client"
version = "2.166.0"
description = "Google API Client Library for Python"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "google_api_python_client-2.166.0-py2.py3-none-any.whl", hash = "sha256:dd8cc74d9fc18538ab05cbd2e93cb4f82382f910c5f6945db06c91f1deae6e45"},
{file = "google_api_python_client-2.166.0.tar.gz", hash = "sha256:b8cf843bd9d736c134aef76cf1dc7a47c9283a2ef24267b97207b9dd43b30ef7"},
]
[package.dependencies]
google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0"
google-auth = ">=1.32.0,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0"
google-auth-httplib2 = ">=0.2.0,<1.0.0"
httplib2 = ">=0.19.0,<1.0.0"
uritemplate = ">=3.0.1,<5"
[[package]]
name = "google-auth"
version = "2.38.0"
description = "Google Authentication Library"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a"},
{file = "google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4"},
]
[package.dependencies]
cachetools = ">=2.0.0,<6.0"
pyasn1-modules = ">=0.2.1"
rsa = ">=3.1.4,<5"
[package.extras]
aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"]
enterprise-cert = ["cryptography", "pyopenssl"]
pyjwt = ["cryptography (>=38.0.3)", "pyjwt (>=2.0)"]
pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"]
reauth = ["pyu2f (>=0.1.5)"]
requests = ["requests (>=2.20.0,<3.0.0.dev0)"]
[[package]]
name = "google-auth-httplib2"
version = "0.2.0"
description = "Google Authentication Library: httplib2 transport"
optional = false
python-versions = "*"
groups = ["main"]
files = [
{file = "google-auth-httplib2-0.2.0.tar.gz", hash = "sha256:38aa7badf48f974f1eb9861794e9c0cb2a0511a4ec0679b1f886d108f5640e05"},
{file = "google_auth_httplib2-0.2.0-py2.py3-none-any.whl", hash = "sha256:b65a0a2123300dd71281a7bf6e64d65a0759287df52729bdd1ae2e47dc311a3d"},
]
[package.dependencies]
google-auth = "*"
httplib2 = ">=0.19.0"
[[package]]
name = "google-auth-oauthlib"
version = "1.2.1"
description = "Google Authentication Library"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "google_auth_oauthlib-1.2.1-py2.py3-none-any.whl", hash = "sha256:2d58a27262d55aa1b87678c3ba7142a080098cbc2024f903c62355deb235d91f"},
{file = "google_auth_oauthlib-1.2.1.tar.gz", hash = "sha256:afd0cad092a2eaa53cd8e8298557d6de1034c6cb4a740500b5357b648af97263"},
]
[package.dependencies]
google-auth = ">=2.15.0"
requests-oauthlib = ">=0.7.0"
[package.extras]
tool = ["click (>=6.0.0)"]
[[package]]
name = "googleapis-common-protos"
version = "1.69.2"
description = "Common protobufs used in Google APIs"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "googleapis_common_protos-1.69.2-py3-none-any.whl", hash = "sha256:0b30452ff9c7a27d80bfc5718954063e8ab53dd3697093d3bc99581f5fd24212"},
{file = "googleapis_common_protos-1.69.2.tar.gz", hash = "sha256:3e1b904a27a33c821b4b749fd31d334c0c9c30e6113023d495e48979a3dc9c5f"},
]
[package.dependencies]
protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<7.0.0"
[package.extras]
grpc = ["grpcio (>=1.44.0,<2.0.0)"]
[[package]]
name = "httplib2"
version = "0.22.0"
description = "A comprehensive HTTP client library."
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["main"]
files = [
{file = "httplib2-0.22.0-py3-none-any.whl", hash = "sha256:14ae0a53c1ba8f3d37e9e27cf37eabb0fb9980f435ba405d546948b009dd64dc"},
{file = "httplib2-0.22.0.tar.gz", hash = "sha256:d7a10bc5ef5ab08322488bde8c726eeee5c8618723fdb399597ec58f3d82df81"},
]
[package.dependencies]
pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0.2,<3.0.3 || >3.0.3,<4", markers = "python_version > \"3.0\""}
[[package]] [[package]]
name = "icalendar" name = "icalendar"
version = "6.1.3" version = "6.1.3"
@@ -430,6 +582,24 @@ files = [
[package.extras] [package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "jsondiff"
version = "2.2.1"
description = "Diff JSON and JSON-like structures in Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "jsondiff-2.2.1-py3-none-any.whl", hash = "sha256:b1f0f7e2421881848b1d556d541ac01a91680cfcc14f51a9b62cdf4da0e56722"},
{file = "jsondiff-2.2.1.tar.gz", hash = "sha256:658d162c8a86ba86de26303cd86a7b37e1b2c1ec98b569a60e2ca6180545f7fe"},
]
[package.dependencies]
pyyaml = "*"
[package.extras]
dev = ["build", "hypothesis", "pytest", "setuptools-scm"]
[[package]] [[package]]
name = "multidict" name = "multidict"
version = "6.3.2" version = "6.3.2"
@@ -535,6 +705,23 @@ files = [
[package.dependencies] [package.dependencies]
typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""}
[[package]]
name = "oauthlib"
version = "3.2.2"
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"},
{file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"},
]
[package.extras]
rsa = ["cryptography (>=3.0.0)"]
signals = ["blinker (>=1.4.0)"]
signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]] [[package]]
name = "propcache" name = "propcache"
version = "0.3.1" version = "0.3.1"
@@ -643,6 +830,85 @@ files = [
{file = "propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf"}, {file = "propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf"},
] ]
[[package]]
name = "proto-plus"
version = "1.26.1"
description = "Beautiful, Pythonic protocol buffers"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"},
{file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"},
]
[package.dependencies]
protobuf = ">=3.19.0,<7.0.0"
[package.extras]
testing = ["google-api-core (>=1.31.5)"]
[[package]]
name = "protobuf"
version = "6.30.2"
description = ""
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "protobuf-6.30.2-cp310-abi3-win32.whl", hash = "sha256:b12ef7df7b9329886e66404bef5e9ce6a26b54069d7f7436a0853ccdeb91c103"},
{file = "protobuf-6.30.2-cp310-abi3-win_amd64.whl", hash = "sha256:7653c99774f73fe6b9301b87da52af0e69783a2e371e8b599b3e9cb4da4b12b9"},
{file = "protobuf-6.30.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:0eb523c550a66a09a0c20f86dd554afbf4d32b02af34ae53d93268c1f73bc65b"},
{file = "protobuf-6.30.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:50f32cc9fd9cb09c783ebc275611b4f19dfdfb68d1ee55d2f0c7fa040df96815"},
{file = "protobuf-6.30.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4f6c687ae8efae6cf6093389a596548214467778146b7245e886f35e1485315d"},
{file = "protobuf-6.30.2-cp39-cp39-win32.whl", hash = "sha256:524afedc03b31b15586ca7f64d877a98b184f007180ce25183d1a5cb230ee72b"},
{file = "protobuf-6.30.2-cp39-cp39-win_amd64.whl", hash = "sha256:acec579c39c88bd8fbbacab1b8052c793efe83a0a5bd99db4a31423a25c0a0e2"},
{file = "protobuf-6.30.2-py3-none-any.whl", hash = "sha256:ae86b030e69a98e08c77beab574cbcb9fff6d031d57209f574a5aea1445f4b51"},
{file = "protobuf-6.30.2.tar.gz", hash = "sha256:35c859ae076d8c56054c25b59e5e59638d86545ed6e2b6efac6be0b6ea3ba048"},
]
[[package]]
name = "pyasn1"
version = "0.6.1"
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"},
{file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"},
]
[[package]]
name = "pyasn1-modules"
version = "0.4.2"
description = "A collection of ASN.1-based protocols modules"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"},
{file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"},
]
[package.dependencies]
pyasn1 = ">=0.6.1,<0.7.0"
[[package]]
name = "pyparsing"
version = "3.2.3"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"},
{file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"},
]
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]] [[package]]
name = "python-dateutil" name = "python-dateutil"
version = "2.9.0.post0" version = "2.9.0.post0"
@@ -670,6 +936,69 @@ files = [
{file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"},
] ]
[[package]]
name = "pyyaml"
version = "6.0.2"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
]
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.32.3" version = "2.32.3"
@@ -692,6 +1021,56 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"] socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "requests-oauthlib"
version = "2.0.0"
description = "OAuthlib authentication support for Requests."
optional = false
python-versions = ">=3.4"
groups = ["main"]
files = [
{file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"},
{file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"},
]
[package.dependencies]
oauthlib = ">=3.0.0"
requests = ">=2.0.0"
[package.extras]
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
[[package]]
name = "rsa"
version = "4.2"
description = "Pure-Python RSA implementation"
optional = false
python-versions = "*"
groups = ["main"]
markers = "python_version >= \"3.13\""
files = [
{file = "rsa-4.2.tar.gz", hash = "sha256:aaefa4b84752e3e99bd8333a2e1e3e7a7da64614042bd66f775573424370108a"},
]
[package.dependencies]
pyasn1 = ">=0.1.3"
[[package]]
name = "rsa"
version = "4.9"
description = "Pure-Python RSA implementation"
optional = false
python-versions = ">=3.6,<4"
groups = ["main"]
markers = "python_version < \"3.13\""
files = [
{file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
{file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
]
[package.dependencies]
pyasn1 = ">=0.1.3"
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "75.3.2" version = "75.3.2"
@@ -750,6 +1129,18 @@ files = [
{file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"},
] ]
[[package]]
name = "uritemplate"
version = "4.1.1"
description = "Implementation of RFC 6570 URI Templates"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "uritemplate-4.1.1-py2.py3-none-any.whl", hash = "sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e"},
{file = "uritemplate-4.1.1.tar.gz", hash = "sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0"},
]
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.2.3" version = "2.2.3"
@@ -928,4 +1319,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.9" python-versions = ">=3.9"
content-hash = "b5f9acef18a15409718a71521e2a39a7cae20793f55425d28e46f6b37ad3b3cd" content-hash = "06976e001b4080474473ef0cb78ee3ab086eea319c391f759d74d72206e2bfa3"
+6 -1
View File
@@ -11,11 +11,16 @@ dependencies = [
"icalendar (>=6.1.3,<7.0.0)", "icalendar (>=6.1.3,<7.0.0)",
"datetime (>=5.5,<6.0)", "datetime (>=5.5,<6.0)",
"requests (>=2.32.3,<3.0.0)", "requests (>=2.32.3,<3.0.0)",
"aiohttp (>=3.11.16,<4.0.0)" "aiohttp (>=3.11.16,<4.0.0)",
"google-api-python-client (>=2.166.0,<3.0.0)",
"google-auth-httplib2 (>=0.2.0,<0.3.0)",
"google-auth-oauthlib (>=1.2.1,<2.0.0)",
"jsondiff (>=2.2.1,<3.0.0)"
] ]
[project.scripts] [project.scripts]
generate-ics = 'events_plus.console:generate_all' generate-ics = 'events_plus.console:generate_all'
clear-gcals = 'events_plus.console:clear_all'
[build-system] [build-system]
+5 -1
View File
@@ -1,7 +1,11 @@
from .scrapers.sakuracon import collect_sakuracon_events from .scrapers.sakuracon import collect_sakuracon_events, clear_sakuracon_events
import asyncio import asyncio
def generate_all(): def generate_all():
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
loop.run_until_complete(collect_sakuracon_events()) loop.run_until_complete(collect_sakuracon_events())
def clear_all():
loop = asyncio.new_event_loop()
loop.run_until_complete(clear_sakuracon_events())
+248 -10
View File
@@ -1,20 +1,41 @@
import requests import requests
from icalendar import Calendar, Event from icalendar import Calendar, Event
from datetime import datetime from datetime import datetime, timezone
from collections import defaultdict from collections import defaultdict
import asyncio import asyncio
import aiohttp import aiohttp
import json import json
import os import os
import base64
import jsondiff as jd
import pprint
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
EVENTNY_ENDPOINT = "https://www.eventeny.com/funcs/event/event-page-elements-2022-03-06.php" EVENTNY_ENDPOINT = "https://www.eventeny.com/funcs/event/event-page-elements-2022-03-06.php"
SAK_BIZ_ID = "233997" SAK_BIZ_ID = "233997"
SAK_EVENT_ID = "13462" SAK_EVENT_ID = "13462"
BATCH_LIMIT = 190
async def collect_sakuracon_events(): async def clear_sakuracon_events(config_path="config/sakuracon.json"):
with open(config_path) as f:
cfg = json.load(f)
print("using config:")
pprint.pp(cfg, indent=2)
clear_gcals(cfg)
async def collect_sakuracon_events(config_path="config/sakuracon.json"):
with open(config_path) as f:
cfg = json.load(f)
#print("using config:")
#pprint.pp(cfg, indent=2)
events, tracks = await get_event_data() events, tracks = await get_event_data()
cals = convert_events_to_icals(events, tracks) cals = convert_events_to_icals(events, tracks, cfg)
write_ics(cals) write_ics(cals)
update_gcal(events, tracks, cfg)
async def get_event_data(): async def get_event_data():
@@ -48,7 +69,7 @@ async def get_event_data():
all_events.extend(data['list']) all_events.extend(data['list'])
all_tracks.update(data['track']) all_tracks.update(data['track'])
all_events = await insert_descriptions(all_events) all_events = await insert_fields(all_events)
return all_events, all_tracks return all_events, all_tracks
@@ -67,7 +88,7 @@ async def get_description(eventid):
results = json.loads(data.decode()) results = json.loads(data.decode())
return results["schedule"]["overview"]["description"] or "" return results["schedule"]["overview"]["description"] or ""
async def insert_descriptions(events): async def insert_fields(events):
tasks = [] tasks = []
for event in events: for event in events:
t = asyncio.create_task( t = asyncio.create_task(
@@ -79,11 +100,27 @@ async def insert_descriptions(events):
for desc, event in zip(descs, events): for desc, event in zip(descs, events):
event["raw_description"] = desc event["raw_description"] = desc
uid_str = f"UID: {event['id']}"
tags_str = ""
if 'hashtag_title' in event and event['hashtag_title']:
tags_str = f"Tags: {','.join(event['hashtag_title'])}\n"
event["description"] = f"Track: {event['tag_title']}\n{tags_str}\n{uid_str}\n{desc}"
return events return events
def convert_events_to_icals(all_events, all_tracks) -> dict[str, Calendar]: def batch_create_handler(_, resp, exception):
if exception is not None:
print(exception)
return
print(f"Event created: g {resp['id']}")
def batch_exception_handler(_, resp, exception):
if exception is not None:
pprint.pp(resp)
pprint.pp(exception)
def convert_events_to_icals(all_events, all_tracks, cfg) -> dict[str, Calendar]:
# Group events by track_title # Group events by track_title
uid_prefix = cfg["uidPrefix"]
calendars = defaultdict(Calendar) calendars = defaultdict(Calendar)
for event in all_events: for event in all_events:
@@ -100,6 +137,7 @@ def convert_events_to_icals(all_events, all_tracks) -> dict[str, Calendar]:
# Create event # Create event
ical_event = Event() ical_event = Event()
ical_event.add("uid", uid_prefix + event["id"]) # we are assuming the ids generated by eventny are unique
ical_event.add("summary", event['title']) ical_event.add("summary", event['title'])
ical_event.add("dtstart", datetime.fromisoformat(event['start_calendar'])) ical_event.add("dtstart", datetime.fromisoformat(event['start_calendar']))
ical_event.add("dtend", datetime.fromisoformat(event['end_calendar'])) ical_event.add("dtend", datetime.fromisoformat(event['end_calendar']))
@@ -109,9 +147,8 @@ def convert_events_to_icals(all_events, all_tracks) -> dict[str, Calendar]:
# Add hashtags as categories if available # Add hashtags as categories if available
if 'hashtag_title' in event and event['hashtag_title']: if 'hashtag_title' in event and event['hashtag_title']:
ical_event.add('categories', event['hashtag_title']) ical_event.add('categories', event['hashtag_title'])
tags_str = f"Tags: {','.join(event['hashtag_title'])}\n"
ical_event.add("description", f"Track: {track_title}\n{tags_str}\n{event['raw_description']}") ical_event.add("description", event['description'])
cal.add_component(ical_event) cal.add_component(ical_event)
return calendars return calendars
@@ -123,4 +160,205 @@ def write_ics(calendars, output_dir="output/sakuracon"):
os.makedirs(os.path.dirname(filename), exist_ok=True) os.makedirs(os.path.dirname(filename), exist_ok=True)
with open(filename, 'wb') as f: with open(filename, 'wb') as f:
f.write(cal.to_ical()) f.write(cal.to_ical())
print(f"Wrote: {filename}") #print(f"Wrote: {filename}")
def update_gcal(scraped_events, tracks, cfg):
SCOPES = ["https://www.googleapis.com/auth/calendar.events"]
creds_b64 = os.getenv("GOOGLE_SERVICE_ACCOUNT_CREDS")
creds_body = json.loads(base64.b64decode(creds_b64))
creds = service_account.Credentials.from_service_account_info(creds_body, scopes=SCOPES)
tz_suffix = cfg["tzSuffix"]
gcal_map = cfg["calendars"]
uid_prefix = cfg["uidPrefix"]
uids = set([uid_prefix + e["id"] for e in scraped_events])
print(f"There seem to be {len(uids)} unique events by ical uids")
try:
service = build("calendar", "v3", credentials=creds)
# list all events in the gcals
gcal_events = []
gcalevent_gcal_map = {}
uncancelled_count = 0
batch = service.new_batch_http_request()
batchlen = 0
for _, gcal in gcal_map.items():
page_token = None
while True:
resp = service.events().list(calendarId=gcal, maxResults=BATCH_LIMIT, pageToken=page_token, showDeleted=True).execute()
for e in resp["items"]:
#print(e.get("iCalUID"))
gcalevent_gcal_map[e["id"]] = gcal
if e["status"] != "cancelled":
uncancelled_count += 1
gcal_events.extend(resp["items"])
page_token = resp.get("nextPageToken")
if not page_token:
break
print(f"Retrieved {uncancelled_count} active events out of {len(gcal_events)} total gcal events")
# associate icaluid
uid_gcalevent_map = {}
for event in gcal_events:
uid = event.get("iCalUID")
if uid and uid.startswith(uid_prefix):
uid_gcalevent_map[uid] = event
# go through scraped events and note changes from gcal (correlated by icaluid), if any
events_by_track = defaultdict(list)
for e in scraped_events:
events_by_track[e["tag_title"]].append(e)
action_taken_map = {
"added": [],
"updated": [],
"deleted": [],
"unchanged": [],
"other": [],
}
for track_name, events in events_by_track.items():
batch = service.new_batch_http_request()
track_gcal_id = gcal_map[track_name]
for e in events:
ical_uid = uid_prefix + e["id"]
try:
start = datetime.fromisoformat(e["start_calendar"])
end = datetime.fromisoformat(e["end_calendar"])
# sometimes events have weird start/end times that fail gcal's validation, but don't let that kill our vibe
if start >= end:
action_taken_map["other"].append(e)
raise Exception((f"Event {e['title']} (uid: {ical_uid}) has an invalid duration, {start} -> {end}"))
e_content = {
"summary": e["title"],
"start": e["start_calendar"] + tz_suffix,
"end": e["end_calendar"] + tz_suffix,
"location": e['location'].replace('&amp;', '&'),
"description": e["description"],
"status": "confirmed"
}
gcal_body = {
"summary": e["title"],
"iCalUID": ical_uid,
"start": {
"dateTime": e["start_calendar"] + tz_suffix,
},
"end": {
"dateTime": e["end_calendar"] + tz_suffix,
},
"location": e['location'].replace('&amp;', '&'),
"description": e["description"],
"status": "confirmed"
}
if ical_uid in uid_gcalevent_map:
g_e = uid_gcalevent_map[ical_uid]
#print(g_e)
g_content = {
"summary": g_e.get("summary"),
"start": g_e["start"]["dateTime"],
"end": g_e["end"]["dateTime"],
"location": g_e.get("location"),
"description": g_e.get("description"),
"status": g_e["status"] # this will likely either be confirmed or cancelled, and because we are using icaluids, we have to fish the cancelled/trashed event out of the bin using this field
}
changed = e_content != g_content
# if changes exist, update existing event
if not changed:
action_taken_map["unchanged"].append(e)
#print(f"Event {e['title']} (uid: {ical_uid}) seems to be the same, leaving as is")
else:
print(f"Updating event {e['title']} (ical: {ical_uid}/g: {g_e['id']})")
pprint.pp(jd.diff(g_content, e_content, syntax="symmetric"), depth=3)
action_taken_map["updated"].append(e)
# print(e_content)
# print(g_content)
g_e.update(gcal_body)
batch.add(service.events().update(calendarId=track_gcal_id, eventId=g_e["id"], body=g_e), callback=batch_exception_handler)
batchlen += 1
else: # if event is new, insert
print(f"Event {e['title']} (uid: {ical_uid}) seems to be new, adding...")
action_taken_map["added"].append(e)
#service.events().insert(calendarId=track_gcal_id, body=gcal_body).execute()
batch.add(service.events().insert(calendarId=track_gcal_id, body=gcal_body), callback=batch_create_handler)
batchlen += 1
if batchlen == BATCH_LIMIT:
batch.execute()
batchlen = 0
batch = service.new_batch_http_request()
except HttpError as error:
print(f"An error occurred while processing the event: {error}")
print(gcal_body)
except Exception as error:
print(f"An error occurred while processing the event: {error}")
batch.execute()
batchlen = 0
# delete all gcal_events which are not in the events array
# we have the list of gcal events
# we have a set of event uids
# if the gcal event does not have a uid or its uid is not in the uids set, then delete
# but we need the cal of the gcal event in order to delete it
#print(gcalevent_gcal_map)
# dont bother batching the deletes b/c cal has to be same in batch, lazy...
for event in gcal_events:
if "iCalUID" not in event or event["iCalUID"] not in uids and event["status"] != "cancelled":
cal = gcalevent_gcal_map[event["id"]]
action_taken_map["unchanged"].append(event)
print(f"gcal event {event['summary']} g {event['id']} (uid: {event.get('iCalUID') or 'unknown'}) not in latest scrape, deleting...")
service.events().delete(calendarId=cal, eventId=event["id"]).execute()
except HttpError as error:
print(f"An error occurred: {error}")
print(gcal_body)
print("-------------------------")
print("Summary:")
summary_map = {}
for k,v in action_taken_map.items():
summary_map[k] = len(v)
pprint.pp(summary_map, indent=2)
def clear_gcals(cfg):
SCOPES = ["https://www.googleapis.com/auth/calendar.events"]
creds_b64 = os.getenv("GOOGLE_SERVICE_ACCOUNT_CREDS")
creds_body = json.loads(base64.b64decode(creds_b64))
creds = service_account.Credentials.from_service_account_info(creds_body, scopes=SCOPES)
gcal_map = cfg["calendars"]
try:
service = build("calendar", "v3", credentials=creds)
# list all events in the gcals
count = 0
for _, gcal in gcal_map.items():
page_token = None
while True:
batch = service.new_batch_http_request()
resp = service.events().list(calendarId=gcal, maxResults=BATCH_LIMIT, pageToken=page_token).execute()
for e in resp["items"]:
print(e.get("iCalUID"))
events = resp["items"]
print(events)
for e in events:
batch.add(service.events().delete(calendarId=gcal, eventId=e["id"]), callback=batch_exception_handler)
count += len(events)
batch.execute()
page_token = resp.get("nextPageToken")
if not page_token:
break
print(f"Removed {count} gcal events")
except HttpError as error:
print(f"An error occurred: {error}")