Skip to content

Commit f3bff96

Browse files
Merge pull request #15 from BottlecapDave/develop
New release
2 parents f60c46e + 9fce3c9 commit f3bff96

24 files changed

+771
-156
lines changed

.build/createGithubRelease.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
const bodySuffix = "---\nEnjoying the integration? Why not make a one time or monthly [GitHub sponsorship](https://github.com/sponsors/bottlecapdave)?"
2+
3+
async function createGithubRelease(githubToken: string, githubOwnerRepo: string, tag: string, notes: string) {
4+
if (!githubToken) {
5+
throw new Error('Github token not specified');
6+
}
7+
8+
if (!githubOwnerRepo) {
9+
throw new Error('Github owner/repo not specified');
10+
}
11+
12+
if (!tag) {
13+
throw new Error('Tag not specified');
14+
}
15+
16+
if (!notes) {
17+
throw new Error('Notes not specified');
18+
}
19+
20+
console.log(`Publishing ${tag} release to ${githubOwnerRepo}`);
21+
22+
const body = JSON.stringify({
23+
tag_name: tag,
24+
name: tag,
25+
body: notes,
26+
draft: false,
27+
prerelease:false
28+
});
29+
30+
const response = await fetch(
31+
`https://api.github.com/repos/${githubOwnerRepo}/releases`,
32+
{
33+
method: 'POST',
34+
headers: {
35+
"Accept": "application/vnd.github+json",
36+
"Authorization": `Bearer ${githubToken}`,
37+
"X-GitHub-Api-Version": "2022-11-28"
38+
},
39+
body
40+
}
41+
);
42+
43+
if (response.status >= 300) {
44+
throw new Error(response.statusText);
45+
}
46+
}
47+
48+
createGithubRelease(
49+
process.env.GITHUB_TOKEN,
50+
process.env.GITHUB_REPOSITORY,
51+
process.argv[2],
52+
`${process.argv[3]}\n${bodySuffix}`
53+
).then(() => console.log('Success'));

.build/tsconfig.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"alwaysStrict": true,
4+
"module": "commonjs",
5+
"noImplicitAny": true,
6+
"removeComments": true,
7+
"preserveConstEnums": true,
8+
"sourceMap": true,
9+
10+
},
11+
"include": ["**/*"]
12+
}

.build/update-manifest.js

Lines changed: 0 additions & 14 deletions
This file was deleted.

.build/updateManifest.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { readFileSync, writeFileSync } from 'fs';
2+
import { join } from 'path';
3+
4+
const filePath = join(__dirname, '../custom_components/first_bus/manifest.json');
5+
6+
function updateManifest(version: string) {
7+
8+
const buffer = readFileSync(filePath);
9+
const content = JSON.parse(buffer.toString());
10+
content.version = version;
11+
12+
writeFileSync(filePath, JSON.stringify(content, null, 2));
13+
console.log(`Updated version to '${version}'`);
14+
}
15+
16+
updateManifest(process.argv[2]);

.releaserc.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"branches": ["main"],
2+
"branches": ["main", "develop"],
33
"plugins": [
44
"@semantic-release/commit-analyzer",
55
"@semantic-release/release-notes-generator",
@@ -9,17 +9,21 @@
99
"changelogFile": "CHANGELOG.md"
1010
}
1111
],
12-
"@semantic-release/github",
1312
[
1413
"@semantic-release/exec", {
15-
"prepareCmd" : "node .build/update-manifest ${nextRelease.version}"
14+
"prepareCmd" : "ts-node .build/updateManifest.ts ${nextRelease.version}"
1615
}
1716
],
1817
[
1918
"@semantic-release/git", {
2019
"assets": ["package.json", "CHANGELOG.md", "./custom_components/first_bus/manifest.json"],
2120
"message": "release: Released v${nextRelease.version} [skip ci]"
2221
}
22+
],
23+
[
24+
"@semantic-release/exec", {
25+
"publishCmd" : "ts-node .build/createGithubRelease.ts v${nextRelease.version} \"${nextRelease.notes}\""
26+
}
2327
]
2428
]
2529
}

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ However, there have been reports of missing ATCO codes. Therefore alternatively,
6161

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

64-
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
64+
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.
65+
66+
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.
67+
68+
The following attributes are available in addition
6569

