Compare commits
133 commits
Author | SHA1 | Date | |
---|---|---|---|
a6ee760eff | |||
b5bab89c49 | |||
![]() |
296fa739f0 | ||
![]() |
21c6b11f65 | ||
ffd81b8856 | |||
![]() |
60fd8e8b8a | ||
30815f08b8 | |||
3f6327a134 | |||
fe5c47e21a | |||
ad5707408c | |||
85f305fb78 | |||
ea30737ae6 | |||
c04bcb8831 | |||
59f28ce868 | |||
c6eadc6045 | |||
40d318bef7 | |||
44907e3462 | |||
93e868845a | |||
387d2a222a | |||
822eee5b11 | |||
5737d22237 | |||
d64eb987a9 | |||
4a10586824 | |||
a9dbc082ae | |||
829eccc4c6 | |||
c718b6544e | |||
fb45c2177b | |||
f73cee3684 | |||
4a2ea88fca | |||
d22fee2b0b | |||
56694ffa68 | |||
8a59994894 | |||
a456a59951 | |||
1ccd66b166 | |||
4871b9d352 | |||
8e83084f8c | |||
140736af68 | |||
451072fed3 | |||
5a189d3266 | |||
![]() |
2168d594c8 | ||
![]() |
f2e20e6932 | ||
![]() |
4b382a9b26 | ||
![]() |
e0903341d1 | ||
![]() |
56be6e4748 | ||
![]() |
c7d8f81654 | ||
![]() |
abaa87c85e | ||
![]() |
ce45f22729 | ||
![]() |
8fecc090bf | ||
![]() |
01c540750a | ||
![]() |
30d23d61cc | ||
![]() |
dd5d95f21f | ||
![]() |
c3238fd0a9 | ||
![]() |
873250b858 | ||
![]() |
c1e2b0e1a9 | ||
![]() |
c10c013167 | ||
![]() |
b21ddc0eeb | ||
![]() |
fe69dcd4c5 | ||
![]() |
51d132d416 | ||
![]() |
fdf3a86d84 | ||
![]() |
533cdb2ce4 | ||
![]() |
e15d4524db | ||
7f24f987e0 | |||
![]() |
7a85737fd4 | ||
![]() |
1ac2e1015b | ||
![]() |
412039b568 | ||
![]() |
6415802f43 | ||
![]() |
619f4d71ae | ||
e47138c0d9 | |||
a9e26c8916 | |||
d3ada0b386 | |||
fda37f413f | |||
![]() |
bf26afb95a | ||
![]() |
14193a705e | ||
f6de01a029 | |||
fa1fff9602 | |||
8ddde41a2e | |||
8ccee82f26 | |||
762080c11d | |||
996b973533 | |||
161546b11e | |||
![]() |
d56d077fde | ||
b371a7c4df | |||
e38d9d8bff | |||
8af887a66c | |||
43da846f7f | |||
e399e6f931 | |||
![]() |
5bc64ed093 | ||
![]() |
7d97b7679e | ||
![]() |
dd49301b09 | ||
9752710f82 | |||
35f19b8bc9 | |||
6591d9f476 | |||
aaa1747517 | |||
901fb96b0f | |||
883a8ee528 | |||
fe48041039 | |||
d27b915855 | |||
437ab8f0b1 | |||
f3a90f71e0 | |||
9549097d6c | |||
e1013c2de7 | |||
2ee8e4a3f5 | |||
9d1b0d638f | |||
52e23e7ee4 | |||
af083fb49a | |||
592a2c661c | |||
cc90b5e196 | |||
f62c31d685 | |||
528255a8cb | |||
30e6c009cc | |||
fd66a53c11 | |||
3b136c30ba | |||
fd372fb483 | |||
a7803ec3ca | |||
d67d72cdd5 | |||
94191a6225 | |||
d9c255b089 | |||
cdc5030baa | |||
70b7cb6f8c | |||
3a67ad8a1d | |||
348e1f43c9 | |||
4ca4a7c8a8 | |||
19c0d9d910 | |||
1d66e7ca76 | |||
ba376466ff | |||
e41c2ccf81 | |||
d5ea1f7af4 | |||
4e612eb92e | |||
3355f6a279 | |||
dbcf659801 | |||
22c79d9349 | |||
ccb4119fdf | |||
e519901bb8 |
81 changed files with 7569 additions and 1327 deletions
14
.github/dependabot.yml
vendored
Normal file
14
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/src/surplus"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/src/spow-telegram-bridge"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: gomod
|
||||
directory: "/src/spow-whatsapp-bridge"
|
||||
schedule:
|
||||
interval: "daily"
|
42
.github/workflows/cd-docs.yml
vendored
Normal file
42
.github/workflows/cd-docs.yml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
name: "continuous deployment: surplus Documentation"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "docs/**"
|
||||
- "mkdocs.yml"
|
||||
- "src/spow-telegram-bridge/*.py"
|
||||
- "src/**/*LICENCE*"
|
||||
- "docs/**/*LICENCE*"
|
||||
- "docs/**/*LICENSE*"
|
||||
- "docs/CC0"
|
||||
- "*LICENCE*"
|
||||
- "src/surplus-on-wheels/s+ow"
|
||||
- "src/surplus-on-wheels/install.sh"
|
||||
- "src/spow-whatsapp-bridge/install.sh"
|
||||
- "src/spow-telegram-bridge/install.sh"
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- run: nix develop --impure --command hatch run docs:build
|
||||
|
||||
- uses: cloudflare/pages-action@v1
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
projectName: surplus
|
||||
directory: site
|
||||
branch: main
|
||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
28
.github/workflows/cd-telegram.yml
vendored
Normal file
28
.github/workflows/cd-telegram.yml
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
name: "continuous deployment: Telegram Bridge"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "src/spow-telegram-bridge/**"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- run: |
|
||||
cd src/spow-telegram-bridge
|
||||
nix develop --command poetry build
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "spow-telegram-bridge"
|
||||
path: src/spow-whatsapp-bridge/dist
|
||||
retention-days: 14
|
28
.github/workflows/cd-whatsapp.yml
vendored
Normal file
28
.github/workflows/cd-whatsapp.yml
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
name: "continuous deployment: WhatsApp Bridge"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "src/spow-whatsapp-bridge/**"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- run: |
|
||||
cd src/spow-whatsapp-bridge
|
||||
nix build .#termux
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "spow-whatsapp-bridge-android"
|
||||
path: src/spow-whatsapp-bridge/result
|
||||
retention-days: 14
|
46
.github/workflows/checks.yml
vendored
46
.github/workflows/checks.yml
vendored
|
@ -1,46 +0,0 @@
|
|||
name: qc
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
analyse:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install devbox
|
||||
uses: jetpack-io/devbox-install-action@v0.3.0
|
||||
|
||||
- name: install dependencies
|
||||
run: devbox run poetry install
|
||||
|
||||
- name: buil wheel
|
||||
id: build
|
||||
run: devbox run poetry build
|
||||
|
||||
- name: analyse with mypy
|
||||
run: devbox run poetry run mypy surplus.py test.py
|
||||
|
||||
- name: check for black formatting compliance
|
||||
run: devbox run poetry run "black --check surplus.py test.py"
|
||||
|
||||
- name: analyse isort compliance
|
||||
run: devbox run poetry run "isort --check surplus.py test.py"
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install devbox
|
||||
uses: jetpack-io/devbox-install-action@v0.3.0
|
||||
|
||||
- name: install dependencies
|
||||
run: devbox run poetry install
|
||||
|
||||
- name: run tests
|
||||
run: devbox run poetry run python test.py
|
22
.github/workflows/ci-spow.yml
vendored
Normal file
22
.github/workflows/ci-spow.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: "continuous integration: s+ow"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "src/surplus-on-wheels/**"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- run: |
|
||||
cd src/spow-whatsapp-bridge
|
||||
nix develop --command sh check.sh
|
22
.github/workflows/ci-telegram.yml
vendored
Normal file
22
.github/workflows/ci-telegram.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: "continuous integration: Telegram Bridge"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "src/spow-telegram-bridge/**"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- run: |
|
||||
cd src/spow-telegram-bridge
|
||||
nix develop --command sh check.sh
|
22
.github/workflows/ci-whatsapp.yml
vendored
Normal file
22
.github/workflows/ci-whatsapp.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: "continuous integration: WhatsApp Bridge"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "src/spow-whatsapp-bridge/**"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- run: |
|
||||
cd src/spow-whatsapp-bridge
|
||||
nix develop --command sh check.sh
|
53
.github/workflows/publish-slsa3.yml
vendored
53
.github/workflows/publish-slsa3.yml
vendored
|
@ -1,53 +0,0 @@
|
|||
name: release with slsa 3 compliance
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
outputs:
|
||||
hashes: ${{ steps.hash.outputs.hashes }}
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install devbox
|
||||
uses: jetpack-io/devbox-install-action@v0.3.0
|
||||
|
||||
- name: install dependencies
|
||||
run: devbox run poetry install
|
||||
|
||||
- name: install dependencies
|
||||
id: build
|
||||
run: devbox run poetry build
|
||||
|
||||
- name: generate provenance subjects
|
||||
id: hash
|
||||
run: |
|
||||
cd dist
|
||||
HASHES=$(sha256sum * | base64 -w0)
|
||||
echo "hashes=$HASHES" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: release
|
||||
uses: softprops/action-gh-release@v0.1.15
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
|
||||
provenance:
|
||||
needs: [build]
|
||||
permissions:
|
||||
actions: read
|
||||
id-token: write
|
||||
contents: write
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.6.0
|
||||
with:
|
||||
base64-subjects: "${{ needs.build.outputs.hashes }}"
|
||||
upload-assets: true
|
11
.gitignore
vendored
11
.gitignore
vendored
|
@ -1,7 +1,18 @@
|
|||
.idea/
|
||||
old/*
|
||||
|
||||
# cached files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.cache/
|
||||
|
||||
# documentation
|
||||
docs/spow.sh
|
||||
docs/termux.sh
|
||||
docs/whatsapp.sh
|
||||
docs/telegram.sh
|
||||
/site
|
||||
|
||||
# distribution
|
||||
.Python
|
||||
|
|
2
CONTRIBUTORS
Normal file
2
CONTRIBUTORS
Normal file
|
@ -0,0 +1,2 @@
|
|||
Mark Joshwel <mark@joshwel.co>
|
||||
shamsu07 <shamsuddeenks@gmail.com>
|
334
README.md
334
README.md
|
@ -1,254 +1,148 @@
|
|||
# surplus
|
||||
|
||||
Plus Code to iOS-Shortcuts-like shareable text
|
||||
surplus (s+) is a Python script to convert [Google Maps Plus Codes](https://maps.google.com/pluscodes/)
|
||||
to iOS Shortcuts-like shareable text
|
||||
|
||||
- [Installing](#installing)
|
||||
- [Usage](#usage)
|
||||
- [Command-line Interface](#command-line-interface)
|
||||
- [API Reference](#api-reference)
|
||||
- [surplus.surplus()](#surplussurplus)
|
||||
- [surplus.parse_query()](#surplusparse_query)
|
||||
- [surplus.handle_query()](#surplushandle_query)
|
||||
- [surplus.Localcode](#surpluslocalcode)
|
||||
- [surplus.Latlong](#surpluslatlong)
|
||||
- [Developing](#developing)
|
||||
- [The format is wrong/different!](#the-format-is-wrongdifferent)
|
||||
- [Licence](#licence)
|
||||
- [quickstart](#quickstart)
|
||||
- [documentation](https://surplus.joshwel.co)
|
||||
- [the user's guide (command-line)](https://surplus.joshwel.co/using#as-a-command-line-tool)
|
||||
- [the user's guide (library)](https://surplus.joshwel.co/using#as-a-library)
|
||||
- [the developer's guide](https://surplus.joshwel.co/developing)
|
||||
- [api reference](https://surplus.joshwel.co/developing)
|
||||
- [the contributor's guide](https://surplus.joshwel.co/contributing)
|
||||
|
||||
this repository is also monorepo for the following sibling projects:
|
||||
|
||||
- **surplus on wheels** (s+ow)
|
||||
a pure shell script to get your location using `termux-location`, process it through surplus, and
|
||||
send it to messaging service or wherever, using "bridges"
|
||||
- **surplus on wheels: Whatsapp Bridge**
|
||||
- **surplus on wheels: Telegram Bridge**
|
||||
|
||||
## quickstart
|
||||
|
||||
> [!TIP]
|
||||
> termux users can consider [surplus on wheels](https://surplus.joshwel.co/onwheels), a sibling
|
||||
> project that allows you to run surplus regularly throughout the day and send it to someone on a
|
||||
> messaging platform
|
||||
|
||||
> [!IMPORTANT]
|
||||
> python 3.11 or later is required due to a bug in earlier versions
|
||||
> [(python/cpython#88089)](https://github.com/python/cpython/issues/88089)
|
||||
|
||||
install surplus with pip, or [pipx](https://pipx.pypa.io/) (recommended):
|
||||
|
||||
```text
|
||||
$ surplus 9R3J+R9 Singapore
|
||||
surplus version 1.1.3
|
||||
Thomson Plaza
|
||||
301 Upper Thomson Road
|
||||
Sin Ming, Bishan
|
||||
574408
|
||||
pipx install surplus
|
||||
```
|
||||
|
||||
then, use the `surplus` command, or its `s+` shorthand:
|
||||
|
||||
```text
|
||||
$ s+ 7RGX+GJ Singapore
|
||||
surplus version 2024.0.0-beta
|
||||
Singapore Conference Hall
|
||||
7 Shenton Way
|
||||
068809
|
||||
Central, Singapore
|
||||
```
|
||||
|
||||
```python
|
||||
>>> from surplus import surplus, Localcode
|
||||
>>> Localcode(code="8RPQ+JW", locality="Singapore").full_length()
|
||||
(True, '6PH58RPQ+JW')
|
||||
>>> surplus("6PH58RPQ+JW")
|
||||
(True, 'Caldecott Stn Exit 4\nToa Payoh Link\n298106\nCentral, Singapore')
|
||||
```
|
||||
the types of queries you can pass in are:
|
||||
|
||||
## Installing
|
||||
- full-length Plus Codes
|
||||
`6PH58QMF+FX`
|
||||
- shortened Plus Codes / 'local codes'
|
||||
`8QMF+FX Singapore`
|
||||
- latitude and longitude coordinate pairs
|
||||
`1.3336875, 103.7749375`
|
||||
- string queries
|
||||
`Wisma Atria`
|
||||
|
||||
Install surplus directly from GitHub using pip:
|
||||
or, alternatively pass in `-` to read from stdin
|
||||
|
||||
```text
|
||||
pip install git+https://github.com/markjoshwel/surplus
|
||||
```
|
||||
more documentation is available at <https://surplus.joshwel.co>,
|
||||
or alternatively available locally in the [docs/](docs) folder
|
||||
|
||||
## Usage
|
||||
## licences
|
||||
|
||||
### Command-line Interface
|
||||
- [**surplus**](src/surplus)
|
||||
The Unlicence
|
||||
|
||||
```text
|
||||
usage: surplus [-h] [-d] [-v] [query ...]
|
||||
surplus is free and unencumbered software released into the public domain. for more information,
|
||||
please refer to the [UNLICENCE](src/surplus/UNLICENCE), <https://unlicense.org>, or the python
|
||||
module docstring
|
||||
|
||||
Plus Code to iOS-Shortcuts-like shareable text
|
||||
however, the dependencies surplus relies on are licenced under different, but still permissive
|
||||
and open-source licences:
|
||||
|
||||
positional arguments:
|
||||
query full-length Plus Code (6PH58QMF+FX),
|
||||
local code (8QMF+FX Singapore), or
|
||||
latlong (1.3336875, 103.7749375)
|
||||
- [**geopy**](https://pypi.org/project/geopy/) —
|
||||
Python Geocoding Toolbox
|
||||
MIT Licence
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-d, --debug prints lat, long and reverser
|
||||
response dict to stderr
|
||||
-v, --version prints version information to stderr
|
||||
and exits
|
||||
```
|
||||
- [**geographiclib**](https://pypi.org/project/geographiclib/) —
|
||||
The geodesic routines from GeographicLib
|
||||
MIT Licence
|
||||
|
||||
### API Reference
|
||||
- [**pluscodes**](https://pypi.org/project/pluscodes/) —
|
||||
Compute Plus Codes (Open Location Codes)
|
||||
Apache 2.0
|
||||
|
||||
#### `surplus.surplus()`
|
||||
- [**surplus on wheels**](src/surplus-on-wheels)
|
||||
The Unlicence
|
||||
|
||||
pluscode to shareable text conversion function
|
||||
surplus on wheels is free and unencumbered software released into the public domain. for more
|
||||
information, please refer to [UNLICENCE](src/surplus-on-wheels/UNLICENCE) or
|
||||
<http://unlicense.org/>
|
||||
|
||||
- signature
|
||||
- [**surplus on wheels: WhatsApp Bridge**](src/spow-whatsapp-bridge)
|
||||
Mozilla Public Licence 2.0
|
||||
|
||||
```python
|
||||
def surplus(
|
||||
query: str | Localcode | Latlong,
|
||||
reverser: typing.Callable = geopy.geocoders.Nominatim(user_agent="surplus").reverse,
|
||||
debug: bool = False,
|
||||
) -> tuple[bool, str]:
|
||||
...
|
||||
```
|
||||
the s+ow WhatsApp Bridge is based off mdtest code from the
|
||||
[whatsmeow](https://github.com/tulir/whatsmeow) project, which is licenced under the Mozilla
|
||||
Public Licence 2.0. for more information, see [LICENCE](src/spow-whatsapp-bridge/LICENCE), or
|
||||
<https://www.mozilla.org/en-US/MPL/2.0/>
|
||||
|
||||
- arguments
|
||||
the direct dependencies s+ow-whatsapp-bridge relies on are licenced under different, but still
|
||||
permissive and open-source licences:
|
||||
|
||||
- `query: str | surplus.Localcode | surplus.Latlong`
|
||||
- str
|
||||
normal longcode (6PH58QMF+FX)
|
||||
- [`surplus.Localcode`](#surpluslocalcode)
|
||||
shortcode with locality (8QMF+FX Singapore)
|
||||
- [`surplus.Latlong`](#surpluslatlong)
|
||||
latlong
|
||||
- [**whatsmeow**](https://github.com/tulir/whatsmeow) —
|
||||
Go library for the WhatsApp web multidevice API
|
||||
Mozilla Public Licence 2.0
|
||||
|
||||
- `reverser: typing.Callable = geopy.geocoders.Nominatim(user_agent="surplus").reverser`
|
||||
latlong to location function, accesses a dict from .raw attribute of return object
|
||||
function should be able to take a string with two floats and return a `geopy.Location`-like object (None checking is done)
|
||||
- [**surplus on wheels: Telegram Bridge**](src/spow-telegram-bridge)
|
||||
The Unlicence
|
||||
|
||||
```python
|
||||
# code used by surplus
|
||||
location: dict[str, Any] = reverser(f"{lat}, {lon}").raw
|
||||
```
|
||||
the s+ow Telegram Bridge is free and unencumbered software released into the public domain. for
|
||||
more information, please refer to the [UNLICENCE](src/spow-telegram-bridge/UNLICENCE),
|
||||
<https://unlicense.org>, or the python module docstring
|
||||
|
||||
dict should be similar to [nominatim raw dicts](https://nominatim.org/release-docs/latest/api/Output/#addressdetails)
|
||||
however, the direct dependencies surplus relies on are licenced under different, but still
|
||||
permissive and open-source licences:
|
||||
|
||||
- `debug: bool = False`
|
||||
prints lat, long and reverser response dict to stderr
|
||||
- [**Telethon**](https://pypi.org/project/Telethon/) —
|
||||
Pure Python 3 MTProto API Telegram client library, for bots too!
|
||||
MIT Licence
|
||||
|
||||
- returns `tuple[bool, str]`
|
||||
- [**surplus documentation**](docs)
|
||||
CC0 1.0 Universal
|
||||
|
||||
- `(True, <str>)`
|
||||
conversion succeeded, second element is the resultant string
|
||||
- `(False, <str>)`
|
||||
conversion failed, second element is an error message string
|
||||
the textual contents of surplus documentation by [Mark Joshwel](https://joshwel.co) is marked
|
||||
with [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||
to view a copy of this license, visit <https://creativecommons.org/publicdomain/zero/1.0/>
|
||||
|
||||
---
|
||||
the fonts the documentation website relies on are licenced under different, but still
|
||||
permissive and open-source licences:
|
||||
|
||||
#### `surplus.parse_query()`
|
||||
- [**Geist and Geist Mono**](https://github.com/vercel/geist-font)
|
||||
SIL Open Font Licence 1.1 ([file](docs/fonts/LICENSE.txt))
|
||||
|
||||
the direct software dependencies the documentation are also licenced under different, but still
|
||||
permissive and open-source licences:
|
||||
|
||||
function that parses a string Plus Code, local code or latlong into a str, [`surplus.Localcode`](#surpluslocalcode) or [`surplus.Latlong`](#surpluslatlong) respectively
|
||||
- [**mkdocs-material**](https://squidfunk.github.io/mkdocs-material/) —
|
||||
Documentation that simply works
|
||||
MIT Licence
|
||||
|
||||
- signature:
|
||||
|
||||
```python
|
||||
def parse_query(
|
||||
query: str, debug: bool = False
|
||||
) -> tuple[Literal[True], str | Localcode | Latlong] | tuple[Literal[False], str]:
|
||||
```
|
||||
|
||||
- arguments:
|
||||
|
||||
- `query: str`
|
||||
string Plus Code, local code or latlong
|
||||
|
||||
- returns `tuple[Literal[True], str | Localcode | Latlong] | tuple[Literal[False], str]`
|
||||
|
||||
- `(True, <str | surplus.Localcode | surplus.Latlong>)`
|
||||
conversion succeeded, second element is resultant Plus code string, [`surplus.Localcode`](#surpluslocalcode) or [`surplus.Latlong`](#surpluslatlong)
|
||||
- `(False, <str>)`
|
||||
conversion failed, second element is an error message string
|
||||
|
||||
---
|
||||
|
||||
#### `surplus.handle_query()`
|
||||
|
||||
function that gets returns a [surplus.Latlong](#surpluslatlong) from a Plus Code string, [`surplus.Localcode`](#surpluslocalcode) or [`surplus.Latlong`](#surpluslatlong) object.
|
||||
used after [`surplus.parse_query()`](#surplusparse_query).
|
||||
|
||||
- signature:
|
||||
|
||||
```python
|
||||
def handle_query(
|
||||
query: str | Localcode | Latlong, debug: bool = False
|
||||
) -> tuple[Literal[True], Latlong] | tuple[Literal[False], str]:
|
||||
```
|
||||
|
||||
- arguments:
|
||||
|
||||
- `query: str | Localcode | Latlong`
|
||||
- str
|
||||
normal longcode (6PH58QMF+FX)
|
||||
- [`surplus.Localcode`](#surpluslocalcode)
|
||||
shortcode with locality (8QMF+FX Singapore)
|
||||
- [`surplus.Latlong`](#surpluslatlong)
|
||||
latlong
|
||||
|
||||
- returns `tuple[Literal[True], Latlong] | tuple[Literal[False], str]`
|
||||
- `(True, <surplus.Latlong>)`
|
||||
conversion succeeded, second element is a [`surplus.Latlong`](#surpluslatlong)
|
||||
- `(False, <str>)`
|
||||
conversion failed, second element is an error message string
|
||||
|
||||
---
|
||||
|
||||
#### `surplus.Localcode`
|
||||
|
||||
`typing.NamedTuple` representing short Plus Code with locality
|
||||
|
||||
- parameters:
|
||||
|
||||
- `code: str`
|
||||
Plus Code - e.g.: `"8QMF+FX"`
|
||||
- `locality: str`
|
||||
e.g.: `"Singapore"`
|
||||
|
||||
---
|
||||
|
||||
##### `surplus.Localcode.full_length()`
|
||||
|
||||
method that calculates full-length Plus Code using locality
|
||||
|
||||
- signature:
|
||||
|
||||
```python
|
||||
def full_length(
|
||||
self, geocoder: Callable = Nominatim(user_agent="surplus").geocode
|
||||
) -> tuple[bool, str]:
|
||||
```
|
||||
|
||||
- arguments:
|
||||
|
||||
- `geocoder: typing.Callable = geopy.geocoders.Nominatim(user_agent="surplus").geocode`
|
||||
place/locality to location function, accesses .longitude and .latitude if returned object is not None
|
||||
|
||||
- returns:
|
||||
|
||||
- `(True, <str>)`
|
||||
conversion succeeded, second element is the resultant Plus Code string
|
||||
- `(False, <str>)`
|
||||
conversion failed, second element is an error message string
|
||||
|
||||
---
|
||||
|
||||
#### `surplus.Latlong`
|
||||
|
||||
`typing.NamedTuple` representing a pair of latitude and longitude coordinates
|
||||
|
||||
- parameters:
|
||||
|
||||
- `lat: float`
|
||||
latitudinal coordinate
|
||||
- `long: float`
|
||||
longitudinal coordinate
|
||||
|
||||
## Developing
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- [Python >=3.10](https://www.python.org/)
|
||||
- [Poetry](https://python-poetry.org/)
|
||||
|
||||
Alternatively, use [devbox](https://get.jetpack.io/devbox) for a hermetic development environment powered by [Nix](https://nixos.org/).
|
||||
|
||||
```text
|
||||
devbox shell # skip this if you aren't using devbox
|
||||
poetry install
|
||||
poetry shell
|
||||
```
|
||||
|
||||
## The format is wrong/different!
|
||||
|
||||
If surplus generates wrong text for a particular Plus Code,
|
||||
[submit an issue](https://github.com/markjoshwel/surplus/issues/new).
|
||||
|
||||
Ensure you detail the following:
|
||||
|
||||
1. The erroneous Plus Code, local code or latlong, or a similar coordinate that reproduces the same error
|
||||
|
||||
2. Output from the terminal, with `--debug` enabled or `debug=True`
|
||||
|
||||
3. How it should look like instead
|
||||
|
||||
## Licence
|
||||
|
||||
surplus is free and unencumbered software released into the public domain.
|
||||
For more information, please refer to the [UNLICENCE](UNLICENCE), <http://unlicense.org/> or the Python module docstring.
|
||||
- [**mkdocs**](https://www.mkdocs.org/) —
|
||||
Project documentation with Markdown
|
||||
BSD-2-Clause Licence
|
||||
|
|
12
devbox.json
12
devbox.json
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"packages": [
|
||||
"python311",
|
||||
"poetry"
|
||||
],
|
||||
"shell": {
|
||||
"init_hook": "poetry shell"
|
||||
},
|
||||
"nixpkgs": {
|
||||
"commit": "f80ac848e3d6f0c12c52758c0f25c10c97ca3b62"
|
||||
}
|
||||
}
|
12
devbox.lock
12
devbox.lock
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"lockfile_version": "1",
|
||||
"packages": {
|
||||
"poetry": {
|
||||
"resolved": "github:NixOS/nixpkgs/f80ac848e3d6f0c12c52758c0f25c10c97ca3b62#poetry"
|
||||
},
|
||||
"python311": {
|
||||
"plugin_version": "0.0.1",
|
||||
"resolved": "github:NixOS/nixpkgs/f80ac848e3d6f0c12c52758c0f25c10c97ca3b62#python311"
|
||||
}
|
||||
}
|
||||
}
|
121
docs/CC0
Normal file
121
docs/CC0
Normal file
|
@ -0,0 +1,121 @@
|
|||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
325
docs/changelog.md
Normal file
325
docs/changelog.md
Normal file
|
@ -0,0 +1,325 @@
|
|||
# changelog
|
||||
|
||||
## surplus v2024.0.0
|
||||
|
||||
(unreleased)
|
||||
|
||||
!!! information
|
||||
this is a tentative release. surplus is currently versioned as `2024.0.0-beta`, as its
|
||||
behaviour is not stabilized
|
||||
|
||||
!!! warning
|
||||
this is an api-breaking release. see 'the great api break'.
|
||||
command-line usage of surplus has not changed
|
||||
|
||||
### what's new
|
||||
|
||||
- added flag `--show-user-agent`, printing the fingerprinted user agent string and exiting
|
||||
|
||||
### what's changed
|
||||
|
||||
- `default_geocoder()` and `default_reverser()` have been deprecated since v2.1.0 and are now
|
||||
removed. use the `SurplusDefaultGeocoding` class instead
|
||||
- `SurplusException` is now `SurplusError`
|
||||
|
||||
### the great api break
|
||||
|
||||
TODO
|
||||
|
||||
### thanks!
|
||||
|
||||
- [vlshields](https://github.com/vlshields/) for their support with a drink!
|
||||
|
||||
full changelog: <https://github.com/markjoshwel/surplus/compare/v2.2.0...v2024.0.0>
|
||||
|
||||
## surplus on wheels v2
|
||||
|
||||
(released on the 1st of July 2024 on tag `v2.2024.25+spow`)
|
||||
|
||||
### changes
|
||||
|
||||
- you can now customize command invocations with `SURPLUS_CMD` and `LOCATION_CMD` environment variables
|
||||
- surplus on wheels will purge logs when setting the `SPOW_PRIVATE` environment flag
|
||||
|
||||
### thanks!
|
||||
|
||||
- [vlshields](https://github.com/vlshields/) for their support with a drink!
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels: WhatsApp Bridge v2.2024.25
|
||||
|
||||
(released on the 17th of June 2024 on tag `v2.2024.25+spow-whatsapp-bridge`)
|
||||
|
||||
!!! note
|
||||
from henceforth, the WhatsApp Bridge is now versioned with a modified calendar versioning scheme
|
||||
of `MAJOR.YEAR.ISOWEEK`, where the `MAJOR` version segment will be bumped with codebase changes,
|
||||
whereas the `YEAR` and `ISOWEEK` segments will represent the time of which the release was
|
||||
built at
|
||||
|
||||
### changes
|
||||
|
||||
- updated dependencies to latest versions
|
||||
- added `pair-phone` and `reconnect` subcommands
|
||||
- TODO added optional helper script to auto-update to newer versions via a user-made daily cron job
|
||||
|
||||
### thanks!
|
||||
|
||||
- [vlshields](https://github.com/vlshields/) for their support with a drink!
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels: Telegram Bridge v2.2024.25
|
||||
|
||||
(released on the 17th of June 2024 on tag `v2.2024.25+spow-telegram-bridge`)
|
||||
|
||||
!!! note
|
||||
from henceforth, the Telegram Bridge will automatically release a new version once a week if
|
||||
there are updates to its dependencies
|
||||
|
||||
as such, the bridge is now versioned with a modified calendar versioning scheme of
|
||||
`MAJOR.YEAR.ISOWEEK`, where the `MAJOR` version segment will be bumped with codebase changes,
|
||||
whereas the `YEAR` and `ISOWEEK` segments will represent the time of which the release was
|
||||
built at
|
||||
|
||||
### changes
|
||||
|
||||
- updated dependencies to latest versions
|
||||
- added `logout` subcommand
|
||||
- TODO added optional helper script to auto-update to newer versions via a user-made daily cron job
|
||||
|
||||
### thanks!
|
||||
|
||||
- [vlshields](https://github.com/vlshields/) for their support with a drink!
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels v1
|
||||
|
||||
initial release on the 9th of November 2023
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels: WhatsApp Bridge v1
|
||||
|
||||
initial release on the 7th of November 2023
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels: Telegram Bridge v1
|
||||
|
||||
initial release on the 7th of November 2023
|
||||
|
||||
---
|
||||
|
||||
## surplus v2.2.0
|
||||
|
||||
(released on the 14th of October 2023)
|
||||
|
||||
!!! warning
|
||||
constants are changed in this update!
|
||||
|
||||
fixed a bug installing surplus on Python 3.12 and italian sharetext fixes
|
||||
|
||||
### what's new
|
||||
|
||||
- special key arrangements for malaysia
|
||||
- support for termux-location json input
|
||||
|
||||
### what's fixed
|
||||
|
||||
- fixed typing-extensions as an unwritten dependency
|
||||
this also fixes a bug in not being able to run surplus in Python 3.12
|
||||
- fixed italian key arrangements [#34](https://github.com/markjoshwel/surplus/pull/34)
|
||||
|
||||
### what's changed
|
||||
|
||||
- **`SHAREABLE*` constants are now dictionaries, see api docs for more information**
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v2.1.1...v2.2.0>
|
||||
|
||||
---
|
||||
|
||||
## surplus v2.1.1
|
||||
|
||||
(released on the 19th of September 2023)
|
||||
|
||||
fix roads not coming first in Italian addresses (#31)
|
||||
|
||||
- documentation enhancements
|
||||
- remove self in `SurplusReverserProtocol` conforming signature
|
||||
- fix mismatching carets and add info on `split_iso3166_2`
|
||||
- alternative line 3 arrangement for IT/Italy in [#31](https://github.com/markjoshwel/surplus/pull/31)
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v2.1.0...v2.1.1>
|
||||
|
||||
---
|
||||
|
||||
## surplus v2.1.0
|
||||
|
||||
(released on the 6th of September 2023)
|
||||
|
||||
!!! warning
|
||||
there are backwards-compatible api changes in this release.
|
||||
|
||||
type-to-type location representation conversions and quality of life changes/fixes
|
||||
|
||||
- **`default_geocoder()` and `default_reverser()` functions have been deprecated in favour of the
|
||||
new [`SurplusDefaultGeocoding` class](https://github.com/markjoshwel/surplus/tree/main#class-surplusdefaultgeocoding)**
|
||||
- add reading from stdin when query is "-" in [#23](https://github.com/markjoshwel/surplus/pull/23)
|
||||
- type to type conversion in [#24](https://github.com/markjoshwel/surplus/pull/24)
|
||||
- fix local codes not being recognised if split with comma in [#29](https://github.com/markjoshwel/surplus/pull/29)
|
||||
- more verbose -v/--version information in [#21](https://github.com/markjoshwel/surplus/pull/21)
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v2.0.1...v2.1.0>
|
||||
|
||||
---
|
||||
|
||||
## surplus v2.0.1
|
||||
|
||||
(released on the 5th of September 2023)
|
||||
|
||||
- expose surplus.Result in `__init__.py` by in [#28](https://github.com/markjoshwel/surplus/pull/28)
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v2.0.0...v2.0.1>
|
||||
|
||||
---
|
||||
|
||||
## surplus v2.0.0
|
||||
|
||||
(released on the 3rd of September 2023)
|
||||
|
||||
!!! warning
|
||||
this is an api-breaking release. see 'the great api break'.
|
||||
command-line usage of surplus has not changed
|
||||
|
||||
!!! information
|
||||
python 3.11 or later is required due to a bug in earlier versions
|
||||
[(python/cpython#88089)](https://github.com/python/cpython/issues/88089)
|
||||
|
||||
complete rewrite and string query support
|
||||
|
||||
### changes
|
||||
|
||||
- surplus has been fully rewritten in [#19](https://github.com/markjoshwel/surplus/pull/19)
|
||||
- support for string queries
|
||||
```text
|
||||
$ s+ Wisma Atria
|
||||
surplus version 2.0.0
|
||||
Wisma Atria
|
||||
435 Orchard Road
|
||||
238877
|
||||
Central, Singapore
|
||||
```
|
||||
- mypy will now recognise surplus as a typed module
|
||||
- **python 3.11 is now the minimum version**
|
||||
|
||||
### the great api break
|
||||
|
||||
#### what is new
|
||||
|
||||
- nominatim keys are now stored in tuple constants
|
||||
- surplus exception classes are now a thing
|
||||
- surplus functions now operate using a unified `Behaviour` object
|
||||
- surplus functions now return a `Result` object for safer value retrieval instead of the previous
|
||||
`(bool, value)` tuple
|
||||
- dedicated NamedTuple classes for each query type
|
||||
|
||||
#### what has been removed
|
||||
|
||||
- `surplus.handle_query()`
|
||||
|
||||
instead, use `.to_lat_long_coord()` on your surplus 2.x query object
|
||||
|
||||
#### what has remained
|
||||
|
||||
- `surplus.surplus()`, the function
|
||||
- `surplus.parse_query()`, the function
|
||||
|
||||
#### what has changed
|
||||
|
||||
- `surplus.surplus()`
|
||||
1. `reverser` and `debug` arguments are now under the unified `surplus.Behaviour` object
|
||||
2. function now returns a `surplus.Result[str]` for safer error handling
|
||||
|
||||
- `surplus.parse_query()`
|
||||
1. `query` and `debug` arguments are now under the unified `surplus.Behaviour` object
|
||||
2. function now returns a `surplus.Result[surplus.Query]` for safer error handling
|
||||
|
||||
- `surplus.Latlong`
|
||||
attributes `lat` and `long` have been renamed to `latitude` and `longitude` respectively
|
||||
|
||||
- `surplus.Localcode`
|
||||
renamed to `surplus.LocalCodeQuery`
|
||||
|
||||
- `Localcode.full_length()`
|
||||
renamed to `LocalCodeQuery.to_full_plus_code()`, and returns a `surplus.Result[str]` for safer
|
||||
error handling
|
||||
|
||||
full changelog: <https://github.com/markjoshwel/surplus/compare/v1.1.3...v2.0.0>
|
||||
|
||||
## surplus v1.1.3
|
||||
|
||||
(released on the 21st of June 2023)
|
||||
|
||||
general output fixes and quality of life updates
|
||||
|
||||
- ci(qc) workflow tweaks by [markjoshwel](https://github.com/markjoshwel) in [#13](https://github.com/markjoshwel/surplus/pull/13)
|
||||
- cc: remove woodlands test + brackets by [markjoshwel](https://github.com/markjoshwel) in [#14](https://github.com/markjoshwel/surplus/pull/14)
|
||||
- s+: display county before state by [markjoshwel](https://github.com/markjoshwel) in [#15](https://github.com/markjoshwel/surplus/pull/15)
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v1.1.2...v1.1.3>
|
||||
|
||||
---
|
||||
|
||||
## surplus v1.1.2
|
||||
|
||||
(released on the 18th of June 2023)
|
||||
|
||||
general output fixes and quality of life updates
|
||||
|
||||
- do not repeat details by [markjoshwel](https://github.com/markjoshwel) in #9
|
||||
- add -v/--version flag by [markjoshwel](https://github.com/markjoshwel) in #11
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v1.1.0...v1.1.1>
|
||||
|
||||
---
|
||||
|
||||
## surplus v1.1.1
|
||||
|
||||
(released on the 16th of June 2023)
|
||||
|
||||
### changes
|
||||
|
||||
fixes and output tweaks
|
||||
|
||||
- fix reverser returning a None location by [shamsu07](https://github.com/shamsu07) in #5
|
||||
|
||||
### thanks!
|
||||
|
||||
- [shamsu07](https://github.com/shamsu07) made their first contribution!
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v1.1.0...v1.1.1>
|
||||
|
||||
---
|
||||
|
||||
## surplus v1.1.0
|
||||
|
||||
(released on the 3rd of June 2023)
|
||||
|
||||
short code and latitude longitude coordinate pair support!
|
||||
|
||||
- code: s+ alternative shorthand script
|
||||
- code: handle none/list locations
|
||||
- code: query by lat long support
|
||||
- code: support shortcodes with localities
|
||||
- code: implement more address detail tags from nominatim
|
||||
- meta: slsa 3 compliance
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v1.0.0...v1.1.0>
|
||||
|
||||
---
|
||||
|
||||
## surplus v1.0.0
|
||||
|
||||
initial release on the 2nd of June 2023
|
80
docs/contributing.md
Normal file
80
docs/contributing.md
Normal file
|
@ -0,0 +1,80 @@
|
|||
# the contributor's handbook
|
||||
|
||||
expected details on development workflows? see [the developer's handbook](developing.md)
|
||||
|
||||
## which forge do i use?
|
||||
|
||||
as at the time of writing this documentation, i am actively using both
|
||||
<https://github.com/markjoshwel/surplus> and <https://forge.joshwel.co/mark/surplus>
|
||||
|
||||
use whatever is more comfortable to you. do you not like microsoft and/or have moved away from github?
|
||||
feel free to use <https://forge.joshwel.co>. don't want to make an account for either? did the forge
|
||||
implode and is down? okay! mail in a git patch at <mark@joshwel.co>
|
||||
|
||||
## git workflow
|
||||
|
||||
1. fork the repository and branch off from the `future` branch,
|
||||
or `main` if not available
|
||||
2. make and commit your changes!
|
||||
3. pull in any changes from upstream, and resolve any conflicts, if any
|
||||
4. if needed, **commit your copyright waiver** (_see [waiving copyright](#waiving-copyright)_)
|
||||
5. submit a pull request (_or mail in a patch_)
|
||||
|
||||
### waiving copyright
|
||||
|
||||
!!! danger "Warning"
|
||||
this section is a **must** to follow if you have modified **any** unlicenced code:
|
||||
|
||||
- top-level surplus files (`releaser.py`, etc)
|
||||
- surplus (`src/surplus`)
|
||||
- surplus Documentation (`docs/`)
|
||||
- surplus on wheels (`src/surplus-on-wheels`)
|
||||
- surplus on wheels: Telegram Bridge (`src/spow-telegram-bridge`)
|
||||
|
||||
!!! info
|
||||
the command to create an empty commit is `git commit --allow-empty`
|
||||
|
||||
when contributing your first changes, please include an empty commit for a copyright
|
||||
waiver using the following message (replace `Your Name` with your name or username):
|
||||
|
||||
```text
|
||||
Your Name Copyright Waiver
|
||||
|
||||
I dedicate any and all copyright interest in this software to the
|
||||
public domain. I make this dedication for the benefit of the public at
|
||||
large and to the detriment of my heirs and successors. I intend this
|
||||
dedication to be an overt act of relinquishment in perpetuity of all
|
||||
present and future rights to this software under copyright law.
|
||||
|
||||
To the best of my knowledge and belief, my contributions are either
|
||||
originally authored by me or are derived from prior works which I have
|
||||
verified are also in the public domain and are not subject to claims
|
||||
of copyright by other parties.
|
||||
|
||||
To the best of my knowledge and belief, no individual, business,
|
||||
organization, government, or other entity has any copyright interest
|
||||
in my contributions, and I affirm that I will not make contributions
|
||||
that are otherwise encumbered.
|
||||
```
|
||||
|
||||
(from <https://unlicense.org/WAIVER>)
|
||||
|
||||
for documentation contributors, if you have contributed a
|
||||
[legally significant](https://www.gnu.org/prep/maintain/maintain.html#Legally-Significant) or have
|
||||
repeatedly commited multiple small changes, waive your copyright with the CC0 deed
|
||||
(replace `Your Name` with your name or username):
|
||||
|
||||
```text
|
||||
Your Name Copyright Waiver
|
||||
|
||||
The person who associated a work with this deed has dedicated the work to
|
||||
the public domain by waiving all of his or her rights to the work worldwide
|
||||
under copyright law, including all related and neighboring rights, to the
|
||||
extent allowed by law.
|
||||
```
|
||||
|
||||
(from <https://creativecommons.org/publicdomain/zero/1.0/>)
|
||||
|
||||
## reporting incorrect output
|
||||
|
||||
TODO
|
337
docs/developing.md
Normal file
337
docs/developing.md
Normal file
|
@ -0,0 +1,337 @@
|
|||
# the developer’s handbook
|
||||
|
||||
!!! abstract
|
||||
i (mark), heavily use nix to manage my projects, either with
|
||||
[devbox](https://github.com/jetpack-io/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](https://github.com/DeterminateSystems/nix-installer):
|
||||
|
||||
```text
|
||||
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
||||
```
|
||||
|
||||
if i, a very very inexperienced Nix and NixOS user to give a rundown on why to use it:
|
||||
|
||||
1. **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 use `nix develop` and you use `nix develop` to 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, etc
|
||||
|
||||
if i can build it, and the locked inputs of the nix flake are still available on the internet,
|
||||
you can build it too
|
||||
|
||||
2. **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 tools
|
||||
|
||||
**what 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 with
|
||||
`nix develop` will **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
|
||||
|
||||
- for NixOS users, [nix-ld](https://github.com/Mic92/nix-ld) is needed. if you use flakes to
|
||||
declare your system, follow accordingly. else, use `/etc/nixos/configuration.nix`
|
||||
|
||||
for NixOS-on-WSL users, use [nix-ld-rs](https://github.com/nix-community/nix-ld-rs)
|
||||
|
||||
once you're done installing `nix-ld` or `nix-ld-rs`,
|
||||
don't forget to run `sudo nixos-rebuild switch`
|
||||
|
||||
prerequisite software:
|
||||
|
||||
- [Python](https://www.python.org/downloads/), 3.11 or newer
|
||||
- [Hatch](https://hatch.pypa.io/latest/): dependency management and build tool
|
||||
|
||||
to start a development environment:
|
||||
|
||||
```text
|
||||
hatch shell
|
||||
```
|
||||
|
||||
for docs:
|
||||
|
||||
```text
|
||||
hatch -e docs shell
|
||||
```
|
||||
|
||||
### workflow for python code
|
||||
|
||||
TODO
|
||||
|
||||
### workflow for markdown documentation
|
||||
|
||||
run the documentation server with:
|
||||
|
||||
```text
|
||||
hatch run docs:serve
|
||||
```
|
||||
|
||||
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)
|
||||
|
||||
---
|
||||
|
||||
## 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](https://github.com/patrickvane/shfmt): formatter
|
||||
- [ShellCheck](https://www.shellcheck.net/): 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.new` into `s+ow`
|
||||
sometimes when piping shfmt's output immediately into the same file
|
||||
results in the file being empty :(
|
||||
|
||||
- checking s+ow:
|
||||
- run `shellcheck s+ow`
|
||||
if there's no output, that means it passed :)
|
||||
|
||||
- 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](https://github.com/nix-community/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:
|
||||
|
||||
- [Python](https://www.python.org/downloads/), 3.11 or newer
|
||||
- [Poetry](https://python-poetry.org/): dependency management and build tool
|
||||
|
||||
to start a development environment:
|
||||
|
||||
```text
|
||||
poetry shell
|
||||
```
|
||||
|
||||
### workflow
|
||||
|
||||
after modifying,
|
||||
|
||||
1. check the source code:
|
||||
1. `mypy bridge.py`
|
||||
2. `ruff format bridge.py`
|
||||
3. `ruff check bridge.py`
|
||||
|
||||
!!! note
|
||||
alternatively, run `check.sh` inside `src/spow-telegram-bridge`
|
||||
|
||||
2. and then [test the binary](#workflow-for-testing-the-binary)
|
||||
|
||||
if the bridge behaves nominally, [bump the version](#versioning-surplus-on-wheels-telegram-bridge)
|
||||
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](https://go.dev): 1.22 or newer
|
||||
- [Android NDK](https://developer.android.com/ndk/downloads), if building for Termux
|
||||
|
||||
### workflow for modifying bridge code
|
||||
|
||||
the bridge's code is just modified [mdtest](https://github.com/tulir/whatsmeow/tree/main/mdtest)
|
||||
code, and as such, whenever in doubt, do a diff between mdtest and the bridge code
|
||||
|
||||
after modifying,
|
||||
|
||||
1. check the source code:
|
||||
1. `go fmt bridge.go`
|
||||
2. `go vet bridge.go`
|
||||
3. `golint bridge.go`
|
||||
|
||||
!!! note
|
||||
alternatively, run `check.sh` inside `src/spow-whatsapp-bridge`
|
||||
|
||||
2. [build a binary](#workflow-for-building-a-binary)
|
||||
|
||||
3. [test the binary](#workflow-for-testing-the-binary)
|
||||
|
||||
4. and if all goes well, [bump the version](#versioning-surplus-on-wheels-whatsapp-bridge)
|
||||
and commit!
|
||||
|
||||
### workflow for bumping dependencies
|
||||
|
||||
- check with your editor, plugin, or online if there's newer patch/minor (see
|
||||
[semantic versioning](https://semver.org/)) versions to update to
|
||||
|
||||
- change the `go.mod` accordingly
|
||||
|
||||
after bumping,
|
||||
|
||||
1. [build a binary](#workflow-for-building-a-binary)
|
||||
2. [test the binary](#workflow-for-testing-the-binary)
|
||||
3. and if all goes well, [bump the version](#versioning-surplus-on-wheels-whatsapp-bridge)
|
||||
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:
|
||||
|
||||
```text
|
||||
CGO_ENABLED=1 go build
|
||||
```
|
||||
|
||||
nix users can alternatively run:
|
||||
|
||||
```text
|
||||
nix build
|
||||
```
|
||||
|
||||
instructions to build a Termux build are located at the
|
||||
[bridges' documentation page](onwheels/whatsapp-bridge.md#anywhere-else), however nix users can run
|
||||
the following instead for a reproducible, deterministic and hermetic build command:
|
||||
|
||||
```text
|
||||
nix build .#termux
|
||||
```
|
||||
the resulting build will be in `result/spow-whatsapp-bridge`
|
||||
|
||||
### workflow for testing the binary
|
||||
|
||||
- test it out, making sure that you write dummy test text to `~/.cache/s+ow/message` before running
|
||||
the binary
|
||||
|
||||
1. run `s+ow-whatsapp-bridge login` first
|
||||
|
||||
2. run `s+ow-whatsapp-bridge list` if you don't already have a chat ID
|
||||
to send the test message to
|
||||
|
||||
3. run `s+ow-whatsapp-bridge` type or copy and paste in a `wa:`-prefixed chat ID
|
||||
after it logs in, and verify it sends
|
||||
|
||||
if the bridge behaves nominally, [bump the version](#versioning-surplus-on-wheels-whatsapp-bridge)
|
||||
and commit!
|
||||
|
||||
## workflow for versioning and tagging releases
|
||||
|
||||
### versioning surplus
|
||||
|
||||
format: `YEAR.MAJOR.MINOR[-PRERELEASE]` ([semantic versioning](https://semver.org/))
|
||||
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](https://calver.org/))
|
||||
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](https://github.com/LonamiWebs/Telethon/), which also
|
||||
follows [semantic versioning](https://semver.org/). 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](https://calver.org/))
|
||||
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](https://github.com/tulir/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:
|
||||
|
||||
``` title="src/spow-whatsapp-bridge/go.mod"
|
||||
--8<-- "src/spow-whatsapp-bridge/go.mod"
|
||||
```
|
||||
|
||||
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](contributing.md) for what's next
|
BIN
docs/fonts/Geist-Regular.ttf
Normal file
BIN
docs/fonts/Geist-Regular.ttf
Normal file
Binary file not shown.
BIN
docs/fonts/GeistMono-Regular.ttf
Normal file
BIN
docs/fonts/GeistMono-Regular.ttf
Normal file
Binary file not shown.
BIN
docs/fonts/GeistMonoVF.woff2
Normal file
BIN
docs/fonts/GeistMonoVF.woff2
Normal file
Binary file not shown.
BIN
docs/fonts/GeistVF.woff2
Normal file
BIN
docs/fonts/GeistVF.woff2
Normal file
Binary file not shown.
92
docs/fonts/LICENSE.txt
Normal file
92
docs/fonts/LICENSE.txt
Normal file
|
@ -0,0 +1,92 @@
|
|||
Copyright (c) 2023 Vercel, in collaboration with basement.studio
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION AND CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
42
docs/index.md
Normal file
42
docs/index.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# surplus (s+)
|
||||
|
||||
surplus (s+) is a Python script to convert [Google Maps Plus Codes](https://maps.google.com/pluscodes/)
|
||||
to iOS Shortcuts-like shareable text
|
||||
|
||||
!!! tip
|
||||
termux users can consider [surplus on wheels](onwheels/index.md), a sibling project that allows
|
||||
you to run surplus regularly throughout the day and send it to someone on a messaging platform
|
||||
|
||||
!!! important
|
||||
python 3.11 or later is required due to a bug in earlier versions
|
||||
[(python/cpython#88089)](https://github.com/python/cpython/issues/88089)
|
||||
|
||||
install surplus with pip, or [pipx](https://pipx.pypa.io/) (recommended):
|
||||
|
||||
```text
|
||||
pipx install surplus
|
||||
```
|
||||
|
||||
then, use the `surplus` command, or its `s+` shorthand:
|
||||
|
||||
```text
|
||||
$ s+ 7RGX+GJ Singapore
|
||||
surplus version 2024.0.0
|
||||
Singapore Conference Hall
|
||||
7 Shenton Way
|
||||
068809
|
||||
Central, Singapore
|
||||
```
|
||||
|
||||
the types of queries you can pass in are:
|
||||
|
||||
- full-length Plus Codes
|
||||
`6PH58QMF+FX`
|
||||
- shortened Plus Codes / 'local codes'
|
||||
`8QMF+FX Singapore`
|
||||
- latitude and longitude coordinate pairs
|
||||
`1.3336875, 103.7749375`
|
||||
- string queries
|
||||
`Wisma Atria`
|
||||
|
||||
or, alternatively pass in `-` to read from stdin
|
113
docs/licences.md
Normal file
113
docs/licences.md
Normal file
|
@ -0,0 +1,113 @@
|
|||
# licences
|
||||
|
||||
## [surplus](index.md)
|
||||
|
||||
**The Unlicence**
|
||||
|
||||
surplus is free and unencumbered software released into the public domain:
|
||||
|
||||
``` title="src/surplus/UNLICENCE"
|
||||
--8<-- "src/surplus/UNLICENCE"
|
||||
```
|
||||
|
||||
however, the dependencies surplus relies on are licenced under different, but still permissive
|
||||
and open-source licences:
|
||||
|
||||
- [**geopy**](https://pypi.org/project/geopy/) —
|
||||
Python Geocoding Toolbox
|
||||
MIT Licence
|
||||
|
||||
- [**geographiclib**](https://pypi.org/project/geographiclib/) —
|
||||
The geodesic routines from GeographicLib
|
||||
MIT Licence
|
||||
|
||||
- [**pluscodes**](https://pypi.org/project/pluscodes/) —
|
||||
Compute Plus Codes (Open Location Codes)
|
||||
Apache 2.0
|
||||
|
||||
---
|
||||
|
||||
## [surplus on wheels](onwheels/index.md)
|
||||
|
||||
**The Unlicence**
|
||||
|
||||
surplus on wheels is free and unencumbered software released into the public domain:
|
||||
|
||||
``` title="src/surplus-on-wheels/UNLICENCE"
|
||||
--8<-- "src/surplus-on-wheels/UNLICENCE"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [surplus on wheels: WhatsApp Bridge](onwheels/whatsapp-bridge.md)
|
||||
|
||||
**Mozilla Public Licence 2.0**
|
||||
|
||||
the s+ow WhatsApp Bridge is based off of mdtest code from the
|
||||
[whatsmeow](https://github.com/tulir/whatsmeow) project, which is licenced under the Mozilla
|
||||
Public Licence 2.0:
|
||||
|
||||
``` title="src/spow-whatsapp-bridge/LICENCE"
|
||||
--8<-- "src/spow-whatsapp-bridge/LICENCE"
|
||||
```
|
||||
|
||||
the direct dependencies s+ow-whatsapp-bridge relies on are licenced under different, but still
|
||||
permissive and open-source licences:
|
||||
|
||||
- [**whatsmeow**](https://github.com/tulir/whatsmeow) —
|
||||
Go library for the WhatsApp web multidevice API
|
||||
Mozilla Public Licence 2.0
|
||||
|
||||
---
|
||||
|
||||
## [surplus on wheels: Telegram Bridge](onwheels/telegram-bridge.md)
|
||||
|
||||
**The Unlicence**
|
||||
|
||||
the s+ow Telegram Bridge is free and unencumbered software released into the public domain:
|
||||
|
||||
``` title="src/spow-telegram-bridge/UNLICENCE"
|
||||
--8<-- "src/spow-telegram-bridge/UNLICENCE"
|
||||
```
|
||||
|
||||
however, the direct dependencies surplus relies on are licenced under different, but still
|
||||
permissive and open-source licences:
|
||||
|
||||
- [**Telethon**](https://pypi.org/project/Telethon/) —
|
||||
Pure Python 3 MTProto API Telegram client library, for bots too!
|
||||
MIT Licence
|
||||
|
||||
---
|
||||
|
||||
## [surplus documentation](index.md)
|
||||
|
||||
**CC0 1.0 Universal**
|
||||
|
||||
the textual contents of surplus documentation by [Mark Joshwel](https://joshwel.co) is marked
|
||||
with [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||
to view a copy of this licence, visit <https://creativecommons.org/publicdomain/zero/1.0/>
|
||||
|
||||
``` title="docs/CC0"
|
||||
--8<-- "docs/CC0"
|
||||
```
|
||||
|
||||
the fonts the documentation website relies on are licenced under different, but still
|
||||
permissive and open-source licences:
|
||||
|
||||
- [**Geist and Geist Mono**](https://github.com/vercel/geist-font)
|
||||
SIL Open Font Licence 1.1
|
||||
|
||||
``` title="docs/fonts/LICENSE.txt"
|
||||
--8<-- "docs/fonts/LICENSE.txt"
|
||||
```
|
||||
|
||||
the direct software dependencies the documentation are also licenced under different, but still
|
||||
permissive and open-source licences:
|
||||
|
||||
- [**mkdocs-material**](https://squidfunk.github.io/mkdocs-material/) —
|
||||
Documentation that simply works
|
||||
MIT Licence
|
||||
|
||||
- [**mkdocs**](https://www.mkdocs.org/) —
|
||||
Project documentation with Markdown
|
||||
BSD-2-Clause Licence
|
69
docs/links.md
Normal file
69
docs/links.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# backup links
|
||||
|
||||
for when first-party links like <https://surplus.joshwel.co> and <https://forge.joshwel.co> are down:
|
||||
|
||||
## surplus
|
||||
|
||||
``` title="Primary Link"
|
||||
https://forge.joshwel.co/mark/surplus.git
|
||||
```
|
||||
|
||||
``` title="Alternative Link"
|
||||
https://github.com/markjoshwel/surplus.git
|
||||
```
|
||||
|
||||
## surplus on wheels
|
||||
|
||||
- shell script
|
||||
|
||||
``` title="Primary Link"
|
||||
https://surplus.joshwel.co/spow.sh
|
||||
```
|
||||
|
||||
``` title="Alternative Link"
|
||||
https://raw.githubusercontent.com/markjoshwel/surplus/main/src/surplus-on-wheels/s+ow
|
||||
```
|
||||
|
||||
- termux installation script
|
||||
|
||||
``` title="Primary Link"
|
||||
https://surplus.joshwel.co/termux.sh
|
||||
```
|
||||
|
||||
``` title="Alternative Link"
|
||||
https://raw.githubusercontent.com/markjoshwel/surplus/main/src/surplus-on-wheels/install.sh
|
||||
```
|
||||
|
||||
## surplus on wheels: Telegram Bridge
|
||||
|
||||
- install/update script:
|
||||
|
||||
``` title="Primary Link"
|
||||
https://surplus.joshwel.co/telegram.sh
|
||||
```
|
||||
|
||||
``` title="Alternative Link"
|
||||
https://raw.githubusercontent.com/markjoshwel/surplus/main/src/spow-telegram-bridge/install.sh
|
||||
```
|
||||
|
||||
- pipx target
|
||||
|
||||
``` title="Primary Link"
|
||||
git+https://forge.joshwel.co/mark/surplus.git#egg=spow-telegram-bridge&subdirectory=src/spow-telegram-bridge
|
||||
```
|
||||
|
||||
``` title="Alternative Link"
|
||||
git+https://github.com/markjoshwel/surplus.git#egg=spow-telegram-bridge&subdirectory=src/spow-telegram-bridge
|
||||
```
|
||||
|
||||
## surplus on wheels: WhatsApp Bridge
|
||||
|
||||
- install/update script:
|
||||
|
||||
``` title="Primary Link"
|
||||
https://surplus.joshwel.co/whatsapp.sh
|
||||
```
|
||||
|
||||
``` title="Alternative Link"
|
||||
https://raw.githubusercontent.com/markjoshwel/surplus/main/src/spow-whatsapp-bridge/install.sh
|
||||
```
|
58
docs/onwheels/bridges.md
Normal file
58
docs/onwheels/bridges.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
# surplus on wheel bridges
|
||||
|
||||
## official bridges
|
||||
|
||||
there are two currently “official” bridges:
|
||||
|
||||
- [surplus on wheels: WhatsApp Bridge](whatsapp-bridge.md)
|
||||
- [surplus on wheels: Telegram Bridge](telegram-bridge.md)
|
||||
|
||||
## bring your own bridge
|
||||
|
||||
### an informal specification
|
||||
|
||||
s+ow bridges are relatively simple as they are:
|
||||
|
||||
1. an executable or script
|
||||
|
||||
2. that reads in `SPOW_TARGETS` given by surplus to the bridge, using the standard input (stdin)
|
||||
stream
|
||||
|
||||
1. bridges do not need to account for the possibility of multiple lines sent to stdin
|
||||
|
||||
2. bridges should account for the possibility of comma and space (`", "` instead of just `","`)
|
||||
delimited targets, and strip each target of preceding and trailing whitespace
|
||||
|
||||
3. bridges should recognise a platform based on a prefix
|
||||
(e.g. `wa:` for WhatsApp, `tg:` for Telegram, etc.)
|
||||
|
||||
3. reads `SPOW_MESSAGE` (`~/.cache/spow/message`) for the message content
|
||||
|
||||
notes:
|
||||
|
||||
1. stderr and stdout are redirected to s+ow’s error and output logs respectively unless the
|
||||
`-p / --private` flag is passed to surplus
|
||||
|
||||
2. any errors encountered by the bridge should always result in a non-zero return. error logs will
|
||||
show the exact error code, so feel free to use other numbers than 1
|
||||
|
||||
3. persistent data such as credentials and session data storage are to be handled by the
|
||||
bridge itself. consider storing them in `$HOME/.local/share/<bridge-name>/`, or wherever
|
||||
appropriate
|
||||
|
||||
### example
|
||||
|
||||
if i were to recommend an example on a basic bridge implementation, it would be the
|
||||
[Telegram Bridge](telegram-bridge.md):
|
||||
|
||||
```python title="src/spow-telegram-bridge/bridge.py"
|
||||
--8<-- "src/spow-telegram-bridge/bridge.py"
|
||||
```
|
||||
|
||||
!!! note
|
||||
the feature of deleting the last sent message (`--delete-last`) is a non-standard feature for
|
||||
bridges, and was simply a use case i personally needed. if you're going to implement a bridge,
|
||||
all you really need is the ability to `login`, `logout`, and [send a message](#an-informal-specification)
|
||||
|
||||
you can add other features as per the needs of your platform, like how the WhatsApp Bridge has
|
||||
a `pair-phone` subcommand, or per your use case needs, like in the Telegram Bridge's `--delete-last`.
|
43
docs/onwheels/emulating-termux-location.md
Normal file
43
docs/onwheels/emulating-termux-location.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
# emulating `termux-location`
|
||||
|
||||
to bodge surplus on wheels (s+ow) on non-Termux systems
|
||||
|
||||
!!! note
|
||||
dummy admonition for colour matching
|
||||
|
||||
!!! warning
|
||||
dummy admonition for colour matching
|
||||
|
||||
`termux-location`, part of [Termux:API](https://wiki.termux.com/wiki/Termux:API), gets the device's
|
||||
location via android apis and returns a json response through stdout:
|
||||
|
||||
```text
|
||||
{
|
||||
"latitude": 1.3277513,
|
||||
"longitude": 103.678317,
|
||||
"altitude": 51.6298828125,
|
||||
"accuracy": 48.46337890625,
|
||||
"vertical_accuracy": 38.4659423828125,
|
||||
"bearing": 0.0,
|
||||
"speed": 0.0,
|
||||
"elapsedMs": 28,
|
||||
"provider": "gps"
|
||||
}
|
||||
```
|
||||
|
||||
see <https://wiki.termux.com/wiki/Termux-location> for more information
|
||||
|
||||
## implementing for surplus on wheels (s+ow)
|
||||
|
||||
s+ow will call the command a total of six times, being three pairs of parallel
|
||||
`$LOCATION_CMD -p "network"` and `$LOCATION_CMD -p "gps"` invocations, before deciding after
|
||||
exhausting all six runs on which output to choose, if any command runs were successful
|
||||
|
||||
even if somewhere in the termux-location implementation fails, it always (begrudgingly) returns
|
||||
zero. s+ow will treat the invocation of the command as successful if there is **any output** to
|
||||
the standard output (stdout) stream
|
||||
|
||||
## implementing for surplus (s+)
|
||||
|
||||
s+, when passed `--t / --using-termux-location`, will consume stdin, parse it as json and then
|
||||
attempt to retrieve the `latitude` and `longitude` keys as floating point numbers
|
46
docs/onwheels/emulating-termux-notification.md
Normal file
46
docs/onwheels/emulating-termux-notification.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
# emulating `termux-notification`
|
||||
|
||||
to bodge surplus on wheels (s+ow) on non-Termux systems
|
||||
|
||||
`termux-notification`, part of [Termux:API](https://wiki.termux.com/wiki/Termux:API), sends out an
|
||||
android notification
|
||||
|
||||
without `termux-notification`, s+ow will still run as it doesn't use `set -e` and very carefully
|
||||
handles all command invocations, with `termux-notification` being the graceful exception\
|
||||
|
||||
however, if you would like to emulate it, make an executable globally reachable with the same name
|
||||
|
||||
s+ow uses the command as such:
|
||||
|
||||
```shell
|
||||
termux-notification \
|
||||
--priority "default" \
|
||||
--title "surplus on wheels: No bridges" \
|
||||
--content "No '$SPOW_BRIDGES' file; message is not sent."
|
||||
```
|
||||
|
||||
```shell
|
||||
termux-notification \
|
||||
--priority "min" \
|
||||
--ongoing \
|
||||
--id "s+ow" \
|
||||
--title "surplus on wheels" \
|
||||
--content "s+ow has started running."
|
||||
```
|
||||
|
||||
```shell
|
||||
termux-notification \
|
||||
--priority "min" \
|
||||
--id "s+ow" \
|
||||
--title "surplus on wheels" \
|
||||
--content ...
|
||||
```
|
||||
|
||||
```shell
|
||||
termux-notification \
|
||||
--priority "default" \
|
||||
--title "surplus on wheels has errored" \
|
||||
--content ...
|
||||
```
|
||||
|
||||
see <https://wiki.termux.com/wiki/Termux-notification> for more information
|
329
docs/onwheels/index.md
Normal file
329
docs/onwheels/index.md
Normal file
|
@ -0,0 +1,329 @@
|
|||
# surplus on wheels (s+ow)
|
||||
|
||||
surplus on wheels is a pure shell script to get your location using
|
||||
[termux-location](https://wiki.termux.com/wiki/Termux-location), process it through surplus, and
|
||||
send it to messaging service or wherever using “bridges”
|
||||
|
||||
surplus was made to emulate sending your location through the iOS Shortcuts app, and surplus on
|
||||
wheels complements it by running surplus automatically using a cron job.
|
||||
(but using it manually also works!)
|
||||
|
||||
## installing
|
||||
|
||||
!!! important
|
||||
s+ow is a Termux-first script, and will not work anywhere else unless you have
|
||||
a utility that emulates [termux-location](https://wiki.termux.com/wiki/termux-location)
|
||||
on `$PATH` alongside bridges that supports your platform
|
||||
|
||||
there are two notable ways to install s+ow:
|
||||
|
||||
1. [as a standalone script](#as-a-standalone-script)
|
||||
2. or, [as a cron job](#as-a-cron-job)
|
||||
|
||||
there is also an [installation script](#using-installation-scripts) for quickly getting
|
||||
started from a _fresh_ termux installation
|
||||
|
||||
### as a standalone script
|
||||
|
||||
1. firstly install python and termux-api if you haven't already:
|
||||
|
||||
```text
|
||||
pkg install python termux-api
|
||||
```
|
||||
|
||||
also install the accompanying Termux:API app from [F-Froid](https://f-droid.org/en/packages/com.termux.api/)
|
||||
|
||||
2. install pipx if you haven't already:
|
||||
|
||||
```text
|
||||
pip install pipx
|
||||
```
|
||||
|
||||
3. install surplus:
|
||||
|
||||
```text
|
||||
pipx install surplus
|
||||
```
|
||||
|
||||
4. install surplus on wheels:
|
||||
|
||||
```text
|
||||
mkdir -p ~/.local/bin/
|
||||
wget -O ~/.local/bin/s+ow https://surplus.joshwel.co/spow.sh
|
||||
chmod +x ~/.local/bin/s+ow
|
||||
```
|
||||
|
||||
!!! note
|
||||
if `wget` throws a 404, see [backup links](../links.md)
|
||||
|
||||
if `~/.local/bin` is not in your `$PATH`, add the following to your shell's rc file:
|
||||
|
||||
```shell
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
```
|
||||
|
||||
et voilà! s+ow is now setup. to actually send the message to a messaging platform,
|
||||
[install an appropriate bridge](bridges.md)
|
||||
|
||||
### as a cron job
|
||||
|
||||
!!! important
|
||||
these instructions rely on following the [previous instructions](#as-a-standalone-script)
|
||||
|
||||
1. install necessary packages to run cron jobs:
|
||||
|
||||
```text
|
||||
pkg install cronie termux-services
|
||||
```
|
||||
|
||||
2. restart termux and start the cron service:
|
||||
|
||||
```text
|
||||
sv-enable cron
|
||||
```
|
||||
|
||||
3. set up the cron job:
|
||||
|
||||
run the following command:
|
||||
|
||||
```text
|
||||
crontab -e
|
||||
```
|
||||
|
||||
and add the following text:
|
||||
|
||||
```text
|
||||
59 * * * * bash -l -c "(SPOW_TARGETS="" SPOW_CRON=y s+ow)"
|
||||
```
|
||||
|
||||
!!! important
|
||||
minimally fill in the `SPOW_TARGETS` variable before running s+ow.
|
||||
[(see usage for more info)](#usage)
|
||||
|
||||
this will run s+ow every hour, a minute before the hour
|
||||
|
||||
modify the variables as per your needs.
|
||||
see [usage](#usage) for more information
|
||||
|
||||
et voilà! s+ow will now send a message every hour. feel free to experiment with the cron
|
||||
job to your liking. see [crontab.guru](https://crontab.guru/) if you’re new to cron jobs
|
||||
|
||||
if you haven’t already, [install an appropriate bridge](bridges.md) to actually send a
|
||||
message to a messaging platform
|
||||
|
||||
### using installation scripts
|
||||
|
||||
!!! warning
|
||||
these scripts assume you're starting from a fresh base installation of Termux.
|
||||
if you have already cron jobs, then manually carry out the instructions in
|
||||
'[as a cron job](#as-a-cron-job)'
|
||||
|
||||
!!! important
|
||||
if not installed already, install
|
||||
[Termux:API from F-Droid](https://f-droid.org/en/packages/com.termux.api/), **not the Play Store**
|
||||
|
||||
1. setup s+ow:
|
||||
|
||||
```text
|
||||
wget -O- https://surplus.joshwel.co/termux.sh | sh
|
||||
```
|
||||
|
||||
!!! note
|
||||
if `wget` throws a 404, see [backup links](../links.md)
|
||||
|
||||
2. restart termux!
|
||||
|
||||
3. and finally, [set up a cron job](#as-a-cron-job) from step 3 onwards ('set up the cron job')
|
||||
|
||||
## usage
|
||||
|
||||
### environment variables
|
||||
|
||||
s+ow's behaviour can be customised environment variables, with `SURPLUS_CMD` being the only
|
||||
required variable:
|
||||
|
||||
1. `SPOW_TARGETS`
|
||||
a single line of comma-delimited chat IDs with bridge prefixes
|
||||
|
||||
```text
|
||||
wa:000000000000000000@g.us,tg:-0000000000000000000,...
|
||||
```
|
||||
|
||||
in the example above, the WhatsApp chat ID is `wa:`-prefixed as recognised by the
|
||||
[spow-whatsapp-bridge](whatsapp-bridge.md), and the
|
||||
Telegram chat ID is `tg:`-prefixed as recognised by the
|
||||
[spow-telegram-bridge](telegram-bridge.md)
|
||||
|
||||
2. `SPOW_CRON` (optional)
|
||||
set as non-empty to declare that s+ow is being run as a cron job
|
||||
|
||||
if running as a cron job, start s+ow one minute earlier than intended to account for the time
|
||||
it takes to run `termux-location` and `surplus`. s+ow assumes this and delays itself
|
||||
appropriately
|
||||
|
||||
setting it to `n` will also be treated as if it were empty
|
||||
|
||||
3. `SPOW_PRIVATE` (optional)
|
||||
set as non-empty to discard all logs when s+ow is done:
|
||||
|
||||
- `$HOME/.cache/s+ow/out.log` will be set to `/dev/null`
|
||||
- `$HOME/.cache/s+ow/err.log` will be set to `/dev/null`
|
||||
- `$HOME/.cache/s+ow/location.net.json` will be cleared after use locating the device
|
||||
- `$HOME/.cache/s+ow/location.gps.json` will be cleared after use locating the device
|
||||
- `$HOME/.cache/s+ow/location.json` will be cleared after use locating the device
|
||||
- `$HOME/.cache/s+ow/surplus.out.log` will be cleared after use generating the message
|
||||
- `$HOME/.cache/s+ow/surplus.err.log` will be set to `/dev/null`
|
||||
- `$HOME/.cache/s+ow/message` will be cleared after all bridges has sent the message
|
||||
|
||||
!!! warning
|
||||
the only file not cleared is s+ow's last successful message file, `$HOME/.cache/s+ow/last`,
|
||||
as s+ow uses this as the first fallback message if it couldn't locate the device in time.
|
||||
if you're fine with using the `LOCATION_FALLBACK` string, feel free to modify your
|
||||
cron job to remove this file after running s+ow
|
||||
|
||||
setting it to `n` will also be treated as if it were empty
|
||||
|
||||
4. `SURPLUS_CMD` (optional)
|
||||
the custom invocation used when calling surplus, modify this if you want to add certain flags
|
||||
|
||||
this defaults to `surplus -td`
|
||||
|
||||
!!! warning
|
||||
when overriding, ensure you also have `-td` (`--using-termux-location` and `--debug`) in
|
||||
your custom invocation!
|
||||
|
||||
5. `LOCATION_CMD` (optional)
|
||||
the custom invocation used when calling `termux-location`, modify this if you want to bodge
|
||||
together surplus on wheels on non-termux systems.
|
||||
see ([emulating `termux-location`](emulating-termux-location.md)) for more information
|
||||
|
||||
this defaults to `termux-location`
|
||||
|
||||
6. `LOCATION_PRIORITISE_NETWORK` (optional)
|
||||
set as non-empty to declare that s+ow can just use network location instead of GPS
|
||||
if GPS is taking too long.
|
||||
you should only turn this on if punctuality means that much to you, or you’re in a
|
||||
country with cell towers close by or everywhere, like Singapore
|
||||
|
||||
the JIDs can be obtained by sending a message to the user/group, while running
|
||||
`s+ow mdtest`, and examining the output for your message. JIDs are email address-like
|
||||
strings
|
||||
|
||||
setting it to `n` will also be treated as if it were empty
|
||||
|
||||
7. `LOCATION_TIMEOUT` (optional)
|
||||
set as a number to override the default first location timeout of `50`
|
||||
|
||||
8. `LOCATION_FALLBACK` (optional)
|
||||
a string that can be formatted with three numbers using `%d`:
|
||||
|
||||
1. s+ow's status
|
||||
2. number of location attempts before giving up
|
||||
3. type of message sent
|
||||
|
||||
see [details on notification numbers](#details-on-notification-numbers) for the meanings of
|
||||
each number. 'a', 'b' and 'c' map to `A`, `B` and `C`
|
||||
|
||||
defaults to `%d%d%d?`
|
||||
|
||||
### faking locations
|
||||
|
||||
> sometimes you gotta do what you gotta do
|
||||
|
||||
you can fake your s+ow messages by either:
|
||||
|
||||
1. setting a dummy `last` file in s+ow cache
|
||||
|
||||
`$HOME/.cache/s+ow/last` is used as the fallback response when a part of s+ow (either
|
||||
`termux-location` or `surplus` errors out). you can set this file to whatever you want
|
||||
and just turn off location on your device
|
||||
|
||||
2. setting a `fake` file in s+ow cache
|
||||
|
||||
!!! warning
|
||||
s+ow uses the `read` command to read the file. as such, it is possible for s+ow to
|
||||
prematurely stop reading the file if the file does not contain a trailing newline.
|
||||
|
||||
you can also write text to `$HOME/.cache/s+ow/fake` to fake upcoming messages. the file
|
||||
is delimited by empty lines. as such, arrange the file like so:
|
||||
|
||||
```text
|
||||
The Clementi Mall
|
||||
3155 Commonwealth Avenue West
|
||||
Westpeak Terrace
|
||||
129588
|
||||
Southwest, Singapore
|
||||
|
||||
Westgate
|
||||
3 Gateway Drive
|
||||
Jurong East
|
||||
608532
|
||||
Southwest, Singapore
|
||||
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
on every run of s+ow, the first group of lines will be consumed, and the file will be
|
||||
updated with the remaining lines. if the file is empty, it will be deleted
|
||||
|
||||
### details on notification numbers
|
||||
|
||||
after each run, or if s+ow had to use a location fallback string, s+ow notifies you:
|
||||
|
||||
!!! abstract "surplus on wheels"
|
||||
Run has finished.
|
||||
|
||||
Singapore Conference Hall
|
||||
7 Shenton Way
|
||||
068809
|
||||
Central, Singapore
|
||||
|
||||
(A, B, C, D<E>)
|
||||
[lc:W sp:X sm:Y - Z]
|
||||
|
||||
!!! abstract "surplus on wheels has errored"
|
||||
(A, B, C, D<E>)
|
||||
[lc:W sp:X sm:Y - Z]
|
||||
|
||||
the top line denotes general statuses:
|
||||
|
||||
- `A`: s+ow's status
|
||||
- `0` is nominal
|
||||
- `1` is a termux-location error
|
||||
- `2` is a surplus error
|
||||
- `3` is a bridge/message send error
|
||||
- `B`: number of location attempts before giving up
|
||||
- `C`: type of message sent
|
||||
- `0` for freshly made sharetext
|
||||
- `1` for recycling a previous successful location sharetext (`last` file)
|
||||
- `2` for using fallback template
|
||||
- `D`: number of bridge failures
|
||||
- `E`: each bridge's return code
|
||||
|
||||
the bottom line details on how long s+ow spent on each stage:
|
||||
|
||||
- `W`: time to locate
|
||||
- `X`: time to run surplus
|
||||
- `Y`: time to send message(s)
|
||||
- `Z`: total run time
|
||||
|
||||
## help! a bridge isn't working!
|
||||
|
||||
cool. do the following:
|
||||
|
||||
1. log out and log back in and try again
|
||||
|
||||
2. if that didn't fix it, update/reinstall the bridge and try again
|
||||
|
||||
3. run the bridge's executable directly to see if there's any connection issues
|
||||
look at your bridge's installation instructions to find out where it's located at.
|
||||
or, use the `which` command
|
||||
|
||||
4. if it connected successfully, or you see no errors, try typing in one of the targets you've set
|
||||
in `SPOW_TARGETS` for the bridge, and then press the enter/return key
|
||||
|
||||
!!! failure
|
||||
on the off chance you reinstalled the bridge, and it still failed either step 3 or 4, the bridge
|
||||
itself is faulty. file a bug report/issue with the bridge's project page or maintainer and tell
|
||||
them where it failed (was it connecting to the messaging service? or failure to send a message?)
|
101
docs/onwheels/telegram-bridge.md
Normal file
101
docs/onwheels/telegram-bridge.md
Normal file
|
@ -0,0 +1,101 @@
|
|||
# surplus on wheels: Telegram Bridge
|
||||
|
||||
Telegram Bridge for surplus on wheels (s+ow)
|
||||
|
||||
s+ow bridges are defined in a file named `$HOME/.s+ow-bridges`. each command in the file is run,
|
||||
and comma-seperated target chat IDs are passed using stdin.
|
||||
|
||||
this bridge recognises targets prefixed with `tg:`.
|
||||
|
||||
```text
|
||||
tg:<chat id>,...
|
||||
```
|
||||
|
||||
## installation
|
||||
|
||||
!!! important
|
||||
the following instructions implies that [surplus](../index.md) and [surplus on wheels](bridges.md)
|
||||
have already been installed
|
||||
|
||||
1. install prerequisite software if not installed:
|
||||
|
||||
```text
|
||||
pkg install git
|
||||
```
|
||||
|
||||
```text
|
||||
pip install pipx
|
||||
```
|
||||
|
||||
2. install spow-telegram-bridge:
|
||||
|
||||
```text
|
||||
wget -O- https://surplus.joshwel.co/telegram.sh | sh
|
||||
```
|
||||
|
||||
!!! note
|
||||
if `wget` throws a 404, see [backup links](../links.md)
|
||||
|
||||
3. add the following to your `$HOME/.s+ow-bridges` file:
|
||||
|
||||
```text
|
||||
SPOW_TELEGRAM_API_HASH="" SPOW_TELEGRAM_API_ID="" s+ow-telegram-bridge
|
||||
```
|
||||
|
||||
fill in SPOW_TELEGRAM_API_HASH and SPOW_TELEGRAM_API_ID accordingly.
|
||||
see the [Telethon docs](https://docs.telethon.dev/en/stable/basic/signing-in.html) for
|
||||
more information
|
||||
|
||||
to keep up to date, look at [updating](#updating) to set up a daily update cron job:
|
||||
|
||||
## updating
|
||||
|
||||
the installation script also sets up a shell script under the `s+ow-telegram-bridge-update` command
|
||||
|
||||
```text
|
||||
s+ow-telegram-bridge-update
|
||||
```
|
||||
|
||||
to do this automatically, make a cron job with `crontab -e`
|
||||
and make a new line with the following text:
|
||||
|
||||
```text
|
||||
0 0 * * * bash -l -c "s+ow-telegram-bridge-update"
|
||||
```
|
||||
|
||||
this cron job will run the command every day at midnight
|
||||
|
||||
## usage
|
||||
|
||||
- `s+ow-telegram-bridge`
|
||||
normal usage; sends latest message to tg:-prefixed targets given in stdin
|
||||
|
||||
- `s+ow-telegram-bridge login`
|
||||
logs in to Telegram
|
||||
|
||||
- `s+ow-telegram-bridge logout`
|
||||
logs out of Telegram
|
||||
|
||||
- `s+ow-telegram-bridge list`
|
||||
lists all chats and their IDs
|
||||
|
||||
optional arguments:
|
||||
|
||||
- `--silent`
|
||||
asks telegram to send message silently
|
||||
- `--delete-last`
|
||||
deletes last location message to prevent clutter
|
||||
|
||||
## versioning scheme
|
||||
|
||||
from `v2.2024.27`, the Telegram Bridge will automatically release a new version once a week if there
|
||||
are updates to its dependencies
|
||||
|
||||
as such, the bridge is now versioned with a modified calendar versioning scheme of
|
||||
`MAJOR.YEAR.ISOWEEK`, where the `MAJOR` version segment will be bumped with codebase changes, whereas
|
||||
the `YEAR` and `ISOWEEK` segments will represent the time of which the release was built at
|
||||
|
||||
## licence
|
||||
|
||||
the s+ow Telegram Bridge is free and unencumbered software released into the public domain.
|
||||
for more information, see [licences](../licences.md).
|
210
docs/onwheels/whatsapp-bridge.md
Normal file
210
docs/onwheels/whatsapp-bridge.md
Normal file
|
@ -0,0 +1,210 @@
|
|||
# surplus on wheels: WhatsApp Bridge
|
||||
|
||||
WhatsApp Bridge for surplus on wheels (s+ow)
|
||||
|
||||
s+ow bridges are defined in a file named `$HOME/.s+ow-bridges`. each command in the file is run,
|
||||
and comma-seperated target chat IDs are passed using stdin.
|
||||
|
||||
this bridge recognises targets prefixed with `wa:`.
|
||||
|
||||
```text
|
||||
wa:<chat id>,...
|
||||
```
|
||||
|
||||
## installation
|
||||
|
||||
### from a pre-built binary
|
||||
|
||||
```text
|
||||
wget -O- https://surplus.joshwel.co/whatsapp.sh | sh
|
||||
```
|
||||
|
||||
!!! note
|
||||
if `wget` throws a 404, see [backup links](../links.md)
|
||||
|
||||
### building from source
|
||||
|
||||
#### on Termux
|
||||
|
||||
1. clone the repository at either `https://forge.joshwel.co/mark/surplus` or
|
||||
`https://github.com/markjoshwel/surplus`, and navigate to `src/spow-whatsapp-bridge` within the
|
||||
cloned repository
|
||||
|
||||
```text
|
||||
git clone https://forge.joshwel.co/mark/surplus
|
||||
cd surplus/src/spow-whatsapp-bridge
|
||||
```
|
||||
|
||||
2. build the bridge:
|
||||
|
||||
```text
|
||||
go build
|
||||
```
|
||||
|
||||
for compatibility with the documentations' instructions as-is, rename the built binary to
|
||||
`s+ow-whatsapp-bridge`
|
||||
|
||||
```text
|
||||
mv spow-whatsapp-bridge s+ow-whatsapp-bridge
|
||||
```
|
||||
|
||||
3. send the built binary over to your Termux environment, and then move it into the
|
||||
`$HOME/.local/bin/` folder. if it doesn't exist, make it with `mkdir` and ensure that the folder
|
||||
is in your `PATH` variable either using your `.profile`, `.bashrc` or whatever file is sourced
|
||||
when opening your shell
|
||||
|
||||
#### anywhere else
|
||||
|
||||
for usage on Termux, see if the [Android NDK](https://developer.android.com/ndk/downloads) supports your platform
|
||||
|
||||
1. grab a copy of the NDK, and extract it somewhere. navigate to
|
||||
`<ndk folder>/toolchains/llvm/prebuilt/<your platform>/bin` and look for a suitable `clang`
|
||||
executable, as it will be your CGO compiler
|
||||
|
||||
```
|
||||
m@csp:~/android-ndk-r26d/toolchains/llvm/prebuilt/linux-x86_64/bin$ ls *clang
|
||||
aarch64-linux-android21-clang aarch64-linux-android30-clang ...
|
||||
aarch64-linux-android22-clang aarch64-linux-android31-clang
|
||||
aarch64-linux-android23-clang aarch64-linux-android32-clang
|
||||
aarch64-linux-android24-clang aarch64-linux-android33-clang
|
||||
aarch64-linux-android25-clang aarch64-linux-android34-clang
|
||||
aarch64-linux-android26-clang armv7a-linux-androideabi21-clang
|
||||
aarch64-linux-android27-clang armv7a-linux-androideabi22-clang
|
||||
aarch64-linux-android28-clang armv7a-linux-androideabi23-clang
|
||||
aarch64-linux-android29-clang armv7a-linux-androideabi24-clang
|
||||
```
|
||||
|
||||
the example output is not exhaustive and is cut short for brevity and example, do take a look
|
||||
at your downloaded NDK archive for what executables are available to you
|
||||
|
||||
many executables are present, so choose a) what architecture you will build for (more often
|
||||
than not it's `aarch64`), and b) what target android api are you building for
|
||||
|
||||
if you're building for yourself, pick an api level/version that correlates to your devices'
|
||||
android version. as an example, my device runs on an ARM processor (`aarch64`) and runs Android 14,
|
||||
which is api level 34. (`android34`) as such, i would use the `aarch64-linux-android34-clang`
|
||||
binary
|
||||
|
||||
2. clone the repository at either `https://forge.joshwel.co/mark/surplus` or
|
||||
`https://github.com/markjoshwel/surplus`, and navigate to `src/spow-whatsapp-bridge` within the
|
||||
cloned repository
|
||||
|
||||
```text
|
||||
git clone https://forge.joshwel.co/mark/surplus
|
||||
cd surplus/src/spow-whatsapp-bridge
|
||||
```
|
||||
|
||||
3. build the bridge:
|
||||
|
||||
```text
|
||||
CC="<path to android ndk clang executable>" GOOS=android GOARCH=arm64 CGO_ENABLED=1 go build
|
||||
```
|
||||
|
||||
for compatibility with the documentations' instructions as-is, rename the built binary to
|
||||
`s+ow-whatsapp-bridge`
|
||||
|
||||
```text
|
||||
mv spow-whatsapp-bridge s+ow-whatsapp-bridge
|
||||
```
|
||||
|
||||
4. send the built binary over to your Termux environment, and then move it into the
|
||||
`$HOME/.local/bin/` folder. if it doesn't exist, make it with `mkdir` and ensure that the folder
|
||||
is in your `PATH` variable either using your `.profile`, `.bashrc` or whatever file is sourced
|
||||
when opening your shell
|
||||
|
||||
### post-installation setup
|
||||
|
||||
1. log into WhatsApp:
|
||||
|
||||
```text
|
||||
s+ow-whatsapp-bridge login
|
||||
```
|
||||
|
||||
give it a minute or two to sync your history. once the screen stops scrolling, you can safely
|
||||
exit with Ctrl+D or Ctrl+C.
|
||||
|
||||
2. find out what chats you want the bridge to target:
|
||||
|
||||
```text
|
||||
s+ow-whatsapp-bridge list
|
||||
```
|
||||
|
||||
!!! note
|
||||
for sending to individuals: their IDs are their internationalised phone numbers ending in
|
||||
`@s.whatsapp.net`
|
||||
|
||||
example: `+65 9123 4567` is `6591234567@s.whatsapp.net`
|
||||
|
||||
then, note these down, prefixed with `wa:`, to them to your `SPOW_TARGETS` variable in your
|
||||
s+ow cron job
|
||||
|
||||
3. finally, add the following to your $HOME/.s+ow-bridges file:
|
||||
|
||||
```text
|
||||
s+ow-whatsapp-bridge
|
||||
```
|
||||
|
||||
## updating
|
||||
|
||||
to keep updated as [whatsmeow](https://github.com/tulir/whatsmeow/), the library the bridge depends
|
||||
on, has to keep updated with the WhatsApp web multidevice API, you can either:
|
||||
|
||||
1. [rebuild when a weekly release comes out](#building-from-source),
|
||||
2. [or rely on the weekly continuous deployment builds](#from-a-pre-built-binary)
|
||||
|
||||
to use the weekly builds without building from scratch every time,
|
||||
|
||||
!!! note
|
||||
this will pull the latest binary, around 20 megabytes in size, every day. if your network or
|
||||
data plan may not take kindly to this, feel free to adjust the cron entry as you wish, or to
|
||||
one that runs once a week instead:
|
||||
|
||||
```text
|
||||
0 0 * * 0 bash -l -c "s+ow-whatsapp-bridge-update"
|
||||
```
|
||||
|
||||
## usage
|
||||
|
||||
- `s+ow-whatsapp-bridge`
|
||||
normal usage; sends latest message to wa:-prefixed targets given in stdin
|
||||
|
||||
- `s+ow-whatsapp-bridge login`
|
||||
logs in to WhatsApp
|
||||
|
||||
- `s+ow-whatsapp-bridge pair-phone`
|
||||
logs in to WhatsApp using a phone number
|
||||
|
||||
- `s+ow-whatsapp-bridge reconnect`
|
||||
reconnects the client
|
||||
|
||||
- `s+ow-whatsapp-bridge logout`
|
||||
logs out of WhatsApp
|
||||
|
||||
- `s+ow-whatsapp-bridge list`
|
||||
lists all group chats and their IDs.
|
||||
|
||||
for sending to individuals: their IDs are their internationalised phone numbers ending in
|
||||
`@s.whatsapp.net`
|
||||
|
||||
example: `+65 9123 4567` is `6591234567@s.whatsapp.net`
|
||||
|
||||
## verifying a pre-built binary
|
||||
|
||||
!!! note
|
||||
if you installed the bridge through an installation script, it would have already
|
||||
|
||||
and if the script or `s+ow-whatsapp-bridge-update` throws an error about failing verification,
|
||||
you can use the environment variable ``
|
||||
|
||||
TODO
|
||||
|
||||
## versioning scheme
|
||||
|
||||
from `v2.2024.25`, the bridge is now versioned with a modified calendar versioning scheme of
|
||||
`MAJOR.YEAR.ISOWEEK`, where the `MAJOR` version segment will be bumped with codebase changes, whereas
|
||||
the `YEAR` and `ISOWEEK` segments will represent the time of which the release was built at
|
||||
|
||||
## licence
|
||||
|
||||
the s+ow Telegram Bridge is free and unencumbered software released into the public domain.
|
||||
for more information, see [licences](../licences.md).
|
212
docs/stylesheets/extra.css
Normal file
212
docs/stylesheets/extra.css
Normal file
|
@ -0,0 +1,212 @@
|
|||
@font-face {
|
||||
font-family: "Geist";
|
||||
src: url('../fonts/GeistVF.woff2') format('woff2'),
|
||||
url('../fonts/Geist-Regular.ttf') format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
src: url('../fonts/GeistMonoVF.woff2') format('woff2'),
|
||||
url('../fonts/GeistMono-Regular.ttf') format('truetype');
|
||||
}
|
||||
|
||||
:root {
|
||||
--md-text-font: "Geist";
|
||||
--md-code-font: "Geist Mono";
|
||||
--md-hue: 180deg;
|
||||
}
|
||||
|
||||
* {
|
||||
text-rendering: geometricprecision !important;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] {
|
||||
color-scheme: light;
|
||||
|
||||
--md-sys-color-primary: rgb(51 71 65);
|
||||
--md-sys-color-surface-tint: rgb(78 99 92);
|
||||
--md-sys-color-on-primary: rgb(255 255 255);
|
||||
--md-sys-color-primary-container: rgb(95 116 109);
|
||||
--md-sys-color-on-primary-container: rgb(255 255 255);
|
||||
--md-sys-color-secondary: rgb(61 69 66);
|
||||
--md-sys-color-on-secondary: rgb(255 255 255);
|
||||
--md-sys-color-secondary-container: rgb(110 118 115);
|
||||
--md-sys-color-on-secondary-container: rgb(255 255 255);
|
||||
--md-sys-color-tertiary: rgb(0 73 93);
|
||||
--md-sys-color-on-tertiary: rgb(255 255 255);
|
||||
--md-sys-color-tertiary-container: rgb(65 124 147);
|
||||
--md-sys-color-on-tertiary-container: rgb(255 255 255);
|
||||
--md-sys-color-error: rgb(124 37 0);
|
||||
--md-sys-color-on-error: rgb(255 255 255);
|
||||
--md-sys-color-error-container: rgb(200 77 28);
|
||||
--md-sys-color-on-error-container: rgb(255 255 255);
|
||||
--md-sys-color-background: rgb(251 249 247);
|
||||
--md-sys-color-on-background: rgb(27 28 27);
|
||||
--md-sys-color-surface: rgb(251 249 247);
|
||||
--md-sys-color-on-surface: rgb(27 28 27);
|
||||
--md-sys-color-surface-variant: rgb(222 228 224);
|
||||
--md-sys-color-on-surface-variant: rgb(62 68 66);
|
||||
--md-sys-color-outline: rgb(90 96 94);
|
||||
--md-sys-color-outline-variant: rgb(118 124 121);
|
||||
--md-sys-color-shadow: rgb(0 0 0);
|
||||
--md-sys-color-scrim: rgb(0 0 0);
|
||||
--md-sys-color-inverse-surface: rgb(48 49 48);
|
||||
--md-sys-color-inverse-on-surface: rgb(242 240 239);
|
||||
--md-sys-color-inverse-primary: rgb(181 203 195);
|
||||
--md-sys-color-primary-fixed: rgb(100 121 114);
|
||||
--md-sys-color-on-primary-fixed: rgb(255 255 255);
|
||||
--md-sys-color-primary-fixed-dim: rgb(76 96 90);
|
||||
--md-sys-color-on-primary-fixed-variant: rgb(255 255 255);
|
||||
--md-sys-color-secondary-fixed: rgb(110 118 115);
|
||||
--md-sys-color-on-secondary-fixed: rgb(255 255 255);
|
||||
--md-sys-color-secondary-fixed-dim: rgb(86 94 90);
|
||||
--md-sys-color-on-secondary-fixed-variant: rgb(255 255 255);
|
||||
--md-sys-color-tertiary-fixed: rgb(65 124 147);
|
||||
--md-sys-color-on-tertiary-fixed: rgb(255 255 255);
|
||||
--md-sys-color-tertiary-fixed-dim: rgb(36 99 121);
|
||||
--md-sys-color-on-tertiary-fixed-variant: rgb(255 255 255);
|
||||
--md-sys-color-surface-dim: rgb(219 218 216);
|
||||
--md-sys-color-surface-bright: rgb(251 249 247);
|
||||
--md-sys-color-surface-container-lowest: rgb(255 255 255);
|
||||
--md-sys-color-surface-container-low: rgb(245 243 242);
|
||||
--md-sys-color-surface-container: rgb(239 238 236);
|
||||
--md-sys-color-surface-container-high: rgb(233 232 230);
|
||||
--md-sys-color-surface-container-highest: rgb(228 226 225);
|
||||
|
||||
--md-hue: 139.2deg;
|
||||
--md-default-fg-color: var(--md-sys-color-primary);
|
||||
--md-default-bg-color: var(--md-sys-color-surface);
|
||||
|
||||
/* primary colours */
|
||||
--md-primary-fg-color: var(--md-sys-color-primary);
|
||||
--md-primary-fg-color--light: var(--md-sys-color-inverse-primary);
|
||||
--md-primary-fg-color--dark: var(--md-sys-color-primary-container);
|
||||
--md-primary-bg-color: var(--md-sys-color-surface);
|
||||
--md-primary-bg-color--light: var(--md-sys-color-surface-dim);
|
||||
|
||||
/* accent (interactable) colours */
|
||||
--md-accent-fg-color: var(--md-sys-color-tertiary);
|
||||
--md-accent-bg-color: var(--md-sys-color-on-tertiary);
|
||||
--md-accent-bg-color--light: var(--md-sys-color-surface-dim);
|
||||
|
||||
/* typesetting colours */
|
||||
--md-typeset-color: var(--md-sys-color-on-surface);
|
||||
--md-typeset-a-color: var(--md-sys-color-tertiary);
|
||||
--md-typeset-del-color: var(--md-sys-color-on-error-container);
|
||||
--md-typeset-ins-color: var(--md-sys-color-on-primary-container);
|
||||
--md-typeset-kbd-color: var(--md-sys-color-surface-container-lowest);
|
||||
--md-typeset-kbd-accent-color: var(--md-sys-color-surface-container);
|
||||
--md-typeset-kbd-border-color: var(--md-sys-color-surface-container-highest);
|
||||
--md-typeset-mark-color: var(--md-sys-color-tertiary-container);
|
||||
--md-typeset-table-color: var(--md-sys-color-outline);
|
||||
--md-code-bg-color: var(--md-sys-color-surface-container-high);
|
||||
|
||||
/* admonition colours */
|
||||
--md-admonition-fg-color: var(--md-sys-color-secondary);
|
||||
--md-admonition-bg-color: var(--md-default-bg-color);
|
||||
--md-warning-fg-color: var(--md-sys-color-on-error-container);
|
||||
--md-warning-bg-color: var(--md-sys-color-error-container);
|
||||
|
||||
/* footer colours */
|
||||
--md-footer-fg-color: var(--md-sys-color-on-surface);
|
||||
--md-footer-fg-color--light: var(--md-sys-color-on-surface-variant);
|
||||
--md-footer-fg-color--lighter: var(--md-sys-color-outline);
|
||||
--md-footer-bg-color: var(--md-sys-color-surface-dim);
|
||||
--md-footer-bg-color--dark: var(--md-sys-color-surface-container-highest);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] {
|
||||
color-scheme: dark;
|
||||
|
||||
--md-sys-color-primary: rgb(185 208 199);
|
||||
--md-sys-color-surface-tint: rgb(181 203 195);
|
||||
--md-sys-color-on-primary: rgb(6 26 21);
|
||||
--md-sys-color-primary-container: rgb(128 149 142);
|
||||
--md-sys-color-on-primary-container: rgb(0 0 0);
|
||||
--md-sys-color-secondary: rgb(196 205 200);
|
||||
--md-sys-color-on-secondary: rgb(16 24 21);
|
||||
--md-sys-color-secondary-container: rgb(138 147 143);
|
||||
--md-sys-color-on-secondary-container: rgb(0 0 0);
|
||||
--md-sys-color-tertiary: rgb(153 211 236);
|
||||
--md-sys-color-on-tertiary: rgb(0 25 34);
|
||||
--md-sys-color-tertiary-container: rgb(99 157 181);
|
||||
--md-sys-color-on-tertiary-container: rgb(0 0 0);
|
||||
--md-sys-color-error: rgb(255 187 164);
|
||||
--md-sys-color-on-error: rgb(48 9 0);
|
||||
--md-sys-color-error-container: rgb(237 104 54);
|
||||
--md-sys-color-on-error-container: rgb(0 0 0);
|
||||
--md-sys-color-background: rgb(19 20 19);
|
||||
--md-sys-color-on-background: rgb(228 226 225);
|
||||
--md-sys-color-surface: rgb(19 20 19);
|
||||
--md-sys-color-on-surface: rgb(252 250 249);
|
||||
--md-sys-color-surface-variant: rgb(66 72 70);
|
||||
--md-sys-color-on-surface-variant: rgb(198 204 200);
|
||||
--md-sys-color-outline: rgb(158 164 161);
|
||||
--md-sys-color-outline-variant: rgb(126 132 129);
|
||||
--md-sys-color-shadow: rgb(0 0 0);
|
||||
--md-sys-color-scrim: rgb(0 0 0);
|
||||
--md-sys-color-inverse-surface: rgb(228 226 225);
|
||||
--md-sys-color-inverse-on-surface: rgb(41 42 41);
|
||||
--md-sys-color-inverse-primary: rgb(56 76 70);
|
||||
--md-sys-color-primary-fixed: rgb(209 232 223);
|
||||
--md-sys-color-on-primary-fixed: rgb(2 20 16);
|
||||
--md-sys-color-primary-fixed-dim: rgb(181 203 195);
|
||||
--md-sys-color-on-primary-fixed-variant: rgb(38 58 52);
|
||||
--md-sys-color-secondary-fixed: rgb(220 228 224);
|
||||
--md-sys-color-on-secondary-fixed: rgb(11 19 16);
|
||||
--md-sys-color-secondary-fixed-dim: rgb(192 200 196);
|
||||
--md-sys-color-on-secondary-fixed-variant: rgb(48 56 53);
|
||||
--md-sys-color-tertiary-fixed: rgb(186 234 255);
|
||||
--md-sys-color-on-tertiary-fixed: rgb(0 20 27);
|
||||
--md-sys-color-tertiary-fixed-dim: rgb(149 207 232);
|
||||
--md-sys-color-on-tertiary-fixed-variant: rgb(0 59 76);
|
||||
--md-sys-color-surface-dim: rgb(19 20 19);
|
||||
--md-sys-color-surface-bright: rgb(57 57 56);
|
||||
--md-sys-color-surface-container-lowest: rgb(13 14 14);
|
||||
--md-sys-color-surface-container-low: rgb(27 28 27);
|
||||
--md-sys-color-surface-container: rgb(31 32 31);
|
||||
--md-sys-color-surface-container-high: rgb(41 42 41);
|
||||
--md-sys-color-surface-container-highest: rgb(52 53 52);
|
||||
|
||||
/*--md-hue: 139.2deg;*/
|
||||
--md-default-fg-color: var(--md-sys-color-primary);
|
||||
--md-default-bg-color: var(--md-sys-color-surface);
|
||||
|
||||
/* primary colours */
|
||||
--md-primary-fg-color: var(--md-sys-color-primary);
|
||||
--md-primary-fg-color--light: var(--md-sys-color-inverse-primary);
|
||||
--md-primary-fg-color--dark: var(--md-sys-color-primary-container);
|
||||
--md-primary-bg-color: var(--md-sys-color-surface);
|
||||
--md-primary-bg-color--light: var(--md-sys-color-surface-dim);
|
||||
|
||||
/* accent (interactable) colours */
|
||||
--md-accent-fg-color: var(--md-sys-color-tertiary);
|
||||
--md-accent-bg-color: var(--md-sys-color-on-tertiary);
|
||||
--md-accent-bg-color--light: var(--md-sys-color-surface-dim);
|
||||
|
||||
/* typesetting colours */
|
||||
--md-typeset-color: var(--md-sys-color-on-surface);
|
||||
--md-typeset-a-color: var(--md-sys-color-tertiary);
|
||||
--md-typeset-del-color: var(--md-sys-color-on-error-container);
|
||||
--md-typeset-ins-color: var(--md-sys-color-on-primary-container);
|
||||
--md-typeset-kbd-color: var(--md-sys-color-surface-container-lowest);
|
||||
--md-typeset-kbd-accent-color: var(--md-sys-color-surface-container);
|
||||
--md-typeset-kbd-border-color: var(--md-sys-color-surface-container-highest);
|
||||
--md-typeset-mark-color: var(--md-sys-color-tertiary-container);
|
||||
--md-typeset-table-color: var(--md-sys-color-outline);
|
||||
--md-typeset-table-color--light: var(--md-sys-color-outline-variant);
|
||||
--md-code-bg-color: var(--md-sys-color-surface-container-high);
|
||||
|
||||
/* admonition colours */
|
||||
--md-admonition-fg-color: var(--md-sys-color-secondary);
|
||||
--md-admonition-bg-color: var(--md-default-bg-color);
|
||||
--md-warning-fg-color: var(--md-sys-color-on-error-container);
|
||||
--md-warning-bg-color: var(--md-sys-color-error-container);
|
||||
|
||||
/* footer colours */
|
||||
--md-footer-fg-color: var(--md-sys-color-on-surface);
|
||||
--md-footer-fg-color--light: var(--md-sys-color-on-surface-variant);
|
||||
--md-footer-fg-color--lighter: var(--md-sys-color-outline);
|
||||
--md-footer-bg-color: var(--md-sys-color-surface-dim);
|
||||
--md-footer-bg-color--dark: var(--md-sys-color-surface-container-highest);
|
||||
}
|
4
docs/stylesheets/pdf.scss
Normal file
4
docs/stylesheets/pdf.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
@page {
|
||||
size: A4;
|
||||
margin: 1.25cm;
|
||||
}
|
11
docs/using.md
Normal file
11
docs/using.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# the user's handbook
|
||||
|
||||
TODO
|
||||
|
||||
## as a command line tool
|
||||
|
||||
TODO
|
||||
|
||||
## as a python library
|
||||
|
||||
TODO
|
78
flake.lock
generated
Normal file
78
flake.lock
generated
Normal file
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1718318537,
|
||||
"narHash": "sha256-4Zu0RYRcAY/VWuu6awwq4opuiD//ahpc2aFHg2CWqFY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e9ee548d90ff586a6471b4ae80ae9cfcbceb3420",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-old-hatch": {
|
||||
"locked": {
|
||||
"lastModified": 1702508050,
|
||||
"narHash": "sha256-imn+/Rj+bqagOSm7GmRDbrqkxtc7QnjY3Cu85gv46BU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "fd04bea4cbf76f86f244b9e2549fca066db8ddff",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "fd04bea4cbf76f86f244b9e2549fca066db8ddff",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-old-hatch": "nixpkgs-old-hatch"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
45
flake.nix
Normal file
45
flake.nix
Normal file
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
description = "development environment for surplus";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
# https://github.com/NixOS/nixpkgs/issues/308121
|
||||
nixpkgs-old-hatch.url = "github:NixOS/nixpkgs/fd04bea4cbf76f86f244b9e2549fca066db8ddff";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, nixpkgs-old-hatch, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
pkgs-old-hatch = nixpkgs-old-hatch.legacyPackages.${system};
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShellNoCC {
|
||||
NIX_LD_LIBRARY_PATH = lib.makeLibraryPath [
|
||||
pkgs.stdenv.cc.cc
|
||||
];
|
||||
NIX_LD = lib.fileContents "${pkgs.stdenv.cc}/nix-support/dynamic-linker";
|
||||
buildInputs =
|
||||
[
|
||||
# surplus
|
||||
pkgs.python3
|
||||
pkgs-old-hatch.hatch
|
||||
|
||||
# mkdocs-exporter
|
||||
pkgs.stdenv.cc.cc.lib
|
||||
pkgs.playwright
|
||||
];
|
||||
shellHook = ''
|
||||
export LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH
|
||||
export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers}
|
||||
export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true
|
||||
echo LD_LIBRARY_PATH=$LD_LIBRARY_PATH
|
||||
echo PLAYWRIGHT_BROWSERS_PATH=$PLAYWRIGHT_BROWSERS_PATH
|
||||
echo PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=$PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
114
mkdocs.yml
Normal file
114
mkdocs.yml
Normal file
|
@ -0,0 +1,114 @@
|
|||
site_name: surplus Documentation
|
||||
site_url: https://surplus.joshwel.co
|
||||
site_author: Mark Joshwel and surplus contributors
|
||||
site_description: documentation for the surplus and sibling projects
|
||||
|
||||
repo_name: markjoshwel/surplus
|
||||
repo_url: https://github.com/markjoshwel/surplus
|
||||
|
||||
copyright: |
|
||||
with with all our hearts, 2023-2024, mark joshwel and contributors<br>
|
||||
documentation is dedicated to the public domain with <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0</a>
|
||||
|
||||
nav:
|
||||
- about:
|
||||
- surplus: "index.md"
|
||||
- licences: "licences.md"
|
||||
- changelog: "changelog.md"
|
||||
- handbooks:
|
||||
- "using.md"
|
||||
- "developing.md"
|
||||
- "contributing.md"
|
||||
- on wheels:
|
||||
- "onwheels/index.md"
|
||||
- bridges:
|
||||
- about bridges: "onwheels/bridges.md"
|
||||
- "onwheels/telegram-bridge.md"
|
||||
- "onwheels/whatsapp-bridge.md"
|
||||
- "onwheels/emulating-termux-location.md"
|
||||
- "onwheels/emulating-termux-notification.md"
|
||||
- backup links:
|
||||
"links.md"
|
||||
|
||||
theme:
|
||||
name: material
|
||||
language: en
|
||||
|
||||
features:
|
||||
- navigation.tabs
|
||||
- navigation.tabs.sticky
|
||||
- navigation.tracking
|
||||
- navigation.expand
|
||||
- toc.integrate
|
||||
- search.suggest
|
||||
- search.highlight
|
||||
- content.tabs.link
|
||||
- content.code.annotation
|
||||
- content.code.copy
|
||||
- pymdownx.snippets
|
||||
|
||||
font: false
|
||||
|
||||
palette:
|
||||
- media: "(prefers-color-scheme)"
|
||||
toggle:
|
||||
icon: material/brightness-auto
|
||||
name: Light Theme
|
||||
primary: custom
|
||||
accent: custom
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
toggle:
|
||||
icon: material/brightness-7
|
||||
name: Dark Theme
|
||||
primary: custom
|
||||
accent: custom
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: System Theme
|
||||
primary: custom
|
||||
accent: custom
|
||||
|
||||
icon:
|
||||
admonition:
|
||||
abstract: material/text-box-outline
|
||||
tip: material/pencil-outline
|
||||
note: material/information-slab-box-outline
|
||||
warning: material/alert-outline
|
||||
danger: material/alert-octagon-outline
|
||||
|
||||
|
||||
extra_css:
|
||||
- stylesheets/extra.css
|
||||
|
||||
plugins:
|
||||
- search
|
||||
- privacy
|
||||
#- git-revision-date-localized:
|
||||
# enable_creation_date: true
|
||||
- exporter:
|
||||
formats:
|
||||
pdf:
|
||||
enabled: !ENV [MKDOCS_EXPORTER_PDF_ENABLED, true]
|
||||
stylesheets:
|
||||
- docs/stylesheets/pdf.scss
|
||||
aggregator:
|
||||
enabled: true
|
||||
output: documentation.pdf
|
||||
buttons:
|
||||
- title: Download as PDF
|
||||
icon: material-file-download-outline
|
||||
enabled: !!python/name:mkdocs_exporter.formats.pdf.buttons.download.enabled
|
||||
attributes: !!python/name:mkdocs_exporter.formats.pdf.buttons.download.attributes
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
line_spans: __span
|
||||
pygments_lang_class: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.snippets
|
||||
- pymdownx.superfences
|
273
poetry.lock
generated
273
poetry.lock
generated
|
@ -1,273 +0,0 @@
|
|||
# This file is automatically @generated by Poetry and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "23.3.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"},
|
||||
{file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"},
|
||||
{file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"},
|
||||
{file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"},
|
||||
{file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"},
|
||||
{file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"},
|
||||
{file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"},
|
||||
{file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"},
|
||||
{file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"},
|
||||
{file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"},
|
||||
{file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"},
|
||||
{file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"},
|
||||
{file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"},
|
||||
{file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"},
|
||||
{file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"},
|
||||
{file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"},
|
||||
{file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"},
|
||||
{file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"},
|
||||
{file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"},
|
||||
{file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"},
|
||||
{file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"},
|
||||
{file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"},
|
||||
{file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"},
|
||||
{file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"},
|
||||
{file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
packaging = ">=22.0"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.7.4)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "geographiclib"
|
||||
version = "2.0"
|
||||
description = "The geodesic routines from GeographicLib"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "geographiclib-2.0-py3-none-any.whl", hash = "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734"},
|
||||
{file = "geographiclib-2.0.tar.gz", hash = "sha256:f7f41c85dc3e1c2d3d935ec86660dc3b2c848c83e17f9a9e51ba9d5146a15859"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "geopy"
|
||||
version = "2.3.0"
|
||||
description = "Python Geocoding Toolbox"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "geopy-2.3.0-py3-none-any.whl", hash = "sha256:4a29a16d41d8e56ba8e07310802a1cbdf098eeb6069cc3d6d3068fc770629ffc"},
|
||||
{file = "geopy-2.3.0.tar.gz", hash = "sha256:228cd53b6eef699b2289d1172e462a90d5057779a10388a7366291812601187f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
geographiclib = ">=1.52,<3"
|
||||
|
||||
[package.extras]
|
||||
aiohttp = ["aiohttp"]
|
||||
dev = ["coverage", "flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"]
|
||||
dev-docs = ["readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"]
|
||||
dev-lint = ["flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)"]
|
||||
dev-test = ["coverage", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "sphinx (<=4.3.2)"]
|
||||
requests = ["requests (>=2.16.2)", "urllib3 (>=1.24.2)"]
|
||||
timezone = ["pytz"]
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.12.0"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"},
|
||||
{file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.3)"]
|
||||
pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
|
||||
plugins = ["setuptools"]
|
||||
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.3.0"
|
||||
description = "Optional static typing for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "mypy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eb485cea53f4f5284e5baf92902cd0088b24984f4209e25981cc359d64448d"},
|
||||
{file = "mypy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4c99c3ecf223cf2952638da9cd82793d8f3c0c5fa8b6ae2b2d9ed1e1ff51ba85"},
|
||||
{file = "mypy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:550a8b3a19bb6589679a7c3c31f64312e7ff482a816c96e0cecec9ad3a7564dd"},
|
||||
{file = "mypy-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cbc07246253b9e3d7d74c9ff948cd0fd7a71afcc2b77c7f0a59c26e9395cb152"},
|
||||
{file = "mypy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:a22435632710a4fcf8acf86cbd0d69f68ac389a3892cb23fbad176d1cddaf228"},
|
||||
{file = "mypy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e33bb8b2613614a33dff70565f4c803f889ebd2f859466e42b46e1df76018dd"},
|
||||
{file = "mypy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7d23370d2a6b7a71dc65d1266f9a34e4cde9e8e21511322415db4b26f46f6b8c"},
|
||||
{file = "mypy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:658fe7b674769a0770d4b26cb4d6f005e88a442fe82446f020be8e5f5efb2fae"},
|
||||
{file = "mypy-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d29e324cdda61daaec2336c42512e59c7c375340bd202efa1fe0f7b8f8ca"},
|
||||
{file = "mypy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:d0b6c62206e04061e27009481cb0ec966f7d6172b5b936f3ead3d74f29fe3dcf"},
|
||||
{file = "mypy-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:76ec771e2342f1b558c36d49900dfe81d140361dd0d2df6cd71b3db1be155409"},
|
||||
{file = "mypy-1.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc95f8386314272bbc817026f8ce8f4f0d2ef7ae44f947c4664efac9adec929"},
|
||||
{file = "mypy-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:faff86aa10c1aa4a10e1a301de160f3d8fc8703b88c7e98de46b531ff1276a9a"},
|
||||
{file = "mypy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8c5979d0deb27e0f4479bee18ea0f83732a893e81b78e62e2dda3e7e518c92ee"},
|
||||
{file = "mypy-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c5d2cc54175bab47011b09688b418db71403aefad07cbcd62d44010543fc143f"},
|
||||
{file = "mypy-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:87df44954c31d86df96c8bd6e80dfcd773473e877ac6176a8e29898bfb3501cb"},
|
||||
{file = "mypy-1.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:473117e310febe632ddf10e745a355714e771ffe534f06db40702775056614c4"},
|
||||
{file = "mypy-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74bc9b6e0e79808bf8678d7678b2ae3736ea72d56eede3820bd3849823e7f305"},
|
||||
{file = "mypy-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:44797d031a41516fcf5cbfa652265bb994e53e51994c1bd649ffcd0c3a7eccbf"},
|
||||
{file = "mypy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ddae0f39ca146972ff6bb4399f3b2943884a774b8771ea0a8f50e971f5ea5ba8"},
|
||||
{file = "mypy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c4c42c60a8103ead4c1c060ac3cdd3ff01e18fddce6f1016e08939647a0e703"},
|
||||
{file = "mypy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e86c2c6852f62f8f2b24cb7a613ebe8e0c7dc1402c61d36a609174f63e0ff017"},
|
||||
{file = "mypy-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f9dca1e257d4cc129517779226753dbefb4f2266c4eaad610fc15c6a7e14283e"},
|
||||
{file = "mypy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:95d8d31a7713510685b05fbb18d6ac287a56c8f6554d88c19e73f724a445448a"},
|
||||
{file = "mypy-1.3.0-py3-none-any.whl", hash = "sha256:a8763e72d5d9574d45ce5881962bc8e9046bf7b375b0abf031f3e6811732a897"},
|
||||
{file = "mypy-1.3.0.tar.gz", hash = "sha256:e1f4d16e296f5135624b34e8fb741eb0eadedca90862405b1f1fde2040b9bd11"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=1.0.0"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = ">=3.10"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
install-types = ["pip"]
|
||||
python2 = ["typed-ast (>=1.4.0,<2)"]
|
||||
reports = ["lxml"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.0.0"
|
||||
description = "Type system extensions for programs checked with the mypy type checker."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "23.1"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
|
||||
{file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.11.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
|
||||
{file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "3.5.1"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "platformdirs-3.5.1-py3-none-any.whl", hash = "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5"},
|
||||
{file = "platformdirs-3.5.1.tar.gz", hash = "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.2.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluscodes"
|
||||
version = "2022.1.3"
|
||||
description = "Compute Plus Codes (Open Location Codes)."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "pluscodes-2022.1.3-py3-none-any.whl", hash = "sha256:50625f472f8d4e8822e005180c2eb41bf09e45e429f362d3cded346f1169dae8"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["black (==22.3.0)", "build (==0.8.0)", "coverage (==6.4)", "isort (==5.8.0)", "pylintv (==2.15.0)", "pytest (==7.1.1)", "pytest-cov (==3.0.0)", "twine (==4.0.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.6.3"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"},
|
||||
{file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "9837c2f9210fa01e5287a85b36c2c95030cc6be0e4ecffa7fc2a5285970b41d3"
|
121
pyproject.toml
121
pyproject.toml
|
@ -1,26 +1,97 @@
|
|||
[tool.poetry]
|
||||
name = "surplus"
|
||||
version = "1.1.3"
|
||||
description = "Plus Code to iOS-Shortcuts-like shareable text"
|
||||
authors = ["Mark Joshwel <mark@joshwel.co>"]
|
||||
license = "Unlicence"
|
||||
readme = "README.md"
|
||||
include = ["surplus.py"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
pluscodes = "^2022.1.3"
|
||||
geopy = "^2.3.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^23.3.0"
|
||||
mypy = "^1.3.0"
|
||||
isort = "^5.12.0"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
surplus = 'surplus:cli'
|
||||
"s+" = 'surplus:cli'
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "surplus"
|
||||
dynamic = ["version"]
|
||||
description = 'convert Plus Codes, coordinates or location strings to shareable text'
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
license = "Unlicense"
|
||||
keywords = ["pluscodes", "openlocationcode"]
|
||||
authors = [
|
||||
{ name = "Mark Joshwel", email = "mark@joshwel.co" },
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 6 - Mature",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
dependencies = [
|
||||
"pluscodes~=2022.1.3",
|
||||
"geopy~=2.4.1",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
surplus = "surplus:cli"
|
||||
"s+" = "surplus:cli"
|
||||
|
||||
[tool.hatch.build.targets.sdist]
|
||||
exclude = [
|
||||
"/.github",
|
||||
"/.devbox",
|
||||
"/src/surplus-on-wheels",
|
||||
"/src/spow*",
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src.surplus"]
|
||||
|
||||
[project.urls]
|
||||
Documentation = "https://surplus.joshwel.co"
|
||||
Issues = "https://surplus.joshwel.co/issues"
|
||||
Source = "https://github.com/markjoshwel/surplus"
|
||||
Changelog = "https://surplus.joshwel.co/changelog"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py311"
|
||||
|
||||
[tool.isort]
|
||||
line_length = 100
|
||||
profile = "black"
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "src/surplus/surplus.py"
|
||||
|
||||
[[tool.hatch.envs.all.matrix]]
|
||||
python = ["3.11", "3.12"]
|
||||
|
||||
[tool.hatch.envs.default]
|
||||
description = "default development environment"
|
||||
dependencies = ["mypy", "ruff", "isort"]
|
||||
|
||||
[tool.hatch.envs.default.scripts]
|
||||
check = [
|
||||
"mypy src",
|
||||
"hatch fmt --check",
|
||||
"isort --check src"
|
||||
]
|
||||
format = [
|
||||
"hatch fmt -f",
|
||||
"isort src"
|
||||
]
|
||||
|
||||
[tool.hatch.envs.hatch-static-analysis]
|
||||
dependencies = ["ruff>=0.3.2"]
|
||||
|
||||
[tool.hatch.envs.docs]
|
||||
detached = true
|
||||
description = "env for generator documentation"
|
||||
dependencies = [
|
||||
"mkdocs",
|
||||
"mkdocs-material",
|
||||
"mkdocs-git-revision-date-localized-plugin",
|
||||
"mkdocs-exporter",
|
||||
"playwright",
|
||||
]
|
||||
[tool.hatch.envs.docs.scripts]
|
||||
build = [
|
||||
"python src/tools/docs-prebuild.py",
|
||||
"mkdocs build --clean --strict",
|
||||
]
|
||||
serve = "mkdocs serve --dev-addr localhost:8000"
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
geographiclib==2.0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734 \
|
||||
--hash=sha256:f7f41c85dc3e1c2d3d935ec86660dc3b2c848c83e17f9a9e51ba9d5146a15859
|
||||
geopy==2.3.0 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:228cd53b6eef699b2289d1172e462a90d5057779a10388a7366291812601187f \
|
||||
--hash=sha256:4a29a16d41d8e56ba8e07310802a1cbdf098eeb6069cc3d6d3068fc770629ffc
|
||||
pluscodes==2022.1.3 ; python_version >= "3.10" and python_version < "4.0" \
|
||||
--hash=sha256:50625f472f8d4e8822e005180c2eb41bf09e45e429f362d3cded346f1169dae8
|
51
src/spow-telegram-bridge/.gitignore
vendored
Normal file
51
src/spow-telegram-bridge/.gitignore
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
.helper
|
||||
*.session
|
||||
*.session-journal
|
||||
|
||||
# cached files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# distribution
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
*.so
|
||||
MANIFEST
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# venv
|
||||
.python-version
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# nix
|
||||
.devbox
|
||||
result
|
5
src/spow-telegram-bridge/README.md
Normal file
5
src/spow-telegram-bridge/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# surplus on wheels: Telegram Bridge
|
||||
|
||||
see <https://surplus.joshwel.co/onwheels/telegram-bridge>
|
||||
or [/docs/onwheels/telegram-bridge.md](../../docs/onwheels/telegram-bridge.md)
|
||||
for more information and documentation
|
24
src/spow-telegram-bridge/UNLICENCE
Normal file
24
src/spow-telegram-bridge/UNLICENCE
Normal file
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
251
src/spow-telegram-bridge/bridge.py
Normal file
251
src/spow-telegram-bridge/bridge.py
Normal file
|
@ -0,0 +1,251 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
s+ow-telegram-bridge: add-on bridge for surplus on wheels (s+ow) to telegram
|
||||
----------------------------------------------------------------------------
|
||||
by mark <mark@joshwel.co>
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from os import environ
|
||||
from pathlib import Path
|
||||
from sys import argv, stderr, stdin
|
||||
from traceback import print_tb
|
||||
from typing import Final
|
||||
|
||||
from telethon import TelegramClient # type: ignore
|
||||
|
||||
# exit codes:
|
||||
# 1 - bad command usage or missing env vars
|
||||
# 2 - bad target
|
||||
# 3 - could not send message
|
||||
|
||||
# rundown:
|
||||
# 1. if argv[-1] is 'login', then run login() and exit
|
||||
# 2. read stdin and comma split it
|
||||
# 3. read ~/.cache/s+ow/message
|
||||
# 4. for each target in comma split stdin that starts with "tg:",
|
||||
# send ~/.cache/s+ow/message
|
||||
|
||||
|
||||
SESSION_NAME: Final[str] = "spowtg"
|
||||
|
||||
dir_data: Path = Path.home().joinpath(".local/share/s+ow-telegram-bridge")
|
||||
dir_data.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
dir_cache: Path = Path.home().joinpath(".cache/s+ow-telegram-bridge")
|
||||
dir_cache.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
api_id: str | None = environ.get("SPOW_TELEGRAM_API_ID", None)
|
||||
api_hash: str | None = environ.get("SPOW_TELEGRAM_API_HASH", None)
|
||||
message_file: Path = Path.home().joinpath(".cache/s+ow/message")
|
||||
session_file: Path = dir_data.joinpath(f"{SESSION_NAME}.session")
|
||||
|
||||
|
||||
def handle_error(
|
||||
exc: Exception | None = None,
|
||||
err_message: str = "error",
|
||||
recoverable: bool = False,
|
||||
exit_code: int = -1,
|
||||
) -> None:
|
||||
try:
|
||||
exc_details: str = ""
|
||||
if isinstance(exc, Exception):
|
||||
exc_details = f": {exc} ({exc.__class__.__name__})"
|
||||
print_tb(exc.__traceback__, file=stderr)
|
||||
|
||||
print(
|
||||
f"s+ow-telegram-bridge: {err_message}{exc_details}",
|
||||
file=stderr,
|
||||
)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not recoverable:
|
||||
exit(exit_code)
|
||||
|
||||
|
||||
def validate_vars() -> None:
|
||||
if api_id is None:
|
||||
print("s+ow-telegram-bridge: error: SPOW_TELEGRAM_API_ID not set", file=stderr)
|
||||
exit(1)
|
||||
|
||||
if api_hash is None:
|
||||
print("s+ow-telegram-bridge: error: SPOW_TELEGRAM_API_HASH not set", file=stderr)
|
||||
exit(1)
|
||||
|
||||
|
||||
async def run() -> None:
|
||||
silent: bool = "--silent" in argv
|
||||
delete_last: bool = "--delete-last" in argv
|
||||
|
||||
if silent:
|
||||
print("s+ow-telegram-bridge: info: --silent passed", file=stderr)
|
||||
|
||||
if delete_last:
|
||||
print("s+ow-telegram-bridge: info: --delete-last passed", file=stderr)
|
||||
|
||||
targets: list[int] = []
|
||||
|
||||
# "spec" point 2:
|
||||
# reads in SPOW_TARGETS given by surplus to the bridge using stdin
|
||||
# "spec" point 2(a):
|
||||
# bridges do not need to account for the possibility of multiple lines sent to stdin
|
||||
# this bridge doesn't do this because it's simpler to iterate through stdin with a 'for'
|
||||
# loop in python
|
||||
for line in stdin:
|
||||
for _target in line.split(","):
|
||||
# "spec" point 2(b):
|
||||
# bridges should account for the possibility of comma and space delimited targets
|
||||
_target = _target.strip()
|
||||
|
||||
# "spec" point 2(c):
|
||||
# bridges should recognise a platform based on a prefix
|
||||
if _target.startswith("tg:"):
|
||||
_target = _target[3:]
|
||||
if not (
|
||||
_target.isnumeric()
|
||||
or (_target.startswith("-") and _target.lstrip("-").isnumeric())
|
||||
):
|
||||
continue
|
||||
|
||||
try:
|
||||
targets.append(int(_target))
|
||||
|
||||
except Exception as exc:
|
||||
handle_error(
|
||||
exc=exc,
|
||||
err_message=f"error: could not cast '{_target}' as int",
|
||||
recoverable=True,
|
||||
exit_code=2,
|
||||
)
|
||||
continue
|
||||
|
||||
# "spec" point 3:
|
||||
# reads SPOW_MESSAGE (~/.cache/spow/message) for the message content
|
||||
if not (message_file.exists() and message_file.is_file()):
|
||||
print("s+ow-telegram-bridge: error: ~/.cache/s+ow/message not found", file=stderr)
|
||||
exit(1)
|
||||
message = message_file.read_text(encoding="utf-8")
|
||||
|
||||
async with TelegramClient(session_file, api_id, api_hash) as client:
|
||||
for target in targets:
|
||||
try:
|
||||
if delete_last is False:
|
||||
await client.send_message(
|
||||
int(target),
|
||||
message,
|
||||
silent=silent,
|
||||
)
|
||||
|
||||
else:
|
||||
target_persist: Path = dir_cache.joinpath(str(target))
|
||||
|
||||
try:
|
||||
# delete old message if persist file exists
|
||||
if target_persist.exists() and target_persist.is_file():
|
||||
await client.delete_messages(
|
||||
entity=target,
|
||||
message_ids=[int(target_persist.read_text(encoding="utf-8"))],
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
handle_error(
|
||||
exc=exc,
|
||||
err_message="error: could not delete old message",
|
||||
recoverable=True,
|
||||
exit_code=3,
|
||||
)
|
||||
continue
|
||||
|
||||
# send new message
|
||||
target_sent_message = await client.send_message(
|
||||
target,
|
||||
message,
|
||||
silent=silent,
|
||||
)
|
||||
|
||||
# persist new message id
|
||||
target_persist.write_text(str(target_sent_message.id), encoding="utf-8")
|
||||
|
||||
except Exception as exc:
|
||||
handle_error(
|
||||
exc=exc,
|
||||
err_message="error: could not send message",
|
||||
recoverable=True,
|
||||
exit_code=3,
|
||||
)
|
||||
continue
|
||||
|
||||
print("s+ow-telegram-bridge: success: message sent to", target)
|
||||
exit()
|
||||
|
||||
|
||||
def login() -> None:
|
||||
with TelegramClient(session_file, api_id, api_hash) as client:
|
||||
client.start()
|
||||
exit()
|
||||
|
||||
|
||||
def logout() -> None:
|
||||
if session_file.exists():
|
||||
session_file.unlink()
|
||||
print("s+ow-telegram-bridge: logged out successfully", file=stderr)
|
||||
else:
|
||||
print("s+ow-telegram-bridge: already logged out", file=stderr)
|
||||
|
||||
|
||||
def list_chats() -> None:
|
||||
with TelegramClient(session_file, api_id, api_hash) as client:
|
||||
for dialog in client.iter_dialogs():
|
||||
print(dialog.id, "\t", dialog.name)
|
||||
exit()
|
||||
|
||||
|
||||
def entry() -> None:
|
||||
if len(argv) < 1:
|
||||
print("s+ow-telegram-bridge: error: len(argv) < 1", file=stderr)
|
||||
exit(1)
|
||||
|
||||
if "login" in argv:
|
||||
validate_vars()
|
||||
login()
|
||||
|
||||
elif "logout" in argv:
|
||||
logout()
|
||||
|
||||
elif "list" in argv:
|
||||
validate_vars()
|
||||
list_chats()
|
||||
|
||||
else:
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
entry()
|
21
src/spow-telegram-bridge/check.sh
Normal file
21
src/spow-telegram-bridge/check.sh
Normal file
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
failures=0
|
||||
|
||||
mypy bridge.py
|
||||
failures=$((failures + $?))
|
||||
|
||||
ruff check bridge.py
|
||||
failures=$((failures + $?))
|
||||
|
||||
ruff format bridge.py
|
||||
failures=$((failures + $?))
|
||||
|
||||
isort --check bridge.py
|
||||
failures=$((failures + $?))
|
||||
|
||||
if [ $failures -eq 0 ]; then
|
||||
printf "\n\nall checks okay! (❁´◡\`❁)\n"
|
||||
else
|
||||
printf "\n\nsome checks failed...\n"
|
||||
fi
|
||||
exit $failures
|
175
src/spow-telegram-bridge/flake.lock
generated
Normal file
175
src/spow-telegram-bridge/flake.lock
generated
Normal file
|
@ -0,0 +1,175 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-github-actions": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1703863825,
|
||||
"narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "5163432afc817cf8bd1f031418d1869e4c9d5547",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1718530797,
|
||||
"narHash": "sha256-pup6cYwtgvzDpvpSCFh1TEUjw2zkNpk8iolbKnyFmmU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b60ebf54c15553b393d144357375ea956f89e9a9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": "systems_3",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1718656656,
|
||||
"narHash": "sha256-/8pXTFOfb7+KrFi+g8G/dFehDkc96/O5eL8L+FjzG1w=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "2c6d07717af20e45fa5b2c823729126be91a3cdf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"poetry2nix": "poetry2nix"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_3": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "systems",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1718522839,
|
||||
"narHash": "sha256-ULzoKzEaBOiLRtjeY3YoGFJMwWSKRYOic6VNw2UyTls=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "68eb1dc333ce82d0ab0c0357363ea17c31ea1f81",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
35
src/spow-telegram-bridge/flake.nix
Normal file
35
src/spow-telegram-bridge/flake.nix
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
description = "development environment for surplus on wheels: Telegram Bridge";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
poetry2nix = {
|
||||
url = "github:nix-community/poetry2nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, poetry2nix }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
inherit (poetry2nix.lib.mkPoetry2Nix { inherit pkgs; }) mkPoetryEnv;
|
||||
|
||||
poetryEnv = mkPoetryEnv {
|
||||
python = pkgs.python311;
|
||||
projectDir = self;
|
||||
preferWheels = true;
|
||||
};
|
||||
in
|
||||
{
|
||||
# nix develop
|
||||
devShells.default = pkgs.mkShellNoCC {
|
||||
packages = [
|
||||
pkgs.poetry
|
||||
poetryEnv
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
5
src/spow-telegram-bridge/install.sh
Normal file
5
src/spow-telegram-bridge/install.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
# surplus on wheels: Telegram Bridge: installation and updater script
|
||||
set -e
|
||||
|
||||
echo TODO
|
167
src/spow-telegram-bridge/poetry.lock
generated
Normal file
167
src/spow-telegram-bridge/poetry.lock
generated
Normal file
|
@ -0,0 +1,167 @@
|
|||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.13.2"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
|
||||
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.10.1"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"},
|
||||
{file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"},
|
||||
{file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"},
|
||||
{file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"},
|
||||
{file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"},
|
||||
{file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"},
|
||||
{file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=1.0.0"
|
||||
typing-extensions = ">=4.1.0"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
install-types = ["pip"]
|
||||
mypyc = ["setuptools (>=50)"]
|
||||
reports = ["lxml"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.0.0"
|
||||
description = "Type system extensions for programs checked with the mypy type checker."
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyaes"
|
||||
version = "1.6.1"
|
||||
description = "Pure-Python Implementation of the AES block-cipher and common modes of operation"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pyaes-1.6.1.tar.gz", hash = "sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.0"
|
||||
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"},
|
||||
{file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "4.9"
|
||||
description = "Pure-Python RSA implementation"
|
||||
optional = false
|
||||
python-versions = ">=3.6,<4"
|
||||
files = [
|
||||
{file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
|
||||
{file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyasn1 = ">=0.1.3"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.5.2"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.5.2-py3-none-linux_armv6l.whl", hash = "sha256:7bab8345df60f9368d5f4594bfb8b71157496b44c30ff035d1d01972e764d3be"},
|
||||
{file = "ruff-0.5.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1aa7acad382ada0189dbe76095cf0a36cd0036779607c397ffdea16517f535b1"},
|
||||
{file = "ruff-0.5.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:aec618d5a0cdba5592c60c2dee7d9c865180627f1a4a691257dea14ac1aa264d"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b62adc5ce81780ff04077e88bac0986363e4a3260ad3ef11ae9c14aa0e67ef"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dc42ebf56ede83cb080a50eba35a06e636775649a1ffd03dc986533f878702a3"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c15c6e9f88c67ffa442681365d11df38afb11059fc44238e71a9d9f1fd51de70"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d3de9a5960f72c335ef00763d861fc5005ef0644cb260ba1b5a115a102157251"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe5a968ae933e8f7627a7b2fc8893336ac2be0eb0aace762d3421f6e8f7b7f83"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04f54a9018f75615ae52f36ea1c5515e356e5d5e214b22609ddb546baef7132"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed02fb52e3741f0738db5f93e10ae0fb5c71eb33a4f2ba87c9a2fa97462a649"},
|
||||
{file = "ruff-0.5.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3cf8fe659f6362530435d97d738eb413e9f090e7e993f88711b0377fbdc99f60"},
|
||||
{file = "ruff-0.5.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:237a37e673e9f3cbfff0d2243e797c4862a44c93d2f52a52021c1a1b0899f846"},
|
||||
{file = "ruff-0.5.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2a2949ce7c1cbd8317432ada80fe32156df825b2fd611688814c8557824ef060"},
|
||||
{file = "ruff-0.5.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:481af57c8e99da92ad168924fd82220266043c8255942a1cb87958b108ac9335"},
|
||||
{file = "ruff-0.5.2-py3-none-win32.whl", hash = "sha256:f1aea290c56d913e363066d83d3fc26848814a1fed3d72144ff9c930e8c7c718"},
|
||||
{file = "ruff-0.5.2-py3-none-win_amd64.whl", hash = "sha256:8532660b72b5d94d2a0a7a27ae7b9b40053662d00357bb2a6864dd7e38819084"},
|
||||
{file = "ruff-0.5.2-py3-none-win_arm64.whl", hash = "sha256:73439805c5cb68f364d826a5c5c4b6c798ded6b7ebaa4011f01ce6c94e4d5583"},
|
||||
{file = "ruff-0.5.2.tar.gz", hash = "sha256:2c0df2d2de685433794a14d8d2e240df619b748fbe3367346baa519d8e6f1ca2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "telethon"
|
||||
version = "1.36.0"
|
||||
description = "Full-featured Telegram client library for Python 3"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "Telethon-1.36.0.tar.gz", hash = "sha256:11db5c7ed7e37f1272d443fb7eea0f1db580d56c6949165233946fb323aaf3a7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyaes = "*"
|
||||
rsa = "*"
|
||||
|
||||
[package.extras]
|
||||
cryptg = ["cryptg"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "613e275515a3ca76e83872c16883548a10a6ad913a6be381a52acf0c4680bd1d"
|
40
src/spow-telegram-bridge/pyproject.toml
Normal file
40
src/spow-telegram-bridge/pyproject.toml
Normal file
|
@ -0,0 +1,40 @@
|
|||
[tool.poetry]
|
||||
name = "spow-telegram-bridge"
|
||||
version = "2.2024.29"
|
||||
description = "add-on bridge for surplus on wheels (s+ow) to telegram"
|
||||
authors = ["Mark Joshwel <mark@joshwel.co>"]
|
||||
license = "Unlicense"
|
||||
readme = "README.md"
|
||||
packages = [
|
||||
{include = "bridge.py"}
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
Telethon = "^1.36.0"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
spow-telegram-bridge = 'bridge:entry'
|
||||
"s+ow-telegram-bridge" = 'bridge:entry'
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
# https://github.com/nix-community/poetry2nix/blob/master/docs/edgecases.md#errors-that-are-related-to-rust-and-cargo
|
||||
# if bumping this, also update the flake.nix file
|
||||
ruff = "0.5.2"
|
||||
mypy = "^1.10.1"
|
||||
isort = "^5.13.2"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py311"
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
|
||||
[tool.isort]
|
||||
line_length = 100
|
||||
profile = "black"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
8
src/spow-whatsapp-bridge/.gitignore
vendored
Normal file
8
src/spow-whatsapp-bridge/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
spow-whatsapp-bridge
|
||||
mdtest.db
|
||||
dist/
|
||||
test/
|
||||
|
||||
# nix
|
||||
.devbox
|
||||
result
|
374
src/spow-whatsapp-bridge/LICENCE
Normal file
374
src/spow-whatsapp-bridge/LICENCE
Normal file
|
@ -0,0 +1,374 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
|
5
src/spow-whatsapp-bridge/README.md
Normal file
5
src/spow-whatsapp-bridge/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# surplus on wheels: WhatsApp Bridge
|
||||
|
||||
see <https://surplus.joshwel.co/onwheels/whatsapp-bridge>
|
||||
or [/docs/onwheels/whatsapp-bridge.md](../../docs/onwheels/whatsapp-bridge.md)
|
||||
for more information and documentation
|
430
src/spow-whatsapp-bridge/bridge.go
Normal file
430
src/spow-whatsapp-bridge/bridge.go
Normal file
|
@ -0,0 +1,430 @@
|
|||
// Copyright (c) 2021 Tulir Asokan
|
||||
// Copyright (c) 2024 Mark Joshwel
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// from https://github.com/tulir/whatsmeow/commit/792d96fbe610bfbf1039ec3b8d3f37f630025aea
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"mime"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/mdp/qrterminal/v3"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.mau.fi/whatsmeow"
|
||||
"go.mau.fi/whatsmeow/appstate"
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/store"
|
||||
"go.mau.fi/whatsmeow/store/sqlstore"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
waLog "go.mau.fi/whatsmeow/util/log"
|
||||
)
|
||||
|
||||
var cli *whatsmeow.Client
|
||||
var log waLog.Logger
|
||||
|
||||
var logLevel = "INFO"
|
||||
var debugLogs = flag.Bool("debug", false, "Enable debug logs?")
|
||||
var dbDialect = flag.String("db-dialect", "sqlite3", "Database dialect (sqlite3 or postgres)")
|
||||
var dbAddress = flag.String("db-address", "file:mdtest.db?_foreign_keys=on", "Database address")
|
||||
var requestFullSync = flag.Bool("request-full-sync", false, "Request full (1 year) history sync when logging in?")
|
||||
var pairRejectChan = make(chan bool, 1)
|
||||
|
||||
// s+ow-whatsapp-bridge: define important paths
|
||||
var dataDir = path.Join(os.Getenv("HOME"), ".local", "share", "s+ow-whatsapp-bridge")
|
||||
var sharetextPath = path.Join(os.Getenv("HOME"), ".cache", "s+ow", "message")
|
||||
|
||||
func main() {
|
||||
waBinary.IndentXML = true
|
||||
flag.Parse()
|
||||
|
||||
if *debugLogs {
|
||||
logLevel = "DEBUG"
|
||||
}
|
||||
if *requestFullSync {
|
||||
store.DeviceProps.RequireFullSync = proto.Bool(true)
|
||||
store.DeviceProps.HistorySyncConfig = &waProto.DeviceProps_HistorySyncConfig{
|
||||
FullSyncDaysLimit: proto.Uint32(3650),
|
||||
FullSyncSizeMbLimit: proto.Uint32(102400),
|
||||
StorageQuotaMb: proto.Uint32(102400),
|
||||
}
|
||||
}
|
||||
log = waLog.Stdout("Main", logLevel, true)
|
||||
|
||||
// s+ow-whatsapp-bridge: make and change dir
|
||||
err := os.MkdirAll(dataDir, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: s+ow-whatsapp-bridge: Failed to create directory: %v", err)
|
||||
return
|
||||
}
|
||||
err = os.Chdir(dataDir)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: s+ow-whatsapp-bridge: Failed to change directory: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
dbLog := waLog.Stdout("Database", logLevel, true)
|
||||
storeContainer, err := sqlstore.New(*dbDialect, *dbAddress, dbLog)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to connect to database: %v", err)
|
||||
return
|
||||
}
|
||||
device, err := storeContainer.GetFirstDevice()
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to get device: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cli = whatsmeow.NewClient(device, waLog.Stdout("Client", logLevel, true))
|
||||
var isWaitingForPair atomic.Bool
|
||||
cli.PrePairCallback = func(jid types.JID, platform, businessName string) bool {
|
||||
isWaitingForPair.Store(true)
|
||||
defer isWaitingForPair.Store(false)
|
||||
log.Infof("s+ow-whatsapp-bridge: Pairing %s (platform: %q, business name: %q). Type r within 3 seconds to reject pair", jid, platform, businessName)
|
||||
select {
|
||||
case reject := <-pairRejectChan:
|
||||
if reject {
|
||||
log.Infof("s+ow-whatsapp-bridge: Rejecting pair")
|
||||
return false
|
||||
}
|
||||
case <-time.After(3 * time.Second):
|
||||
}
|
||||
log.Infof("s+ow-whatsapp-bridge: Accepting pair")
|
||||
return true
|
||||
}
|
||||
|
||||
ch, err := cli.GetQRChannel(context.Background())
|
||||
if err != nil {
|
||||
// This error means that we're already logged in, so ignore it.
|
||||
if !errors.Is(err, whatsmeow.ErrQRStoreContainsID) {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to get QR channel: %v", err)
|
||||
}
|
||||
} else {
|
||||
go func() {
|
||||
for evt := range ch {
|
||||
if evt.Event == "code" {
|
||||
qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: QR channel result: %s", evt.Event)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
cli.AddEventHandler(handler)
|
||||
err = cli.Connect()
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to connect: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
input := make(chan string)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
defer close(input)
|
||||
scan := bufio.NewScanner(os.Stdin)
|
||||
for scan.Scan() {
|
||||
line := strings.TrimSpace(scan.Text())
|
||||
if len(line) > 0 {
|
||||
input <- line
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// s+ow-whatsapp-bridge
|
||||
// - if 'login' in os.Args, we exit here
|
||||
for _, arg := range os.Args {
|
||||
if arg == "login" {
|
||||
for {
|
||||
select {
|
||||
case <-c:
|
||||
log.Infof("Interrupt received, exiting")
|
||||
cli.Disconnect()
|
||||
return
|
||||
case cmd := <-input:
|
||||
if len(cmd) == 0 {
|
||||
log.Infof("Stdin closed, exiting")
|
||||
cli.Disconnect()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// - if using as cli
|
||||
args := os.Args[1:]
|
||||
if len(args) > 0 {
|
||||
handleCmd(strings.ToLower(args[0]), args[1:])
|
||||
return
|
||||
}
|
||||
|
||||
// - else, "normal" operation:
|
||||
|
||||
// - - read file ~/.cache/s+ow/message
|
||||
sharetext, err := os.ReadFile(sharetextPath)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to open file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// - - read JID targets from stdin
|
||||
targets := <-input
|
||||
split := strings.Split(targets, ",")
|
||||
for _, target := range split {
|
||||
// strip whitespace
|
||||
target = strings.TrimSpace(target)
|
||||
|
||||
// check if prefixed with "wa:"
|
||||
if strings.HasPrefix(target, "wa:") {
|
||||
// send message to group
|
||||
recipient, ok := parseJID(target[3:])
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// reference the "send" case in handleCmd as a single source of truth
|
||||
msg := &waProto.Message{Conversation: proto.String(strings.TrimSpace(string(sharetext)))}
|
||||
resp, err := cli.SendMessage(context.Background(), recipient, msg)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Error sending message: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Message sent (server timestamp: %s)", resp.Timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("s+ow-whatsapp-bridge: Exiting")
|
||||
cli.Disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
func parseJID(arg string) (types.JID, bool) {
|
||||
if arg[0] == '+' {
|
||||
arg = arg[1:]
|
||||
}
|
||||
|
||||
if !strings.ContainsRune(arg, '@') {
|
||||
return types.NewJID(arg, types.DefaultUserServer), true
|
||||
}
|
||||
|
||||
recipient, err := types.ParseJID(arg)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Invalid JID %s: %v", arg, err)
|
||||
return recipient, false
|
||||
} else if recipient.User == "" {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Invalid JID %s: no server specified", arg)
|
||||
return recipient, false
|
||||
}
|
||||
return recipient, true
|
||||
}
|
||||
|
||||
func handleCmd(cmd string, args []string) {
|
||||
// s+ow-whatsapp-bridge: we only need the bare minimum:
|
||||
// - account-related: pair-phone, logout, reconnect
|
||||
// - chat-related: list, send
|
||||
switch cmd {
|
||||
case "pair-phone":
|
||||
if len(args) < 1 {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Usage: pair-phone <number>")
|
||||
return
|
||||
}
|
||||
linkingCode, err := cli.PairPhone(args[0], true, whatsmeow.PairClientChrome, "Chrome (Linux)")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Linking code:", linkingCode)
|
||||
case "reconnect":
|
||||
cli.Disconnect()
|
||||
err := cli.Connect()
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to connect: %v", err)
|
||||
}
|
||||
case "logout":
|
||||
err := cli.Logout()
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Error logging out: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Successfully logged out")
|
||||
}
|
||||
case "list":
|
||||
groups, err := cli.GetJoinedGroups()
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to get group list: %v", err)
|
||||
} else {
|
||||
for _, group := range groups {
|
||||
fmt.Printf("%s\t\t%s\n", group.JID, group.Name)
|
||||
}
|
||||
}
|
||||
case "send":
|
||||
if len(args) < 2 {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Usage: send <jid> <text>")
|
||||
return
|
||||
}
|
||||
recipient, ok := parseJID(args[0])
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
msg := &waProto.Message{Conversation: proto.String(strings.Join(args[1:], " "))}
|
||||
resp, err := cli.SendMessage(context.Background(), recipient, msg)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Error sending message: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Message sent (server timestamp: %s)", resp.Timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var historySyncID int32
|
||||
var startupTime = time.Now().Unix()
|
||||
|
||||
func handler(rawEvt interface{}) {
|
||||
switch evt := rawEvt.(type) {
|
||||
case *events.AppStateSyncComplete:
|
||||
if len(cli.Store.PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock {
|
||||
err := cli.SendPresence(types.PresenceAvailable)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to send available presence: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Marked self as available")
|
||||
}
|
||||
}
|
||||
case *events.Connected, *events.PushNameSetting:
|
||||
if len(cli.Store.PushName) == 0 {
|
||||
return
|
||||
}
|
||||
// Send presence available when connecting and when the pushname is changed.
|
||||
// This makes sure that outgoing messages always have the right pushname.
|
||||
err := cli.SendPresence(types.PresenceAvailable)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to send available presence: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Marked self as available")
|
||||
}
|
||||
case *events.StreamReplaced:
|
||||
os.Exit(0)
|
||||
case *events.Message:
|
||||
metaParts := []string{fmt.Sprintf("pushname: %s", evt.Info.PushName), fmt.Sprintf("timestamp: %s", evt.Info.Timestamp)}
|
||||
if evt.Info.Type != "" {
|
||||
metaParts = append(metaParts, fmt.Sprintf("type: %s", evt.Info.Type))
|
||||
}
|
||||
if evt.Info.Category != "" {
|
||||
metaParts = append(metaParts, fmt.Sprintf("category: %s", evt.Info.Category))
|
||||
}
|
||||
if evt.IsViewOnce {
|
||||
metaParts = append(metaParts, "view once")
|
||||
}
|
||||
if evt.IsViewOnce {
|
||||
metaParts = append(metaParts, "ephemeral")
|
||||
}
|
||||
if evt.IsViewOnceV2 {
|
||||
metaParts = append(metaParts, "ephemeral (v2)")
|
||||
}
|
||||
if evt.IsDocumentWithCaption {
|
||||
metaParts = append(metaParts, "document with caption")
|
||||
}
|
||||
if evt.IsEdit {
|
||||
metaParts = append(metaParts, "edit")
|
||||
}
|
||||
|
||||
log.Infof("s+ow-whatsapp-bridge: Received message %s from %s (%s): %+v", evt.Info.ID, evt.Info.SourceString(), strings.Join(metaParts, ", "), evt.Message)
|
||||
|
||||
if evt.Message.GetPollUpdateMessage() != nil {
|
||||
decrypted, err := cli.DecryptPollVote(evt)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to decrypt vote: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Selected options in decrypted vote:")
|
||||
for _, option := range decrypted.SelectedOptions {
|
||||
log.Infof("s+ow-whatsapp-bridge: - %X", option)
|
||||
}
|
||||
}
|
||||
} else if evt.Message.GetEncReactionMessage() != nil {
|
||||
decrypted, err := cli.DecryptReaction(evt)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to decrypt encrypted reaction: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Decrypted reaction: %+v", decrypted)
|
||||
}
|
||||
}
|
||||
|
||||
img := evt.Message.GetImageMessage()
|
||||
if img != nil {
|
||||
data, err := cli.Download(img)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to download image: %v", err)
|
||||
return
|
||||
}
|
||||
exts, _ := mime.ExtensionsByType(img.GetMimetype())
|
||||
savePath := fmt.Sprintf("%s%s", evt.Info.ID, exts[0])
|
||||
err = os.WriteFile(savePath, data, 0600)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to save image: %v", err)
|
||||
return
|
||||
}
|
||||
log.Infof("s+ow-whatsapp-bridge: Saved image in message to %s", savePath)
|
||||
}
|
||||
case *events.Receipt:
|
||||
if evt.Type == types.ReceiptTypeRead || evt.Type == types.ReceiptTypeReadSelf {
|
||||
log.Infof("s+ow-whatsapp-bridge: %v was read by %s at %s", evt.MessageIDs, evt.SourceString(), evt.Timestamp)
|
||||
} else if evt.Type == types.ReceiptTypeDelivered {
|
||||
log.Infof("s+ow-whatsapp-bridge: %s was delivered to %s at %s", evt.MessageIDs[0], evt.SourceString(), evt.Timestamp)
|
||||
}
|
||||
case *events.Presence:
|
||||
if evt.Unavailable {
|
||||
if evt.LastSeen.IsZero() {
|
||||
log.Infof("s+ow-whatsapp-bridge: %s is now offline", evt.From)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: %s is now offline (last seen: %s)", evt.From, evt.LastSeen)
|
||||
}
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: %s is now online", evt.From)
|
||||
}
|
||||
case *events.HistorySync:
|
||||
id := atomic.AddInt32(&historySyncID, 1)
|
||||
fileName := fmt.Sprintf("history-%d-%d.json", startupTime, id)
|
||||
file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to open file to write history sync: %v", err)
|
||||
return
|
||||
}
|
||||
enc := json.NewEncoder(file)
|
||||
enc.SetIndent("", " ")
|
||||
err = enc.Encode(evt.Data)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to write history sync: %v", err)
|
||||
return
|
||||
}
|
||||
log.Infof("s+ow-whatsapp-bridge: Wrote history sync to %s", fileName)
|
||||
_ = file.Close()
|
||||
case *events.AppState:
|
||||
log.Debugf("s+ow-whatsapp-bridge: App state event: %+v / %+v", evt.Index, evt.SyncActionValue)
|
||||
case *events.KeepAliveTimeout:
|
||||
log.Debugf("s+ow-whatsapp-bridge: Keepalive timeout event: %+v", evt)
|
||||
case *events.KeepAliveRestored:
|
||||
log.Debugf("s+ow-whatsapp-bridge: Keepalive restored")
|
||||
case *events.Blocklist:
|
||||
log.Infof("s+ow-whatsapp-bridge: Blocklist event: %+v", evt)
|
||||
}
|
||||
}
|
4
src/spow-whatsapp-bridge/build-termux.sh
Normal file
4
src/spow-whatsapp-bridge/build-termux.sh
Normal file
|
@ -0,0 +1,4 @@
|
|||
mkdir -p "$HOME/.local/bin"
|
||||
pkg install golang
|
||||
go build
|
||||
mv spow_whatsapp_bridge "$HOME/.local/bin/s+ow-whatsapp-bridge"
|
24
src/spow-whatsapp-bridge/check.sh
Normal file
24
src/spow-whatsapp-bridge/check.sh
Normal file
|
@ -0,0 +1,24 @@
|
|||
#!/bin/sh
|
||||
failures=0
|
||||
|
||||
ORI_HASH=$(md5sum < bridge.go)
|
||||
FMT_HASH=$(gofmt bridge.go | md5sum)
|
||||
if ! [ "$FMT_HASH" = "$ORI_HASH" ]; then
|
||||
printf "formatted file (%s) is not the same as the original file (%s)" "$FMT_HASH" "$ORI_HASH"
|
||||
failures=$((failures + 1))
|
||||
else
|
||||
printf "formatted file is same as original file - %s (yay!)" "$FMT_HASH"
|
||||
fi
|
||||
|
||||
go vet bridge.go
|
||||
failures=$((failures + $?))
|
||||
|
||||
golint bridge.go
|
||||
failures=$((failures + $?))
|
||||
|
||||
if [ $failures -eq 0 ]; then
|
||||
printf "\n\nall checks okay! (❁´◡\`❁)\n"
|
||||
else
|
||||
printf "\n\nsome checks failed...\n"
|
||||
fi
|
||||
exit $failures
|
85
src/spow-whatsapp-bridge/flake.lock
generated
Normal file
85
src/spow-whatsapp-bridge/flake.lock
generated
Normal file
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gomod2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1722589758,
|
||||
"narHash": "sha256-sbbA8b6Q2vB/t/r1znHawoXLysCyD4L/6n6/RykiSnA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "gomod2nix",
|
||||
"rev": "4e08ca09253ef996bd4c03afa383b23e35fe28a1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "gomod2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1724224976,
|
||||
"narHash": "sha256-Z/ELQhrSd7bMzTO8r7NZgi9g5emh+aRKoCdaAv5fiO0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c374d94f1536013ca8e92341b540eba4c22f9c62",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"gomod2nix": "gomod2nix",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
116
src/spow-whatsapp-bridge/flake.nix
Normal file
116
src/spow-whatsapp-bridge/flake.nix
Normal file
|
@ -0,0 +1,116 @@
|
|||
{
|
||||
description = "development environment for surplus on wheels: WhatsApp Bridge";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
gomod2nix = {
|
||||
url = "github:nix-community/gomod2nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.flake-utils.follows = "flake-utils";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, gomod2nix }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
bridge = {
|
||||
name = "spow-whatsapp-bridge";
|
||||
version = "2.2024.34";
|
||||
};
|
||||
|
||||
pkgs = import nixpkgs {
|
||||
system = system;
|
||||
overlays = [
|
||||
gomod2nix.overlays.default
|
||||
];
|
||||
config = {
|
||||
android_sdk.accept_license = true;
|
||||
allowUnfree = true;
|
||||
};
|
||||
};
|
||||
|
||||
sdk = (pkgs.androidenv.composeAndroidPackages {
|
||||
includeSources = false;
|
||||
includeSystemImages = false;
|
||||
includeEmulator = false;
|
||||
includeNDK = true;
|
||||
ndkVersions = ["26.3.11579264"];
|
||||
}).androidsdk;
|
||||
|
||||
androidClang = (
|
||||
"${sdk}/libexec/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/"
|
||||
+
|
||||
(
|
||||
{
|
||||
"x86_64-darwin" = "darwin-x86_64";
|
||||
"x86_64-linux" = "linux-x86_64";
|
||||
}."${system}" or
|
||||
(throw "the android ndk does not support your platform... (;′⌒`) (apple silicon users see https://github.com/android/ndk/issues/1299)")
|
||||
)
|
||||
+
|
||||
"/bin/aarch64-linux-android30-clang"
|
||||
);
|
||||
|
||||
bridgeBuildTermux = pkgs.buildGoApplication {
|
||||
pname = bridge.name;
|
||||
version = bridge.version;
|
||||
|
||||
go = pkgs.go_1_22;
|
||||
src = ./.;
|
||||
modules = ./gomod2nix.toml;
|
||||
|
||||
buildInputs = [ sdk ];
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
mkdir -p $out/bin
|
||||
CC="${androidClang}" CGO_ENABLED=1 GOOS=android GOARCH=arm64 go build -o $out/bin
|
||||
runHook postBuild
|
||||
'';
|
||||
};
|
||||
|
||||
bridgeBuildNative = pkgs.buildGoApplication {
|
||||
pname = bridge.name;
|
||||
version = bridge.version;
|
||||
|
||||
go = pkgs.go_1_22;
|
||||
src = ./.;
|
||||
modules = ./gomod2nix.toml;
|
||||
};
|
||||
in
|
||||
with pkgs; {
|
||||
# nix develop
|
||||
devShells.default = mkShell {
|
||||
buildInputs = [
|
||||
go
|
||||
golint
|
||||
gomod2nix.packages.${system}.default
|
||||
];
|
||||
};
|
||||
|
||||
# nix build .#termux
|
||||
packages.termux = stdenvNoCC.mkDerivation {
|
||||
pname = bridge.name;
|
||||
version = bridge.version;
|
||||
src = bridgeBuildTermux;
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp $src/bin/$pname $out/$pname
|
||||
'';
|
||||
};
|
||||
|
||||
# nix build
|
||||
packages.default = stdenvNoCC.mkDerivation {
|
||||
pname = bridge.name;
|
||||
version = bridge.version;
|
||||
src = bridgeBuildNative;
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp $src/bin/$pname $out/$pname
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
26
src/spow-whatsapp-bridge/go.mod
Normal file
26
src/spow-whatsapp-bridge/go.mod
Normal file
|
@ -0,0 +1,26 @@
|
|||
module forge.joshwel.co/mark/surplus/src/spow-whatsapp-bridge
|
||||
|
||||
go 1.22.3
|
||||
|
||||
require (
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/mdp/qrterminal/v3 v3.2.0
|
||||
go.mau.fi/whatsmeow v0.0.0-20240821142752-3d63c6fcc1a7
|
||||
google.golang.org/protobuf v1.34.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.7.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
)
|
85
src/spow-whatsapp-bridge/go.sum
Normal file
85
src/spow-whatsapp-bridge/go.sum
Normal file
|
@ -0,0 +1,85 @@
|
|||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mdp/qrterminal/v3 v3.2.0 h1:qteQMXO3oyTK4IHwj2mWsKYYRBOp1Pj2WRYFYYNTCdk=
|
||||
github.com/mdp/qrterminal/v3 v3.2.0/go.mod h1:XGGuua4Lefrl7TLEsSONiD+UEjQXJZ4mPzF+gWYIJkk=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.mau.fi/libsignal v0.1.0 h1:vAKI/nJ5tMhdzke4cTK1fb0idJzz1JuEIpmjprueC+c=
|
||||
go.mau.fi/libsignal v0.1.0/go.mod h1:R8ovrTezxtUNzCQE5PH30StOQWWeBskBsWE55vMfY9I=
|
||||
go.mau.fi/libsignal v0.1.1 h1:m/0PGBh4QKP/I1MQ44ti4C0fMbLMuHb95cmDw01FIpI=
|
||||
go.mau.fi/libsignal v0.1.1/go.mod h1:QLs89F/OA3ThdSL2Wz2p+o+fi8uuQUz0e1BRa6ExdBw=
|
||||
go.mau.fi/util v0.4.2 h1:RR3TOcRHmCF9Bx/3YG4S65MYfa+nV6/rn8qBWW4Mi30=
|
||||
go.mau.fi/util v0.4.2/go.mod h1:PlAVfUUcPyHPrwnvjkJM9UFcPE7qGPDJqk+Oufa1Gtw=
|
||||
go.mau.fi/util v0.5.0 h1:8yELAl+1CDRrwGe9NUmREgVclSs26Z68pTWePHVxuDo=
|
||||
go.mau.fi/util v0.5.0/go.mod h1:DsJzUrJAG53lCZnnYvq9/mOyLuPScWwYhvETiTrpdP4=
|
||||
go.mau.fi/util v0.6.0 h1:W6SyB3Bm/GjenQ5iq8Z8WWdN85Gy2xS6L0wmnR7SVjg=
|
||||
go.mau.fi/util v0.6.0/go.mod h1:ljYdq3sPfpICc3zMU+/mHV/sa4z0nKxc67hSBwnrk8U=
|
||||
go.mau.fi/util v0.7.0 h1:l31z+ivrSQw+cv/9eFebEqtQW2zhxivGypn+JT0h/ws=
|
||||
go.mau.fi/util v0.7.0/go.mod h1:bWYreIoTULL/UiRbZdfddPh7uWDFW5yX4YCv5FB0eE0=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240603101645-64bc969fbe78 h1:zST/E2cOjQEjXuis0miwSd20Uf+ffdJna6QefQyxEcc=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240603101645-64bc969fbe78/go.mod h1:0+65CYaE6r4dWzr0dN8i+UZKy0gIfJ79VuSqIl0nKRM=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240619210240-329c2336a6f1 h1:gpFEqwk7WtbF/8HaOMASKE6JvxWqhTmaR0CqoPpoly8=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240619210240-329c2336a6f1/go.mod h1:0+65CYaE6r4dWzr0dN8i+UZKy0gIfJ79VuSqIl0nKRM=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240716084021-eb41d1f09552 h1:3cI+n5D79nOlS3hef6PD1D8wkXEyxSIW0mvotE8ymVE=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240716084021-eb41d1f09552/go.mod h1:BhHKalSq0qNtSCuGIUIvoJyU5KbT4a7k8DQ5yw1Ssk4=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240821142752-3d63c6fcc1a7 h1:Aa4uov0rM0SQQ7Fc/TZZpmQEGksie2SVTv/UuCJwViI=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240821142752-3d63c6fcc1a7/go.mod h1:BhHKalSq0qNtSCuGIUIvoJyU5KbT4a7k8DQ5yw1Ssk4=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
54
src/spow-whatsapp-bridge/gomod2nix.toml
Normal file
54
src/spow-whatsapp-bridge/gomod2nix.toml
Normal file
|
@ -0,0 +1,54 @@
|
|||
schema = 3
|
||||
|
||||
[mod]
|
||||
[mod."filippo.io/edwards25519"]
|
||||
version = "v1.1.0"
|
||||
hash = "sha256-9ACANrgWZSd5HYPfDZHY8DVbPSC9LOMgy8deq3rDOoc="
|
||||
[mod."github.com/google/uuid"]
|
||||
version = "v1.6.0"
|
||||
hash = "sha256-VWl9sqUzdOuhW0KzQlv0gwwUQClYkmZwSydHG2sALYw="
|
||||
[mod."github.com/gorilla/websocket"]
|
||||
version = "v1.5.3"
|
||||
hash = "sha256-vTIGEFMEi+30ZdO6ffMNJ/kId6pZs5bbyqov8xe9BM0="
|
||||
[mod."github.com/mattn/go-colorable"]
|
||||
version = "v0.1.13"
|
||||
hash = "sha256-qb3Qbo0CELGRIzvw7NVM1g/aayaz4Tguppk9MD2/OI8="
|
||||
[mod."github.com/mattn/go-isatty"]
|
||||
version = "v0.0.20"
|
||||
hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ="
|
||||
[mod."github.com/mattn/go-sqlite3"]
|
||||
version = "v1.14.22"
|
||||
hash = "sha256-CWF2Hjg43658NhaePWbGzS19gHJXjuTroG5c0W3hgYQ="
|
||||
[mod."github.com/mdp/qrterminal/v3"]
|
||||
version = "v3.2.0"
|
||||
hash = "sha256-2ZcpLFu6P+a3qHH32uiFKUwzgza1NF0Bmayl41GQCEI="
|
||||
[mod."github.com/rs/zerolog"]
|
||||
version = "v1.33.0"
|
||||
hash = "sha256-jT/Y/izhZiCdrDbC/ty83FGs8UQavTU+OW03O4vKFkY="
|
||||
[mod."go.mau.fi/libsignal"]
|
||||
version = "v0.1.0"
|
||||
hash = "sha256-hSZQkw/0eV5Y0pj1N+idYuKb/jtiw/qTfaOGdYCXmn0="
|
||||
[mod."go.mau.fi/util"]
|
||||
version = "v0.4.2"
|
||||
hash = "sha256-o/d7Wd+2byFxmVxjl5o/AAUO/2d12vzItq6H5yUtcow="
|
||||
[mod."go.mau.fi/whatsmeow"]
|
||||
version = "v0.0.0-20240603101645-64bc969fbe78"
|
||||
hash = "sha256-MBDcxTHM+ZxxzIrendWWEhNdkPA7cLgkduC424+j+fU="
|
||||
[mod."golang.org/x/crypto"]
|
||||
version = "v0.24.0"
|
||||
hash = "sha256-wpxJApwSmmn9meVdpFdOU0gzeJbIXcKuFfYUUVogSss="
|
||||
[mod."golang.org/x/net"]
|
||||
version = "v0.26.0"
|
||||
hash = "sha256-WfY33QERNbcIiDkH3+p2XGrAVqvWBQfc8neUt6TH6dQ="
|
||||
[mod."golang.org/x/sys"]
|
||||
version = "v0.21.0"
|
||||
hash = "sha256-gapzPWuEqY36V6W2YhIDYR49sEvjJRd7bSuf9K1f4JY="
|
||||
[mod."golang.org/x/term"]
|
||||
version = "v0.21.0"
|
||||
hash = "sha256-zRm7uPBM1+TJkODYHkk/BtN3la5QAaSgslE2hSTm27Y="
|
||||
[mod."google.golang.org/protobuf"]
|
||||
version = "v1.34.2"
|
||||
hash = "sha256-nMTlrDEE2dbpWz50eQMPBQXCyQh4IdjrTIccaU0F3m0="
|
||||
[mod."rsc.io/qr"]
|
||||
version = "v0.2.0"
|
||||
hash = "sha256-I3fAJwwZhIrgBbCjWvIElAE9JqG2y59KRBc78EYi3RM="
|
5
src/spow-whatsapp-bridge/install.sh
Normal file
5
src/spow-whatsapp-bridge/install.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
# surplus on wheels: WhatsApp Bridge: installation and updater script
|
||||
set -e
|
||||
|
||||
echo TODO
|
2
src/surplus-on-wheels/.s+ow-bridges
Normal file
2
src/surplus-on-wheels/.s+ow-bridges
Normal file
|
@ -0,0 +1,2 @@
|
|||
SPOW_TELEGRAM_API_HASH="" SPOW_TELEGRAM_API_ID="" s+ow-telegram-bridge
|
||||
s+ow-whatsapp-bridge
|
13
src/surplus-on-wheels/README.md
Normal file
13
src/surplus-on-wheels/README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# surplus on wheels
|
||||
|
||||
surplus on wheels (s+ow) is a pure shell script to get your location using
|
||||
[termux-location](https://wiki.termux.com/wiki/Termux-location), process it through surplus, and
|
||||
send it to messaging service or wherever using “bridges”
|
||||
|
||||
surplus was made to emulate sending your location through the iOS Shortcuts app, and surplus on
|
||||
wheels complements it by running surplus automatically using a cron job.
|
||||
(but using it manually also works!)
|
||||
|
||||
see <https://surplus.joshwel.co/onwheels/>
|
||||
or [/docs/onwheels/index.md](../../docs/onwheels/index.md)
|
||||
for more information and documentation
|
24
src/surplus-on-wheels/UNLICENCE
Normal file
24
src/surplus-on-wheels/UNLICENCE
Normal file
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
21
src/surplus-on-wheels/check.sh
Normal file
21
src/surplus-on-wheels/check.sh
Normal file
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
failures=0
|
||||
|
||||
FMT_HASH=$(shfmt s+ow | md5sum)
|
||||
ORI_HASH=$(md5sum < s+ow)
|
||||
if ! [ "$FMT_HASH" = "$ORI_HASH" ]; then
|
||||
printf "formatted file (%s) is not the same as the original file (%s)" "$FMT_HASH" "$ORI_HASH"
|
||||
failures=$((failures + 1))
|
||||
else
|
||||
printf "formatted file is same as original file - %s (yay!)" "$FMT_HASH"
|
||||
fi
|
||||
|
||||
shellcheck s+ow
|
||||
failures=$((failures + $?))
|
||||
|
||||
if [ $failures -eq 0 ]; then
|
||||
printf "\n\nall checks okay! (❁´◡\`❁)\n"
|
||||
else
|
||||
printf "\n\nsome checks failed...\n"
|
||||
fi
|
||||
exit $failures
|
61
src/surplus-on-wheels/flake.lock
generated
Normal file
61
src/surplus-on-wheels/flake.lock
generated
Normal file
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1718318537,
|
||||
"narHash": "sha256-4Zu0RYRcAY/VWuu6awwq4opuiD//ahpc2aFHg2CWqFY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e9ee548d90ff586a6471b4ae80ae9cfcbceb3420",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
20
src/surplus-on-wheels/flake.nix
Normal file
20
src/surplus-on-wheels/flake.nix
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
description = "development environment for surplus on wheels";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
with pkgs; {
|
||||
devShells.default = mkShellNoCC {
|
||||
buildInputs = [ shfmt shellcheck ];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
37
src/surplus-on-wheels/install.sh
Normal file
37
src/surplus-on-wheels/install.sh
Normal file
|
@ -0,0 +1,37 @@
|
|||
#!/bin/sh
|
||||
# surplus on wheels: termux installation script
|
||||
set -e
|
||||
|
||||
# get packages
|
||||
yes | pkg upgrade
|
||||
yes | pkg install python cronie termux-api termux-services wget
|
||||
|
||||
# install pipx and surplus
|
||||
pip install pipx
|
||||
pipx install surplus
|
||||
|
||||
# install s+ow
|
||||
mkdir -p ~/.local/bin/
|
||||
if ping -c 1 surplus.joshwel.co ; then
|
||||
wget -O ~/.local/bin/s+ow https://surplus.joshwel.co/spow.sh
|
||||
else
|
||||
wget -O ~/.local/bin/s+ow https://raw.githubusercontent.com/markjoshwel/surplus/main/src/surplus-on-wheels/s+ow
|
||||
fi
|
||||
chmod +x ~/.local/bin/s+ow
|
||||
|
||||
# setup path
|
||||
echo "export PATH=\$PATH:\$HOME/.local/bin/" >> ~/.profile
|
||||
|
||||
printf "
|
||||
----- done! -----
|
||||
|
||||
if you're going to set a cron job up:
|
||||
|
||||
1. restart termux
|
||||
2. run crontab -e
|
||||
3. add \"59 * * * * bash -l -c \"(SPOW_TARGETS="" SPOW_CRON=y s+ow)\"\"
|
||||
(remember to minimally fill in the SPOW_TARGETS variable)
|
||||
|
||||
else, surplus on wheels has been set up!
|
||||
|
||||
"
|
550
src/surplus-on-wheels/s+ow
Normal file
550
src/surplus-on-wheels/s+ow
Normal file
|
@ -0,0 +1,550 @@
|
|||
#!/bin/sh
|
||||
|
||||
# surplus on wheels (s+ow) - a pure shell script to run surplus with mdtest using the termux-api
|
||||
# ------------------------
|
||||
# This is free and unencumbered software released into the public domain.
|
||||
#
|
||||
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
# distribute this software, either in source code form or as a compiled
|
||||
# binary, for any purpose, commercial or non-commercial, and by any
|
||||
# means.
|
||||
#
|
||||
# In jurisdictions that recognize copyright laws, the author or authors
|
||||
# of this software dedicate any and all copyright interest in the
|
||||
# software to the public domain. We make this dedication for the benefit
|
||||
# of the public at large and to the detriment of our heirs and
|
||||
# successors. We intend this dedication to be an overt act of
|
||||
# relinquishment in perpetuity of all present and future rights to this
|
||||
# software under copyright law.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# For more information, please refer to <http://unlicense.org/>
|
||||
|
||||
SURPLUS_CMD_DEFAULT="surplus -td"
|
||||
SURPLUS_CMD=${SURPLUS_CMD:-$SURPLUS_CMD_DEFAULT}
|
||||
LOCATION_CMD_DEFAULT="termux-location"
|
||||
LOCATION_CMD=${LOCATION_CMD:-$LOCATION_CMD_DEFAULT}
|
||||
|
||||
# shellcheck disable=SC2059
|
||||
LOCATION_FALLBACK=${LOCATION_FALLBACK:-"%d%d%d\nSingapore?"}
|
||||
# shellcheck disable=SC2269
|
||||
LOCATION_PRIORITISE_NETWORK="$LOCATION_PRIORITISE_NETWORK"
|
||||
LOCATION_TIMEOUT=${LOCATION_TIMEOUT:-50}
|
||||
|
||||
# shellcheck disable=SC2269
|
||||
SPOW_TARGETS="$SPOW_TARGETS"
|
||||
SPOW_CACHE_DIR="$HOME/.cache/s+ow"
|
||||
SPOW_BRIDGES="$HOME/.s+ow-bridges"
|
||||
SPOW_CRON=${SPOW_CRON:-n}
|
||||
SPOW_PRIVATE=${SPOW_PRIVATE:-n}
|
||||
|
||||
# per-tool session logs
|
||||
SPOW_NETLC_OUT="$SPOW_CACHE_DIR/location.net.json" # honours SPOW_PRIVATE (cleared after use)
|
||||
SPOW_GPSLC_OUT="$SPOW_CACHE_DIR/location.gps.json" # honours SPOW_PRIVATE (cleared after use)
|
||||
SPOW_LOCTN_OUT="$SPOW_CACHE_DIR/location.json" # honours SPOW_PRIVATE (cleared after use)
|
||||
SPOW_SPLUS_OUT="$SPOW_CACHE_DIR/surplus.out.log" # honours SPOW_PRIVATE (cleared after use)
|
||||
SPOW_SPLUS_ERR="$SPOW_CACHE_DIR/surplus.err.log" # honours SPOW_PRIVATE (set to /dev/null + cleared after use)
|
||||
|
||||
# per-session collated logs
|
||||
SPOW_SESH_OUT="$SPOW_CACHE_DIR/out.log" # honours SPOW_PRIVATE (set to /dev/null)
|
||||
SPOW_SESH_ERR="$SPOW_CACHE_DIR/err.log" # honours SPOW_PRIVATE (set to /dev/null)
|
||||
|
||||
# per-week collated logs
|
||||
SPOW_WEEK_PRE="$SPOW_CACHE_DIR/$(date +%Y)W$(date +"%V")"
|
||||
SPOW_WEEK_OUT="$SPOW_WEEK_PRE.out.log"
|
||||
SPOW_WEEK_ERR="$SPOW_WEEK_PRE.err.log"
|
||||
|
||||
# last successful surplus output
|
||||
SPOW_LAST_OUT="$SPOW_CACHE_DIR/last"
|
||||
|
||||
# message to be sent
|
||||
SPOW_MESSAGE="$SPOW_CACHE_DIR/message" # honours SPOW_PRIVATE (cleared after use)
|
||||
|
||||
# list of fakes
|
||||
SPOW_FAKE_OUT="$SPOW_CACHE_DIR/fake"
|
||||
|
||||
# check for network location priority
|
||||
if [ "$LOCATION_PRIORITISE_NETWORK" = "n" ]; then
|
||||
LOCATION_PRIORITISE_NETWORK=""
|
||||
fi
|
||||
|
||||
# check for cron status
|
||||
if [ "$SPOW_CRON" = "n" ]; then
|
||||
SPOW_CRON=""
|
||||
fi
|
||||
|
||||
# check if running in 'private' mode
|
||||
if [ "$SPOW_PRIVATE" = "n" ]; then
|
||||
SPOW_PRIVATE=""
|
||||
fi
|
||||
|
||||
# extract command names for checking
|
||||
TERMUX_EXE=$(echo "$TERMUX_CMD" | awk '{print $1}')
|
||||
SURPLUS_EXE=$(echo "$SURPLUS_CMD" | awk '{print $1}')
|
||||
|
||||
# ensure commands exist
|
||||
if ! command -v "$SURPLUS_EXE" >/dev/null 2>&1; then
|
||||
if [ "$SURPLUS_EXE" = "surplus" ]; then
|
||||
printf "s+ow: error: surplus is not installed.\ninstall it with 'pip install surplus'. see <https://surplus.joshwel.co> for more information.\n"
|
||||
else
|
||||
printf "s+ow: error: custom surplus command '%s' is not accessible" "$SURPLUS_EXE"
|
||||
fi
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! command -v "$TERMUX_EXE" >/dev/null 2>&1; then
|
||||
if [ "$TERMUX_EXE" = $LOCATION_CMD_DEFAULT ]; then
|
||||
printf "s+ow: error: termux-location is not installed.\ninstall it with 'pkg install termux-api' and with installing the termux:api app from the play store or f-droid.\n"
|
||||
else
|
||||
printf "s+ow: error: custom location command '%s' is not accessible" "$TERMUX_EXE"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ensure directories
|
||||
mkdir -p "$SPOW_CACHE_DIR"
|
||||
|
||||
# create new session logs
|
||||
rm -f "$SPOW_LOCTN_OUT" "$SPOW_SPLUS_OUT" "$SPOW_SPLUS_ERR" \
|
||||
"$SPOW_SESH_OUT" "$SPOW_SESH_ERR"
|
||||
touch "$SPOW_NETLC_OUT" "$SPOW_GPSLC_OUT" "$SPOW_LOCTN_OUT" \
|
||||
"$SPOW_SPLUS_OUT" "$SPOW_SPLUS_ERR" \
|
||||
"$SPOW_SESH_OUT" "$SPOW_SESH_ERR" \
|
||||
"$SPOW_WEEK_OUT" "$SPOW_WEEK_ERR" \
|
||||
"$SPOW_BRIDGES" "$SPOW_FAKE_OUT"
|
||||
|
||||
# disable logs if private
|
||||
if [ -n "$SPOW_PRIVATE" ]; then
|
||||
SPOW_SESH_OUT="/dev/null"
|
||||
SPOW_SESH_ERR="/dev/null"
|
||||
SPOW_SPLUS_ERR="/dev/null"
|
||||
fi
|
||||
|
||||
# 0 is nominal
|
||||
# 1 is an termux-location error
|
||||
# 2 is a surplus error
|
||||
# 3 is a bridge/message send error
|
||||
status=0
|
||||
|
||||
bridge_failures=0
|
||||
bridge_returns=""
|
||||
|
||||
locate() {
|
||||
# spawn termux-location processes
|
||||
(
|
||||
$LOCATION_CMD -p "network" >"$SPOW_NETLC_OUT"
|
||||
if [ -s "$SPOW_NETLC_OUT" ]; then
|
||||
printf "net" | tee -a "$SPOW_SESH_ERR"
|
||||
else
|
||||
printf "net?" | tee -a "$SPOW_SESH_ERR"
|
||||
fi
|
||||
cat "$SPOW_NETLC_OUT" >>"$SPOW_SESH_OUT"
|
||||
) &
|
||||
tl_net_pid="$!"
|
||||
sleep 1
|
||||
(
|
||||
$LOCATION_CMD -p "gps" >"$SPOW_GPSLC_OUT"
|
||||
if [ -s "$SPOW_GPSLC_OUT" ]; then
|
||||
printf "gps" | tee -a "$SPOW_SESH_ERR"
|
||||
else
|
||||
printf "gps?" | tee -a "$SPOW_SESH_ERR"
|
||||
fi
|
||||
cat "$SPOW_GPSLC_OUT" >>"$SPOW_SESH_OUT"
|
||||
) &
|
||||
tl_gps_pid="$!"
|
||||
|
||||
# wait until timeout or both finished
|
||||
printf "running '%s'" "$LOCATION_CMD" | tee -a "$SPOW_SESH_ERR"
|
||||
while [ "$LOCATION_TIMEOUT" -gt 0 ]; do
|
||||
# get process statuses
|
||||
kill -0 "$tl_net_pid" >/dev/null 2>&1
|
||||
tl_net_status="$?"
|
||||
kill -0 "$tl_gps_pid" >/dev/null 2>&1
|
||||
tl_gps_status="$?"
|
||||
|
||||
# break if both finished
|
||||
if [ "$tl_net_status" -eq 1 ] && [ "$tl_gps_status" -eq 1 ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
# exception: if network is prioritised: just use that
|
||||
if [ "$tl_net_status" -eq 1 ] && [ -n "$LOCATION_PRIORITISE_NETWORK" ]; then
|
||||
# break only if theres an actual response
|
||||
if [ -s "$SPOW_NETLC_OUT" ]; then
|
||||
break
|
||||
fi
|
||||
# else just keep on waiting for gps to finish
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
printf "." | tee -a "$SPOW_SESH_ERR"
|
||||
LOCATION_TIMEOUT=$((LOCATION_TIMEOUT - 1))
|
||||
done
|
||||
if [ "$LOCATION_TIMEOUT" -eq 0 ]; then
|
||||
printf " errored (timeout)\n" | tee -a "$SPOW_SESH_ERR"
|
||||
else
|
||||
printf " nominal\n" | tee -a "$SPOW_SESH_ERR"
|
||||
fi
|
||||
|
||||
# check outputs
|
||||
printf "determining output: " | tee -a "$SPOW_SESH_ERR"
|
||||
if [ -s "$SPOW_NETLC_OUT" ] && [ -s "$SPOW_GPSLC_OUT" ]; then
|
||||
printf "both succeeded, "
|
||||
acc_net="$(grep "\"accuracy\"" <"$SPOW_NETLC_OUT" | awk -F ': ' '{print $2}' | tr -d ',')"
|
||||
acc_gps="$(grep "\"accuracy\"" <"$SPOW_GPSLC_OUT" | awk -F ': ' '{print $2}' | tr -d ',')"
|
||||
|
||||
# compare accuracy
|
||||
if awk -v n1="$acc_net" -v n2="$acc_gps" 'BEGIN { if (n1 < n2) exit 0; else exit 1; }'; then
|
||||
printf "choosing network (%s < %s)" "$acc_net" "$acc_gps" | tee -a "$SPOW_SESH_ERR"
|
||||
cat "$SPOW_NETLC_OUT" >"$SPOW_LOCTN_OUT"
|
||||
else
|
||||
printf "choosing gps (%s < %s)" "$acc_gps" "$acc_net" | tee -a "$SPOW_SESH_ERR"
|
||||
cat "$SPOW_GPSLC_OUT" >"$SPOW_LOCTN_OUT"
|
||||
fi
|
||||
|
||||
cat "$SPOW_GPSLC_OUT" >"$SPOW_LOCTN_OUT"
|
||||
else
|
||||
# one or none succeeded
|
||||
if [ -s "$SPOW_NETLC_OUT" ]; then
|
||||
if [ -n "$LOCATION_PRIORITISE_NETWORK" ]; then
|
||||
printf "using network (prioritised)" | tee -a "$SPOW_SESH_ERR"
|
||||
else
|
||||
printf "using network" | tee -a "$SPOW_SESH_ERR"
|
||||
fi
|
||||
cat "$SPOW_NETLC_OUT" >"$SPOW_LOCTN_OUT"
|
||||
fi
|
||||
if [ -s "$SPOW_GPSLC_OUT" ]; then
|
||||
printf "using gps" | tee -a "$SPOW_SESH_ERR"
|
||||
cat "$SPOW_GPSLC_OUT" >"$SPOW_LOCTN_OUT"
|
||||
fi
|
||||
fi
|
||||
if [ ! -s "$SPOW_LOCTN_OUT" ]; then
|
||||
printf "none (error)" | tee -a "$SPOW_SESH_ERR"
|
||||
fi
|
||||
printf "\n" | tee -a "$SPOW_SESH_ERR"
|
||||
}
|
||||
|
||||
gensharetext() {
|
||||
$SURPLUS_CMD "$1" >"$SPOW_SPLUS_OUT" 2>"$SPOW_SPLUS_ERR"
|
||||
ret="$?"
|
||||
cat "$SPOW_SPLUS_OUT" >>"$SPOW_SESH_OUT"
|
||||
cat "$SPOW_SPLUS_ERR" >>"$SPOW_SESH_ERR"
|
||||
return "$ret"
|
||||
}
|
||||
|
||||
send() {
|
||||
# $1 is sharetext
|
||||
|
||||
# use fake if any
|
||||
fake_first=""
|
||||
fake_rest=""
|
||||
|
||||
if [ -n "$(cat "$SPOW_FAKE_OUT")" ]; then
|
||||
inside_first_group=false
|
||||
first_group_done=false
|
||||
|
||||
while IFS= read -r line; do
|
||||
# skip preceding empty lines
|
||||
if [ -z "$line" ] && [ "$inside_first_group" = false ] && [ -z "$fake_first" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# if not empty and not inside first group, then we are inside first group
|
||||
if [ -n "$line" ] && [ "$inside_first_group" = false ] && [ "$first_group_done" = false ]; then
|
||||
inside_first_group=true
|
||||
|
||||
# if empty and inside first group, then we are outside first group
|
||||
elif [ -z "$line" ] && [ "$inside_first_group" = true ]; then
|
||||
inside_first_group=false
|
||||
first_group_done=true
|
||||
|
||||
# if empty and outside first group but first group is done, add newline to fake_rest
|
||||
elif [ -z "$line" ] && [ "$inside_first_group" = false ] && [ "$first_group_done" = true ]; then
|
||||
fake_rest="$fake_rest\n"
|
||||
fi
|
||||
|
||||
# append to fake_first (if message_fake is not empty)
|
||||
if [ "$inside_first_group" = true ]; then
|
||||
if [ -z "$fake_first" ]; then
|
||||
fake_first="$line"
|
||||
else
|
||||
fake_first="$(printf "%s\n%s" "$fake_first" "$line")"
|
||||
fi
|
||||
|
||||
# append to fake_rest (if message_fake is not empty)
|
||||
else
|
||||
if [ -z "$fake_rest" ]; then
|
||||
fake_rest="$line"
|
||||
else
|
||||
fake_rest="$(printf "%s\n%s" "$fake_rest" "$line")"
|
||||
fi
|
||||
fi
|
||||
|
||||
done <"$SPOW_FAKE_OUT"
|
||||
|
||||
if [ -n "$fake_first" ]; then
|
||||
printf "%s\n" "$fake_rest" >"$SPOW_FAKE_OUT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# choose what message to use
|
||||
message=""
|
||||
if [ -n "$fake_first" ]; then
|
||||
echo using fake
|
||||
message="$fake_first"
|
||||
else
|
||||
echo using message
|
||||
message="$1"
|
||||
fi
|
||||
|
||||
# store message
|
||||
printf "%s\n" "$message" >"$SPOW_MESSAGE"
|
||||
cat "$SPOW_MESSAGE" >>"$SPOW_SESH_OUT"
|
||||
cat "$SPOW_MESSAGE" >>"$SPOW_SESH_ERR"
|
||||
|
||||
# run bridges
|
||||
if [ -f "$SPOW_BRIDGES" ]; then
|
||||
# run commands in bridge file
|
||||
while IFS= read -r command; do
|
||||
# skip command if its actually a comment
|
||||
if [ "$(printf "%s" "$command" | head -c 1)" = "#" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# run bridge
|
||||
echo "$command"
|
||||
echo "$SPOW_TARGETS" | eval "$command" >>"$SPOW_SESH_ERR" 2>>"$SPOW_SESH_ERR"
|
||||
bridge_return="$?"
|
||||
|
||||
# check if bridge failed
|
||||
if [ ! "$bridge_return" -eq 0 ]; then
|
||||
bridge_failures=$((bridge_failures + 1))
|
||||
fi
|
||||
|
||||
# store return value
|
||||
if [ -z "$bridge_returns" ]; then
|
||||
bridge_returns="$bridge_return"
|
||||
else
|
||||
bridge_returns="$bridge_returns, $bridge_return"
|
||||
fi
|
||||
done <"$SPOW_BRIDGES"
|
||||
else
|
||||
printf "s+ow: warning: no '%s' file; message is not sent.\n" "$SPOW_BRIDGES"
|
||||
termux-notification \
|
||||
--priority "default" \
|
||||
--title "surplus on wheels: No bridges" \
|
||||
--content "No '$SPOW_BRIDGES' file; message is not sent."
|
||||
fi
|
||||
|
||||
echo "$bridge_returns" >>"$SPOW_SESH_ERR"
|
||||
}
|
||||
|
||||
notify_start() {
|
||||
termux-notification \
|
||||
--priority "min" \
|
||||
--ongoing \
|
||||
--id "s+ow" \
|
||||
--title "surplus on wheels" \
|
||||
--content "s+ow has started running."
|
||||
}
|
||||
|
||||
notify() {
|
||||
# $1 is text
|
||||
# $2 is attempt number (if any)
|
||||
attempt_text="$1"
|
||||
if [ $# -eq 2 ]; then
|
||||
attempt_text="$1 (attempt $2)"
|
||||
fi
|
||||
termux-notification \
|
||||
--priority "min" \
|
||||
--ongoing \
|
||||
--id "s+ow" \
|
||||
--title "surplus on wheels" \
|
||||
--content "$attempt_text"
|
||||
}
|
||||
|
||||
notify_end() {
|
||||
# $1 is done tuple
|
||||
# $2 is sharetext
|
||||
termux-notification \
|
||||
--priority "min" \
|
||||
--id "s+ow" \
|
||||
--title "surplus on wheels" \
|
||||
--content "$(printf 'Run has finished. %s\n\n%s' "$1" "$2")"
|
||||
}
|
||||
|
||||
# program functions
|
||||
|
||||
run() {
|
||||
notify_start
|
||||
printf "[run! stdout (%s)]\n" "$(date)" >>"$SPOW_SESH_OUT"
|
||||
printf "[run! stderr (%s)]\n" "$(date)" >>"$SPOW_SESH_ERR"
|
||||
|
||||
time_run_start="$(date +%s)"
|
||||
|
||||
# if cron: wait until its the new hour
|
||||
if [ -n "$SPOW_CRON" ]; then
|
||||
notify "Waiting for the 30th second to pass..."
|
||||
printf "waiting for the 30th second to pass...\n"
|
||||
while [ "$(date +'%S')" -lt 30 ]; do
|
||||
printf " %s\n" "$(date)"
|
||||
sleep 1
|
||||
done
|
||||
printf "proceeding\n"
|
||||
fi
|
||||
|
||||
time_locate_start="$(date +%s)"
|
||||
|
||||
# termux-location
|
||||
location=""
|
||||
for locate_run in 1 2 3; do # run three times in case :p
|
||||
notify "Running '%s'" "$LOCATION_CMD" "$locate_run"
|
||||
|
||||
if [ "$locate_run" -gt "1" ]; then
|
||||
LOCATION_TIMEOUT=75 locate
|
||||
else
|
||||
locate
|
||||
fi
|
||||
if [ ! -s "$SPOW_LOCTN_OUT" ]; then
|
||||
# erroneous: is empty
|
||||
echo "s+ow: error: failed to get location" | tee -a "$SPOW_SESH_ERR"
|
||||
status=1
|
||||
else
|
||||
# nominal: is not empty
|
||||
location="$(cat "$SPOW_LOCTN_OUT")"
|
||||
status=0
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$SPOW_PRIVATE" ]; then
|
||||
cat /dev/null >"$SPOW_GPSLC_OUT"
|
||||
cat /dev/null >"$SPOW_NETLC_OUT"
|
||||
cat /dev/null >"$SPOW_LOCTN_OUT"
|
||||
fi
|
||||
|
||||
time_locate_end="$(date +%s)"
|
||||
time_locate=$((time_locate_end - time_locate_start))
|
||||
|
||||
time_surplus_start="$(date +%s)"
|
||||
|
||||
# surplus
|
||||
printf "running '%s'... " "$SURPLUS_CMD"
|
||||
notify "Running $SURPLUS_CMD $location"
|
||||
if [ "$status" -eq 0 ]; then
|
||||
if gensharetext "$location"; then
|
||||
# surplus ran nominally
|
||||
cp "$SPOW_SPLUS_OUT" "$SPOW_LAST_OUT"
|
||||
status=0
|
||||
printf "nominal\n"
|
||||
else
|
||||
# something happened :^)
|
||||
status=2
|
||||
printf "errored\n"
|
||||
fi
|
||||
else
|
||||
printf "skipped\n"
|
||||
fi
|
||||
|
||||
time_surplus_end="$(date +%s)"
|
||||
time_surplus=$((time_surplus_end - time_surplus_start))
|
||||
|
||||
# if cron: wait until its the new hour
|
||||
if [ -n "$SPOW_CRON" ]; then
|
||||
notify "Waiting for the new hour..."
|
||||
printf "waiting until the new hour...\n"
|
||||
while [ "$(date +'%M')" -eq 59 ]; do
|
||||
printf " %s\n" "$(date)"
|
||||
sleep 1
|
||||
done
|
||||
printf "proceeding\n"
|
||||
fi
|
||||
|
||||
time_sendmsg_start="$(date +%s)"
|
||||
|
||||
# send message
|
||||
printf "sending message(s)... "
|
||||
notify "Sending message(s)"
|
||||
# 0 for freshly made sharetext
|
||||
# 1 for recycling a last location
|
||||
# 2 for using fallback template
|
||||
sent_type=0
|
||||
bridge_failures=0
|
||||
sharetext=""
|
||||
send_error_notif=false
|
||||
if [ "$status" -eq 0 ]; then
|
||||
# s+ow has behaved nominally until now, send as per normal
|
||||
sharetext="$(cat "$SPOW_SPLUS_OUT")"
|
||||
printf "\n"
|
||||
|
||||
send "$sharetext"
|
||||
else
|
||||
# something has gone wrong, send an appropriate fallback
|
||||
sharetext=""
|
||||
if [ -s "$SPOW_LAST_OUT" ]; then
|
||||
# use last successful location
|
||||
sharetext="$(cat "$SPOW_LAST_OUT")"
|
||||
sent_type=1
|
||||
printf "using last...\n"
|
||||
|
||||
else
|
||||
# no last location, use fallback
|
||||
sent_type=2
|
||||
# shellcheck disable=SC2059
|
||||
sharetext="$(printf "$LOCATION_FALLBACK" "$status" "$locate_run" "$sent_type")"
|
||||
printf "using fallback... \n"
|
||||
|
||||
fi
|
||||
|
||||
send "$sharetext"
|
||||
|
||||
# send done info except
|
||||
printf "(%d, %d, %d, %s<%s>) [lc:%ds sp:%ds]\n" "$status" "$locate_run" "$sent_type" "$bridge_failures" "$bridge_returns" "$time_locate" "$time_surplus" >>"$SPOW_SESH_ERR"
|
||||
|
||||
send_error_notif=true
|
||||
fi
|
||||
|
||||
# delete s+ logs
|
||||
if [ -n "$SPOW_PRIVATE" ]; then
|
||||
cat /dev/null >"$SPOW_SPLUS_OUT"
|
||||
cat /dev/null >"$SPOW_SPLUS_ERR"
|
||||
cat /dev/null >"$SPOW_MESSAGE"
|
||||
fi
|
||||
|
||||
time_sendmsg_end="$(date +%s)"
|
||||
time_sendmsg=$((time_sendmsg_end - time_sendmsg_start))
|
||||
|
||||
time_run_end="$(date +%s)"
|
||||
time_run=$((time_run_end - time_run_start))
|
||||
|
||||
done_info="$(printf "(%d, %d, %d, %s<%s>) [lc:%ds sp:%ds sm:%ds - %ds]\n" "$status" "$locate_run" "$sent_type" "$bridge_failures" "$bridge_returns" "$time_locate" "$time_surplus" "$time_sendmsg" "$time_run")"
|
||||
echo "done $done_info" | tee -a "$SPOW_SESH_ERR"
|
||||
|
||||
# finish
|
||||
notify_end "$done_info" "$sharetext"
|
||||
if [ ! "$send_error_notif" = false ]; then
|
||||
# error notification
|
||||
termux-notification \
|
||||
--priority "default" \
|
||||
--title "surplus on wheels has errored" \
|
||||
--content "$(printf "\n(%d, %d, %d, %s<%s>)\n[lc:%ds sp:%ds sm:%ds - %ds]\n" "$status" "$locate_run" "$sent_type" "$bridge_failures" "$bridge_returns" "$time_locate" "$time_surplus" "$time_sendmsg" "$time_run")"
|
||||
fi
|
||||
}
|
||||
|
||||
# script entry
|
||||
|
||||
if [ -z "$SPOW_TARGETS" ]; then
|
||||
echo "s+ow: error: SPOW_TARGETS are not set"
|
||||
exit 3
|
||||
fi
|
||||
run
|
||||
|
||||
printf "%s\n\n" "$(cat "$SPOW_SESH_OUT")" >>"$SPOW_WEEK_OUT"
|
||||
printf "%s\n\n" "$(cat "$SPOW_SESH_ERR")" >>"$SPOW_WEEK_ERR"
|
26
src/surplus-on-wheels/test.sh
Normal file
26
src/surplus-on-wheels/test.sh
Normal file
|
@ -0,0 +1,26 @@
|
|||
#!/bin/sh
|
||||
|
||||
SURPLUS_CMD_DEFAULT="surplus --debugp -tp"
|
||||
SURPLUS_CMD=${SURPLUS_CMD:-$SURPLUS_CMD_DEFAULT}
|
||||
|
||||
# parse SURPLUS_CMD to see if "-p" or "--private" is in the args
|
||||
set -f
|
||||
# shellcheck disable=SC2086
|
||||
set -- $SURPLUS_CMD
|
||||
for arg; do
|
||||
case "$arg" in
|
||||
--private)
|
||||
echo YAY
|
||||
break
|
||||
;;
|
||||
--*)
|
||||
;;
|
||||
-*)
|
||||
if echo "$arg" | grep -q "p"; then
|
||||
echo YAY
|
||||
break
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
set +f
|
8
src/surplus/README.md
Normal file
8
src/surplus/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# surplus
|
||||
|
||||
surplus (s+) is a Python script to convert [Google Maps Plus Codes](https://maps.google.com/pluscodes/)
|
||||
to iOS Shortcuts-like shareable text
|
||||
|
||||
see <https://surplus.joshwel.co/>
|
||||
or [/docs/index.md](../../docs/index.md)
|
||||
for more information and documentation
|
24
src/surplus/UNLICENCE
Normal file
24
src/surplus/UNLICENCE
Normal file
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
68
src/surplus/__init__.py
Normal file
68
src/surplus/__init__.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
surplus: Google Maps Plus Code to iOS Shortcuts-like shareable text
|
||||
-------------------------------------------------------------------
|
||||
by mark <mark@joshwel.co> and contributors
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
"""
|
||||
|
||||
# surplus was and would've been a single-file module, but typing is in the way :(
|
||||
# https://github.com/python/typing/issues/1333
|
||||
|
||||
from .surplus import ( # noqa: F401, TID252
|
||||
BUILD_BRANCH,
|
||||
BUILD_COMMIT,
|
||||
BUILD_DATETIME,
|
||||
CONNECTION_MAX_RETRIES,
|
||||
CONNECTION_WAIT_SECONDS,
|
||||
EMPTY_LATLONG,
|
||||
VERSION,
|
||||
VERSION_SUFFIX,
|
||||
Behaviour,
|
||||
ConversionResultTypeEnum,
|
||||
EmptyQueryError,
|
||||
IncompletePlusCodeError,
|
||||
Latlong,
|
||||
LatlongParseError,
|
||||
LatlongQuery,
|
||||
LocalCodeQuery,
|
||||
NoSuitableLocationError,
|
||||
PlusCodeNotFoundError,
|
||||
PlusCodeQuery,
|
||||
Query,
|
||||
Result,
|
||||
ResultType,
|
||||
StringQuery,
|
||||
SurplusDefaultGeocoding,
|
||||
SurplusError,
|
||||
SurplusGeocoderProtocol,
|
||||
SurplusReverserProtocol,
|
||||
__version__,
|
||||
cli,
|
||||
generate_fingerprinted_user_agent,
|
||||
parse_query,
|
||||
surplus,
|
||||
)
|
0
src/surplus/py.typed
Normal file
0
src/surplus/py.typed
Normal file
1763
src/surplus/surplus.py
Normal file
1763
src/surplus/surplus.py
Normal file
File diff suppressed because it is too large
Load diff
24
src/tools/docs-prebuild.py
Normal file
24
src/tools/docs-prebuild.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
"""
|
||||
script to copy shell scripts into the docs folder for publishing
|
||||
|
||||
src/surplus-on-wheels/s+ow -> docs/spow.sh
|
||||
src/surplus-on-wheels/termux-s+ow-setup -> docs/termux.sh
|
||||
src/spow-whatsapp-bridge/install.sh -> docs/whatsapp.sh
|
||||
src/spow-telegram-bridge/install.sh -> docs/telegram.sh
|
||||
"""
|
||||
from pathlib import Path
|
||||
from shutil import copyfile
|
||||
|
||||
repo_root: Path = Path(__file__).parent.parent.parent
|
||||
docs_path: Path = repo_root.joinpath("docs")
|
||||
|
||||
copy_map: dict[Path, Path] = {
|
||||
repo_root.joinpath("src/surplus-on-wheels/s+ow"): docs_path.joinpath("spow.sh"),
|
||||
repo_root.joinpath("src/surplus-on-wheels/install.sh"): docs_path.joinpath("termux.sh"),
|
||||
repo_root.joinpath("src/spow-whatsapp-bridge/install.sh"): docs_path.joinpath("whatsapp.sh"),
|
||||
repo_root.joinpath("src/spow-telegram-bridge/install.sh"): docs_path.joinpath("telegram.sh"),
|
||||
}
|
||||
|
||||
for target, destination in copy_map.items():
|
||||
copyfile(target, destination)
|
||||
print(f"{target}\t->\t{destination}")
|
107
src/tools/releaser.py
Normal file
107
src/tools/releaser.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
"""
|
||||
surplus: Google Maps Plus Code to iOS Shortcuts-like shareable text
|
||||
-------------------------------------------------------------------
|
||||
by mark <mark@joshwel.co>
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from os import getenv
|
||||
from pathlib import Path
|
||||
from subprocess import run
|
||||
from sys import exit as sysexit
|
||||
|
||||
# NOTE: change this if surplus has moved
|
||||
path_surplus = Path(__file__).parent.joinpath("./src/surplus/surplus.py")
|
||||
|
||||
build_time = datetime.now(timezone(timedelta(hours=8))) # using SGT
|
||||
|
||||
_insert_build_branch = getenv(
|
||||
"SURPLUS_BUILD_BRANCH",
|
||||
run(
|
||||
"git branch --show-current".split(),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
.stdout.strip("\n")
|
||||
.strip(),
|
||||
)
|
||||
insert_build_branch = _insert_build_branch if _insert_build_branch != "" else "unknown"
|
||||
|
||||
_insert_build_commit: str = (
|
||||
run(
|
||||
"git rev-parse HEAD".split(),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
.stdout.strip("\n")
|
||||
.strip()
|
||||
)
|
||||
insert_build_commit = _insert_build_commit if _insert_build_commit != "" else "unknown"
|
||||
|
||||
insert_build_datetime: str = repr(build_time).replace("datetime.", "")
|
||||
|
||||
# NOTE: change this if the respective lines in surplus.py have changed
|
||||
targets: list[tuple[str, str]] = [
|
||||
(
|
||||
'VERSION_SUFFIX: Final[str] = "-local"',
|
||||
'VERSION_SUFFIX: Final[str] = "-alpha"',
|
||||
),
|
||||
(
|
||||
'BUILD_BRANCH: Final[str] = "future"',
|
||||
f'BUILD_BRANCH: Final[str] = "{insert_build_branch}"',
|
||||
),
|
||||
(
|
||||
'BUILD_COMMIT: Final[str] = "latest"',
|
||||
f'BUILD_COMMIT: Final[str] = "{insert_build_commit}"',
|
||||
),
|
||||
(
|
||||
"BUILD_DATETIME: Final[datetime] = datetime.now(timezone(timedelta(hours=8))) # using SGT",
|
||||
f"BUILD_DATETIME: Final[datetime] = {insert_build_datetime}",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not (path_surplus.is_file() and path_surplus.exists()):
|
||||
raise FileNotFoundError(path_surplus)
|
||||
|
||||
source_surplus: str = path_surplus.read_text(encoding="utf-8")
|
||||
|
||||
for old, new in targets:
|
||||
print(f" old: {old}\n-> new: {new}\n") # noqa: T201
|
||||
source_surplus = source_surplus.replace(old, new)
|
||||
|
||||
# path_surplus.write_text(source_surplus, encoding="utf-8")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sysexit(main())
|
518
surplus.py
518
surplus.py
|
@ -1,518 +0,0 @@
|
|||
"""
|
||||
surplus: Plus Code to iOS-Shortcuts-like shareable text
|
||||
-------------------------------------------------------
|
||||
by mark <mark@joshwel.co> and contributors
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
"""
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from collections import OrderedDict
|
||||
from sys import stderr
|
||||
from typing import Any, Callable, Final, Literal, NamedTuple
|
||||
|
||||
from geopy import Location # type: ignore
|
||||
from geopy.geocoders import Nominatim # type: ignore
|
||||
from pluscodes import PlusCode # type: ignore
|
||||
from pluscodes.openlocationcode import recoverNearest # type: ignore
|
||||
from pluscodes.validator import Validator # type: ignore
|
||||
|
||||
VERSION: Final[tuple[int, int, int]] = (1, 1, 3)
|
||||
|
||||
|
||||
class Localcode(NamedTuple):
|
||||
"""
|
||||
typing.NamedTuple representing short Plus Code with locality
|
||||
|
||||
code: str
|
||||
Plus Code - e.g.: "8QMF+FX"
|
||||
locality: str
|
||||
e.g.: "Singapore"
|
||||
"""
|
||||
|
||||
code: str
|
||||
locality: str
|
||||
|
||||
def full_length(
|
||||
self, geocoder: Callable = Nominatim(user_agent="surplus").geocode
|
||||
) -> tuple[bool, str]:
|
||||
"""
|
||||
method that calculates full-length Plus Code using locality
|
||||
|
||||
geocoder: typing.Callable = geopy.geocoders.Nominatim(user_agent="surplus").geocode
|
||||
place/locality to location function, accesses .longitude and .latitude if
|
||||
returned object is not None
|
||||
|
||||
returns tuple[bool, str]
|
||||
(True, <str>) - conversion was successful, str is resultant Plus Code
|
||||
(False, <str>) - conversion failed, str is error message
|
||||
"""
|
||||
location: Location | None = geocoder(self.locality)
|
||||
lat: float = 0.0
|
||||
lon: float = 0.0
|
||||
|
||||
if location is None:
|
||||
return False, f"no coordinates found for '{self.locality}'"
|
||||
|
||||
recv_pcode = recoverNearest(
|
||||
code=self.code,
|
||||
referenceLongitude=location.longitude,
|
||||
referenceLatitude=location.latitude,
|
||||
)
|
||||
|
||||
return True, recv_pcode
|
||||
|
||||
|
||||
class Latlong(NamedTuple):
|
||||
"""
|
||||
typing.NamedTuple representing a pair of latitude and longitude coordinates
|
||||
|
||||
lat: float
|
||||
latitudinal coordinate
|
||||
long: float
|
||||
longitudinal coordinate
|
||||
"""
|
||||
|
||||
lat: float
|
||||
long: float
|
||||
|
||||
|
||||
def surplus(
|
||||
query: str | Localcode | Latlong,
|
||||
reverser: Callable = Nominatim(user_agent="surplus").reverse,
|
||||
debug: bool = False,
|
||||
) -> tuple[bool, str]:
|
||||
"""
|
||||
pluscode to shareable text conversion function
|
||||
|
||||
query: str | surplus.Localcode | surplus.Latlong
|
||||
str - normal longcode (6PH58QMF+FX)
|
||||
surplus.Localcode - shortcode with locality (8QMF+FX Singapore)
|
||||
surplus.Latlong - latlong
|
||||
|
||||
reverser: typing.Callable = geopy.geocoders.Nominatim(user_agent="surplus").reverser
|
||||
latlong to data function, accesses a dict from .raw attribute of return object
|
||||
function should be able to take a string with two floats and return a
|
||||
geopy.Location-like object (None checking is done)
|
||||
|
||||
# code used by surplus
|
||||
location: dict[str, Any] = reverser(f"{lat}, {lon}").raw
|
||||
|
||||
dict should be similar to nominatim raw dicts, see
|
||||
<https://nominatim.org/release-docs/latest/api/Output/#addressdetails>
|
||||
|
||||
debug: bool = False
|
||||
prints lat, long and reverser response dict to stderr
|
||||
|
||||
returns tuple[bool, str]
|
||||
(True, <str>) - conversion was successful, str is resultant text
|
||||
(False, <str>) - conversion failed, str is error message
|
||||
"""
|
||||
|
||||
def _unique(l: list[str]) -> list[str]:
|
||||
"""(internal function) returns a in-order unique list from list"""
|
||||
unique: OrderedDict = OrderedDict()
|
||||
for line in l:
|
||||
unique.update({line: None})
|
||||
return list(unique.keys())
|
||||
|
||||
def _generate_text(address: dict[str, str], debug: bool = False) -> list[str]:
|
||||
"""(internal function) separation of concern function for text generation"""
|
||||
|
||||
text: list[str] = []
|
||||
seen_names: list[str] = []
|
||||
|
||||
text.append(
|
||||
("0\t" if debug else "")
|
||||
+ ", ".join(
|
||||
seen_names := [
|
||||
d
|
||||
for d in _unique(
|
||||
[
|
||||
address.get(detail, "")
|
||||
for detail in (
|
||||
"emergency, historic, military, natural, landuse, place, "
|
||||
"railway, man_made, aerialway, boundary, amenity, aeroway, "
|
||||
"club, craft, leisure, office, mountain_pass, shop, "
|
||||
"tourism, bridge, tunnel, waterway"
|
||||
).split(", ")
|
||||
]
|
||||
)
|
||||
if d != ""
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
if address.get("building") != address.get("house_number"):
|
||||
seen_names += [address.get("building", "")]
|
||||
text.append(("1\t" if debug else "") + address.get("building", ""))
|
||||
|
||||
seen_names += [address.get("highway", "")]
|
||||
text.append(("2\t" if debug else "") + address.get("highway", ""))
|
||||
|
||||
seen_names += [address.get("house_name", "")]
|
||||
text.append(
|
||||
("3\t" if debug else "")
|
||||
+ (
|
||||
address.get("house_number", "")
|
||||
+ (" " + address.get("house_name", "")).strip()
|
||||
+ " "
|
||||
+ address.get("road", "")
|
||||
).strip()
|
||||
)
|
||||
|
||||
if debug:
|
||||
stderr.write(f"debug: {seen_names=}\n")
|
||||
|
||||
text.append("4\t" if debug else "")
|
||||
basket: list[str] = []
|
||||
for d in _unique(
|
||||
[
|
||||
address.get(detail, "")
|
||||
for detail in (
|
||||
"residential, neighbourhood, allotments, quarter, "
|
||||
"city_district, district, borough, suburb, subdivision, "
|
||||
"municipality, city, town, village"
|
||||
).split(", ")
|
||||
]
|
||||
):
|
||||
if all(
|
||||
_dvtm4 := [
|
||||
d != "",
|
||||
d not in address.get("road", ""),
|
||||
d
|
||||
not in [
|
||||
address.get(detail, "")
|
||||
for detail in (
|
||||
"region, state, state_district, county, "
|
||||
"state, country, continent"
|
||||
).split(", ")
|
||||
],
|
||||
all(
|
||||
_dvcm4 := [
|
||||
True if (d not in sn) else False for sn in seen_names
|
||||
]
|
||||
),
|
||||
]
|
||||
):
|
||||
basket.append(d)
|
||||
|
||||
if debug:
|
||||
stderr.write(f"debug: {d=}\t{_dvtm4=}\t{_dvcm4=}\n")
|
||||
|
||||
text[-1] += ", ".join(basket)
|
||||
|
||||
# text.append(
|
||||
# ("4\t" if debug else "")
|
||||
# + ", ".join(
|
||||
# [
|
||||
# d
|
||||
# for d in _unique(
|
||||
# [
|
||||
# address.get(detail, "")
|
||||
# for detail in (
|
||||
# "residential, neighbourhood, allotments, quarter, "
|
||||
# "city_district, district, borough, suburb, subdivision, "
|
||||
# "municipality, city, town, village"
|
||||
# ).split(", ")
|
||||
# ]
|
||||
# )
|
||||
# if all(
|
||||
# _dvtm4 := [
|
||||
# d != "",
|
||||
# d not in address.get("road", ""),
|
||||
# d
|
||||
# not in [
|
||||
# address.get(detail, "")
|
||||
# for detail in (
|
||||
# "region, state, state_district, county, "
|
||||
# "state, country, continent"
|
||||
# ).split(", ")
|
||||
# ],
|
||||
# any(
|
||||
# _dvcm4 := [
|
||||
# True if (d not in sn) else False for sn in seen_names
|
||||
# ]
|
||||
# ),
|
||||
# ]
|
||||
# )
|
||||
# ]
|
||||
# )
|
||||
# )
|
||||
|
||||
text.append(("5\t" if debug else "") + address.get("postcode", ""))
|
||||
|
||||
text.append(
|
||||
("6\t" if debug else "")
|
||||
+ ", ".join(
|
||||
[
|
||||
d
|
||||
for d in _unique(
|
||||
[
|
||||
address.get(detail, "")
|
||||
for detail in (
|
||||
"region, county, state, state_district, "
|
||||
"country, continent"
|
||||
).split(", ")
|
||||
]
|
||||
)
|
||||
if d != ""
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
return [d for d in _unique(text) if all([d != None, d != ""])]
|
||||
|
||||
_latlong = handle_query(query=query, debug=debug)
|
||||
|
||||
if _latlong[0] is False:
|
||||
assert isinstance(_latlong[1], str)
|
||||
return False, _latlong[1]
|
||||
|
||||
assert isinstance(_latlong[1], Latlong)
|
||||
latlong = _latlong[1]
|
||||
|
||||
try:
|
||||
_reversed: Location | None = reverser(f"{latlong.lat}, {latlong.long}")
|
||||
|
||||
if _reversed is None:
|
||||
raise Exception(f"reverser function returned None")
|
||||
|
||||
location: dict[str, Any] = _reversed.raw
|
||||
|
||||
except Exception as reverr:
|
||||
return (
|
||||
False,
|
||||
f"error while reversing latlong ({Latlong}): {reverr.__class__.__name__} - {reverr}",
|
||||
)
|
||||
|
||||
if debug:
|
||||
stderr.write(f"debug: {location=}\n")
|
||||
return True, "\n".join(
|
||||
_generate_text(address=location.get("address", {}), debug=debug)
|
||||
+ _generate_text(address=location.get("address", {}))
|
||||
)
|
||||
|
||||
return True, "\n".join(_generate_text(address=location.get("address", {})))
|
||||
|
||||
|
||||
def parse_query(
|
||||
query: str, debug: bool = False
|
||||
) -> tuple[Literal[True], str | Localcode | Latlong] | tuple[Literal[False], str]:
|
||||
"""
|
||||
function that parses a string Plus Code, local code or latlong into a str,
|
||||
surplus.Localcode or surplus.Latlong respectively
|
||||
|
||||
query: str
|
||||
string Plus Code, local code or latlong
|
||||
debug: bool = False
|
||||
prints query parsing information to stderr
|
||||
|
||||
returns tuple[bool, str | Localcode | Latlong]
|
||||
(True, <str | Localcode | Latlong>) - conversion was successful, second element is result
|
||||
(False, <str>) - conversion failed, str is error message
|
||||
"""
|
||||
|
||||
def _word_match(
|
||||
oquery: str, squery: list[str]
|
||||
) -> tuple[Literal[True], str | Localcode | Latlong] | tuple[Literal[False], str]:
|
||||
"""
|
||||
internal helper code reuse function
|
||||
|
||||
looks through each 'word' and attempts to match to a Plus Code
|
||||
if found, remove from original query and strip of whitespace and commas
|
||||
use resulting stripped query as locality
|
||||
"""
|
||||
|
||||
pcode: str = ""
|
||||
|
||||
for word in squery:
|
||||
if Validator().is_valid(word):
|
||||
pcode = word
|
||||
|
||||
if Validator().is_full(word):
|
||||
return True, word
|
||||
|
||||
if pcode != "": # found a pluscode
|
||||
locality = oquery.replace(pcode, "")
|
||||
locality = locality.strip().strip(",").strip()
|
||||
|
||||
if debug:
|
||||
stderr.write(f"debug: {pcode=}, {locality=}\n")
|
||||
|
||||
return True, Localcode(code=pcode, locality=locality)
|
||||
|
||||
return False, "unable to find a pluscode/match to a format"
|
||||
|
||||
squery = [word.strip(",").strip() for word in query.split()]
|
||||
|
||||
if debug:
|
||||
stderr.write(f"debug: {squery=}\n")
|
||||
|
||||
match squery:
|
||||
# attempt to match to conjoined latlong ('lat,long')
|
||||
case [a]:
|
||||
try:
|
||||
plat, plong = a.split(",")
|
||||
lat = float(plat)
|
||||
long = float(plong)
|
||||
|
||||
except ValueError:
|
||||
return _word_match(oquery=query, squery=squery)
|
||||
|
||||
else:
|
||||
return True, Latlong(lat=lat, long=long)
|
||||
|
||||
# attempt to match to latlong ('lat, long')
|
||||
case [a, b]:
|
||||
try:
|
||||
lat = float(a)
|
||||
long = float(b)
|
||||
|
||||
except ValueError:
|
||||
return _word_match(oquery=query, squery=squery)
|
||||
|
||||
else:
|
||||
return True, Latlong(lat=lat, long=long)
|
||||
|
||||
case _:
|
||||
return _word_match(oquery=query, squery=squery)
|
||||
|
||||
|
||||
def handle_query(
|
||||
query: str | Localcode | Latlong, debug: bool = False
|
||||
) -> tuple[Literal[True], Latlong] | tuple[Literal[False], str]:
|
||||
"""
|
||||
function that gets returns a surplus.Latlong from a Plus Code string,
|
||||
surplus.Localcode or surplus.Latlong object.
|
||||
used after surplus.parse_query().
|
||||
|
||||
query: str | Localcode | Latlong
|
||||
|
||||
debug: bool = False
|
||||
|
||||
returns tuple[bool, str | Latlong]
|
||||
(True, Latlong) - conversion was successful, second element is latlong
|
||||
(False, <str>) - conversion failed, str is error message
|
||||
"""
|
||||
lat: float = 0.0
|
||||
lon: float = 0.0
|
||||
|
||||
if isinstance(query, Latlong):
|
||||
return True, query
|
||||
|
||||
else: # instances: str | Localcode
|
||||
str_pcode: str = ""
|
||||
|
||||
if isinstance(query, Localcode):
|
||||
result = query.full_length()
|
||||
|
||||
if not result[0]:
|
||||
return False, result[1]
|
||||
|
||||
str_pcode = result[1]
|
||||
|
||||
else:
|
||||
str_pcode = query
|
||||
|
||||
try:
|
||||
pcode = PlusCode(str_pcode)
|
||||
|
||||
except KeyError:
|
||||
return (
|
||||
False,
|
||||
"code given is not a full-length Plus Code (including area code), e.g.: 6PH58QMF+FX",
|
||||
)
|
||||
|
||||
except Exception as pcderr:
|
||||
return (
|
||||
False,
|
||||
f"error while decoding Plus Code: {pcderr.__class__.__name__} - {pcderr}",
|
||||
)
|
||||
|
||||
lat = pcode.area.center().lat
|
||||
lon = pcode.area.center().lon
|
||||
|
||||
if debug:
|
||||
stderr.write(f"debug: {lat=}, {lon=}\n")
|
||||
|
||||
return True, Latlong(lat=lat, long=lon)
|
||||
|
||||
|
||||
def cli() -> None:
|
||||
parser = ArgumentParser(
|
||||
prog="surplus",
|
||||
description=__doc__[__doc__.find(":") + 2 : __doc__.find("\n", 1)],
|
||||
)
|
||||
parser.add_argument(
|
||||
"query",
|
||||
type=str,
|
||||
help="full-length Plus Code (6PH58QMF+FX), local code (8QMF+FX Singapore), or latlong (1.3336875, 103.7749375)",
|
||||
nargs="*",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--debug",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="prints lat, long and reverser response dict to stderr",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--version",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="prints version information to stderr and exits",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
stderr.write(
|
||||
f"surplus version {'.'.join([str(v) for v in VERSION])}"
|
||||
+ (f", debug mode" if args.debug else "")
|
||||
+ "\n"
|
||||
)
|
||||
|
||||
if args.version:
|
||||
exit()
|
||||
|
||||
if args.debug:
|
||||
stderr.write("debug: args.query='" + " ".join(args.query) + "'\n")
|
||||
|
||||
query = parse_query(" ".join(args.query), debug=args.debug)
|
||||
if not query[0]:
|
||||
stderr.write(f"{query[-1]}\n")
|
||||
exit(1)
|
||||
|
||||
result: tuple[bool, str] = surplus(query[-1], debug=args.debug)
|
||||
if not result[0]:
|
||||
stderr.write(f"{result[-1]}\n")
|
||||
exit(2)
|
||||
|
||||
print(result[-1])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
160
test.py
160
test.py
|
@ -1,160 +0,0 @@
|
|||
"""
|
||||
surplus test runner
|
||||
-------------------
|
||||
by mark <mark@joshwel.co> and contributors
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
"""
|
||||
|
||||
from sys import stderr
|
||||
from textwrap import indent
|
||||
from traceback import format_exception
|
||||
from typing import Final, NamedTuple
|
||||
|
||||
import surplus
|
||||
|
||||
INDENT: Final[int] = 3
|
||||
|
||||
|
||||
class ContinuityTest(NamedTuple):
|
||||
query: str
|
||||
expected: str
|
||||
|
||||
|
||||
class TestFailure(NamedTuple):
|
||||
test: ContinuityTest
|
||||
exception: Exception
|
||||
output: str
|
||||
|
||||
|
||||
tests: list[ContinuityTest] = [
|
||||
ContinuityTest(
|
||||
query="8RPQ+JW Singapore",
|
||||
expected=(
|
||||
"Caldecott Stn Exit 4\n" "Toa Payoh Link\n" "298106\n" "Central, Singapore"
|
||||
),
|
||||
),
|
||||
ContinuityTest(
|
||||
query="9R3J+R9 Singapore",
|
||||
expected=(
|
||||
"Thomson Plaza\n"
|
||||
"301 Upper Thomson Road\n"
|
||||
"Sin Ming, Bishan\n"
|
||||
"574408\n"
|
||||
"Central, Singapore"
|
||||
),
|
||||
),
|
||||
ContinuityTest(
|
||||
query="3RQ3+HW3 Pemping, Batam City, Riau Islands, Indonesia",
|
||||
expected=("Batam\n" "Kepulauan Riau, Indonesia"),
|
||||
),
|
||||
# ContinuityTest(
|
||||
# query="CQPP+QM9 Singapore",
|
||||
# expected=(
|
||||
# "Woodlands Integrated Transport Hub\n" "738343\n" "Northwest, Singapore"
|
||||
# ),
|
||||
# ),
|
||||
ContinuityTest(
|
||||
query="8RRX+75Q Singapore",
|
||||
expected=(
|
||||
"Braddell Station/Blk 106\n"
|
||||
"Lorong 1 Toa Payoh\n"
|
||||
"319758\n"
|
||||
"Central, Singapore"
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class SurplusFailure(Exception):
|
||||
...
|
||||
|
||||
|
||||
class QueryParseFailure(Exception):
|
||||
...
|
||||
|
||||
|
||||
class ContinuityFailure(Exception):
|
||||
...
|
||||
|
||||
|
||||
def main() -> int:
|
||||
failures: list[TestFailure] = []
|
||||
|
||||
for idx, test in enumerate(tests, start=1):
|
||||
print(f"[{idx}/{len(tests)}] {test.query}")
|
||||
output: str = ""
|
||||
|
||||
try:
|
||||
query = surplus.parse_query(query=test.query)
|
||||
|
||||
if query[0] is False:
|
||||
raise QueryParseFailure(f"test query parse result returned False")
|
||||
|
||||
result = surplus.surplus(query=query[1])
|
||||
|
||||
if result[0] is False:
|
||||
raise SurplusFailure(result[1])
|
||||
|
||||
output = result[1]
|
||||
|
||||
print(indent(text=output, prefix=INDENT * " "))
|
||||
|
||||
if output != test.expected:
|
||||
raise ContinuityFailure(f"test did not match output")
|
||||
|
||||
except Exception as exc:
|
||||
failures.append(TestFailure(test=test, exception=exc, output=output))
|
||||
stderr.write(indent(text="(fail)", prefix=INDENT * " ") + "\n")
|
||||
|
||||
else:
|
||||
stderr.write(indent(text="(pass)", prefix=INDENT * " ") + "\n\n")
|
||||
|
||||
if len(failures) > 0:
|
||||
print(f"\n--- failures ---\n")
|
||||
|
||||
for fail in failures:
|
||||
print(
|
||||
f"[{tests.index(fail.test) + 1}/{len(tests)}] {fail.test.query}\n"
|
||||
+ (
|
||||
indent("\n".join(format_exception(fail.exception)), prefix=INDENT * " ")
|
||||
+ "\n"
|
||||
)
|
||||
+ (indent(text="Expected:", prefix=INDENT * " ") + "\n")
|
||||
+ (indent(text=repr(fail.test.expected), prefix=(2 * INDENT) * " ") + "\n")
|
||||
+ (indent(text=fail.test.expected, prefix=(2 * INDENT) * " ") + "\n\n")
|
||||
+ (indent(text="Actual:", prefix=INDENT * " ") + "\n")
|
||||
+ (indent(text=repr(fail.output), prefix=(2 * INDENT) * " ") + "\n")
|
||||
+ (indent(text=fail.output, prefix=(2 * INDENT) * " "))
|
||||
)
|
||||
|
||||
print(f"\ncomplete: {len(tests) - len(failures)} passed, {len(failures)} failed")
|
||||
|
||||
return len(failures)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
Loading…
Add table
Reference in a new issue