many: monorepo-ise
This commit is contained in:
parent
d64eb987a9
commit
5737d22237
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
old/*
|
||||
|
||||
# cached files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
# surplus changelog
|
||||
|
||||
this is temporary and will be used to keep track of breaking changes as 2024.0.0 is developed
|
||||
|
||||
## 2024.0.0
|
||||
|
||||
- `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`
|
116
CONTRIBUTING.md
116
CONTRIBUTING.md
|
@ -1,116 +0,0 @@
|
|||
# the contributors guide to surplus
|
||||
|
||||
- [git workflow](#git-workflow)
|
||||
- [reporting incorrect output](#reporting-incorrect-output)
|
||||
- [the reporting process](#the-reporting-process)
|
||||
- [what counts as "incorrect"](#what-counts-as-incorrect)
|
||||
|
||||
also see the [DEVELOPING.md](/DEVELOPING.md) file for more information on the codebase.
|
||||
|
||||
## git workflow
|
||||
|
||||
1. fork the repository and branch off from the `future` branch,
|
||||
or `main` if `future` is not available
|
||||
2. make and commit your changes!
|
||||
3. pull in any changes from upstream, and resolve any conflicts, if any
|
||||
4. **commit your copyright waiver** (_see below_)
|
||||
5. submit a pull request (_or mail in a diff_)
|
||||
|
||||
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 nickname):
|
||||
|
||||
```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.
|
||||
```
|
||||
|
||||
the command to create an empty commit is `git commit --allow-empty`
|
||||
|
||||
## reporting incorrect output
|
||||
|
||||
> [!NOTE]
|
||||
> this section is independent of the rest of the contributing section.
|
||||
|
||||
different output from the iOS Shortcuts app is expected, however incorrect output is not.
|
||||
|
||||
### the reporting process
|
||||
|
||||
open an issue in the
|
||||
[repositories issue tracker](https://github.com/markjoshwel/surplus/issues/new),
|
||||
and do the following:
|
||||
|
||||
1. ensure that your issue is not an error of incorrect data returned by your reverser
|
||||
function, which by default is OpenStreetMap Nominatim.
|
||||
(_don't know what the above means? then you are using the default reverser._)
|
||||
|
||||
also look at the ['what counts as "incorrect"'](#what-counts-as-incorrect) section
|
||||
before moving on.
|
||||
|
||||
2. include the erroneous query.
|
||||
(_the Plus Code/local code/latlong coordinate/query string you passed into surplus_)
|
||||
|
||||
3. include output from the terminal with the
|
||||
[`--debug` flag](/README.md#command-line-usage) passed to the surplus CLI or with
|
||||
`debug=True` set in function calls.
|
||||
|
||||
> [!NOTE]
|
||||
> if you are using the surplus API and have passed custom stdout and stderr parameters
|
||||
> to redirect output, include that instead.
|
||||
|
||||
4. how it should look like instead, with reasoning if the error is not obvious. (e.g.,
|
||||
missing details)
|
||||
|
||||
for reference, see how the following issues were written:
|
||||
|
||||
- [issue #4: "Incorrect format: repeated lines"](https://github.com/markjoshwel/surplus/issues/4)
|
||||
- [issue #6: "Incorrect format: missing details"](https://github.com/markjoshwel/surplus/issues/6)
|
||||
- [issue #12: "Incorrect format: State before county"](https://github.com/markjoshwel/surplus/issues/12)
|
||||
|
||||
#### what counts as "incorrect"
|
||||
|
||||
- **example** (correct)
|
||||
|
||||
- iOS Shortcuts Output
|
||||
|
||||
```text
|
||||
Plaza Singapura
|
||||
68 Orchard Rd
|
||||
238839
|
||||
Singapore
|
||||
```
|
||||
|
||||
- surplus Output
|
||||
|
||||
```text
|
||||
Plaza Singapura
|
||||
68 Orchard Road
|
||||
Museum
|
||||
238839
|
||||
Central, Singapore
|
||||
```
|
||||
|
||||
this _should not_ be reported as incorrect, as the only difference between the two is
|
||||
that surplus displays more information.
|
||||
|
||||
other examples that _should not_ be reported are:
|
||||
|
||||
- name of place is incorrect/different
|
||||
|
||||
this may be due to incorrect data from the geocoder function, which is OpenStreetMap
|
||||
Nominatim by default. in the case of Nominatim, it means that the data on OpenStreetMap
|
||||
is incorrect.
|
||||
|
||||
(_if so, then consider updating OpenStreetMap to help not just you, but other surplus
|
||||
and OpenStreetMap users!_)
|
||||
|
||||
**you should report** when the output does not make logical sense, or something similar
|
||||
wherein the output of surplus is illogical to read or is not correct in the traditional
|
||||
sense of a correct address.
|
||||
|
||||
see the linked issues in [the reporting process](#the-reporting-process) for examples
|
||||
of incorrect outputs.
|
316
DEVELOPING.md
316
DEVELOPING.md
|
@ -1,316 +0,0 @@
|
|||
# the developers guide to surplus
|
||||
|
||||
- [quickstart](#quickstart)
|
||||
- [common commands](#common-commands)
|
||||
- [the technical details of surplus's output](#the-technical-details-of-surpluss-output)
|
||||
|
||||
## quickstart
|
||||
|
||||
prerequisites:
|
||||
|
||||
- [Python >=3.11](https://www.python.org/)
|
||||
- [Hatch](https://hatch.pypa.io/latest/)
|
||||
|
||||
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
|
||||
hatch shell
|
||||
```
|
||||
|
||||
## common commands
|
||||
|
||||
- `hatch fmt`
|
||||
formats and statically analyses the codebase
|
||||
|
||||
- `hatch run dev:check`
|
||||
runs mypy and isort to check the codebase
|
||||
|
||||
- `hatch run dev:fix`
|
||||
runs isort to fix imports
|
||||
|
||||
## the technical details of surplus's output
|
||||
|
||||
> [!NOTE]
|
||||
> this is a breakdown of surplus's output when converting to shareable text.
|
||||
> when converting to other output types, output may be different.
|
||||
|
||||
```text
|
||||
$ s+ --debug 8QJF+RP Singapore
|
||||
surplus version 2.2.0, debug mode (latest@future, Tue 05 Sep 2023 23:38:59 +0800)
|
||||
debug: parse_query: behaviour.query=['8QJF+RP', 'Singapore']
|
||||
debug: _match_plus_code: portion_plus_code='8QJF+RP', portion_locality='Singapore'
|
||||
debug: cli: query=Result(value=LocalCodeQuery(code='8QJF+RP', locality='Singapore'), error=None)
|
||||
debug: latlong_result.get()=Latlong(latitude=1.3320625, longitude=103.7743125)
|
||||
debug: location={...}
|
||||
debug: _generate_text: split_iso3166_2=['SG', '03']
|
||||
debug: _generate_text: using special key arrangements for 'SG-03' (Singapore)
|
||||
debug: _generate_text: seen_names=['Ngee Ann Polytechnic', 'Clementi Road']
|
||||
debug: _generate_text_line: [True] -> True -------- 'Ngee Ann Polytechnic'
|
||||
debug: _generate_text_line: [True] -> True -------- '535'
|
||||
debug: _generate_text_line: [True] -> True -------- 'Clementi Road'
|
||||
debug: _generate_text_line: [True, True] -> True -------- 'Bukit Timah'
|
||||
debug: _generate_text_line: [False, True] -> False filtered 'Singapore'
|
||||
debug: _generate_text_line: [True] -> True -------- '599489'
|
||||
debug: _generate_text_line: [True] -> True -------- 'Northwest'
|
||||
debug: _generate_text_line: [True] -> True -------- 'Singapore'
|
||||
0 Ngee Ann Polytechnic
|
||||
1
|
||||
2
|
||||
3 535 Clementi Road
|
||||
4 Bukit Timah
|
||||
5 599489
|
||||
6 Northwest, Singapore
|
||||
Ngee Ann Polytechnic
|
||||
535 Clementi Road
|
||||
Bukit Timah
|
||||
599489
|
||||
Northwest, Singapore
|
||||
```
|
||||
|
||||
variables
|
||||
|
||||
- **variables `behaviour.query`, `split_query` and `original_query`**
|
||||
|
||||
(_`split_query` and `original_query` are only shown if query is a latlong coordinate
|
||||
or query string_)
|
||||
|
||||
`behaviour.query` is the original query string or a list of strings from space-splitting the original query
|
||||
string passed to [`parse_query()`](/README.md#def-parse_query) for parsing
|
||||
|
||||
`split_query` is the original query string split by spaces
|
||||
|
||||
`original_query` is a single non-split string
|
||||
|
||||
```text
|
||||
$ s+ Temasek Polytechnic
|
||||
-------------------
|
||||
query
|
||||
|
||||
behaviour.query -> ['Temasek', 'Polytechnic']
|
||||
split_query -> ['Temasek', 'Polytechnic']
|
||||
original_query -> 'Temasek Polytechnic'
|
||||
```
|
||||
|
||||
```text
|
||||
>>> surplus("77Q4+7X Austin, Texas, USA", surplus.Behaviour())
|
||||
|
||||
behaviour.query -> '77Q4+7X Austin, Texas, USA'
|
||||
split_query -> ['77Q4+7X', 'Austin,', 'Texas,', 'USA']
|
||||
original_query -> '77Q4+7X Austin, Texas, USA'
|
||||
```
|
||||
|
||||
- **variables `portion_plus_code` and `portion_locality`**
|
||||
|
||||
(_only shown if the query is a local code, not shown on full-length Plus Codes,
|
||||
latlong coordinates or string queries_)
|
||||
|
||||
represents the Plus Code and locality portions of a
|
||||
[shortened Plus Code](https://en.wikipedia.org/wiki/Open_Location_Code#Common_usage_and_shortening)
|
||||
(_referred to as a "local code" in the codebase_) respectively
|
||||
|
||||
- **variable `query`**
|
||||
|
||||
query is a variable of type [`Result[Query]`](/README.md#query)
|
||||
|
||||
this variable is displayed to show what query type [`parse_query()`](/README.md#def-parse_query) has
|
||||
recognised, and if there were any errors during query parsing
|
||||
|
||||
- **expression `latlong_result.get()=`**
|
||||
|
||||
(_only shown if the query is a Plus Code_)
|
||||
|
||||
the latitude longitude coordinates derived from the Plus Code
|
||||
|
||||
- **variable `location`**
|
||||
|
||||
the response dictionary from the reverser function passed to
|
||||
[`surplus()`](/README.md#def-surplus)
|
||||
|
||||
for more information on the reverser function, see
|
||||
[`SurplusReverserProtocol`](/README.md#surplusreverserprotocol)
|
||||
|
||||
- **variable `split_iso3166_2` and special key arrangements**
|
||||
|
||||
a list of strings containing the split iso3166-2 code (country/subdivision identifier)
|
||||
|
||||
if special key arrangements are available for the code, a line similar to the following
|
||||
will be shown:
|
||||
|
||||
```text
|
||||
debug: _generate_text: using special key arrangements for 'SG-03' (Singapore)
|
||||
```
|
||||
|
||||
- **variable `seen_names`**
|
||||
|
||||
a list of unique important names found in certain Nominatim keys used in final output
|
||||
lines 0-3
|
||||
|
||||
- **`_generate_text_line` seen name checks**
|
||||
|
||||
```text
|
||||
# filter function boolean list status element
|
||||
# ============================= ======== ======================
|
||||
debug: _generate_text_line: [True] -> True -------- 'Ngee Ann Polytechnic'
|
||||
debug: _generate_text_line: [False, True] -> False filtered 'Singapore'
|
||||
```
|
||||
|
||||
a check is done on shareable text line 4 keys (`SHAREABLE_TEXT_LINE_4_KEYS` - general
|
||||
regional location) to reduce repeated elements found in `seen_names`
|
||||
|
||||
reasoning is, if an element on line 4 (general regional location) is the exact same as
|
||||
a previously seen name, there is no need to include the element
|
||||
|
||||
- **filter function boolean list**
|
||||
|
||||
`_generate_text_line`, an internal function defined inside `_generate_text` can be
|
||||
passed a filter function as a way to filter out certain elements on a line
|
||||
|
||||
```python
|
||||
# the filter used in _generate_text, for line 4's seen name checks
|
||||
filter=lambda ak: [
|
||||
# everything here should be True if the element is to be kept
|
||||
ak not in general_global_info,
|
||||
not any(True if (ak in sn) else False for sn in seen_names),
|
||||
]
|
||||
```
|
||||
|
||||
`general_global_info` is a list of strings containing elements from line 6. (general
|
||||
global information)
|
||||
|
||||
- **status**
|
||||
|
||||
what `all(filter(detail))` evaluates to, `filter` being the filter function passed to
|
||||
`_generate_text_line` and `detail` being the current element
|
||||
|
||||
- **element**
|
||||
|
||||
the current iteration from iterating through a list of strings containing elements
|
||||
from line 4. (general regional location)
|
||||
|
||||
line breakdown of shareable text output, accompanied by their Nominatim keys:
|
||||
|
||||
```text
|
||||
0 name of a place
|
||||
1 building name
|
||||
2 highway name
|
||||
3 block/house/building number, house name, road
|
||||
4 general regional location
|
||||
5 postal code
|
||||
6 general global information
|
||||
```
|
||||
|
||||
0. **name of a place**
|
||||
|
||||
(_usually important places or landmarks_)
|
||||
|
||||
- examples
|
||||
|
||||
```text
|
||||
The University of Queensland
|
||||
Ngee Ann Polytechnic
|
||||
Botanic Gardens
|
||||
```
|
||||
|
||||
- nominatim keys
|
||||
|
||||
```text
|
||||
emergency, historic, military, natural, landuse, place, railway, man_made,
|
||||
aerialway, boundary, amenity, aeroway, club, craft, leisure, office, mountain_pass,
|
||||
shop, tourism, bridge, tunnel, waterway
|
||||
```
|
||||
|
||||
1. **building name**
|
||||
|
||||
- examples
|
||||
|
||||
```text
|
||||
Novena Square Office Tower A
|
||||
Visitor Centre
|
||||
```
|
||||
|
||||
- nominatim keys
|
||||
|
||||
```text
|
||||
building
|
||||
```
|
||||
|
||||
2. **highway name**
|
||||
|
||||
- examples
|
||||
|
||||
```text
|
||||
Marina Coastal Expressway
|
||||
Lornie Highway
|
||||
```
|
||||
|
||||
- nominatim keys
|
||||
|
||||
```text
|
||||
highway
|
||||
```
|
||||
|
||||
3. **block/house/building number, house name, road**
|
||||
|
||||
- examples
|
||||
|
||||
```text
|
||||
535 Clementi Road
|
||||
Macquarie Street
|
||||
Braddell Road
|
||||
```
|
||||
|
||||
- nominatim keys
|
||||
|
||||
```text
|
||||
house_number, house_name, road
|
||||
```
|
||||
|
||||
4. **general regional location**
|
||||
|
||||
- examples
|
||||
|
||||
```text
|
||||
St Lucia, Greater Brisbane
|
||||
The Drag, Austin
|
||||
Toa Payoh Crest
|
||||
```
|
||||
|
||||
- nominatim keys
|
||||
|
||||
```text
|
||||
residential, neighbourhood, allotments, quarter, city_district, district, borough,
|
||||
suburb, subdivision, municipality, city, town, village
|
||||
```
|
||||
|
||||
5. **postal code**
|
||||
|
||||
- examples
|
||||
|
||||
```text
|
||||
310131
|
||||
78705
|
||||
4066
|
||||
```
|
||||
|
||||
- nominatim key
|
||||
|
||||
```text
|
||||
postcode
|
||||
```
|
||||
|
||||
6. **general global information**
|
||||
|
||||
- examples
|
||||
|
||||
```text
|
||||
Travis County, Texas, United States
|
||||
Southeast, Singapore
|
||||
Queensland, Australia
|
||||
```
|
||||
|
||||
- nominatim keys
|
||||
|
||||
```text
|
||||
region, county, state, state_district, country, continent
|
||||
```
|
10
devbox.json
10
devbox.json
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"packages": [
|
||||
"python@3.11.8",
|
||||
"hatch@latest",
|
||||
"ruff@latest"
|
||||
],
|
||||
"nixpkgs": {
|
||||
"commit": "f80ac848e3d6f0c12c52758c0f25c10c97ca3b62"
|
||||
}
|
||||
}
|
51
devbox.lock
51
devbox.lock
|
@ -1,51 +0,0 @@
|
|||
{
|
||||
"lockfile_version": "1",
|
||||
"packages": {
|
||||
"hatch@latest": {
|
||||
"last_modified": "2024-03-19T05:49:19Z",
|
||||
"resolved": "github:NixOS/nixpkgs/5710127d9693421e78cca4f74fac2db6d67162b1#hatch",
|
||||
"source": "devbox-search",
|
||||
"version": "1.9.0",
|
||||
"systems": {
|
||||
"x86_64-linux": {
|
||||
"store_path": "/nix/store/nxmypvfw8wxjnrx2ngq3dwd2pj2i07q7-hatch-1.9.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"python@3.11.8": {
|
||||
"last_modified": "2024-03-22T11:26:23Z",
|
||||
"plugin_version": "0.0.3",
|
||||
"resolved": "github:NixOS/nixpkgs/a3ed7406349a9335cb4c2a71369b697cecd9d351#python3",
|
||||
"source": "devbox-search",
|
||||
"version": "3.11.8",
|
||||
"systems": {
|
||||
"aarch64-darwin": {
|
||||
"store_path": "/nix/store/c05vbvkjxarxkws9zkwrcwrzlsx9nd68-python3-3.11.8"
|
||||
},
|
||||
"aarch64-linux": {
|
||||
"store_path": "/nix/store/pxzzyri1wbq7kc7pain665g94afkl4ww-python3-3.11.8"
|
||||
},
|
||||
"x86_64-darwin": {
|
||||
"store_path": "/nix/store/1zaap1xxxvw2ypsgh1mfxb3wzdd49873-python3-3.11.8"
|
||||
},
|
||||
"x86_64-linux": {
|
||||
"store_path": "/nix/store/7wz6hm9i8wljz0hgwz1wqmn2zlbgavrq-python3-3.11.8"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ruff@latest": {
|
||||
"last_modified": "2024-03-22T11:26:23Z",
|
||||
"resolved": "github:NixOS/nixpkgs/a3ed7406349a9335cb4c2a71369b697cecd9d351#ruff",
|
||||
"source": "devbox-search",
|
||||
"version": "0.3.2",
|
||||
"systems": {
|
||||
"aarch64-darwin": {
|
||||
"store_path": "/nix/store/s3xi1kwxzq7q33pbx8af908724mis2m2-ruff-0.3.2"
|
||||
},
|
||||
"x86_64-darwin": {
|
||||
"store_path": "/nix/store/f8327vcf7gr5jhj8rh2rp9cadm15ac37-ruff-0.3.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,16 +34,18 @@ surplus = "surplus:cli"
|
|||
exclude = [
|
||||
"/.github",
|
||||
"/.devbox",
|
||||
"/src/surplus-on-wheels",
|
||||
"/src/spow*",
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["surplus"]
|
||||
packages = ["src.surplus"]
|
||||
|
||||
[project.urls]
|
||||
Documentation = "https://github.com/markjoshwel/surplus#readme"
|
||||
Issues = "https://github.com/markjoshwel/surplus/issues"
|
||||
Documentation = "https://joshwel.co/surplus"
|
||||
Issues = "https://joshwel.co/surplus/issues"
|
||||
Source = "https://github.com/markjoshwel/surplus"
|
||||
Changelog = "https://github.com/markjoshwel/surplus/releases"
|
||||
Changelog = "https://joshwel.co/surplus/changelog"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
|
@ -54,7 +56,7 @@ line_length = 100
|
|||
profile = "black"
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "surplus/surplus.py"
|
||||
path = "src/surplus/surplus.py"
|
||||
|
||||
[[tool.hatch.envs.all.matrix]]
|
||||
python = ["3.11", "3.12"]
|
||||
|
|
|
@ -36,7 +36,7 @@ from subprocess import run
|
|||
from sys import exit as sysexit
|
||||
|
||||
# NOTE: change this if surplus has moved
|
||||
path_surplus = Path(__file__).parent.joinpath("./surplus/surplus.py")
|
||||
path_surplus = Path(__file__).parent.joinpath("./src/surplus/surplus.py")
|
||||
|
||||
build_time = datetime.now(timezone(timedelta(hours=8))) # using SGT
|
||||
|
||||
|
@ -47,16 +47,17 @@ _insert_build_branch = getenv(
|
|||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
).stdout.strip("\n"),
|
||||
).stdout.strip("\n").strip(),
|
||||
)
|
||||
insert_build_branch = _insert_build_branch if _insert_build_branch != "" else "unknown"
|
||||
|
||||
insert_build_commit: str = run(
|
||||
_insert_build_commit: str = run(
|
||||
"git rev-parse HEAD".split(),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
).stdout.strip("\n")
|
||||
).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.", "")
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
geographiclib==2.0 ; python_version >= "3.11" and python_version < "4.0" \
|
||||
--hash=sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734 \
|
||||
--hash=sha256:f7f41c85dc3e1c2d3d935ec86660dc3b2c848c83e17f9a9e51ba9d5146a15859
|
||||
geopy==2.4.0 ; python_version >= "3.11" and python_version < "4.0" \
|
||||
--hash=sha256:a59392bf17adb486b25dbdd71fbed27733bdf24a2dac588047a619de56695e36 \
|
||||
--hash=sha256:d2639a46d0ce4c091e9688b750ba94348a14b898a1e55c68f4b4a07e7d1afa20
|
||||
pluscodes==2022.1.3 ; python_version >= "3.11" 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
|
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/>
|
226
src/spow-telegram-bridge/bridge.py
Normal file
226
src/spow-telegram-bridge/bridge.py
Normal file
|
@ -0,0 +1,226 @@
|
|||
#!/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 datetime import datetime
|
||||
from os import environ, chdir
|
||||
from pathlib import Path
|
||||
from sys import argv, stderr, stdin
|
||||
from traceback import print_tb
|
||||
from typing import Generic, NamedTuple, TypeVar
|
||||
|
||||
from telethon import TelegramClient # type: ignore
|
||||
from telethon.tl import functions # 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
|
||||
|
||||
|
||||
dir_data: Path = Path.home().joinpath(".local/share/s+ow-telegram-bridge")
|
||||
dir_data.mkdir(parents=True, exist_ok=True)
|
||||
chdir(dir_data)
|
||||
dir_cache: Path = Path.home().joinpath(".cache/s+ow-telegram-bridge")
|
||||
dir_cache.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
session = "s+ow-telegram-bridge"
|
||||
api_id = environ.get("SPOW_TELEGRAM_API_ID", None)
|
||||
api_hash = environ.get("SPOW_TELEGRAM_API_HASH", None)
|
||||
message = Path.home().joinpath(".cache/s+ow/message")
|
||||
|
||||
|
||||
def handle_error(
|
||||
exc: Exception | None = None,
|
||||
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: {message}{exc_details}",
|
||||
file=stderr,
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
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)
|
||||
|
||||
if not (message.exists() and message.is_file()):
|
||||
print("s+ow-telegram-bridge: error: ~/.cache/s+ow/message not found", file=stderr)
|
||||
exit(1)
|
||||
|
||||
|
||||
async def run() -> None:
|
||||
validate_vars()
|
||||
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] = []
|
||||
for line in stdin:
|
||||
for _target in line.split(","):
|
||||
if (_target := _target.strip()).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,
|
||||
message=f"error: could not cast '{_target}' as int",
|
||||
recoverable=True,
|
||||
exit_code=2,
|
||||
)
|
||||
continue
|
||||
|
||||
async with TelegramClient(session, api_id, api_hash) as client:
|
||||
for target in targets:
|
||||
try:
|
||||
if delete_last is False:
|
||||
await client.send_message(
|
||||
int(target),
|
||||
message.read_text(encoding="utf-8"),
|
||||
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,
|
||||
message=f"error: could not delete old message",
|
||||
recoverable=True,
|
||||
exit_code=3,
|
||||
)
|
||||
continue
|
||||
|
||||
# send new message
|
||||
target_sent_message = await client.send_message(
|
||||
target,
|
||||
message.read_text(),
|
||||
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,
|
||||
message=f"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:
|
||||
validate_vars()
|
||||
with TelegramClient(session, api_id, api_hash) as client:
|
||||
client.start()
|
||||
exit()
|
||||
|
||||
|
||||
def list_chats() -> None:
|
||||
validate_vars()
|
||||
with TelegramClient(session, 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:
|
||||
login()
|
||||
|
||||
elif "list" in argv:
|
||||
list_chats()
|
||||
|
||||
else:
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
entry()
|
244
src/spow-telegram-bridge/poetry.lock
generated
Normal file
244
src/spow-telegram-bridge/poetry.lock
generated
Normal file
|
@ -0,0 +1,244 @@
|
|||
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "23.10.1"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"},
|
||||
{file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"},
|
||||
{file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"},
|
||||
{file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"},
|
||||
{file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"},
|
||||
{file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"},
|
||||
{file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"},
|
||||
{file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"},
|
||||
{file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"},
|
||||
{file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"},
|
||||
{file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"},
|
||||
{file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"},
|
||||
{file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"},
|
||||
{file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"},
|
||||
{file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"},
|
||||
{file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"},
|
||||
{file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"},
|
||||
{file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
packaging = ">=22.0"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
|
||||
[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.7"
|
||||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
|
||||
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
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 = "isort"
|
||||
version = "5.12.0"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
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.6.1"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c"},
|
||||
{file = "mypy-1.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb"},
|
||||
{file = "mypy-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e"},
|
||||
{file = "mypy-1.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f"},
|
||||
{file = "mypy-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c"},
|
||||
{file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"},
|
||||
{file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"},
|
||||
{file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"},
|
||||
{file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"},
|
||||
{file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"},
|
||||
{file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"},
|
||||
{file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"},
|
||||
{file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"},
|
||||
{file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"},
|
||||
{file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"},
|
||||
{file = "mypy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169"},
|
||||
{file = "mypy-1.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143"},
|
||||
{file = "mypy-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46"},
|
||||
{file = "mypy-1.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85"},
|
||||
{file = "mypy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45"},
|
||||
{file = "mypy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208"},
|
||||
{file = "mypy-1.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd"},
|
||||
{file = "mypy-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332"},
|
||||
{file = "mypy-1.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f"},
|
||||
{file = "mypy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30"},
|
||||
{file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"},
|
||||
{file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=1.0.0"
|
||||
typing-extensions = ">=4.1.0"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
install-types = ["pip"]
|
||||
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 = "packaging"
|
||||
version = "23.2"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"},
|
||||
{file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.11.2"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"},
|
||||
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "3.11.0"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"},
|
||||
{file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
|
||||
|
||||
[[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.5.0"
|
||||
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
|
||||
files = [
|
||||
{file = "pyasn1-0.5.0-py2.py3-none-any.whl", hash = "sha256:87a2121042a1ac9358cabcaf1d07680ff97ee6404333bacca15f76aa8ad01a57"},
|
||||
{file = "pyasn1-0.5.0.tar.gz", hash = "sha256:97b7290ca68e62a832558ec3976f15cbf911bf5d7c7039d8b861c2a0ece69fde"},
|
||||
]
|
||||
|
||||
[[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 = "telethon"
|
||||
version = "1.32.0"
|
||||
description = "Full-featured Telegram client library for Python 3"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "Telethon-1.32.0.tar.gz", hash = "sha256:a7efe0da9002e545236d636833e29e1be460e268d20ca49d03f8b9e8c9dab82a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyaes = "*"
|
||||
rsa = "*"
|
||||
|
||||
[package.extras]
|
||||
cryptg = ["cryptg"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.8.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"},
|
||||
{file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "be959138054d5d36d8a880f957864c6968bee660b7e8d376f1694b96274295ec"
|
34
src/spow-telegram-bridge/pyproject.toml
Normal file
34
src/spow-telegram-bridge/pyproject.toml
Normal file
|
@ -0,0 +1,34 @@
|
|||
[tool.poetry]
|
||||
name = "spow-telegram-bridge"
|
||||
version = "0.1.0"
|
||||
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.32.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^23.10.1"
|
||||
mypy = "^1.6.1"
|
||||
isort = "^5.12.0"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
spow-telegram-bridge = 'bridge:entry'
|
||||
"s+ow-telegram-bridge" = 'bridge:entry'
|
||||
|
||||
[tool.black]
|
||||
line-length = 90
|
||||
|
||||
[tool.isort]
|
||||
line_length = 90
|
||||
profile = "black"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
7
src/spow-whatsapp-bridge/.gitignore
vendored
Normal file
7
src/spow-whatsapp-bridge/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
spow-whatsapp-bridge
|
||||
mdtest.db
|
||||
dist
|
||||
|
||||
# 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.
|
||||
|
4
src/spow-whatsapp-bridge/build-termux.sh
Normal file
4
src/spow-whatsapp-bridge/build-termux.sh
Normal file
|
@ -0,0 +1,4 @@
|
|||
pkg install golang
|
||||
go build
|
||||
mkdir -p $HOME/.local/bin
|
||||
mv spow-whatsapp-bridge $HOME/.local/bin/s+ow-whatsapp-bridge
|
22
src/spow-whatsapp-bridge/go.mod
Normal file
22
src/spow-whatsapp-bridge/go.mod
Normal file
|
@ -0,0 +1,22 @@
|
|||
module spow-whatsapp-bridge
|
||||
|
||||
go 1.21.3
|
||||
|
||||
require (
|
||||
github.com/mattn/go-sqlite3 v1.14.18
|
||||
github.com/mdp/qrterminal/v3 v3.2.0
|
||||
go.mau.fi/whatsmeow v0.0.0-20231104103606-23bd57d939ca
|
||||
google.golang.org/protobuf v1.31.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.1 // indirect
|
||||
go.mau.fi/libsignal v0.1.0 // indirect
|
||||
go.mau.fi/util v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
)
|
42
src/spow-whatsapp-bridge/go.sum
Normal file
42
src/spow-whatsapp-bridge/go.sum
Normal file
|
@ -0,0 +1,42 @@
|
|||
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
|
||||
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ=
|
||||
github.com/mdp/qrterminal/v3 v3.0.0 h1:ywQqLRBXWTktytQNDKFjhAvoGkLVN3J2tAFZ0kMd9xQ=
|
||||
github.com/mdp/qrterminal/v3 v3.0.0/go.mod h1:NJpfAs7OAm77Dy8EkWrtE4aq+cE6McoLXlBqXQEwvE0=
|
||||
github.com/mdp/qrterminal/v3 v3.2.0 h1:qteQMXO3oyTK4IHwj2mWsKYYRBOp1Pj2WRYFYYNTCdk=
|
||||
github.com/mdp/qrterminal/v3 v3.2.0/go.mod h1:XGGuua4Lefrl7TLEsSONiD+UEjQXJZ4mPzF+gWYIJkk=
|
||||
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/util v0.2.0 h1:AMGBEdg9Ya/smb/09dljo9wBwKr432EpfjDWF7aFQg0=
|
||||
go.mau.fi/util v0.2.0/go.mod h1:AxuJUMCxpzgJ5eV9JbPWKRH8aAJJidxetNdUj7qcb84=
|
||||
go.mau.fi/whatsmeow v0.0.0-20230805111647-405414b9b5c0 h1:6kAOyrp8E9p99X1I3uj7BtEFspdcVjnYzUZpqcHo/mE=
|
||||
go.mau.fi/whatsmeow v0.0.0-20230805111647-405414b9b5c0/go.mod h1:+ObGpFE6cbbY4hKc1FmQH9MVfqaemmlXGXSnwDvCOyE=
|
||||
go.mau.fi/whatsmeow v0.0.0-20231104103606-23bd57d939ca h1:r1/XGlSlYUFR4WTDKURPf8bTuPncrADZfOGPNrfr1oI=
|
||||
go.mau.fi/whatsmeow v0.0.0-20231104103606-23bd57d939ca/go.mod h1:u557d2vph8xcLrk3CKTBknUHoB6icUpqazA4w+binRU=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
385
src/spow-whatsapp-bridge/main.go
Normal file
385
src/spow-whatsapp-bridge/main.go
Normal file
|
@ -0,0 +1,385 @@
|
|||
// Copyright (c) 2021 Tulir Asokan
|
||||
// Copyright (c) 2023 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/.
|
||||
|
||||
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/libsignal/groups"
|
||||
// "go.mau.fi/libsignal/keys/message"
|
||||
"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)
|
||||
|
||||
var data_dir = path.Join(os.Getenv("HOME"), ".local", "share", "s+ow-whatsapp-bridge")
|
||||
var sharetext_path = path.Join(os.Getenv("HOME"), ".cache", "s+ow", "message")
|
||||
|
||||
func main() {
|
||||
if *debugLogs {
|
||||
logLevel = "DEBUG"
|
||||
}
|
||||
log = waLog.Stdout("Main", logLevel, true)
|
||||
|
||||
// make and change dir
|
||||
err := os.MkdirAll(data_dir, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to create directory: %v", err)
|
||||
return
|
||||
}
|
||||
err = os.Chdir(data_dir)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to change directory: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// mdtest code
|
||||
waBinary.IndentXML = true
|
||||
flag.Parse()
|
||||
|
||||
if *requestFullSync {
|
||||
store.DeviceProps.RequireFullSync = proto.Bool(true)
|
||||
}
|
||||
|
||||
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)
|
||||
input := make(chan string)
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, 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
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// read file ~/.cache/s+ow/message
|
||||
sharetext, err := os.ReadFile(sharetext_path)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to open file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// "normal" operation; 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
|
||||
}
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
switch cmd {
|
||||
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)
|
||||
return
|
||||
}
|
||||
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("s+ow-whatsapp-bridge: 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("s+ow-whatsapp-bridge: 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())
|
||||
path := fmt.Sprintf("%s%s", evt.Info.ID, exts[0])
|
||||
err = os.WriteFile(path, 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", path)
|
||||
}
|
||||
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("App state event: %+v / %+v", evt.Index, evt.SyncActionValue)
|
||||
case *events.KeepAliveTimeout:
|
||||
log.Debugf("Keepalive timeout event: %+v", evt)
|
||||
case *events.KeepAliveRestored:
|
||||
log.Debugf("Keepalive restored")
|
||||
}
|
||||
}
|
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
|
284
src/surplus-on-wheels/README.md
Normal file
284
src/surplus-on-wheels/README.md
Normal file
|
@ -0,0 +1,284 @@
|
|||
# surplus on wheels
|
||||
|
||||
surplus on wheels (s+ow) is a pure shell script to get your location using
|
||||
`termux-location`, process it through [surplus](https://github.com/markjoshwel/surplus),
|
||||
and send it to messaging service or wherever using [bridges](#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](#installing)
|
||||
- [as a standalone script](#as-a-standalone-script)
|
||||
- [as a cron job](#as-a-cron-job)
|
||||
- [using installation scripts](#using-installation-scripts)
|
||||
- [usage](#usage)
|
||||
- [environment variables](#environment-variables)
|
||||
- [faking locations](#faking-locations)
|
||||
- [bridges](#bridges)
|
||||
- [bring your own bridge](#bring-your-own-bridge)
|
||||
- [licence](#licence)
|
||||
|
||||
## 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 the Termux:API app from [F-Froid](https://f-droid.org/en/packages/com.termux.api/).
|
||||
|
||||
2. install pipx
|
||||
|
||||
```text
|
||||
pip install pipx
|
||||
```
|
||||
|
||||
3. install surplus:
|
||||
|
||||
```text
|
||||
pip install https://github.com/markjoshwel/surplus/releases/latest/download/surplus-latest-py3-none-any.whl
|
||||
```
|
||||
|
||||
4. install surplus on wheels:
|
||||
|
||||
```text
|
||||
mkdir -p ~/.local/bin/
|
||||
curl https://raw.githubusercontent.com/markjoshwel/surplus-on-wheels/main/s+ow > ~/.local/bin/s+ow
|
||||
chmod +x ~/.local/bin/s+ow
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
### 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:
|
||||
|
||||
> [!IMPORTANT]
|
||||
> minimally fill in the `SPOW_TARGETS` variable before running s+ow.
|
||||
> [(see usage for more info)](#usage)
|
||||
|
||||
run the following command:
|
||||
|
||||
```text
|
||||
crontab -e
|
||||
```
|
||||
|
||||
and add the following text:
|
||||
|
||||
```text
|
||||
59 * * * * bash -l -c "SPOW_TARGETS="" SPOW_CRON=y s+ow"
|
||||
```
|
||||
|
||||
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) 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 instructiions in
|
||||
> [as a cron job](#as-a-cron-job).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> if not installed already, install [Termux:API](https://f-droid.org/en/packages/com.termux.api/)
|
||||
> from F-Droid.
|
||||
|
||||
1. setup s+ow:
|
||||
|
||||
```text
|
||||
curl https://raw.githubusercontent.com/markjoshwel/surplus-on-wheels/main/termux-s+ow-setup | sh
|
||||
```
|
||||
|
||||
2. restart termux!
|
||||
|
||||
3. setup cron job:
|
||||
|
||||
```text
|
||||
curl https://raw.githubusercontent.com/markjoshwel/surplus-on-wheels/main/termux-s+ow-setup-cron | sh
|
||||
```
|
||||
|
||||
the script will run `crontab -e`, and you can then edit the variables as per your
|
||||
needs. minimally fill in the `SPOW_TARGETS` variable before running s+ow.
|
||||
see [usage](#usage) for more information.
|
||||
|
||||
et voilà! s+ow is now setup. to actually send the message to a messaging platform,
|
||||
[install an appropriate bridge](#bridges).
|
||||
|
||||
## usage
|
||||
|
||||
### environment variables
|
||||
|
||||
s+ow uses three environment variables, two of which are optional:
|
||||
|
||||
1. `SPOW_TARGETS`
|
||||
a single line of comma-deliminated 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](https://github.com/markjoshwel/spow-whatsapp-bridge), and the
|
||||
Telegram chat ID is `tg:`-prefixed as recognised by the
|
||||
[spow-telegram-bridge](https://github.com/markjoshwel/spow-telegram-bridge).
|
||||
|
||||
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 will delay itself appropriately.
|
||||
|
||||
setting it to `n` will also be treated as empty.
|
||||
|
||||
3. `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.
|
||||
|
||||
setting it to `n` will also be treated as empty.
|
||||
|
||||
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.
|
||||
|
||||
4. `LOCATION_TIMEOUT` (optional)
|
||||
set as a number to override the default first location timeout of `50`.
|
||||
|
||||
### 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.
|
||||
|
||||
## bridges
|
||||
|
||||
there are two “official” bridges for s+ow:
|
||||
|
||||
- [spow-whatsapp-bridge](https://github.com/markjoshwel/spow-whatsapp-bridge)
|
||||
- [spow-telegram-bridge](https://github.com/markjoshwel/spow-telegram-bridge)
|
||||
|
||||
bridges can be located anywhere, as long as they are reachable by the shell s+ow is
|
||||
running in.
|
||||
|
||||
s+ow will run the bridges through definitions in in `$HOME/.s+ow-bridges`.
|
||||
each line of `$HOME/.s+ow-bridges` is evaluated as a shell command, and is piped
|
||||
`SPOW_TARGETS`.
|
||||
|
||||
> [!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.
|
||||
|
||||
### bring your own bridge
|
||||
|
||||
custom bridges are relatively easy as they are:
|
||||
|
||||
1. an executable or script
|
||||
|
||||
2. that reads `SPOW_TARGETS` (see [usage](#usage)) from stdin
|
||||
|
||||
- bridges should account for the possibility of comma and space (`, ` instead of just
|
||||
`,`) delimited targets, and strip each target of preceding and trailing whitespace.
|
||||
|
||||
- bridges should recognise a platform based on a prefix
|
||||
(e.g. `wa:` for WhatsApp, `tg:` for Telegram, etc.)
|
||||
|
||||
- bridges do not need to account for the possibility of multiple lines sent to stdin.
|
||||
|
||||
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.
|
||||
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 similar.
|
||||
|
||||
## licence
|
||||
|
||||
surplus on wheels is free and unencumbered software released into the public domain.
|
||||
for more information, please refer to [UNLICENCE](/UNLICENCE) or <http://unlicense.org/>.
|
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/>
|
483
src/surplus-on-wheels/s+ow
Normal file
483
src/surplus-on-wheels/s+ow
Normal file
|
@ -0,0 +1,483 @@
|
|||
#!/bin/sh
|
||||
|
||||
# surplus on wheels (s+ow) - a pure shell script to run surplus with mdtest using the termux-api
|
||||
# ------------------------
|
||||
# public domain, unlicence
|
||||
|
||||
# shellcheck disable=SC2059
|
||||
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}
|
||||
|
||||
# per-tool session logs
|
||||
SPOW_NETLC_OUT="$SPOW_CACHE_DIR/location.net.json"
|
||||
SPOW_GPSLC_OUT="$SPOW_CACHE_DIR/location.gps.json"
|
||||
SPOW_LOCTN_OUT="$SPOW_CACHE_DIR/location.json"
|
||||
SPOW_SPLUS_OUT="$SPOW_CACHE_DIR/surplus.out.log"
|
||||
SPOW_SPLUS_ERR="$SPOW_CACHE_DIR/surplus.err.log"
|
||||
|
||||
# per-session collated logs
|
||||
SPOW_SESH_OUT="$SPOW_CACHE_DIR/out.log"
|
||||
SPOW_SESH_ERR="$SPOW_CACHE_DIR/err.log"
|
||||
|
||||
# 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"
|
||||
|
||||
# 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
|
||||
|
||||
# ensure commands exist
|
||||
if ! command -v termux-location >/dev/null 2>&1; 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"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v surplus >/dev/null 2>&1; then
|
||||
printf "s+ow: error: surplus is not installed.\ninstall it with 'pip install https://github.com/markjoshwel/surplus/releases/latest/download/surplus-latest-py3-none-any.whl'\n"
|
||||
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"
|
||||
|
||||
# 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
|
||||
(
|
||||
termux-location -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
|
||||
(
|
||||
termux-location -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 termux-location" | 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 proritised: 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 -td "$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 "$fake_rest\n" >"$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 '$SPOW_BRIDGES' file; message is not sent.\n"
|
||||
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 " $(date)\n"
|
||||
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 termux-location" "$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
|
||||
|
||||
time_locate_end="$(date +%s)"
|
||||
time_locate=$((time_locate_end - time_locate_start))
|
||||
|
||||
time_surplus_start="$(date +%s)"
|
||||
|
||||
# surplus
|
||||
printf "running surplus... "
|
||||
notify "Running surplus -td $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 " $(date)\n"
|
||||
sleep 1
|
||||
done
|
||||
printf "proceeding\n"
|
||||
fi
|
||||
|
||||
time_sendmsg_start="$(date +%s)"
|
||||
|
||||
# mdtest/send message
|
||||
printf "sending message(s)... "
|
||||
notify "Sending message(s)"
|
||||
sent_type=0 # 0 for freshly made sharetext
|
||||
# 1 for recycling a last location
|
||||
# 2 for using fallback template
|
||||
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
|
||||
# shellcheck disable=SC2059
|
||||
sharetext="$(printf "$LOCATION_FALLBACK" "$status" "$locate_run" "$sent_type")"
|
||||
sent_type=2
|
||||
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
|
||||
|
||||
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 1
|
||||
fi
|
||||
run
|
||||
|
||||
printf "%s\n\n" "$(cat "$SPOW_SESH_OUT")" >>"$SPOW_WEEK_OUT"
|
||||
printf "%s\n\n" "$(cat "$SPOW_SESH_ERR")" >>"$SPOW_WEEK_ERR"
|
22
src/surplus-on-wheels/termux-s+ow-setup
Normal file
22
src/surplus-on-wheels/termux-s+ow-setup
Normal file
|
@ -0,0 +1,22 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# get packages
|
||||
yes | pkg upgrade
|
||||
yes | pkg install python cronie termux-api termux-services wget
|
||||
|
||||
# install pipx
|
||||
pip install pipx
|
||||
|
||||
# install surplus
|
||||
pipx install https://github.com/markjoshwel/surplus/releases/latest/download/surplus-latest-py3-none-any.whl
|
||||
|
||||
# install s+ow
|
||||
mkdir -p ~/.local/bin/
|
||||
curl https://raw.githubusercontent.com/markjoshwel/surplus-on-wheels/main/s+ow > ~/.local/bin/s+ow
|
||||
chmod +x ~/.local/bin/s+ow
|
||||
|
||||
# setup path
|
||||
echo "export PATH=\$PATH:\$HOME/.local/bin/" >> ~/.profile
|
||||
|
||||
printf "\ndone\n"
|
12
src/surplus-on-wheels/termux-s+ow-setup-cron
Normal file
12
src/surplus-on-wheels/termux-s+ow-setup-cron
Normal file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/sh
|
||||
|
||||
# enable cron service and add to crontab
|
||||
sv-enable crond
|
||||
printf "59 * * * *\tSPOW_TARGETS=\"\" SPOW_CRON=y ~/.local/bin/s+ow\n" > s+ow.cron
|
||||
crontab s+ow.cron
|
||||
rm s+ow.cron
|
||||
|
||||
# open editor
|
||||
crontab -e
|
||||
|
||||
printf "\ndone\n"
|
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/>
|
|
@ -69,16 +69,16 @@ if TYPE_CHECKING:
|
|||
|
||||
# constants
|
||||
|
||||
__version__ = "2024.0.0-alpha"
|
||||
__version__ = "2024.0.0-beta"
|
||||
VERSION: Final[tuple[int, int, int]] = (2024, 0, 0)
|
||||
VERSION_SUFFIX: Final[str] = "-local"
|
||||
VERSION_SUFFIX: Final[str] = "-beta-local"
|
||||
BUILD_BRANCH: Final[str] = "future"
|
||||
BUILD_COMMIT: Final[str] = "latest"
|
||||
BUILD_DATETIME: Final[datetime] = datetime.now(timezone(timedelta(hours=8))) # using SGT
|
||||
CONNECTION_MAX_RETRIES: int = 9
|
||||
CONNECTION_WAIT_SECONDS: int = 10
|
||||
LOCALITY_GEOCODER_LEVEL: int = 13 # adjusts geocoder zoom level when
|
||||
# geocoding latlong into an address
|
||||
# geocoding lat long into an address
|
||||
|
||||
# default shareable text line keys
|
||||
SHAREABLE_TEXT_LINE_0_KEYS: dict[str, tuple[str, ...]] = {
|
Loading…
Reference in a new issue