the developer's handbook
- surplus (and Documentation)
- surplus on wheels
- surplus on wheels: Telegram Bridge
- surplus on wheels: WhatsApp Bridge
- workflow for versioning and tagging releases
- i've made my changes. what now?
Abstract
i (mark), heavily use nix to manage my projects, either with devbox or flakes
if you are going to develop for surplus or its sibling projects (except surplus on wheels,
which only needs shfmt and shellcheck), i would recommend you install Nix using
Determinate Systems' Nix Installer:
if i, a very very inexperienced Nix and NixOS user to give a rundown on why to use it:
-
environments and builds are reproducible
nix is part package manager, operating system (as NixOS), and functional programming language. because it's functional, having X as an input will always produce Y as an output, no matter what. even in the event of environmental, social, economic, or structural collapse
what does this mean for you?
when i usenix developand you usenix developto start a development environment, your version of python will be the same as my version of python. your version of go will be same as my version of go, etcif i can build it, and the locked inputs of the nix flake are still available on the internet, you can build it too
-
the nix store, located literally at
/nix/store
it's where it stores every package you need, separate and isolated from other packages. lets say you have a tool that needs python (3.8), and another tool that needs python (3.11). nix store will download and store the binaries for both python installations, instead of sharing the earliest downloaded python version for both toolswhat does this mean for you?
whatever project you're working on that can use nix for development environments and builds will not dirty anything else on your system. any build dependencies of surplus provided withnix developwill not mess up software installed for other projects or even the system. neat, if you ask me tbh
tl;dr: things will just werk with nix. but if you see all of this and go, "eh. i can manage what i install.", then power to you! i list down exactly what prerequisite software needs to be installed for each project anyways, so have fun! :)
surplus (and Documentation)
environment setup
Note
all prerequisite software are available in a nix flake. if you want a reproducible environment,
run nix develop --impure in the repository root
prerequisite software:
to start a development environment:
for docs:
workflow for python code
after modifying Python code:
-
format the source:
-
run static checks:
-
manually exercise the CLI against at least one query form touched by your change:
-
if the change affects shareable-text generation, compare the output with a few known addresses and include debug output when filing or reviewing issues
the 2024.0.0-beta refactor did not add a dedicated Python test suite before the project was
sunset, so the best available workflow is formatter, static checks, and targeted manual CLI runs.
workflow for markdown documentation
run the documentation server with:
i personally don't use a linter for markdown files, if it looks good on my code editor, then whatever. if you're going to contribute back, i ask for three things:
-
run it through a spell checker or something similar
-
line limit of 100
-
should be readable as-is on a code editor, not the markdown preview pane.
my stance is, if you can afford a fancy preview of the markdown file, use the nice-ned documentation website. else, read it as a plaintext file
(make it look pretty on the doc site and in plaintext)
sharetext technical details
Note
this is a breakdown of surplus's output when converting to shareable text. when converting to other output types, output may be different.
$ s+ --debug 8QJF+RP Singapore
surplus version 2.2.0, debug mode (latest@future, Tue 05 Sep 2023 23:38:59 +0800)
debug: parse_query: behaviour.query=['8QJF+RP', 'Singapore']
debug: _match_plus_code: portion_plus_code='8QJF+RP', portion_locality='Singapore'
debug: cli: query=Result(value=LocalCodeQuery(code='8QJF+RP', locality='Singapore'), error=None)
debug: latlong_result.get()=Latlong(latitude=1.3320625, longitude=103.7743125)
debug: location={...}
debug: _generate_text: split_iso3166_2=['SG', '03']
debug: _generate_text: using special key arrangements for 'SG-03' (Singapore)
debug: _generate_text: seen_names=['Ngee Ann Polytechnic', 'Clementi Road']
debug: _generate_text_line: [True] -> True -------- 'Ngee Ann Polytechnic'
debug: _generate_text_line: [True] -> True -------- '535'
debug: _generate_text_line: [True] -> True -------- 'Clementi Road'
debug: _generate_text_line: [True, True] -> True -------- 'Bukit Timah'
debug: _generate_text_line: [False, True] -> False filtered 'Singapore'
debug: _generate_text_line: [True] -> True -------- '599489'
debug: _generate_text_line: [True] -> True -------- 'Northwest'
debug: _generate_text_line: [True] -> True -------- 'Singapore'
0 Ngee Ann Polytechnic
1
2
3 535 Clementi Road
4 Bukit Timah
5 599489
6 Northwest, Singapore
Ngee Ann Polytechnic
535 Clementi Road
Bukit Timah
599489
Northwest, Singapore
debug output
behaviour.query,split_query, andoriginal_query
behaviour.queryis the original query string or a list of strings from space-splitting the original query string passed toparse_query()for parsing.
split_query is the original query string split by spaces.
original_query is a single non-split string.
$ s+ Temasek Polytechnic
-------------------
query
behaviour.query -> ['Temasek', 'Polytechnic']
split_query -> ['Temasek', 'Polytechnic']
original_query -> 'Temasek Polytechnic'
portion_plus_codeandportion_locality
only shown if the query is a local code, not shown on full-length Plus Codes, latlong coordinates or string queries.
represents the Plus Code and locality portions of a shortened Plus Code respectively.
-
query
query is a variable of typeResult[Query]. this variable is displayed to show what query typeparse_query()has recognised, and if there were any errors during query parsing. -
latlong_result.get()
only shown if the query is a Plus Code. the latitude longitude coordinates derived from the Plus Code. -
location
the response dictionary from the reverser function passed tosurplus(). -
split_iso3166_2and special key arrangements
a list of strings containing the split iso3166-2 code, meaning the country/subdivision identifier.
if special key arrangements are available for the code, a line similar to the following will be shown:
seen_names
a list of unique important names found in certain Nominatim keys used in final output lines 0-3.
_generate_text_line seen name checks
# filter function boolean list status element
# ============================= ======== ======================
debug: _generate_text_line: [True] -> True -------- 'Ngee Ann Polytechnic'
debug: _generate_text_line: [False, True] -> False filtered 'Singapore'
a check is done on shareable text line 4 keys, general regional location, to reduce repeated
elements found in seen_names.
reasoning is, if an element on line 4 is the exact same as a previously seen name, there is no need to include the element.
- filter function boolean list
_generate_text_line, an internal function defined inside_generate_text, can be passed a filter function as a way to filter out certain elements on a line.
filter=lambda ak: [
ak not in general_global_info,
not any(True if (ak in sn) else False for sn in seen_names),
]
general_global_info is a list of strings containing elements from line 6.
-
status
whatall(filter(detail))evaluates to,filterbeing the filter function passed to_generate_text_lineanddetailbeing the current element. -
element
the current iteration from iterating through a list of strings containing elements from line 4.
line breakdown
0 name of a place
1 building name
2 highway name
3 block/house/building number, house name, road
4 general regional location
5 postal code
6 general global information
0. name of a place
usually important places or landmarks.
examples:
Nominatim keys:
emergency, historic, military, natural, landuse, place, railway, man_made,
aerialway, boundary, amenity, aeroway, club, craft, leisure, office, mountain_pass,
shop, tourism, bridge, tunnel, waterway
1. building name
examples:
Nominatim key:
2. highway name
examples:
Nominatim key:
3. block/house/building number, house name, road
examples:
Nominatim keys:
4. general regional location
examples:
Nominatim keys:
residential, neighbourhood, allotments, quarter, city_district, district, borough,
suburb, subdivision, municipality, city, town, village
5. postal code
examples:
Nominatim key:
6. general global information
examples:
Nominatim keys:
api reference
- constants
- exception classes
- types
QueryResultTypeSurplusGeocoderProtocolSurplusReverserProtocolclass Behaviourclass SurplusDefaultGeocodingSurplusDefaultGeocoding.update_geocoding_functions()SurplusDefaultGeocoding.geocoder()SurplusDefaultGeocoding.reverser()class ConversionResultTypeEnumclass ResultResult.__bool__()Result.cry()Result.get()class LatlongLatlong.__str__()class PlusCodeQueryPlusCodeQuery.to_lat_long_coord()PlusCodeQuery.__str__()class LocalCodeQueryLocalCodeQuery.to_full_plus_code()LocalCodeQuery.to_lat_long_coord()LocalCodeQuery.__str__()class LatlongQueryLatlongQuery.to_lat_long_coord()LatlongQuery.__str__()class StringQueryStringQuery.to_lat_long_coord()StringQuery.__str__()def surplus()def parse_query()def generate_fingerprinted_user_agent()- details on the fingerprinted user agent
constants
VERSION: tuple[int, int, int]
a tuple of integers representing the version of surplus, in the format
[major, minor, patch]
VERSION_SUFFIX: typing.Final[str]
BUILD_BRANCH: typing.Final[str]
BUILD_COMMIT: typing.Final[str]
BUILD_DATETIME: typing.Final[datetime]
string and a datetime.datetime object
containing version and build information, set by src/tools/releaser.py
CONNECTION_MAX_RETRIES: int = 9
CONNECTION_WAIT_SECONDS: int = 10
defines if and how many times to retry a connection, alongside how many seconds to wait in between tries, for Nominatim
[!NOTE]
this constant only affects the default surplus Nominatim geocoding functions. custom functions do not read from this, unless deliberately programmed to do so
SHAREABLE_TEXT_LINE_0_KEYS: dict[str, tuple[str, ...]]
SHAREABLE_TEXT_LINE_1_KEYS: dict[str, tuple[str, ...]]
SHAREABLE_TEXT_LINE_2_KEYS: dict[str, tuple[str, ...]]
SHAREABLE_TEXT_LINE_3_KEYS: dict[str, tuple[str, ...]]
SHAREABLE_TEXT_LINE_4_KEYS: dict[str, tuple[str, ...]]
SHAREABLE_TEXT_LINE_5_KEYS: dict[str, tuple[str, ...]]
SHAREABLE_TEXT_LINE_6_KEYS: dict[str, tuple[str, ...]]
SHAREABLE_TEXT_LINE_SETTINGS: dict[str, dict[int, tuple[str, bool]]]
SHAREABLE_TEXT_NAMES: dict[str, tuple[str, ...]]
SHAREABLE_TEXT_LOCALITY: dict[str, tuple[str, ...]]-
SHAREABLE_TEXT_DEFAULT: typing.Final[str]
constant for what is the "default" key in theSHAREABLE*constants -
EMPTY_LATLONG: typing.Final[Latlong]
a constant for an empty latlong coordinate, with latitude and longitude set to 0.0
exception classes
class SurplusError(Exception)
base skeleton exception for handling and typing surplus exception classesclass NoSuitableLocationError(SurplusError)class IncompletePlusCodeError(SurplusError)class PlusCodeNotFoundError(SurplusError)class LatlongParseError(SurplusError)class EmptyQueryError(SurplusError)
types
Query
type alias representing
either a
PlusCodeQuery,
LocalCodeQuery,
LatlongQuery or
StringQuery
ResultType
generic type used by
Result
SurplusGeocoderProtocol
typing_extensions.Protocol class for documentation and static type checking of surplus geocoder functions
- signature and conforming function signature
functions that conform to this protocol should have the following signature:
- information on conforming functions
function takes in a location name as a string, and returns a Latlong.
function MUST supply a bounding_box attribute to the to-be-returned
Latlong. the bounding box is used when surplus shortens Plus Codes.
function can and should be at minimum
functools.lru_cache()-wrapped
if the geocoding service asks for caching
exceptions are handled by the caller
SurplusReverserProtocol
typing_extensions.Protocol class for documentation and static type checking of surplus reverser functions
- signature and conforming function signature
class SurplusReverserProtocol(Protocol):
def __call__(self, latlong: Latlong, level: int = 18) -> dict[str, Any]:
...
functions that conform to this protocol should have the following signature:
- information on conforming functions
function takes in a Latlong object and return a dictionary with SHAREABLE_TEXT_LINE_*_KEYS keys at the dictionaries' top-level.
keys are used to access address information.
function should also take in an int representing the level of detail for the returned address, 0-18 (country-level to building), inclusive. should default to 18.
keys for latitude, longitude and an iso3166-2 (or closest equivalent) should also be
included at the dictionaries top level as the keys latitude, longitude and
ISO3166-2 (non-case sensitive, or at least something starting with ISO3166)
respectively.
{
'ISO3166-2-lvl6': 'SG-03',
'amenity': 'Ngee Ann Polytechnic',
...
'country': 'Singapore',
'latitude': 1.33318835,
'longitude': 103.77461234638255,
'postcode': '599489',
'raw': {...},
}
function can and should be at minimum
functools.lru_cache()-wrapped
if the geocoding service asks for caching
exceptions are handled by the caller
class Behaviour
typing.NamedTuple
representing how surplus operations should behave
attributes
-
query: str | list[str] = ""
original user-passed query string or a list of strings from splitting user-passed query string by spaces -
geocoder: SurplusGeocoderProtocol = default_geocoding.geocoder
name string to location function, seeSurplusGeocoderProtocolfor more information -
reverser: SurplusReverserProtocol = default_geocoding.reverser
Latlong object to address information dictionary function, seeSurplusReverserProtocolfor more information -
stderr: typing.TextIO = sys.stderr
TextIO-like object representing a writeable file. defaults tosys.stderr. -
stdout: typing.TextIO = sys.stdout
TextIO-like object representing a writeable file. defaults tosys.stdout. -
debug: bool = False
whether to print debug information to stderr -
version_header: bool = False
whether to print version information and exit -
convert_to_type: ConversionResultTypeEnum = ConversionResultTypeEnum.SHAREABLE_TEXT
what type to convert the query to -
using_termux_location: bool = False
treats query as a termux-location output json string, and parses it accordingly -
show_user_agent: bool = False
whether to print the fingerprinted user agent and exit
class SurplusDefaultGeocoding
[!IMPORTANT]
this has replaced the now deprecated default geocoding functions,default_geocoder()anddefault_reverser(), in surplus 2.1 and later.
see SurplusGeocoderProtocol and SurplusReverserProtocol for more information how to implement a compliant custom geocoder functions.
dataclasses.dataclass providing
the default geocoding functionality for surplus, via
OpenStreetMap Nominatim
attributes
user_agent: str = default_fingerprint
pass in a custom user agent here, else it will be the default fingerprinted user agent
example usage
from surplus import surplus, Behaviour, SurplusDefaultGeocoding
geocoding = SurplusDefaultGeocoding("custom user agent")
geocoding.update_geocoding_functions() # not necessary but recommended
behaviour = Behaviour(
...,
geocoder=geocoding.geocoder,
reverser=geocoding.reverser
)
result = surplus("query", behaviour=behaviour)
...
methods
def update_geocoding_functions(self) -> None: ...def geocoder(self, place: str) -> Latlong: ...def reverser(self, latlong: Latlong, level: int = 18) -> dict[str, Any]: ...
SurplusDefaultGeocoding.update_geocoding_functions()
re-initialise the geocoding functions with the current user agent, also generate a new user agent if not set properly
it is recommended to call this before using surplus as by default the geocoding functions are uninitialised
- signature
SurplusDefaultGeocoding.geocoder()
[!WARNING]
this function is primarily given to be passed into aBehaviourobject, and is not meant to be called directly.
default geocoder for surplus
see SurplusGeocoderProtocol for more information on surplus geocoder functions
SurplusDefaultGeocoding.reverser()
[!WARNING]
this function is primarily given to be passed into aBehaviourobject, and is not meant to be called directly.
default reverser for surplus
see SurplusReverserProtocol for more information on surplus reverser functions
class ConversionResultTypeEnum
enum.Enum representing what the result type of conversion should be
values
PLUS_CODE: str = "pluscode"LOCAL_CODE: str = "localcode"LATLONG: str = "latlong"SHAREABLE_TEXT: str = "sharetext"
class Result
typing.NamedTuple
representing the result for safe value retrieval
attributes
-
value: ResultType
value to return or fallback value if erroneous -
error: BaseException | None = None
exception if any
example usage
# do something
def some_operation(path) -> Result[str]:
try:
file = open(path)
contents = file.read()
except Exception as exc:
# must pass a default value
return Result[str]("", error=exc)
else:
return Result[str](contents)
# call function and handle result
result = some_operation("some_file.txt")
if not result: # check if the result is erroneous
# .cry() raises the exception
# (or returns it as a string error message using string=True)
result.cry()
...
else:
# .get() raises exception or returns value,
# but since we checked for errors this is safe
print(result.get())
methods
def __bool__(self) -> bool: ...def cry(self, string: bool = False) -> str: ...def get(self) -> ResultType: ...
Result.__bool__()
method that returns True if self.error is None
- signature
- returns
bool
Result.cry()
method that raises self.error if is an instance of BaseException, returns
self.error if is an instance of str, or returns an empty string if self.error is None
- signature
-
arguments
-
string: bool = False
ifself.erroris an Exception, returns it as a string error message -
returns
str
Result.get()
method that returns self.value if Result is non-erroneous else raises error
- signature
- returns
self.value
class Latlong
typing.NamedTuple
representing a latitude-longitude coordinate pair
attributes
latitude: floatlongitude: floatbounding_box: tuple[float, float, float, float] | None = None
a four-tuple representing a bounding box,(lat1, lat2, lon1, lon2)or None.
the user does not need to enter this. the attribute is only used when shortening plus codes, and would be supplied by the geocoding service during shortening.
methods
Latlong.__str__()
method that returns a comma-and-space-separated string of self.latitude and
self.longitude
- signature
- returns
str
class PlusCodeQuery
typing.NamedTuple
representing a full-length Plus Code (e.g., 6PH58QMF+FX)
attributes
code: str
methods
PlusCodeQuery.to_lat_long_coord()
- signature
-
arguments
-
geocoder: SurplusGeocoderProtocol
name string to location function, see SurplusGeocoderProtocol for more information
PlusCodeQuery.__str__()
method that returns string representation of query
- signature
- returns
str
class LocalCodeQuery
typing.NamedTuple
representing a
shortened Plus Code
with locality, referred to by surplus as a "local code"
attributes
-
code: str
Plus Code portion of local code, e.g., "8QMF+FX" -
locality: str
remaining string of local code, e.g., "Singapore"
methods
def to_full_plus_code(self, ...) -> Result[str]: ...def to_lat_long_coord(self, ...) -> Result[Latlong]: ...def __str__(self) -> str: ...
LocalCodeQuery.to_full_plus_code()
exclusive method that returns a full-length Plus Code as a string
- signature
-
arguments
-
geocoder: SurplusGeocoderProtocol
name string to location function, see SurplusGeocoderProtocol for more information -
returns
Result[str]
LocalCodeQuery.to_lat_long_coord()
method that returns a latitude-longitude coordinate pair
- signature
-
arguments
-
geocoder: SurplusGeocoderProtocol
name string to location function, see SurplusGeocoderProtocol for more information
LocalCodeQuery.__str__()
method that returns string representation of query
- signature
- returns
str
class LatlongQuery
typing.NamedTuple
representing a latitude-longitude coordinate pair
attributes
latlong: Latlong
methods
LatlongQuery.to_lat_long_coord()
method that returns a latitude-longitude coordinate pair
- signature
-
arguments
-
geocoder: SurplusGeocoderProtocol
name string to location function, see SurplusGeocoderProtocol for more information
LatlongQuery.__str__()
method that returns string representation of query
- signature
- returns
str
class StringQuery
typing.NamedTuple
representing a pure string query
attributes
query: str
methods
StringQuery.to_lat_long_coord()
method that returns a latitude-longitude coordinate pair
- signature
-
arguments
-
geocoder: SurplusGeocoderProtocol
name string to location function, see SurplusGeocoderProtocol for more information
StringQuery.__str__()
method that returns string representation of query
- signature
- returns
str
def surplus()
query to shareable text conversion function
- signature
-
arguments
-
query: str | Query
query object to convert or string to attempt to query for then convert -
behaviour: Behaviour
surplus behaviour namedtuple -
returns
Result[str]
def parse_query()
function that parses a query string into a query object
- signature
-
arguments
-
behaviour: Behaviour
surplus behaviour namedtuple
def generate_fingerprinted_user_agent()
function that attempts to return a unique user agent string.
- signature
- returns
Result[str]
this result will always have a valid value as erroneous results will have a
resulting value of 'surplus/<version> (generic-user)'
valid results will have a value of 'surplus/<version> (<fingerprint hash>)', where
the fingerprint hash is a 12 character hexadecimal string
details on the fingerprinted user agent
why do this in the first place?
if too many people use surplus at the same time,
Nominatim will start to think it's just one person being greedy. so to prevent this,
surplus will try to generate a unique user agent string for each user through
fingerprinting.
at the time of writing, the pre-hashed fingerprint string is as follows:
it contains the following, in order, alongside an example:
version- the surplus version alongside a suffix, if any
system_info- generic machine and operating system information
hostname- your computer's hostname
mac_address- your computer's mac address
after hashing, this string becomes a 12 character hexadecimal string, as shown below:
if at any time the retrieval of any of these four elements fail, surplus will just give
up and default to 'surplus/<version> (generic-user)'.
if any of this seems weird to you, that's fine. pass in a custom user agent flag to
surplus with -u or --user-agent to override the default user agent, or override the
default user agent in your own code by passing in a custom user agent string to
Behaviour.
>>> from surplus import surplus, Behaviour
>>> surplus(..., Behaviour(user_agent="a-shiny-custom-and-unique-user-agent"))
...
surplus on wheels
environment setup
Note
all prerequisite software are available in a nix flake. if you want a reproducible environment,
run nix develop in src/surplus-on-wheels
prerequisite software:
- shfmt: formatter
- ShellCheck: static analyser
workflow
Note
alternatively, run check.sh inside src/surplus-on-wheels
-
formatting s+ow:
- run
shfmt s+ow > s+ow.new - mv
s+ow.newintos+ow
sometimes when piping shfmt's output immediately into the same file results in the file being empty :(
- run
-
checking s+ow:
- run
shellcheck s+ow
if there's no output, that means it passed :)
- run
-
if commiting back into the repository, try it out on your Termux system for a day or two, just to make sure it runs correctly
surplus on wheels: Telegram Bridge
environment setup
Note
all prerequisite software are available in a nix flake. if you want a reproducible environment,
run nix develop in src/spow-telegram-bridge. it uses
poetry2nix, so you won't need to run
poetry shell afterwards. if you've changed the pyproject.toml file,
just exit and re-run nix develop
prerequisite software:
to start a development environment:
workflow
after modifying,
-
check the source code:
mypy bridge.pyruff format bridge.pyruff check bridge.py
Note
alternatively, run
check.shinsidesrc/spow-telegram-bridge -
and then test the binary
if the bridge behaves nominally, bump the version and commit!
surplus on wheels: WhatsApp Bridge
environment setup
Note
all prerequisite software are available in a nix flake. if you want a reproducible environment,
run nix develop in src/spow-whatsapp-bridge
the flake will pull in the Android SDK and NDK for building on Termux, and as such can only be
ran on x86_64-linux and x86_64-darwin
prerequisite software:
- Go: 1.22 or newer
- Android NDK, if building for Termux
workflow for modifying bridge code
the bridge's code is just modified mdtest code, and as such, whenever in doubt, do a diff between mdtest and the bridge code
after modifying,
-
check the source code:
go fmt bridge.gogo vet bridge.gogolint bridge.go
Note
alternatively, run
check.shinsidesrc/spow-whatsapp-bridge -
and if all goes well, bump the version and commit!
workflow for bumping dependencies
-
check with your editor, plugin, or online if there's newer patch/minor (see semantic versioning) versions to update to
-
change the
go.modaccordingly
after bumping,
- build a binary
- test the binary
- and if all goes well, bump the version and commit!
workflow for building a binary
ensure you already have c compiler on the system (if you're using nix develop then yes you do), then run:
nix users can alternatively run:
instructions to build a Termux build are located at the bridges' documentation page, however nix users can run the following instead for a reproducible, deterministic and hermetic build command:
the resulting build will be inresult/spow-whatsapp-bridge
workflow for testing the binary
-
test it out, making sure that you write dummy test text to
~/.cache/s+ow/messagebefore running the binary-
run
s+ow-whatsapp-bridge loginfirst -
run
s+ow-whatsapp-bridge listif you don't already have a chat ID to send the test message to -
run
s+ow-whatsapp-bridgetype or copy and paste in awa:-prefixed chat ID after it logs in, and verify it sends
-
if the bridge behaves nominally, bump the version and commit!
workflow for versioning and tagging releases
versioning surplus
format: YEAR.MAJOR.MINOR[-PRERELEASE] (semantic versioning)
example: 2024.0.0, 2024.0.0-beta
change: update the __version__ variable in src/surplus/surplus.py
versioning surplus on wheels
i've tried to make surplus on wheels as reliable as it could be given a POSIX compliant shell and commands you'd find available on virtually every linux system, Termux included
as such, it doesn't really follow a versioning scheme as it doesn't need to. also there's no automatic updater for it, which would be overkill anyway
versioning surplus on wheels: Telegram Bridge
format: REVISION.YYYY.WW[+BUILD] (calendar versioning)
example: 2.2024.24, 2.2024.24+1
change: version key in src/spow-telegram-bridge/pyproject.toml
REVISION here meaning any general revision/change
the Telegram Bridge relies on Telethon, which also follows semantic versioning. so, as long as major isn't bumped, or as long as Telegram doesn't become Discord, the MTProto APIs to talk to Telegram should be stable.
however because Telethon also relies on a bunch of networking libraries, it made some sense to
still do weekly builds to bump dependencies, getting pipx to download the newest compatible
dependencies as compared to dubiously running some sort of script to pipx inject dependencies
under normal circumstances, a non-working version of the bridge would and should not have a version bump. but for any reason if an already tagged bridge is faulty and/or erroneous in normal/expected usage, add a revision number to the end after a period (see example above)
versioning surplus on wheels: WhatsApp Bridge
format: REVISION.YYYY.WW[+BUILD] (calendar versioning)
example: 2.2024.25, 2.2024.25+1
change: version attribute of bridge attribute set in src/spow-whatsapp-bridge/flake.nix
REVISION here meaning any general revision/change
the WhatsApp Bridge relies on whatsmeow, a rolling release library due to the volatile, undocumented nature of WhatsApp's multidevice API and also directly and indirectly relies on a bunch of networking libraries:
module forge.joshwel.co/mark/surplus/src/spow-whatsapp-bridge
go 1.22.3
require (
github.com/mattn/go-sqlite3 v1.14.24
github.com/mdp/qrterminal/v3 v3.2.0
go.mau.fi/whatsmeow v0.0.0-20240716084021-eb41d1f09552
google.golang.org/protobuf v1.35.2
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/rs/zerolog v1.33.0 // indirect
go.mau.fi/libsignal v0.1.1 // indirect
go.mau.fi/util v0.6.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
rsc.io/qr v0.2.0 // indirect
)
as such, it uses a calendar versioning scheme and is built weekly
under normal circumstances, a non-working version of the bridge would and should not have a version bump. but for any reason if an already tagged bridge is faulty and/or erroneous in normal/expected usage, add a revision number to the end after a period (see example above)
i've made my changes. what now?
if you're contributing back to surplus and/or the sibling projects, firstly, thanks! see the contributor's handbook for what's next