├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── issue.md └── workflows │ └── validate.yaml ├── .gitlab-ci.yml ├── LICENSE ├── README.md ├── _config.yml ├── custom_components └── untappd │ ├── __init__.py │ ├── manifest.json │ └── sensor.py ├── package.yaml └── resources.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: Toast 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.buymeacoffee.com/zJtVxUAgH'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | 16 | 17 | ## Version of Home Assistant and installation type 18 | 19 | ## Version of the custom_component 20 | 23 | 24 | ## Configuration 25 | 26 | ```yaml 27 | 28 | Add your logs here. 29 | 30 | ``` 31 | 32 | ## Describe the bug 33 | A clear and concise description of what the bug is. 34 | 35 | 36 | ## Debug log 37 | 38 | 39 | 40 | ```text 41 | 42 | Add your logs here. 43 | 44 | ``` 45 | -------------------------------------------------------------------------------- /.github/workflows/validate.yaml: -------------------------------------------------------------------------------- 1 | name: "Validation And Formatting" 2 | on: 3 | push: 4 | pull_request: 5 | schedule: 6 | - cron: '0 0 * * *' 7 | jobs: 8 | ci: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | name: Download repo 13 | with: 14 | fetch-depth: 0 15 | - uses: actions/setup-python@v2 16 | name: Setup Python 17 | - uses: actions/cache@v2 18 | name: Cache 19 | with: 20 | path: | 21 | ~/.cache/pip 22 | key: custom-component-ci 23 | - uses: KTibow/ha-blueprint@stable 24 | name: CI 25 | with: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | update_readme: 2 | stage: build 3 | script: 4 | - curl -X POST -F token=$README_TOKEN -F ref=master -F variables[single_repo]=untapped https://gitlab.com/api/v4/projects/7064305/trigger/pipeline 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Peter Skopa @swetoast 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sensor.untappd 2 | 3 | [![GitHub Release][releases-shield]][releases] 4 | [![License][license-shield]](LICENSE.md) 5 | [![GitHub Activity][commits-shield]][commits] 6 | [![Discord][discord-shield]][discord] 7 | [![Community Forum][forum-shield]][forum] 8 | [![Contributors][contributors-shield]][contributors] 9 | ![Project Maintenances][maintenance-shield1] 10 | 11 | **IMPORTANT: https://insiders.untappd.com/ gotta pay to play for exporting api so dont consider this project alive anymore** 12 | 13 | Get Untappd last badge, check-in, wishlist and more in Home Assistant use the [list-card](https://github.com/custom-cards/list-card) by [iantrich](https://github.com/iantrich) to display your wishlist in Lovelace. 14 | 15 | To get started put `/custom_components/untappd/` here: 16 | `/custom_components/untappd/` 17 | 18 | **Example configuration.yaml:** 19 | ```yaml 20 | sensor: 21 | - platform: untappd 22 | username: average_joe 23 | id: FSDJLKHDF786287UGHLE 24 | secret: FJKSDLHKS8337R6948F 25 | ``` 26 | **Configuration variables:** 27 | 28 | key | description 29 | :--- | :--- 30 | **platform (Required)** | untappd 31 | **id (Required)** | Your Untappd API id. 32 | **secret (Required)** | Your Untappd API secret. 33 | **username (Required)** | The username of the Untappd user, you want updates for. 34 | 35 | You will need to apply for an [API from Untappd](https://untappd.com/api) to use this. 36 | 37 | *** 38 | Due to how `custom_components` are loaded, it is normal to see a `ModuleNotFoundError` error on first boot after adding this, to resolve it, restart Home-Assistant. 39 | 40 | [commits-shield]: https://img.shields.io/github/commit-activity/y/custom-components/sensor.untapped.svg?style=for-the-badge 41 | [commits]: https://github.com/custom-components/sensor.untapped/commits/master 42 | [discord]: https://discord.gg/Qa5fW2R 43 | [discord-shield]: https://img.shields.io/discord/330944238910963714.svg?style=for-the-badge 44 | [forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=for-the-badge 45 | [forum]: https://community.home-assistant.io/t/untappd-api/54627 46 | [license-shield]: https://img.shields.io/github/license/custom-components/sensor.untapped.svg?style=for-the-badge 47 | [maintenance-shield1]: https://img.shields.io/badge/maintainer-Peter%20Skopa%20%40swetoast%20&%20Ian%20Richardson%20%40iantrich-blue.svg?style=for-the-badge 48 | [releases-shield]: https://img.shields.io/github/release/custom-components/sensor.untapped.svg?style=for-the-badge 49 | [releases]: https://github.com/custom-components/sensor.untapped/releases 50 | [contributors]: https://github.com/custom-components/sensor.untappd/graphs/contributors 51 | [contributors-shield]: https://img.shields.io/github/contributors/custom-components/sensor.untappd.svg?style=for-the-badge 52 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /custom_components/untappd/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /custom_components/untappd/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "untappd", 3 | "version": "0.1.6", 4 | "name": "Untappd", 5 | "documentation": "https://github.com/custom-components/sensor.untappd/blob/master/README.md", 6 | "dependencies": [], 7 | "codeowners": [ 8 | "@iantrich", 9 | "@swetoast" 10 | ], 11 | "requirements": ["pyuntappd==0.0.5"] 12 | } 13 | -------------------------------------------------------------------------------- /custom_components/untappd/sensor.py: -------------------------------------------------------------------------------- 1 | """ 2 | A component which allows you to get information from Untappd. 3 | 4 | For more details about this component, please refer to the documentation at 5 | https://github.com/custom-components/sensor.untappd 6 | """ 7 | 8 | import logging 9 | from datetime import datetime, timedelta 10 | 11 | import homeassistant.helpers.config_validation as cv 12 | import voluptuous as vol 13 | from dateutil import parser 14 | from homeassistant.components.switch import PLATFORM_SCHEMA 15 | from homeassistant.const import ATTR_ATTRIBUTION 16 | from homeassistant.helpers.entity import Entity 17 | 18 | REQUIREMENTS = ["pyuntappd==0.0.5"] 19 | 20 | __version__ = "0.1.5" 21 | 22 | _LOGGER = logging.getLogger(__name__) 23 | 24 | ATTRIBUTION = "Information provided by Untappd" 25 | 26 | CONF_USERNAME = "username" 27 | CONF_ID = "id" 28 | CONF_SECRET = "secret" 29 | 30 | COMPONENT_REPO = "https://github.com/custom-components/sensor.untappd/" 31 | 32 | WISHLIST_DATA = "untappd_wishlist" 33 | 34 | ATTR_ABV = "abv" 35 | ATTR_BEER = "beer" 36 | ATTR_BREWERY = "brewery" 37 | ATTR_SCORE = "score" 38 | ATTR_TOTAL_BADGES = "total_badges" 39 | ATTR_TOTAL_BEERS = "total_beers" 40 | ATTR_TOTAL_CREATED_BEERS = "total_created_beers" 41 | ATTR_TOTAL_CHECKINS = "checkins" 42 | ATTR_TOTAL_FOLLOWINGS = "followings" 43 | ATTR_TOTAL_FRIENDS = "friends" 44 | ATTR_TOTAL_PHOTOS = "photos" 45 | 46 | ATTR_BADGE = "badge" 47 | ATTR_LEVEL = "level" 48 | ATTR_DESCRIPTION = "description" 49 | 50 | SCAN_INTERVAL = timedelta(seconds=300) 51 | 52 | ICON = "mdi:mdi-glass-mug-variant" 53 | 54 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( 55 | { 56 | vol.Required(CONF_USERNAME): cv.string, 57 | vol.Required(CONF_ID): cv.string, 58 | vol.Required(CONF_SECRET): cv.string, 59 | } 60 | ) 61 | 62 | 63 | def setup_platform(hass, config, add_devices, discovery_info=None): 64 | username = config.get(CONF_USERNAME) 65 | api_id = config.get(CONF_ID) 66 | api_secret = config.get(CONF_SECRET) 67 | add_devices([UntappdCheckinSensor(username, api_id, api_secret)]) 68 | add_devices([UntappdWishlistSensor(hass, username, api_id, api_secret)]) 69 | add_devices([UntappdLastBadgeSensor(hass, username, api_id, api_secret)]) 70 | 71 | 72 | class UntappdCheckinSensor(Entity): 73 | def __init__(self, username, api_id, api_secret): 74 | from pyuntappd import Untappd 75 | 76 | self._untappd = Untappd() 77 | self._username = username 78 | self._apiid = api_id 79 | self._apisecret = api_secret 80 | self._total_badges = None 81 | self._total_beers = None 82 | self._total_created_beers = None 83 | self._total_checkins = None 84 | self._total_followings = None 85 | self._total_friends = None 86 | self._total_photos = None 87 | self._abv = None 88 | self._state = None 89 | self._picture = None 90 | self.update() 91 | 92 | def update(self): 93 | current_date = parser.parse(str(datetime.now())).replace(tzinfo=None) 94 | result = self._untappd.get_last_activity( 95 | self._apiid, self._apisecret, self._username 96 | ) 97 | if not result: 98 | return False 99 | else: 100 | checkin_date = parser.parse(result["created_at"]).replace(tzinfo=None) 101 | if (current_date - checkin_date).days > 0: 102 | relative_checkin_date = ( 103 | str((current_date - checkin_date).days + 1) + " days ago" 104 | ) 105 | elif (current_date - checkin_date).days == 0: 106 | relative_checkin_date = "Yesterday" 107 | else: 108 | relative_checkin_date = "Today" 109 | 110 | self._state = relative_checkin_date 111 | self._beer = result["beer"]["beer_name"] 112 | self._brewery = result["brewery"]["brewery_name"] 113 | self._score = str(result["rating_score"]) 114 | self._picture = result["beer"]["beer_label"] 115 | self._abv = str(result["beer"]["beer_abv"]) + "%" 116 | result = self._untappd.get_info(self._apiid, self._apisecret, self._username) 117 | if not result: 118 | return False 119 | else: 120 | self._total_badges = result["stats"]["total_badges"] 121 | self._total_beers = result["stats"]["total_beers"] 122 | self._total_checkins = result["stats"]["total_checkins"] 123 | self._total_created_beers = result["stats"]["total_created_beers"] 124 | self._total_friends = result["stats"]["total_friends"] 125 | self._total_followings = result["stats"]["total_followings"] 126 | self._total_photos = result["stats"]["total_photos"] 127 | 128 | @property 129 | def name(self): 130 | return "Untappd Last Check-in (" + self._username + ")" 131 | 132 | @property 133 | def entity_picture(self): 134 | return self._picture 135 | 136 | @property 137 | def state(self): 138 | return self._state 139 | 140 | @property 141 | def icon(self): 142 | return ICON 143 | 144 | @property 145 | def extra_state_attributes(self): 146 | return { 147 | ATTR_ABV: self._abv, 148 | ATTR_BEER: self._beer, 149 | ATTR_BREWERY: self._brewery, 150 | ATTR_SCORE: self._score, 151 | ATTR_TOTAL_BADGES: self._total_badges, 152 | ATTR_TOTAL_BEERS: self._total_beers, 153 | ATTR_TOTAL_CHECKINS: self._total_checkins, 154 | ATTR_TOTAL_CREATED_BEERS: self._total_created_beers, 155 | ATTR_TOTAL_FRIENDS: self._total_friends, 156 | ATTR_TOTAL_FOLLOWINGS: self._total_followings, 157 | ATTR_TOTAL_PHOTOS: self._total_photos, 158 | ATTR_ATTRIBUTION: ATTRIBUTION, 159 | } 160 | 161 | 162 | class UntappdWishlistSensor(Entity): 163 | def __init__(self, hass, username, api_id, api_secret): 164 | from pyuntappd import Untappd 165 | 166 | self.hass = hass 167 | self._untappd = Untappd() 168 | self._username = username 169 | self._apiid = api_id 170 | self._apisecret = api_secret 171 | self._total_wishlist = None 172 | self._state = None 173 | self.hass.data[WISHLIST_DATA] = {} 174 | self.update() 175 | 176 | def update(self): 177 | result = self._untappd.get_wishlist( 178 | self._apiid, self._apisecret, self._username 179 | ) 180 | if not result: 181 | return False 182 | else: 183 | self._state = result["count"] 184 | for beer in result["items"]: 185 | name = beer["beer"]["beer_name"] 186 | 187 | self.hass.data[WISHLIST_DATA][name] = { 188 | "beer_name": name, 189 | "beer_label": beer["beer"]["beer_label"], 190 | "beer_description": beer["beer"]["beer_description"], 191 | "beer_abv": beer["beer"]["beer_abv"], 192 | "beer_style": beer["beer"]["beer_style"], 193 | "beer_ibu": beer["beer"]["beer_ibu"], 194 | "beer_link": "https://untappd.com/b/" 195 | + beer["beer"]["beer_slug"] 196 | + "/" 197 | + str(beer["beer"]["bid"]), 198 | "rating_score": beer["beer"]["rating_score"], 199 | "rating_count": beer["beer"]["rating_count"], 200 | "brewery_label": beer["brewery"]["brewery_label"], 201 | "brewery_name": beer["brewery"]["brewery_name"], 202 | "country_name": beer["brewery"]["country_name"], 203 | } 204 | 205 | @property 206 | def name(self): 207 | return "Untappd Wishlist (" + self._username + ")" 208 | 209 | @property 210 | def state(self): 211 | return self._state 212 | 213 | @property 214 | def icon(self): 215 | return ICON 216 | 217 | @property 218 | def extra_state_attributes(self): 219 | return self.hass.data[WISHLIST_DATA] 220 | 221 | 222 | class UntappdLastBadgeSensor(Entity): 223 | def __init__(self, hass, username, api_id, api_secret): 224 | from pyuntappd import Untappd 225 | 226 | self.hass = hass 227 | self._untappd = Untappd() 228 | self._username = username 229 | self._apiid = api_id 230 | self._apisecret = api_secret 231 | self._state = None 232 | self.update() 233 | 234 | def update(self): 235 | result = self._untappd.get_badges(self._apiid, self._apisecret, self._username) 236 | if not result or len(result) < 1: 237 | return False 238 | else: 239 | current_date = parser.parse(str(datetime.now())).replace(tzinfo=None) 240 | checkin_date = parser.parse(result[0]["created_at"]).replace(tzinfo=None) 241 | if (current_date - checkin_date).days > 0: 242 | relative_checkin_date = ( 243 | str((current_date - checkin_date).days + 1) + " days ago" 244 | ) 245 | elif (current_date - checkin_date).days == 0: 246 | relative_checkin_date = "Yesterday" 247 | else: 248 | relative_checkin_date = "Today" 249 | 250 | self._state = relative_checkin_date 251 | self._badge = result[0]["badge_name"] 252 | self._level = result[0]["levels"]["count"] if result[0]["is_level"] else 1 253 | self._description = result[0]["badge_description"] 254 | self._picture = result[0]["media"]["badge_image_sm"] 255 | 256 | @property 257 | def name(self): 258 | return "Untappd Last Badge (" + self._username + ")" 259 | 260 | @property 261 | def entity_picture(self): 262 | return self._picture 263 | 264 | @property 265 | def state(self): 266 | return self._state 267 | 268 | @property 269 | def icon(self): 270 | return ICON 271 | 272 | @property 273 | def extra_state_attributes(self): 274 | return { 275 | ATTR_BADGE: self._badge, 276 | ATTR_LEVEL: self._level, 277 | ATTR_DESCRIPTION: self._description, 278 | ATTR_ATTRIBUTION: ATTRIBUTION, 279 | } 280 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name: sensor.untappd 2 | description: 🍻 Untappd component for Home Assistant 3 | type: component 4 | keywords: 5 | - sensor 6 | - untappd 7 | - beer 8 | license: MIT 9 | files: 10 | - custom_components/untappd/sensor.py 11 | -------------------------------------------------------------------------------- /resources.json: -------------------------------------------------------------------------------- 1 | [ 2 | "https://raw.githubusercontent.com/custom-components/sensor.untappd/master/custom_components/untappd/__init__.py", 3 | "https://raw.githubusercontent.com/custom-components/sensor.untappd/master/custom_components/untappd/manifest.json" 4 | ] 5 | --------------------------------------------------------------------------------