Skip to content

Commit f6ddc39

Browse files
author
asd
committed
init
0 parents  commit f6ddc39

18 files changed

+603
-0
lines changed

CONTRIBUTING.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Contribution guidelines
2+
3+
Contributing to this project should be as easy and transparent as possible, whether it's:
4+
5+
- Reporting a bug
6+
- Discussing the current state of the code
7+
- Submitting a fix
8+
- Proposing new features
9+
10+
## Github is used for everything
11+
12+
Github is used to host code, to track issues and feature requests, as well as accept pull requests.
13+
14+
Pull requests are the best way to propose changes to the codebase.
15+
16+
1. Fork the repo and create your branch from `main`.
17+
2. If you've changed something, update the documentation.
18+
3. Make sure your code lints (using `scripts/lint`).
19+
4. Test you contribution.
20+
5. Issue that pull request!
21+
22+
## Any contributions you make will be under the MIT Software License
23+
24+
In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.
25+
26+
## Report bugs using Github's [issues](../../issues)
27+
28+
GitHub issues are used to track public bugs.
29+
Report a bug by [opening a new issue](../../issues/new/choose); it's that easy!
30+
31+
## Write bug reports with detail, background, and sample code
32+
33+
**Great Bug Reports** tend to have:
34+
35+
- A quick summary and/or background
36+
- Steps to reproduce
37+
- Be specific!
38+
- Give sample code if you can.
39+
- What you expected would happen
40+
- What actually happens
41+
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
42+
43+
People *love* thorough bug reports. I'm not even kidding.
44+
45+
## Use a Consistent Coding Style
46+
47+
Use [black](https://github.com/ambv/black) to make sure the code follows the style.
48+
49+
## Test your code modification
50+
51+
This custom component is based on [integration_blueprint template](https://github.com/ludeeus/integration_blueprint).
52+
53+
It comes with development environment in a container, easy to launch
54+
if you use Visual Studio Code. With this container you will have a stand alone
55+
Home Assistant instance running and already configured with the included
56+
[`configuration.yaml`](./config/configuration.yaml)
57+
file.
58+
59+
## License
60+
61+
By contributing, you agree that your contributions will be licensed under its MIT License.

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 - 2024 Joakim Sørensen @ludeeus
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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
[![License][license-shield]](LICENSE)
3+
![Project Maintenance][maintenance-shield]
4+
5+
# Instagram followers Integration for Home Assistant
6+
7+
8+
_Integration to integrate with [integration_blueprint][integration_blueprint]._
9+
10+
**This integration will set up the following platforms.**
11+
12+
Platform | Description
13+
-- | --
14+
`sensor` | Show how many subscribers given instagram user has.
15+
16+
## Installation
17+
18+
1. Add this repository URL as a custom repository in HACS
19+
2. Restart Home Assistant
20+
3. In the HA UI go to "Settings" -> "Devices & services" click "+ ADD INTEGRATION" and search for "Insta Follower"*
21+
22+
## Configuration is done in the UI
23+
24+
<!---->
25+
26+
27+
28+
***
29+
30+
[integration_blueprint]: https://github.com/ludeeus/integration_blueprint
31+
[license-shield]: https://img.shields.io/github/license/ludeeus/integration_blueprint.svg?style=for-the-badge
32+
[maintenance-shield]: https://img.shields.io/badge/maintainer-xamrex-blue.svg?style=for-the-badge
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
6+
from homeassistant.helpers.aiohttp_client import async_get_clientsession
7+
from homeassistant.loader import async_get_loaded_integration
8+
9+
from .api import IntegrationInstaApiClient
10+
from .coordinator import InstaDataUpdateCoordinator
11+
from .data import IntegrationInstaData
12+
13+
if TYPE_CHECKING:
14+
from homeassistant.core import HomeAssistant
15+
16+
from .data import IntegrationInstaConfigEntry
17+
18+
PLATFORMS: list[Platform] = [
19+
Platform.SENSOR,
20+
]
21+
22+
23+
# https://developers.home-assistant.io/docs/config_entries_index/#setting-up-an-entry
24+
async def async_setup_entry(
25+
hass: HomeAssistant,
26+
entry: IntegrationInstaConfigEntry,
27+
) -> bool:
28+
"""Set up this integration using UI."""
29+
coordinator = InstaDataUpdateCoordinator(
30+
hass=hass,
31+
)
32+
entry.runtime_data = IntegrationInstaData(
33+
client=IntegrationInstaApiClient(
34+
username=entry.data[CONF_USERNAME],
35+
password=entry.data[CONF_PASSWORD],
36+
session=async_get_clientsession(hass),
37+
),
38+
integration=async_get_loaded_integration(hass, entry.domain),
39+
coordinator=coordinator,
40+
)
41+
42+
# https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities
43+
await coordinator.async_config_entry_first_refresh()
44+
45+
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
46+
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
47+
48+
return True
49+
50+
51+
async def async_unload_entry(
52+
hass: HomeAssistant,
53+
entry: IntegrationInstaConfigEntry,
54+
) -> bool:
55+
"""Handle removal of an entry."""
56+
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
57+
58+
59+
async def async_reload_entry(
60+
hass: HomeAssistant,
61+
entry: IntegrationInstaConfigEntry,
62+
) -> None:
63+
"""Reload config entry."""
64+
await async_unload_entry(hass, entry)
65+
await async_setup_entry(hass, entry)
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import logging
2+
import socket
3+
from typing import Any
4+
5+
import aiohttp
6+
import async_timeout
7+
8+
# Konfiguracja logowania
9+
_LOGGER = logging.getLogger(__name__)
10+
11+
class IntegrationInstaApiClientError(Exception):
12+
"""Exception to indicate a general API error."""
13+
14+
15+
class IntegrationInstaApiClientCommunicationError(
16+
IntegrationInstaApiClientError,
17+
):
18+
"""Exception to indicate a communication error."""
19+
20+
21+
class IntegrationInstaApiClientAuthenticationError(
22+
IntegrationInstaApiClientError,
23+
):
24+
"""Exception to indicate an authentication error."""
25+
26+
27+
def _verify_response_or_raise(response: aiohttp.ClientResponse) -> None:
28+
"""Verify that the response is valid."""
29+
if response.status in (401, 403):
30+
msg = "Invalid credentials"
31+
_LOGGER.error("Authentication failed: %s", msg) # Log error
32+
raise IntegrationInstaApiClientAuthenticationError(
33+
msg,
34+
)
35+
response.raise_for_status()
36+
37+
38+
class IntegrationInstaApiClient:
39+
"""Sample API Client."""
40+
41+
def __init__(
42+
self,
43+
username: str,
44+
password: str,
45+
session: aiohttp.ClientSession,
46+
) -> None:
47+
"""Sample API Client."""
48+
self._username = username
49+
self._password = password
50+
self._session = session
51+
_LOGGER.info("API Client initialized for user: %s", username) # Log
52+
53+
async def async_get_data(self) -> Any:
54+
"""Get data from the API."""
55+
_LOGGER.info("Fetching data from the API...") # Logowanie try get data
56+
return await self._api_wrapper(
57+
method="get",
58+
url="https://jsonplaceholder.typicode.com/posts/1",
59+
)
60+
61+
async def async_set_title(self, value: str) -> Any: # NOT USED
62+
"""Set title in the API."""
63+
_LOGGER.info("Setting new title: %s", value)
64+
return await self._api_wrapper(
65+
method="patch",
66+
url="https://jsonplaceholder.typicode.com/posts/1",
67+
data={"title": value},
68+
headers={"Content-type": "application/json; charset=UTF-8"},
69+
)
70+
71+
async def async_get_followers(self) -> Any:
72+
"""Get number of followers from Instagram."""
73+
_LOGGER.info("Fetching followers for user: %s", self._username)
74+
url = f"https://instagram-scraper-api2.p.rapidapi.com/v1/info?username_or_id_or_url={self._username}"
75+
76+
headers = {
77+
'x-rapidapi-key': self._password,
78+
'x-rapidapi-host': "instagram-scraper-api2.p.rapidapi.com"
79+
}
80+
81+
# Call API method
82+
response_data = await self._api_wrapper(
83+
method="get",
84+
url=url,
85+
headers=headers,
86+
)
87+
88+
# Get numbers
89+
follower_count = response_data["data"]["follower_count"]
90+
_LOGGER.info("User %s has %d followers", self._username, follower_count)
91+
return response_data
92+
93+
async def _api_wrapper(
94+
self,
95+
method: str,
96+
url: str,
97+
data: dict | None = None,
98+
headers: dict | None = None,
99+
) -> Any:
100+
"""Get information from the API."""
101+
try:
102+
_LOGGER.info("Making %s request to %s", method.upper(), url) # Log request
103+
async with async_timeout.timeout(10):
104+
response = await self._session.request(
105+
method=method,
106+
url=url,
107+
headers=headers,
108+
json=data,
109+
)
110+
_verify_response_or_raise(response)
111+
_LOGGER.info("Received response: %s", response.status) # Log answer
112+
response_data = await response.json()
113+
_LOGGER.info("Received response: %s", response_data) # Log whole answer
114+
return response_data
115+
116+
except TimeoutError as exception:
117+
msg = f"Timeout error fetching information - {exception}"
118+
_LOGGER.error(msg)
119+
raise IntegrationInstaApiClientCommunicationError(
120+
msg,
121+
) from exception
122+
except (aiohttp.ClientError, socket.gaierror) as exception:
123+
msg = f"Error fetching information - {exception}"
124+
_LOGGER.error(msg)
125+
raise IntegrationInstaApiClientCommunicationError(
126+
msg,
127+
) from exception
128+
except Exception as exception: # pylint: disable=broad-except
129+
msg = f"Something really wrong happened! - {exception}"
130+
_LOGGER.error(msg)
131+
raise IntegrationInstaApiClientError(
132+
msg,
133+
) from exception

0 commit comments

Comments
 (0)