diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eeab94..7607ab5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # Changelog -## 2.3.1 - 2025-08-?? +## 2.4.0 - 2025-08-?? - 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 diff --git a/README.md b/README.md index 443ea95..0a98889 100644 --- a/README.md +++ b/README.md @@ -1,177 +1,182 @@ # 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. - -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] +Perfect for keeping a record of your travels and activities in your preferred calendar application. [4sq]: https://foursquare.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 -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 -in with Foursquare. +*Thanks to [Simon Willison](https://github.com/dogsheep/swarm-to-sqlite/issues/4) for this tool.* -Accept the permissions, and then copy the long code, which is your Access -Token, into your `config.ini`. +### Method B: Manual OAuth Flow -That's it. [Thanks to Simon Willison for that.](https://github.com/dogsheep/swarm-to-sqlite/issues/4) - -#### (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: +1. Set your app's Redirect URI to `http://localhost:8000/` in the Foursquare developer console +2. Run the following Python commands: ```python import foursquare -client = foursquare.Foursquare(client_id='YOUR_CLIENT_ID' client_secret='YOUR_CLIENT_SECRET', redirect_uri='http://localhost:8000') -client.oauth.auth_url() +client = foursquare.Foursquare( + 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: - - 'https://foursquare.com/oauth2/authenticate?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2F' - -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: +3. Visit the printed URL in your browser +4. Copy the code from the redirect URL +5. Get your token: ```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 - -## 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: +Create a `.ics` file with your recent check-ins: ```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 -`--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: +Get all your check-ins (may take a while): ```bash -$ ./generate_feeds.py -k ics -$ ./generate_feeds.py -k kml -$ ./generate_feeds.py --kind=ics -$ ./generate_feeds.py --kind=kml +uv run ./generate_feeds.py --all ``` -#### `-v` or `--verbose` +### Sync to CalDAV -By default the script will only output text if something goes wrong. To get -brief output use `-v` or `--verbose`: +Upload check-ins directly to a CalDAV server: ```bash -$ ./generate_feeds.py -v -Fetched 250 checkins from the API -Generated calendar file ./mycalendar.ics +uv run ./generate_feeds.py --kind caldav ``` -If fetching `--all` checkins then increasing the verbosity with another `-v` -will show more info than the above: +Make sure your CalDAV settings are configured in `config.ini`. +## 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 -$ ./generate_feeds.py -vv --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 +uv run ./generate_feeds.py --all ``` -(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 -By Phil Gyford -phil@gyford.com -https://www.gyford.com -https://github.com/philgyford/foursquare-feeds +**Original Author**: Phil Gyford +**Repository**: 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/). diff --git a/config_example.ini b/config_example.ini index 727f8cc..cfe8c56 100644 --- a/config_example.ini +++ b/config_example.ini @@ -8,5 +8,4 @@ password=xxxxx calendar_name=Swarm Check-Ins [Local] -IcsFilepath=./foursquare.ics -KmlFilepath=./foursquare.kml \ No newline at end of file +IcsFilepath=./foursquare.ics \ No newline at end of file diff --git a/generate_feeds.py b/generate_feeds.py index 063815d..f681c97 100755 --- a/generate_feeds.py +++ b/generate_feeds.py @@ -16,7 +16,7 @@ current_dir = os.path.realpath(os.path.dirname(__file__)) CONFIG_FILE = os.path.join(current_dir, "config.ini") # The kinds of file we can generate: -VALID_KINDS = ["ics", "kml", "caldav"] +VALID_KINDS = ["ics", "caldav"] class FeedGenerator: @@ -44,7 +44,6 @@ class FeedGenerator: self.api_access_token = config.get("Foursquare", "AccessToken") 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_username = config.get("CalDAV", "username", 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)) if kind == "ics": - filepath = self._generate_ics_file(checkins) - elif kind == "kml": - filepath = self._generate_kml_file(checkins) + calendar = self._generate_calendar(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: "Make one request to the API for the most recent checkins." @@ -167,7 +167,7 @@ class FeedGenerator: continue venue_name = checkin["venue"]["name"] - tz_offset = self._get_checkin_timezone(checkin) + tz_offset = tzoffset(None, checkin["timeZoneOffset"] * 60) e = Event() start = arrow.get(checkin["createdAt"]).replace(tzinfo=tz_offset) @@ -209,91 +209,6 @@ class FeedGenerator: 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 = ['@{}'.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="".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): """ 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) self.logger.debug("Calendar has {} events".format(len(calendar.events))) + # Upload each event from the ics.Calendar object 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/ - 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)) cal.add_event(event.serialize()) @@ -375,7 +270,7 @@ def main(): "-k", "--kind", action="store", - help="Either ics, kml, or caldav. Default is ics.", + help="Either ics, or caldav. Default is ics.", choices=VALID_KINDS, default="ics", required=False, @@ -411,8 +306,6 @@ def main(): # Generate the requested kind of file generator.generate(kind=args.kind) - return 0 - if __name__ == "__main__": import sys - sys.exit(main()) \ No newline at end of file + sys.exit(main() or 0) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index c482643..2633f57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,5 +8,4 @@ dependencies = [ "caldav>=2.0.1", "foursquare", "ics", - "simplekml", ] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 76077c0..0000000 --- a/requirements.txt +++ /dev/null @@ -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 diff --git a/uv.lock b/uv.lock index 7a849f2..563136d 100644 --- a/uv.lock +++ b/uv.lock @@ -123,13 +123,12 @@ wheels = [ [[package]] name = "foursquare-feeds" -version = "2.3.0" +version = "2.3.1" source = { virtual = "." } dependencies = [ { name = "caldav" }, { name = "foursquare" }, { name = "ics" }, - { name = "simplekml" }, ] [package.metadata] @@ -137,7 +136,6 @@ requires-dist = [ { name = "caldav", specifier = ">=2.0.1" }, { name = "foursquare" }, { name = "ics" }, - { name = "simplekml" }, ] [[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" }, ] -[[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]] name = "six" version = "1.16.0"