Skip to content

Commit

Permalink
Merge pull request #15 from BottlecapDave/develop
Browse files Browse the repository at this point in the history
New release
  • Loading branch information
BottlecapDave authored Oct 21, 2023
2 parents f60c46e + 9fce3c9 commit f3bff96
Show file tree
Hide file tree
Showing 24 changed files with 771 additions and 156 deletions.
53 changes: 53 additions & 0 deletions .build/createGithubRelease.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const bodySuffix = "---\nEnjoying the integration? Why not make a one time or monthly [GitHub sponsorship](https://github.com/sponsors/bottlecapdave)?"

async function createGithubRelease(githubToken: string, githubOwnerRepo: string, tag: string, notes: string) {
if (!githubToken) {
throw new Error('Github token not specified');
}

if (!githubOwnerRepo) {
throw new Error('Github owner/repo not specified');
}

if (!tag) {
throw new Error('Tag not specified');
}

if (!notes) {
throw new Error('Notes not specified');
}

console.log(`Publishing ${tag} release to ${githubOwnerRepo}`);

const body = JSON.stringify({
tag_name: tag,
name: tag,
body: notes,
draft: false,
prerelease:false
});

const response = await fetch(
`https://api.github.com/repos/${githubOwnerRepo}/releases`,
{
method: 'POST',
headers: {
"Accept": "application/vnd.github+json",
"Authorization": `Bearer ${githubToken}`,
"X-GitHub-Api-Version": "2022-11-28"
},
body
}
);

if (response.status >= 300) {
throw new Error(response.statusText);
}
}

createGithubRelease(
process.env.GITHUB_TOKEN,
process.env.GITHUB_REPOSITORY,
process.argv[2],
`${process.argv[3]}\n${bodySuffix}`
).then(() => console.log('Success'));
12 changes: 12 additions & 0 deletions .build/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"alwaysStrict": true,
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": true,

},
"include": ["**/*"]
}
14 changes: 0 additions & 14 deletions .build/update-manifest.js

This file was deleted.

16 changes: 16 additions & 0 deletions .build/updateManifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';

const filePath = join(__dirname, '../custom_components/first_bus/manifest.json');

function updateManifest(version: string) {

const buffer = readFileSync(filePath);
const content = JSON.parse(buffer.toString());
content.version = version;

writeFileSync(filePath, JSON.stringify(content, null, 2));
console.log(`Updated version to '${version}'`);
}

updateManifest(process.argv[2]);
10 changes: 7 additions & 3 deletions .releaserc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"branches": ["main"],
"branches": ["main", "develop"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
Expand All @@ -9,17 +9,21 @@
"changelogFile": "CHANGELOG.md"
}
],
"@semantic-release/github",
[
"@semantic-release/exec", {
"prepareCmd" : "node .build/update-manifest ${nextRelease.version}"
"prepareCmd" : "ts-node .build/updateManifest.ts ${nextRelease.version}"
}
],
[
"@semantic-release/git", {
"assets": ["package.json", "CHANGELOG.md", "./custom_components/first_bus/manifest.json"],
"message": "release: Released v${nextRelease.version} [skip ci]"
}
],
[
"@semantic-release/exec", {
"publishCmd" : "ts-node .build/createGithubRelease.ts v${nextRelease.version} \"${nextRelease.notes}\""
}
]
]
}
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ However, there have been reports of missing ATCO codes. Therefore alternatively,

You can setup the integration as many times as you like, with each time tracking a different stop and potential buses from that stop.

Each instance will create a single sensor in the form `sensor.first_bus_<<NAME_OF_SENSOR>>_next_bus`. This will provide the number of minutes until one of the specified buses (or any if none were specified) reach the bus stop. The following attributes are available in addition
Each instance will create a single sensor in the form `sensor.first_bus_<<NAME_OF_SENSOR>>_next_bus`. This will provide the number of minutes until one of the specified buses (or any if no specific buses were specified) reach the bus stop. If there is no known next bus, then `none`/`unknown` will be returned.

The sensor will pull the latest times for the stop every **5 minutes**. This is done so that the unofficial API isn't hammered and support is taken away, and I felt 5 minutes was long enough so that information wasn't too stale. This means that there is a chance that the time won't reflect the times on the app/website if they are updated within this 5 minute timeframe.

