Remove KML output support

This commit is contained in:
2025-08-09 13:18:41 +01:00
parent f1496a2194
commit a5c1c9a6d0
7 changed files with 135 additions and 323 deletions

View File

@@ -1,10 +1,10 @@
# Changelog # Changelog
## 2.3.1 - 2025-08-?? ## 2.4.0 - 2025-08-??
- Add support for pushing events to a CalDAV server. - Add support for pushing events to a CalDAV server.
- Add a more detailed description to the events - Add a more detailed description to the events.
- - Remove KML support.
## 2.3.0 - 2024-11-30 ## 2.3.0 - 2024-11-30

235
README.md
View File

@@ -1,177 +1,182 @@
# Foursquare Feeds # Foursquare Feeds
A Python script that will generate iCal (`.ics`) or KML files of your checkins on [Foursquare][4sq]/[Swarm][swarm]. A Python tool that downloads your check-ins from [Foursquare][4sq]/[Swarm][swarm] and converts them into calendar events. You can either generate an iCal (`.ics`) file or sync directly to a CalDAV server.
If you set it up to save the iCal file to a publicly-visible location on a webserver, and run the script regularly, you can subscribe to the feed in your favourite calendar application. Perfect for keeping a record of your travels and activities in your preferred calendar application.
A KML file can be loaded into a mapping application (such as Google Earth or
Maps) to view the checkins on a map.
Foursquare [used to have such feeds][feeds] but they've stopped working for me.
[I wrote a bit about this.][blog]
[4sq]: https://foursquare.com [4sq]: https://foursquare.com
[swarm]: https://www.swarmapp.com [swarm]: https://www.swarmapp.com
[feeds]: https://foursquare.com/feeds/
[blog]: https://www.gyford.com/phil/writing/2019/05/13/foursquare-swarm-ical-feed/
## Features
- **iCal Export**: Generate `.ics` files that can be imported into any calendar application
- **CalDAV Sync**: Upload check-ins directly to CalDAV servers (Google Calendar, iCloud, etc.)
- **Flexible Fetching**: Get recent check-ins or your entire history
- **Rich Event Data**: Includes location details, notes (shouts), and check-in metadata
## Installation ## Installation
This should work with python 3.12 (and maybe others). This project requires Python 3.12+ and uses [uv](https://github.com/astral-sh/uv) for dependency management.
### 1. Make a Foursquare app ### 1. Install uv
Go to https://foursquare.com/developers/apps and create a new App. If you don't have uv installed:
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
### 2. Install python requirements ### 2. Set up the project
Either using [uv](https://github.com/astral-sh/uv): Clone the repository and create a virtual environment:
$ uv sync ```bash
git clone https://github.com/nikdoof/foursquare-feeds.git
cd foursquare-feeds
uv sync
```
or [pip](https://pip.pypa.io/en/stable/): ### 3. Create a Foursquare app
$ pip install -r requirements.txt 1. Go to https://foursquare.com/developers/apps
2. Create a new App
3. Note your Client ID and Client Secret
### 4. Configure the application
### 3. Set up config file Copy the example configuration file:
Copy `config_example.ini` to `config.ini`. ```bash
cp config_example.ini config.ini
```
Change the `IcsFilepath` and `KmlFilepath` to wherever you want your files to be saved. Edit `config.ini` with your settings:
To get the `AccessToken` for your Foursquare app, you will have to go through the sometimes laborious procedure in step 4... - **AccessToken**: Your Foursquare access token (see below)
- **IcsFilepath**: Where to save the `.ics` file (for local export)
- **CalDAV settings**: Your CalDAV server details (for direct sync)
## Getting an Access Token
### 4. Get an access token You need a Foursquare access token to use this tool. Here are two methods:
There are two ways to do this: (A) The quick way, using a third-party website or (B) the slow way, on the command line. Use (A) unless the website isn't working. ### Method A: Quick Web-based Authentication
#### (A) The quick way 1. Visit https://your-foursquare-oauth-token.glitch.me
2. Follow the Foursquare login link
3. Accept the permissions
4. Copy the access token into your `config.ini`
Go to https://your-foursquare-oauth-token.glitch.me and follow the link to log *Thanks to [Simon Willison](https://github.com/dogsheep/swarm-to-sqlite/issues/4) for this tool.*
in with Foursquare.
Accept the permissions, and then copy the long code, which is your Access ### Method B: Manual OAuth Flow
Token, into your `config.ini`.
That's it. [Thanks to Simon Willison for that.](https://github.com/dogsheep/swarm-to-sqlite/issues/4) 1. Set your app's Redirect URI to `http://localhost:8000/` in the Foursquare developer console
2. Run the following Python commands:
#### (B) The slow way
On https://foursquare.com/developers/apps, in your app, set the Redirect URI to `http://localhost:8000/`
In a terminal window, open a python shell:
$ python
and, using your app's Client ID and Client Secret in place of `YOUR_CLIENT_ID` and `YOUR_CLIENT_SECRET` enter this:
```python ```python
import foursquare import foursquare
client = foursquare.Foursquare(client_id='YOUR_CLIENT_ID' client_secret='YOUR_CLIENT_SECRET', redirect_uri='http://localhost:8000') client = foursquare.Foursquare(
client.oauth.auth_url() client_id='YOUR_CLIENT_ID',
client_secret='YOUR_CLIENT_SECRET',
redirect_uri='http://localhost:8000'
)
print(client.oauth.auth_url())
``` ```
This will output something like: 3. Visit the printed URL in your browser
4. Copy the code from the redirect URL
'https://foursquare.com/oauth2/authenticate?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2F' 5. Get your token:
Copy the URL from your terminal *without the surrounding quotes* and paste it into a web browser.
Your browser should redirect to a URL like the one below, with an error about not being able to connect to the server (unless you have a webserver running locally on your machine):
http://localhost:8000/?code=XX_CODE_RETURNED_IN_REDIRECT_XX#_=_
Copy the code represented by `XX_CODE_RETURNED_IN_REDIRECT_XX` (note that there may be an extra `#_=_` on the end which *you should not copy*).
Back in your python shell, with that code, enter this, replacing
`XX_CODE_RETURNED_IN_REDIRECT_XX` with the code you just copied:
```python ```python
client.oauth.get_token('XX_CODE_RETURNED_IN_REDIRECT_XX') client.oauth.get_token('YOUR_CODE_HERE')
``` ```
This will output another long code, which is your Access Token. ## Usage
Enter this in your `config.ini`. ### Generate an iCal file
Create a `.ics` file with your recent check-ins:
## Run the script
Generate a `.ics` file:
$ ./generate_feeds.py
This should create an `.ics` file containing up to 250 of your most recent
checkins (see `--all` argument below to get more).
If the file is generated in a location on your website that's publicly-visible, you should be able to subscribe to it from a calendar application. Then run the script periodically to have it update.
Note that the file might contain private checkins or information you don't want to be public. In which case, it's probably best to make the name of any such publicly-readable file very obscure.
To generate a `.kml` file, see the `kind` option below.
### Script options
#### `--all`
By default the script only fetches the most recent 250 checkins. To fetch ALL checkins add the `--all` flag:
```bash ```bash
$ ./generate_feeds.py --all uv run ./generate_feeds.py
``` ```
Depending on how many checkins you have you might only want to run it with Get all your check-ins (may take a while):
`--all` the first time and, once that's imported into a calendar application,
subsequently only fetch recent checkins.
### `-k` or `--kind`
By default the script generates an iCal `.ics` file. Or, use this option to
specify an `.ics` file or a `.kml` file:
```bash ```bash
$ ./generate_feeds.py -k ics uv run ./generate_feeds.py --all
$ ./generate_feeds.py -k kml
$ ./generate_feeds.py --kind=ics
$ ./generate_feeds.py --kind=kml
``` ```
#### `-v` or `--verbose` ### Sync to CalDAV
By default the script will only output text if something goes wrong. To get Upload check-ins directly to a CalDAV server:
brief output use `-v` or `--verbose`:
```bash ```bash
$ ./generate_feeds.py -v uv run ./generate_feeds.py --kind caldav
Fetched 250 checkins from the API
Generated calendar file ./mycalendar.ics
``` ```
If fetching `--all` checkins then increasing the verbosity with another `-v` Make sure your CalDAV settings are configured in `config.ini`.
will show more info than the above:
## Configuration
The `config.ini` file supports these sections:
### [Foursquare]
- `AccessToken`: Your Foursquare API access token
### [Local]
- `IcsFilepath`: Path where the `.ics` file should be saved
### [CalDAV]
- `url`: Your CalDAV server URL
- `username`: CalDAV username
- `password`: CalDAV password
- `calendar_name`: Name of the calendar to create/use
## Command Line Options
### `--all`
Fetch all check-ins instead of just the recent 250:
```bash ```bash
$ ./generate_feeds.py -vv --all uv run ./generate_feeds.py --all
5746 checkins to fetch
Fetched checkins 1-250
Fetched checkins 251-500
[etc...]
Fetched checkins 5501-5750
Fetched 5744 checkins from the API
Generated calendar file ./mycalendar.ics
``` ```
(No I don't know why it fetched 2 fewer checkins than it says I have.) ### `--kind` / `-k`
Specify output type (`ics` or `caldav`):
```bash
uv run ./generate_feeds.py --kind ics
uv run ./generate_feeds.py --kind caldav
```
### `--verbose` / `-v`
Enable verbose output:
```bash
uv run ./generate_feeds.py -v # Basic info
uv run ./generate_feeds.py -vv # Detailed progress (with --all)
```
## What Gets Exported
Each check-in becomes a calendar event with:
- **Title**: "@ [Venue Name]"
- **Location**: Venue name and address
- **Time**: 15-minute event starting at check-in time
- **Description**: Your shout/comment, plus metadata like:
- Days since last visit
- Mayor status at the time
- **URL**: Link to the check-in on Foursquare
## Privacy Considerations
- Check-ins may contain private information
- If hosting `.ics` files publicly, use obscure filenames
- Consider filtering private check-ins before sharing
## About ## About
By Phil Gyford **Original Author**: Phil Gyford
phil@gyford.com **Repository**: https://github.com/philgyford/foursquare-feeds
https://www.gyford.com
https://github.com/philgyford/foursquare-feeds This tool exists because Foursquare's [official feeds](https://foursquare.com/feeds/) stopped working reliably. [Read more about the original motivation of Phil to create the tool](https://www.gyford.com/phil/writing/2019/05/13/foursquare-swarm-ical-feed/).

View File

@@ -8,5 +8,4 @@ password=xxxxx
calendar_name=Swarm Check-Ins calendar_name=Swarm Check-Ins
[Local] [Local]
IcsFilepath=./foursquare.ics IcsFilepath=./foursquare.ics
KmlFilepath=./foursquare.kml

View File

@@ -16,7 +16,7 @@ current_dir = os.path.realpath(os.path.dirname(__file__))
CONFIG_FILE = os.path.join(current_dir, "config.ini") CONFIG_FILE = os.path.join(current_dir, "config.ini")
# The kinds of file we can generate: # The kinds of file we can generate:
VALID_KINDS = ["ics", "kml", "caldav"] VALID_KINDS = ["ics", "caldav"]
class FeedGenerator: class FeedGenerator:
@@ -44,7 +44,6 @@ class FeedGenerator:
self.api_access_token = config.get("Foursquare", "AccessToken") self.api_access_token = config.get("Foursquare", "AccessToken")
self.ics_filepath = config.get("Local", "IcsFilepath") self.ics_filepath = config.get("Local", "IcsFilepath")
self.kml_filepath = config.get("Local", "KmlFilepath")
self.caldav_url = config.get("CalDAV", "url", fallback=None) self.caldav_url = config.get("CalDAV", "url", fallback=None)
self.caldav_username = config.get("CalDAV", "username", fallback=None) self.caldav_username = config.get("CalDAV", "username", fallback=None)
self.caldav_password = config.get("CalDAV", "password", fallback=None) self.caldav_password = config.get("CalDAV", "password", fallback=None)
@@ -64,11 +63,12 @@ class FeedGenerator:
self.logger.info("Fetched {} checkin{} from the API".format(len(checkins), plural)) self.logger.info("Fetched {} checkin{} from the API".format(len(checkins), plural))
if kind == "ics": if kind == "ics":
filepath = self._generate_ics_file(checkins) calendar = self._generate_calendar(checkins)
elif kind == "kml":
filepath = self._generate_kml_file(checkins)
self.logger.info("Generated file {}".format(filepath)) with open(self.ics_filepath, "w") as f:
f.writelines(calendar)
self.logger.info("Generated file {}".format(self.ics_filepath))
def _get_recent_checkins(self) -> list: def _get_recent_checkins(self) -> list:
"Make one request to the API for the most recent checkins." "Make one request to the API for the most recent checkins."
@@ -167,7 +167,7 @@ class FeedGenerator:
continue continue
venue_name = checkin["venue"]["name"] venue_name = checkin["venue"]["name"]
tz_offset = self._get_checkin_timezone(checkin) tz_offset = tzoffset(None, checkin["timeZoneOffset"] * 60)
e = Event() e = Event()
start = arrow.get(checkin["createdAt"]).replace(tzinfo=tz_offset) start = arrow.get(checkin["createdAt"]).replace(tzinfo=tz_offset)
@@ -209,91 +209,6 @@ class FeedGenerator:
return c return c
def _generate_kml_file(self, checkins):
"""Supplied with a list of checkin data from the API, generates
and saves a kml file.
Returns the filepath of the saved file.
Keyword arguments:
checkins -- A list of dicts, each one data about a single checkin.
"""
import simplekml
user = self._get_user()
kml = simplekml.Kml()
# The original Foursquare files had a Folder with name and
# description like this, so:
names = [user.get("firstName", ""), user.get("lastName", "")]
user_name = " ".join(names).strip()
name = "foursquare checkin history for {}".format(user_name)
fol = kml.newfolder(name=name, description=name)
for checkin in checkins:
if "venue" not in checkin:
# I had some checkins with no data other than
# id, createdAt and source.
continue
venue_name = checkin["venue"]["name"]
tz_offset = self._get_checkin_timezone(checkin)
url = "https://foursquare.com/v/{}".format(checkin["venue"]["id"])
description = ['@<a href="{}">{}</a>'.format(url, venue_name)]
if "shout" in checkin and len(checkin["shout"]) > 0:
description.append('"{}"'.format(checkin["shout"]))
description.append("Timezone offset: {}".format(tz_offset))
coords = [
(
checkin["venue"]["location"]["lng"],
checkin["venue"]["location"]["lat"],
)
]
visibility = 0 if "private" in checkin else 1
pnt = fol.newpoint(
name=venue_name,
description="<![CDATA[{}]]>".format("\n".join(description)),
coords=coords,
visibility=visibility,
# Both of these were set like this in Foursquare's original KML:
altitudemode=simplekml.AltitudeMode.relativetoground,
extrude=1,
)
# Foursquare's KML feeds had 'updated' and 'published' elements
# in the Placemark, but I don't *think* those are standard, so:
pnt.timestamp.when = arrow.get(
checkin["createdAt"],
tzinfo=self._get_checkin_timezone(checkin),
).isoformat()
# Use the address, if any:
if "location" in checkin["venue"]:
loc = checkin["venue"]["location"]
if "formattedAddress" in loc and len(loc["formattedAddress"]) > 0:
address = ", ".join(loc["formattedAddress"])
# While simplexml escapes other strings, it threw a wobbly
# over '&' in addresses, so escape them:
pnt.address = xml_escape(address)
kml.save(self.kml_filepath)
return self.kml_filepath
def _get_checkin_timezone(self, checkin):
"""Given a checkin from the API, returns an arrow timezone object
representing the timezone offset of that checkin.
Keyword arguments
checkin -- A dict of data about a single checkin
"""
return tzoffset(None, checkin["timeZoneOffset"] * 60)
def sync_calendar_to_caldav(self): def sync_calendar_to_caldav(self):
""" """
Syncs all events from the generated calendar to a CalDAV server. Syncs all events from the generated calendar to a CalDAV server.
@@ -328,29 +243,9 @@ class FeedGenerator:
cal = principal.make_calendar(name=self.caldav_calendar_name) cal = principal.make_calendar(name=self.caldav_calendar_name)
self.logger.debug("Calendar has {} events".format(len(calendar.events))) self.logger.debug("Calendar has {} events".format(len(calendar.events)))
# Upload each event from the ics.Calendar object # Upload each event from the ics.Calendar object
for event in calendar.events: for event in calendar.events:
# Each event must have a unique UID
# Use the event UID if present, otherwise generate a deterministic one from checkin ID
if not event.uid:
# Try to extract checkin ID from event.url or event.name as fallback
checkin_id = None
if hasattr(event, "url") and event.url:
# URL format: .../checkin/<checkin_id>
parts = event.url.rstrip("/").split("/")
if "checkin" in parts:
idx = parts.index("checkin")
if idx + 1 < len(parts):
checkin_id = parts[idx + 1]
if not checkin_id and hasattr(event, "uid") and event.uid:
# fallback: try to parse from event.uid
if "@" in event.uid:
checkin_id = event.uid.split("@")[0]
if not checkin_id:
# fallback: use event.name
checkin_id = event.name
# Generate a repeatable UID using a namespace and checkin_id
event.uid = "{}@foursquare.com".format(checkin_id)
self.logger.debug("Uploading event with UID: {}".format(event.uid)) self.logger.debug("Uploading event with UID: {}".format(event.uid))
cal.add_event(event.serialize()) cal.add_event(event.serialize())
@@ -375,7 +270,7 @@ def main():
"-k", "-k",
"--kind", "--kind",
action="store", action="store",
help="Either ics, kml, or caldav. Default is ics.", help="Either ics, or caldav. Default is ics.",
choices=VALID_KINDS, choices=VALID_KINDS,
default="ics", default="ics",
required=False, required=False,
@@ -411,8 +306,6 @@ def main():
# Generate the requested kind of file # Generate the requested kind of file
generator.generate(kind=args.kind) generator.generate(kind=args.kind)
return 0
if __name__ == "__main__": if __name__ == "__main__":
import sys import sys
sys.exit(main()) sys.exit(main() or 0)

View File

@@ -8,5 +8,4 @@ dependencies = [
"caldav>=2.0.1", "caldav>=2.0.1",
"foursquare", "foursquare",
"ics", "ics",
"simplekml",
] ]

View File

@@ -1,76 +0,0 @@
# This file was autogenerated by uv via the following command:
# uv export --format requirements-txt --output-file requirements.txt
arrow==1.3.0 \
--hash=sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85 \
--hash=sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80
attrs==24.2.0 \
--hash=sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346 \
--hash=sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2
certifi==2024.8.30 \
--hash=sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9 \
--hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8
charset-normalizer==3.4.0 \
--hash=sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e \
--hash=sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6 \
--hash=sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf \
--hash=sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db \
--hash=sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1 \
--hash=sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03 \
--hash=sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284 \
--hash=sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15 \
--hash=sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8 \
--hash=sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2 \
--hash=sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719 \
--hash=sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631 \
--hash=sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b \
--hash=sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565 \
--hash=sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7 \
--hash=sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9 \
--hash=sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114 \
--hash=sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed \
--hash=sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250 \
--hash=sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920 \
--hash=sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64 \
--hash=sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23 \
--hash=sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc \
--hash=sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d \
--hash=sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88 \
--hash=sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90 \
--hash=sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b \
--hash=sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d \
--hash=sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482 \
--hash=sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67 \
--hash=sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b \
--hash=sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079
foursquare==1!2020.1.30 \
--hash=sha256:a9f27c3c426783a18ab96b1872345c9617a5513e0896c0bb4243840fab80eed3 \
--hash=sha256:a5aa9c5b6609ea2610e929288fead0cf27a8bcea2141e2ae7289f6feb57d2041
ics==0.7.2 \
--hash=sha256:6743539bca10391635249b87d74fcd1094af20b82098bebf7c7521df91209f05 \
--hash=sha256:5fcf4d29ec6e7dfcb84120abd617bbba632eb77b097722b7df70e48dbcf26103
idna==3.10 \
--hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
--hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
python-dateutil==2.9.0.post0 \
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
--hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427
pytz==2024.2 \
--hash=sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a \
--hash=sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725
requests==2.32.3 \
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
simplekml==1.3.6 \
--hash=sha256:cda687be2754395fcab664e908ebf589facd41e8436d233d2be37a69efb1c536
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
tatsu==5.12.2 \
--hash=sha256:5894dc7ddba9a1886a95ff2f06cef1be2b3d3a37c776eba8177ef4dcd80ccb03 \
--hash=sha256:9c313186ae5262662cb3fbec52c9a12db1ef752e615f46cac3eb568cb91eacf9
types-python-dateutil==2.9.0.20241003 \
--hash=sha256:58cb85449b2a56d6684e41aeefb4c4280631246a0da1a719bdbe6f3fb0317446 \
--hash=sha256:250e1d8e80e7bbc3a6c99b907762711d1a1cdd00e978ad39cb5940f6f0a87f3d
urllib3==2.2.3 \
--hash=sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9 \
--hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac

10
uv.lock generated
View File

@@ -123,13 +123,12 @@ wheels = [
[[package]] [[package]]
name = "foursquare-feeds" name = "foursquare-feeds"
version = "2.3.0" version = "2.3.1"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "caldav" }, { name = "caldav" },
{ name = "foursquare" }, { name = "foursquare" },
{ name = "ics" }, { name = "ics" },
{ name = "simplekml" },
] ]
[package.metadata] [package.metadata]
@@ -137,7 +136,6 @@ requires-dist = [
{ name = "caldav", specifier = ">=2.0.1" }, { name = "caldav", specifier = ">=2.0.1" },
{ name = "foursquare" }, { name = "foursquare" },
{ name = "ics" }, { name = "ics" },
{ name = "simplekml" },
] ]
[[package]] [[package]]
@@ -260,12 +258,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" }, { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
] ]
[[package]]
name = "simplekml"
version = "1.3.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/e4/c333a93b7e3346437ad1ff42b8e362b853eb405ad6243ab6163f9af2a460/simplekml-1.3.6.tar.gz", hash = "sha256:cda687be2754395fcab664e908ebf589facd41e8436d233d2be37a69efb1c536", size = 52999, upload-time = "2021-09-16T17:08:27.038Z" }
[[package]] [[package]]
name = "six" name = "six"
version = "1.16.0" version = "1.16.0"