6670
| Attribute | Notes |
6771
|-----------|-------|
@@ -70,4 +74,4 @@ Each instance will create a single sensor in the form `sensor.first_bus_<<NAME_O
7074
| `Due` | The timestamp of when the next bus is due, in ISO format |
7175
| `IsFG` | Determines if the bus is a First bus (`Y`) or not (`N`) |
7276
| `IsLive` | Determines if the bus is being tracked (`Y`) or is from the timetable (`N`) |
73-
| `stop` | The ATCO code of the bus stop that is being tracked |
77+
| `stop` | The ATCO code of the bus stop that is being tracked |

custom_components/first_bus/api_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ class FirstBusApiClient:
55
def __init__(self):
66
self._base_url = 'https://www.firstbus.co.uk'
77

8-
async def async_get_buses(self, stop):
9-
"""Get the user's account"""
8+
async def async_get_bus_times(self, stop):
9+
"""Get the bus times for a given stop"""
1010
async with aiohttp.ClientSession() as client:
1111
url = f'{self._base_url}/getNextBus?stop={stop}'
1212
async with client.get(url) as response:
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import logging
2+
import re
3+
from ..const import CONFIG_BUSES, REGEX_BUSES
4+
5+
_LOGGER = logging.getLogger(__name__)
6+
7+
def merge_config(data: dict, options: dict, updated_config: dict = None):
8+
config = dict(data)
9+
if options is not None:
10+
config.update(options)
11+
12+
if updated_config is not None:
13+
config.update(updated_config)
14+
15+
if CONFIG_BUSES not in updated_config:
16+
del config[CONFIG_BUSES]
17+
18+
_LOGGER.debug(f'data: {data}; options: {options}; updated_config: {updated_config};')
19+
20+
return config
21+
22+
def validate_config(config: dict):
23+
new_config = dict(config)
24+
errors = {}
25+
if CONFIG_BUSES in new_config and new_config[CONFIG_BUSES] is not None and len(new_config[CONFIG_BUSES]) > 0:
26+
matches = re.search(REGEX_BUSES, new_config[CONFIG_BUSES])
27+
if (matches is None):
28+
errors[CONFIG_BUSES] = "invalid_buses"
29+
else:
30+
new_config[CONFIG_BUSES] = new_config[CONFIG_BUSES].split(",")
31+
else:
32+
new_config[CONFIG_BUSES] = []
33+
34+
return (errors, new_config)
Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import voluptuous as vol
2-
import re
32
import logging
43

54
from homeassistant.config_entries import (ConfigFlow, OptionsFlow)
@@ -12,9 +11,10 @@
1211
CONFIG_BUSES,
1312

1413
DATA_SCHEMA_STOP,
15-
REGEX_BUSES,
1614
)
1715

16+
from .config import merge_config, validate_config
17+
1818
_LOGGER = logging.getLogger(__name__)
1919

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

2828
errors = {}
2929
if user_input is not None:
30-
if CONFIG_BUSES in user_input and user_input[CONFIG_BUSES] is not None:
31-
matches = re.search(REGEX_BUSES, user_input[CONFIG_BUSES])
32-
if (matches is None):
33-
errors[CONFIG_BUSES] = "invalid_buses"
34-
else:
35-
user_input[CONFIG_BUSES] = user_input[CONFIG_BUSES].split(",")
36-
else:
37-
user_input[CONFIG_BUSES] = []
30+
(errors, config) = validate_config(user_input)
3831

3932
# Setup our basic sensors
4033
if len(errors) < 1:
4134
return self.async_create_entry(
42-
title=f"Bus Stop {user_input[CONFIG_NAME]}",
43-
data=user_input
35+
title=f"Bus Stop {config[CONFIG_NAME]}",
36+
data=config
4437
)
4538

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

64-
config = dict(self._entry.data)
65-
if self._entry.options is not None:
66-
config.update(self._entry.options)
57+
config = merge_config(self._entry.data, self._entry.options)
6758

6859
return self.async_show_form(
6960
step_id="user",
70-
data_schema=vol.Schema({
71-
vol.Optional(CONFIG_BUSES, default=','.join(config[CONFIG_BUSES])): str,
72-
})
61+
data_schema=self.add_suggested_values_to_schema(
62+
vol.Schema({
63+
vol.Optional(CONFIG_BUSES): str,
64+
}),
65+
{
66+
CONFIG_BUSES: ','.join(config[CONFIG_BUSES])
67+
}
68+
)
7369
)
7470

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

7874
errors = {}
79-
config = dict(self._entry.data)
80-
if self._entry.options is not None:
81-
config.update(self._entry.options)
82-
83-
if user_input is not None:
84-
config.update(user_input)
75+
76+
config = merge_config(self._entry.data, self._entry.options, user_input if user_input is not None else {})
8577

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

88-
if CONFIG_BUSES in config and config[CONFIG_BUSES] is not None and len(config[CONFIG_BUSES]) > 0:
89-
matches = re.search(REGEX_BUSES, config[CONFIG_BUSES])
90-
if (matches is None):
91-
errors[CONFIG_BUSES] = "invalid_buses"
92-
else:
93-
config[CONFIG_BUSES] = config[CONFIG_BUSES].split(",")
94-
else:
95-
config[CONFIG_BUSES] = []
80+
(errors, config) = validate_config(config)
9681

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

10085
return self.async_show_form(
10186
step_id="user",
102-
data_schema=vol.Schema({
103-
vol.Optional(CONFIG_BUSES, default=','.join(config[CONFIG_BUSES])): str,
104-
}),
87+
data_schema=self.add_suggested_values_to_schema(
88+
vol.Schema({
89+
vol.Optional(CONFIG_BUSES): str,
90+
}),
91+
{
92+
CONFIG_BUSES: ','.join(config[CONFIG_BUSES])
93+
}
94+
),
10595
errors=errors
10696
)

custom_components/first_bus/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
CONFIG_STOP = "Stop"
77
CONFIG_BUSES = "Buses"
88

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

0 commit comments

Comments
 (0)