The following attributes are available in addition

| Attribute | Notes |
|-----------|-------|
Expand All @@ -70,4 +74,4 @@ Each instance will create a single sensor in the form `sensor.first_bus_<<NAME_O
| `Due` | The timestamp of when the next bus is due, in ISO format |
| `IsFG` | Determines if the bus is a First bus (`Y`) or not (`N`) |
| `IsLive` | Determines if the bus is being tracked (`Y`) or is from the timetable (`N`) |
| `stop` | The ATCO code of the bus stop that is being tracked |
| `stop` | The ATCO code of the bus stop that is being tracked |
4 changes: 2 additions & 2 deletions custom_components/first_bus/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ class FirstBusApiClient:
def __init__(self):
self._base_url = 'https://www.firstbus.co.uk'

async def async_get_buses(self, stop):
"""Get the user's account"""
async def async_get_bus_times(self, stop):
"""Get the bus times for a given stop"""
async with aiohttp.ClientSession() as client:
url = f'{self._base_url}/getNextBus?stop={stop}'
async with client.get(url) as response:
Expand Down
34 changes: 34 additions & 0 deletions custom_components/first_bus/config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import logging
import re
from ..const import CONFIG_BUSES, REGEX_BUSES

_LOGGER = logging.getLogger(__name__)

def merge_config(data: dict, options: dict, updated_config: dict = None):
config = dict(data)
if options is not None:
config.update(options)

if updated_config is not None:
config.update(updated_config)

if CONFIG_BUSES not in updated_config:
del config[CONFIG_BUSES]

_LOGGER.debug(f'data: {data}; options: {options}; updated_config: {updated_config};')

return config

def validate_config(config: dict):
new_config = dict(config)
errors = {}
if CONFIG_BUSES in new_config and new_config[CONFIG_BUSES] is not None and len(new_config[CONFIG_BUSES]) > 0:
matches = re.search(REGEX_BUSES, new_config[CONFIG_BUSES])
if (matches is None):
errors[CONFIG_BUSES] = "invalid_buses"
else:
new_config[CONFIG_BUSES] = new_config[CONFIG_BUSES].split(",")
else:
new_config[CONFIG_BUSES] = []

return (errors, new_config)
60 changes: 25 additions & 35 deletions custom_components/first_bus/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import voluptuous as vol
import re
import logging

from homeassistant.config_entries import (ConfigFlow, OptionsFlow)
Expand All @@ -12,9 +11,10 @@
CONFIG_BUSES,

DATA_SCHEMA_STOP,
REGEX_BUSES,
)

from .config import merge_config, validate_config

_LOGGER = logging.getLogger(__name__)

class FirstBusConfigFlow(ConfigFlow, domain=DOMAIN):
Expand All @@ -27,20 +27,13 @@ async def async_step_user(self, user_input):

errors = {}
if user_input is not None:
if CONFIG_BUSES in user_input and user_input[CONFIG_BUSES] is not None:
matches = re.search(REGEX_BUSES, user_input[CONFIG_BUSES])
if (matches is None):
errors[CONFIG_BUSES] = "invalid_buses"
else:
user_input[CONFIG_BUSES] = user_input[CONFIG_BUSES].split(",")
else:
user_input[CONFIG_BUSES] = []
(errors, config) = validate_config(user_input)

# Setup our basic sensors
if len(errors) < 1:
return self.async_create_entry(
title=f"Bus Stop {user_input[CONFIG_NAME]}",
data=user_input
title=f"Bus Stop {config[CONFIG_NAME]}",
data=config
)

return self.async_show_form(
Expand All @@ -61,46 +54,43 @@ def __init__(self, entry) -> None:
async def async_step_init(self, user_input):
"""Manage the options for the custom component."""

config = dict(self._entry.data)
if self._entry.options is not None:
config.update(self._entry.options)
config = merge_config(self._entry.data, self._entry.options)

return self.async_show_form(
step_id="user",
data_schema=vol.Schema({
vol.Optional(CONFIG_BUSES, default=','.join(config[CONFIG_BUSES])): str,
})
data_schema=self.add_suggested_values_to_schema(
vol.Schema({
vol.Optional(CONFIG_BUSES): str,
}),
{
CONFIG_BUSES: ','.join(config[CONFIG_BUSES])
}
)
)

async def async_step_user(self, user_input):
"""Manage the options for the custom component."""

errors = {}
config = dict(self._entry.data)
if self._entry.options is not None:
config.update(self._entry.options)

if user_input is not None:
config.update(user_input)

config = merge_config(self._entry.data, self._entry.options, user_input if user_input is not None else {})

_LOGGER.debug(f"Update config {config}")

if CONFIG_BUSES in config and config[CONFIG_BUSES] is not None and len(config[CONFIG_BUSES]) > 0:
matches = re.search(REGEX_BUSES, config[CONFIG_BUSES])
if (matches is None):
errors[CONFIG_BUSES] = "invalid_buses"
else:
config[CONFIG_BUSES] = config[CONFIG_BUSES].split(",")
else:
config[CONFIG_BUSES] = []
(errors, config) = validate_config(config)

if len(errors) < 1:
return self.async_create_entry(title="", data=config)

return self.async_show_form(
step_id="user",
data_schema=vol.Schema({
vol.Optional(CONFIG_BUSES, default=','.join(config[CONFIG_BUSES])): str,
}),
data_schema=self.add_suggested_values_to_schema(
vol.Schema({
vol.Optional(CONFIG_BUSES): str,
}),
{
CONFIG_BUSES: ','.join(config[CONFIG_BUSES])
}
),
errors=errors
)
2 changes: 1 addition & 1 deletion custom_components/first_bus/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
CONFIG_STOP = "Stop"
CONFIG_BUSES = "Buses"

REGEX_BUSES="^[a-zA-Z0-9]+(,[a-zA-Z0-9]+)*$"
REGEX_BUSES="^[a-zA-Z0-9 ]+(,[a-zA-Z0-9 ]+)*$"
REGEX_TIME="^[0-9]{2}:[0-9]{2}$"
REGEX_TIME_MINS="([0-9]+) mins"

Expand Down
5 changes: 3 additions & 2 deletions custom_components/first_bus/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from .api_client import (FirstBusApiClient)
from .utils import (
get_next_bus,
async_get_buses,
get_buses,
calculate_minutes_remaining
)

Expand Down Expand Up @@ -77,7 +77,8 @@ async def async_update(self):

# We only want to update every 5 minutes so we don't hammer the service
if self._minsSinceLastUpdate <= 0:
buses = await async_get_buses(self._client, self._data[CONFIG_STOP], now())
bus_times = await self._client.async_get_bus_times(self._data[CONFIG_STOP])
buses = get_buses(bus_times, now())
self._buses = buses
self._minsSinceLastUpdate = 5

Expand Down
18 changes: 12 additions & 6 deletions custom_components/first_bus/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@
"title": "First Bus Stop",
"description": "Setup your bus stop information.",
"data": {
"Name": "The friendly name for the stop",
"Name": "Name",
"Stop": "Stop ATCO code",
"Buses": "The buses you want to filter to. If you want all buses, leave blank."
"Buses": "Buses"
},
"data_description": {
"Stop": "Instructions to find your ATCO code can be found at https://github.com/BottlecapDave/HomeAssistant-FirstBus#atco-code",
"Buses": "You can filter to specific buses, comma separated, or leave blank to see all buses"
}
}
},
"error": {
"invalid_buses": "Buses must be alphanumeric separated by commas"
"invalid_buses": "Buses must be alphanumeric and spaces separated by commas"
},
"abort": {
}
Expand All @@ -24,13 +28,15 @@
"title": "First Bus Stop",
"description": "Update your bus stop information.",
"data": {
"Name": "The friendly name for the stop",
"Buses": "The buses you want to filter to. If you want all buses, leave blank."
"Buses": "Buses"
},
"data_description": {
"Buses": "You can filter to specific buses, comma separated, or leave blank to see all buses"
}
}
},
"error": {
"invalid_buses": "Buses must be alphanumeric separated by commas"
"invalid_buses": "Buses must be alphanumeric and spaces separated by commas"
},
"abort": {
}
Expand Down
Loading

0 comments on commit f3bff96

Please sign in to comment.