Compare commits
59 commits
Author | SHA1 | Date | |
---|---|---|---|
a6ee760eff | |||
b5bab89c49 | |||
![]() |
296fa739f0 | ||
![]() |
21c6b11f65 | ||
ffd81b8856 | |||
![]() |
60fd8e8b8a | ||
30815f08b8 | |||
3f6327a134 | |||
fe5c47e21a | |||
ad5707408c | |||
85f305fb78 | |||
ea30737ae6 | |||
c04bcb8831 | |||
59f28ce868 | |||
c6eadc6045 | |||
40d318bef7 | |||
44907e3462 | |||
93e868845a | |||
387d2a222a | |||
822eee5b11 | |||
5737d22237 | |||
d64eb987a9 | |||
4a10586824 | |||
a9dbc082ae | |||
829eccc4c6 | |||
c718b6544e | |||
fb45c2177b | |||
f73cee3684 | |||
4a2ea88fca | |||
d22fee2b0b | |||
56694ffa68 | |||
8a59994894 | |||
a456a59951 | |||
1ccd66b166 | |||
4871b9d352 | |||
8e83084f8c | |||
140736af68 | |||
451072fed3 | |||
5a189d3266 | |||
![]() |
2168d594c8 | ||
![]() |
f2e20e6932 | ||
![]() |
4b382a9b26 | ||
![]() |
e0903341d1 | ||
![]() |
56be6e4748 | ||
![]() |
c7d8f81654 | ||
![]() |
abaa87c85e | ||
![]() |
ce45f22729 | ||
![]() |
8fecc090bf | ||
![]() |
01c540750a | ||
![]() |
30d23d61cc | ||
![]() |
dd5d95f21f | ||
![]() |
c3238fd0a9 | ||
![]() |
873250b858 | ||
![]() |
c1e2b0e1a9 | ||
![]() |
c10c013167 | ||
![]() |
b21ddc0eeb | ||
![]() |
fe69dcd4c5 | ||
![]() |
51d132d416 | ||
![]() |
fdf3a86d84 |
82 changed files with 6112 additions and 3509 deletions
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
|
@ -1,7 +1,14 @@
|
|||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
directory: "/src/surplus"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/src/spow-telegram-bridge"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: gomod
|
||||
directory: "/src/spow-whatsapp-bridge"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
|
42
.github/workflows/cd-docs.yml
vendored
Normal file
42
.github/workflows/cd-docs.yml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
name: "continuous deployment: surplus Documentation"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "docs/**"
|
||||
- "mkdocs.yml"
|
||||
- "src/spow-telegram-bridge/*.py"
|
||||
- "src/**/*LICENCE*"
|
||||
- "docs/**/*LICENCE*"
|
||||
- "docs/**/*LICENSE*"
|
||||
- "docs/CC0"
|
||||
- "*LICENCE*"
|
||||
- "src/surplus-on-wheels/s+ow"
|
||||
- "src/surplus-on-wheels/install.sh"
|
||||
- "src/spow-whatsapp-bridge/install.sh"
|
||||
- "src/spow-telegram-bridge/install.sh"
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- run: nix develop --impure --command hatch run docs:build
|
||||
|
||||
- uses: cloudflare/pages-action@v1
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
projectName: surplus
|
||||
directory: site
|
||||
branch: main
|
||||
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
28
.github/workflows/cd-telegram.yml
vendored
Normal file
28
.github/workflows/cd-telegram.yml
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
name: "continuous deployment: Telegram Bridge"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "src/spow-telegram-bridge/**"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- run: |
|
||||
cd src/spow-telegram-bridge
|
||||
nix develop --command poetry build
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "spow-telegram-bridge"
|
||||
path: src/spow-whatsapp-bridge/dist
|
||||
retention-days: 14
|
28
.github/workflows/cd-whatsapp.yml
vendored
Normal file
28
.github/workflows/cd-whatsapp.yml
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
name: "continuous deployment: WhatsApp Bridge"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "src/spow-whatsapp-bridge/**"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- run: |
|
||||
cd src/spow-whatsapp-bridge
|
||||
nix build .#termux
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: "spow-whatsapp-bridge-android"
|
||||
path: src/spow-whatsapp-bridge/result
|
||||
retention-days: 14
|
49
.github/workflows/checks.yml
vendored
49
.github/workflows/checks.yml
vendored
|
@ -1,49 +0,0 @@
|
|||
name: qc
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- '*.py'
|
||||
- '**.py'
|
||||
|
||||
jobs:
|
||||
analyse:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install devbox
|
||||
uses: jetpack-io/devbox-install-action@v0.3.0
|
||||
|
||||
- name: install dependencies
|
||||
run: devbox run poetry install
|
||||
|
||||
- name: build wheel
|
||||
id: build
|
||||
run: devbox run poetry build
|
||||
|
||||
- name: analyse with mypy
|
||||
run: devbox run poetry run mypy .
|
||||
|
||||
- name: check for black formatting compliance
|
||||
run: devbox run poetry run "black --check ."
|
||||
|
||||
- name: analyse isort compliance
|
||||
run: devbox run poetry run "isort --check *.py **/*.py"
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install devbox
|
||||
uses: jetpack-io/devbox-install-action@v0.3.0
|
||||
|
||||
- name: install dependencies
|
||||
run: devbox run poetry install
|
||||
|
||||
- name: run tests
|
||||
run: devbox run poetry run python test.py
|
22
.github/workflows/ci-spow.yml
vendored
Normal file
22
.github/workflows/ci-spow.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: "continuous integration: s+ow"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "src/surplus-on-wheels/**"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- run: |
|
||||
cd src/spow-whatsapp-bridge
|
||||
nix develop --command sh check.sh
|
22
.github/workflows/ci-telegram.yml
vendored
Normal file
22
.github/workflows/ci-telegram.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: "continuous integration: Telegram Bridge"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "src/spow-telegram-bridge/**"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- run: |
|
||||
cd src/spow-telegram-bridge
|
||||
nix develop --command sh check.sh
|
22
.github/workflows/ci-whatsapp.yml
vendored
Normal file
22
.github/workflows/ci-whatsapp.yml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
name: "continuous integration: WhatsApp Bridge"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- "src/spow-whatsapp-bridge/**"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cachix/install-nix-action@v27
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- run: |
|
||||
cd src/spow-whatsapp-bridge
|
||||
nix develop --command sh check.sh
|
72
.github/workflows/publish-slsa3-auto.yml
vendored
72
.github/workflows/publish-slsa3-auto.yml
vendored
|
@ -1,72 +0,0 @@
|
|||
name: automated tagged release with slsa 3 compliance
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
outputs:
|
||||
hashes: ${{ steps.hash.outputs.hashes }}
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: get branch name
|
||||
id: get-branch-name
|
||||
uses: tj-actions/branch-names@v7
|
||||
|
||||
- name: install devbox
|
||||
uses: jetpack-io/devbox-install-action@v0.6.1
|
||||
|
||||
- name: install dependencies
|
||||
run: devbox run poetry install
|
||||
|
||||
- name: run releaser.py
|
||||
run: devbox run python releaser.py
|
||||
env:
|
||||
SURPLUS_BUILD_BRANCH: ${{ steps.get-branch-name.outputs.base_ref_branch }}
|
||||
|
||||
- name: build project
|
||||
id: build
|
||||
run: devbox run poetry build
|
||||
|
||||
- name: duplicate non-versioned wheel
|
||||
run: cp dist/surplus-*.whl dist/surplus-latest-py3-none-any.whl
|
||||
|
||||
- name: generate provenance subjects
|
||||
id: hash
|
||||
run: |
|
||||
cd dist
|
||||
HASHES=$(sha256sum * | base64 -w0)
|
||||
echo "hashes=$HASHES" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist/
|
||||
|
||||
- name: release
|
||||
uses: softprops/action-gh-release@v0.1.15
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
|
||||
provenance:
|
||||
needs: [build]
|
||||
permissions:
|
||||
actions: read
|
||||
id-token: write
|
||||
contents: write
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.6.0
|
||||
with:
|
||||
base64-subjects: "${{ needs.build.outputs.hashes }}"
|
||||
upload-assets: true
|
70
.github/workflows/publish-slsa3-manual.yml
vendored
70
.github/workflows/publish-slsa3-manual.yml
vendored
|
@ -1,70 +0,0 @@
|
|||
name: manual release with slsa 3 compliance
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
outputs:
|
||||
hashes: ${{ steps.hash.outputs.hashes }}
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: get branch name
|
||||
id: get-branch-name
|
||||
uses: tj-actions/branch-names@v7
|
||||
|
||||
- name: install devbox
|
||||
uses: jetpack-io/devbox-install-action@v0.6.1
|
||||
|
||||
- name: install dependencies
|
||||
run: devbox run poetry install
|
||||
|
||||
- name: run releaser.py
|
||||
run: devbox run python releaser.py
|
||||
env:
|
||||
SURPLUS_BUILD_BRANCH: ${{ steps.get-branch-name.outputs.base_ref_branch }}
|
||||
|
||||
- name: build project
|
||||
id: build
|
||||
run: devbox run poetry build
|
||||
|
||||
- name: duplicate non-versioned wheel
|
||||
run: cp dist/surplus-*.whl dist/surplus-latest-py3-none-any.whl
|
||||
|
||||
- name: generate provenance subjects
|
||||
id: hash
|
||||
run: |
|
||||
cd dist
|
||||
HASHES=$(sha256sum * | base64 -w0)
|
||||
echo "hashes=$HASHES" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: release
|
||||
uses: softprops/action-gh-release@v0.1.15
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist/
|
||||
|
||||
provenance:
|
||||
needs: [build]
|
||||
permissions:
|
||||
actions: read
|
||||
id-token: write
|
||||
contents: write
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.6.0
|
||||
with:
|
||||
base64-subjects: "${{ needs.build.outputs.hashes }}"
|
||||
upload-assets: true
|
11
.gitignore
vendored
11
.gitignore
vendored
|
@ -1,7 +1,18 @@
|
|||
.idea/
|
||||
old/*
|
||||
|
||||
# cached files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.cache/
|
||||
|
||||
# documentation
|
||||
docs/spow.sh
|
||||
docs/termux.sh
|
||||
docs/whatsapp.sh
|
||||
docs/telegram.sh
|
||||
/site
|
||||
|
||||
# distribution
|
||||
.Python
|
||||
|
|
2
CONTRIBUTORS
Normal file
2
CONTRIBUTORS
Normal file
|
@ -0,0 +1,2 @@
|
|||
Mark Joshwel <mark@joshwel.co>
|
||||
shamsu07 <shamsuddeenks@gmail.com>
|
16
devbox.json
16
devbox.json
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"packages": [
|
||||
"python311",
|
||||
"python311Packages.ipykernel",
|
||||
"poetry"
|
||||
],
|
||||
"shell": {
|
||||
"init_hook": [
|
||||
"poetry env use $(which python)",
|
||||
"poetry shell"
|
||||
]
|
||||
},
|
||||
"nixpkgs": {
|
||||
"commit": "f80ac848e3d6f0c12c52758c0f25c10c97ca3b62"
|
||||
}
|
||||
}
|
15
devbox.lock
15
devbox.lock
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"lockfile_version": "1",
|
||||
"packages": {
|
||||
"poetry": {
|
||||
"resolved": "github:NixOS/nixpkgs/f80ac848e3d6f0c12c52758c0f25c10c97ca3b62#poetry"
|
||||
},
|
||||
"python311": {
|
||||
"plugin_version": "0.0.1",
|
||||
"resolved": "github:NixOS/nixpkgs/f80ac848e3d6f0c12c52758c0f25c10c97ca3b62#python311"
|
||||
},
|
||||
"python311Packages.ipykernel": {
|
||||
"resolved": "github:NixOS/nixpkgs/f80ac848e3d6f0c12c52758c0f25c10c97ca3b62#python311Packages.ipykernel"
|
||||
}
|
||||
}
|
||||
}
|
121
docs/CC0
Normal file
121
docs/CC0
Normal file
|
@ -0,0 +1,121 @@
|
|||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
325
docs/changelog.md
Normal file
325
docs/changelog.md
Normal file
|
@ -0,0 +1,325 @@
|
|||
# changelog
|
||||
|
||||
## surplus v2024.0.0
|
||||
|
||||
(unreleased)
|
||||
|
||||
!!! information
|
||||
this is a tentative release. surplus is currently versioned as `2024.0.0-beta`, as its
|
||||
behaviour is not stabilized
|
||||
|
||||
!!! warning
|
||||
this is an api-breaking release. see 'the great api break'.
|
||||
command-line usage of surplus has not changed
|
||||
|
||||
### what's new
|
||||
|
||||
- added flag `--show-user-agent`, printing the fingerprinted user agent string and exiting
|
||||
|
||||
### what's changed
|
||||
|
||||
- `default_geocoder()` and `default_reverser()` have been deprecated since v2.1.0 and are now
|
||||
removed. use the `SurplusDefaultGeocoding` class instead
|
||||
- `SurplusException` is now `SurplusError`
|
||||
|
||||
### the great api break
|
||||
|
||||
TODO
|
||||
|
||||
### thanks!
|
||||
|
||||
- [vlshields](https://github.com/vlshields/) for their support with a drink!
|
||||
|
||||
full changelog: <https://github.com/markjoshwel/surplus/compare/v2.2.0...v2024.0.0>
|
||||
|
||||
## surplus on wheels v2
|
||||
|
||||
(released on the 1st of July 2024 on tag `v2.2024.25+spow`)
|
||||
|
||||
### changes
|
||||
|
||||
- you can now customize command invocations with `SURPLUS_CMD` and `LOCATION_CMD` environment variables
|
||||
- surplus on wheels will purge logs when setting the `SPOW_PRIVATE` environment flag
|
||||
|
||||
### thanks!
|
||||
|
||||
- [vlshields](https://github.com/vlshields/) for their support with a drink!
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels: WhatsApp Bridge v2.2024.25
|
||||
|
||||
(released on the 17th of June 2024 on tag `v2.2024.25+spow-whatsapp-bridge`)
|
||||
|
||||
!!! note
|
||||
from henceforth, the WhatsApp Bridge is now versioned with a modified calendar versioning scheme
|
||||
of `MAJOR.YEAR.ISOWEEK`, where the `MAJOR` version segment will be bumped with codebase changes,
|
||||
whereas the `YEAR` and `ISOWEEK` segments will represent the time of which the release was
|
||||
built at
|
||||
|
||||
### changes
|
||||
|
||||
- updated dependencies to latest versions
|
||||
- added `pair-phone` and `reconnect` subcommands
|
||||
- TODO added optional helper script to auto-update to newer versions via a user-made daily cron job
|
||||
|
||||
### thanks!
|
||||
|
||||
- [vlshields](https://github.com/vlshields/) for their support with a drink!
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels: Telegram Bridge v2.2024.25
|
||||
|
||||
(released on the 17th of June 2024 on tag `v2.2024.25+spow-telegram-bridge`)
|
||||
|
||||
!!! note
|
||||
from henceforth, the Telegram Bridge will automatically release a new version once a week if
|
||||
there are updates to its dependencies
|
||||
|
||||
as such, the bridge is now versioned with a modified calendar versioning scheme of
|
||||
`MAJOR.YEAR.ISOWEEK`, where the `MAJOR` version segment will be bumped with codebase changes,
|
||||
whereas the `YEAR` and `ISOWEEK` segments will represent the time of which the release was
|
||||
built at
|
||||
|
||||
### changes
|
||||
|
||||
- updated dependencies to latest versions
|
||||
- added `logout` subcommand
|
||||
- TODO added optional helper script to auto-update to newer versions via a user-made daily cron job
|
||||
|
||||
### thanks!
|
||||
|
||||
- [vlshields](https://github.com/vlshields/) for their support with a drink!
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels v1
|
||||
|
||||
initial release on the 9th of November 2023
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels: WhatsApp Bridge v1
|
||||
|
||||
initial release on the 7th of November 2023
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels: Telegram Bridge v1
|
||||
|
||||
initial release on the 7th of November 2023
|
||||
|
||||
---
|
||||
|
||||
## surplus v2.2.0
|
||||
|
||||
(released on the 14th of October 2023)
|
||||
|
||||
!!! warning
|
||||
constants are changed in this update!
|
||||
|
||||
fixed a bug installing surplus on Python 3.12 and italian sharetext fixes
|
||||
|
||||
### what's new
|
||||
|
||||
- special key arrangements for malaysia
|
||||
- support for termux-location json input
|
||||
|
||||
### what's fixed
|
||||
|
||||
- fixed typing-extensions as an unwritten dependency
|
||||
this also fixes a bug in not being able to run surplus in Python 3.12
|
||||
- fixed italian key arrangements [#34](https://github.com/markjoshwel/surplus/pull/34)
|
||||
|
||||
### what's changed
|
||||
|
||||
- **`SHAREABLE*` constants are now dictionaries, see api docs for more information**
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v2.1.1...v2.2.0>
|
||||
|
||||
---
|
||||
|
||||
## surplus v2.1.1
|
||||
|
||||
(released on the 19th of September 2023)
|
||||
|
||||
fix roads not coming first in Italian addresses (#31)
|
||||
|
||||
- documentation enhancements
|
||||
- remove self in `SurplusReverserProtocol` conforming signature
|
||||
- fix mismatching carets and add info on `split_iso3166_2`
|
||||
- alternative line 3 arrangement for IT/Italy in [#31](https://github.com/markjoshwel/surplus/pull/31)
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v2.1.0...v2.1.1>
|
||||
|
||||
---
|
||||
|
||||
## surplus v2.1.0
|
||||
|
||||
(released on the 6th of September 2023)
|
||||
|
||||
!!! warning
|
||||
there are backwards-compatible api changes in this release.
|
||||
|
||||
type-to-type location representation conversions and quality of life changes/fixes
|
||||
|
||||
- **`default_geocoder()` and `default_reverser()` functions have been deprecated in favour of the
|
||||
new [`SurplusDefaultGeocoding` class](https://github.com/markjoshwel/surplus/tree/main#class-surplusdefaultgeocoding)**
|
||||
- add reading from stdin when query is "-" in [#23](https://github.com/markjoshwel/surplus/pull/23)
|
||||
- type to type conversion in [#24](https://github.com/markjoshwel/surplus/pull/24)
|
||||
- fix local codes not being recognised if split with comma in [#29](https://github.com/markjoshwel/surplus/pull/29)
|
||||
- more verbose -v/--version information in [#21](https://github.com/markjoshwel/surplus/pull/21)
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v2.0.1...v2.1.0>
|
||||
|
||||
---
|
||||
|
||||
## surplus v2.0.1
|
||||
|
||||
(released on the 5th of September 2023)
|
||||
|
||||
- expose surplus.Result in `__init__.py` by in [#28](https://github.com/markjoshwel/surplus/pull/28)
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v2.0.0...v2.0.1>
|
||||
|
||||
---
|
||||
|
||||
## surplus v2.0.0
|
||||
|
||||
(released on the 3rd of September 2023)
|
||||
|
||||
!!! warning
|
||||
this is an api-breaking release. see 'the great api break'.
|
||||
command-line usage of surplus has not changed
|
||||
|
||||
!!! information
|
||||
python 3.11 or later is required due to a bug in earlier versions
|
||||
[(python/cpython#88089)](https://github.com/python/cpython/issues/88089)
|
||||
|
||||
complete rewrite and string query support
|
||||
|
||||
### changes
|
||||
|
||||
- surplus has been fully rewritten in [#19](https://github.com/markjoshwel/surplus/pull/19)
|
||||
- support for string queries
|
||||
```text
|
||||
$ s+ Wisma Atria
|
||||
surplus version 2.0.0
|
||||
Wisma Atria
|
||||
435 Orchard Road
|
||||
238877
|
||||
Central, Singapore
|
||||
```
|
||||
- mypy will now recognise surplus as a typed module
|
||||
- **python 3.11 is now the minimum version**
|
||||
|
||||
### the great api break
|
||||
|
||||
#### what is new
|
||||
|
||||
- nominatim keys are now stored in tuple constants
|
||||
- surplus exception classes are now a thing
|
||||
- surplus functions now operate using a unified `Behaviour` object
|
||||
- surplus functions now return a `Result` object for safer value retrieval instead of the previous
|
||||
`(bool, value)` tuple
|
||||
- dedicated NamedTuple classes for each query type
|
||||
|
||||
#### what has been removed
|
||||
|
||||
- `surplus.handle_query()`
|
||||
|
||||
instead, use `.to_lat_long_coord()` on your surplus 2.x query object
|
||||
|
||||
#### what has remained
|
||||
|
||||
- `surplus.surplus()`, the function
|
||||
- `surplus.parse_query()`, the function
|
||||
|
||||
#### what has changed
|
||||
|
||||
- `surplus.surplus()`
|
||||
1. `reverser` and `debug` arguments are now under the unified `surplus.Behaviour` object
|
||||
2. function now returns a `surplus.Result[str]` for safer error handling
|
||||
|
||||
- `surplus.parse_query()`
|
||||
1. `query` and `debug` arguments are now under the unified `surplus.Behaviour` object
|
||||
2. function now returns a `surplus.Result[surplus.Query]` for safer error handling
|
||||
|
||||
- `surplus.Latlong`
|
||||
attributes `lat` and `long` have been renamed to `latitude` and `longitude` respectively
|
||||
|
||||
- `surplus.Localcode`
|
||||
renamed to `surplus.LocalCodeQuery`
|
||||
|
||||
- `Localcode.full_length()`
|
||||
renamed to `LocalCodeQuery.to_full_plus_code()`, and returns a `surplus.Result[str]` for safer
|
||||
error handling
|
||||
|
||||
full changelog: <https://github.com/markjoshwel/surplus/compare/v1.1.3...v2.0.0>
|
||||
|
||||
## surplus v1.1.3
|
||||
|
||||
(released on the 21st of June 2023)
|
||||
|
||||
general output fixes and quality of life updates
|
||||
|
||||
- ci(qc) workflow tweaks by [markjoshwel](https://github.com/markjoshwel) in [#13](https://github.com/markjoshwel/surplus/pull/13)
|
||||
- cc: remove woodlands test + brackets by [markjoshwel](https://github.com/markjoshwel) in [#14](https://github.com/markjoshwel/surplus/pull/14)
|
||||
- s+: display county before state by [markjoshwel](https://github.com/markjoshwel) in [#15](https://github.com/markjoshwel/surplus/pull/15)
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v1.1.2...v1.1.3>
|
||||
|
||||
---
|
||||
|
||||
## surplus v1.1.2
|
||||
|
||||
(released on the 18th of June 2023)
|
||||
|
||||
general output fixes and quality of life updates
|
||||
|
||||
- do not repeat details by [markjoshwel](https://github.com/markjoshwel) in #9
|
||||
- add -v/--version flag by [markjoshwel](https://github.com/markjoshwel) in #11
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v1.1.0...v1.1.1>
|
||||
|
||||
---
|
||||
|
||||
## surplus v1.1.1
|
||||
|
||||
(released on the 16th of June 2023)
|
||||
|
||||
### changes
|
||||
|
||||
fixes and output tweaks
|
||||
|
||||
- fix reverser returning a None location by [shamsu07](https://github.com/shamsu07) in #5
|
||||
|
||||
### thanks!
|
||||
|
||||
- [shamsu07](https://github.com/shamsu07) made their first contribution!
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v1.1.0...v1.1.1>
|
||||
|
||||
---
|
||||
|
||||
## surplus v1.1.0
|
||||
|
||||
(released on the 3rd of June 2023)
|
||||
|
||||
short code and latitude longitude coordinate pair support!
|
||||
|
||||
- code: s+ alternative shorthand script
|
||||
- code: handle none/list locations
|
||||
- code: query by lat long support
|
||||
- code: support shortcodes with localities
|
||||
- code: implement more address detail tags from nominatim
|
||||
- meta: slsa 3 compliance
|
||||
|
||||
<https://github.com/markjoshwel/surplus/compare/v1.0.0...v1.1.0>
|
||||
|
||||
---
|
||||
|
||||
## surplus v1.0.0
|
||||
|
||||
initial release on the 2nd of June 2023
|
80
docs/contributing.md
Normal file
80
docs/contributing.md
Normal file
|
@ -0,0 +1,80 @@
|
|||
# the contributor's handbook
|
||||
|
||||
expected details on development workflows? see [the developer's handbook](developing.md)
|
||||
|
||||
## which forge do i use?
|
||||
|
||||
as at the time of writing this documentation, i am actively using both
|
||||
<https://github.com/markjoshwel/surplus> and <https://forge.joshwel.co/mark/surplus>
|
||||
|
||||
use whatever is more comfortable to you. do you not like microsoft and/or have moved away from github?
|
||||
feel free to use <https://forge.joshwel.co>. don't want to make an account for either? did the forge
|
||||
implode and is down? okay! mail in a git patch at <mark@joshwel.co>
|
||||
|
||||
## git workflow
|
||||
|
||||
1. fork the repository and branch off from the `future` branch,
|
||||
or `main` if not available
|
||||
2. make and commit your changes!
|
||||
3. pull in any changes from upstream, and resolve any conflicts, if any
|
||||
4. if needed, **commit your copyright waiver** (_see [waiving copyright](#waiving-copyright)_)
|
||||
5. submit a pull request (_or mail in a patch_)
|
||||
|
||||
### waiving copyright
|
||||
|
||||
!!! danger "Warning"
|
||||
this section is a **must** to follow if you have modified **any** unlicenced code:
|
||||
|
||||
- top-level surplus files (`releaser.py`, etc)
|
||||
- surplus (`src/surplus`)
|
||||
- surplus Documentation (`docs/`)
|
||||
- surplus on wheels (`src/surplus-on-wheels`)
|
||||
- surplus on wheels: Telegram Bridge (`src/spow-telegram-bridge`)
|
||||
|
||||
!!! info
|
||||
the command to create an empty commit is `git commit --allow-empty`
|
||||
|
||||
when contributing your first changes, please include an empty commit for a copyright
|
||||
waiver using the following message (replace `Your Name` with your name or username):
|
||||
|
||||
```text
|
||||
Your Name Copyright Waiver
|
||||
|
||||
I dedicate any and all copyright interest in this software to the
|
||||
public domain. I make this dedication for the benefit of the public at
|
||||
large and to the detriment of my heirs and successors. I intend this
|
||||
dedication to be an overt act of relinquishment in perpetuity of all
|
||||
present and future rights to this software under copyright law.
|
||||
|
||||
To the best of my knowledge and belief, my contributions are either
|
||||
originally authored by me or are derived from prior works which I have
|
||||
verified are also in the public domain and are not subject to claims
|
||||
of copyright by other parties.
|
||||
|
||||
To the best of my knowledge and belief, no individual, business,
|
||||
organization, government, or other entity has any copyright interest
|
||||
in my contributions, and I affirm that I will not make contributions
|
||||
that are otherwise encumbered.
|
||||
```
|
||||
|
||||
(from <https://unlicense.org/WAIVER>)
|
||||
|
||||
for documentation contributors, if you have contributed a
|
||||
[legally significant](https://www.gnu.org/prep/maintain/maintain.html#Legally-Significant) or have
|
||||
repeatedly commited multiple small changes, waive your copyright with the CC0 deed
|
||||
(replace `Your Name` with your name or username):
|
||||
|
||||
```text
|
||||
Your Name Copyright Waiver
|
||||
|
||||
The person who associated a work with this deed has dedicated the work to
|
||||
the public domain by waiving all of his or her rights to the work worldwide
|
||||
under copyright law, including all related and neighboring rights, to the
|
||||
extent allowed by law.
|
||||
```
|
||||
|
||||
(from <https://creativecommons.org/publicdomain/zero/1.0/>)
|
||||
|
||||
## reporting incorrect output
|
||||
|
||||
TODO
|
337
docs/developing.md
Normal file
337
docs/developing.md
Normal file
|
@ -0,0 +1,337 @@
|
|||
# the developer’s handbook
|
||||
|
||||
!!! abstract
|
||||
i (mark), heavily use nix to manage my projects, either with
|
||||
[devbox](https://github.com/jetpack-io/devbox) or flakes
|
||||
|
||||
if you are going to develop for surplus or its' sibling projects (except surplus on wheels,
|
||||
which only needs `shfmt` and `shellcheck`), i would recommend you install Nix using
|
||||
[Determinate Systems' Nix Installer](https://github.com/DeterminateSystems/nix-installer):
|
||||
|
||||
```text
|
||||
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
||||
```
|
||||
|
||||
if i, a very very inexperienced Nix and NixOS user to give a rundown on why to use it:
|
||||
|
||||
1. **environments and builds are reproducible**
|
||||
|
||||
nix is part package manager, operating system (as NixOS), and functional programming
|
||||
language. because it's functional, having X as an input will always produce Y as an output,
|
||||
no matter what. even in the event of environmental, social, economic, or structural collapse
|
||||
|
||||
**what does this mean for you?**
|
||||
when i use `nix develop` and you use `nix develop` to start a development environment, your
|
||||
version of python will be the same as my version of python. your version of go will be same
|
||||
as my version of go, etc
|
||||
|
||||
if i can build it, and the locked inputs of the nix flake are still available on the internet,
|
||||
you can build it too
|
||||
|
||||
2. **the nix store**, located literally at `/nix/store`
|
||||
it's where it stores every package you need, separate and isolated from other packages.
|
||||
lets say you have a tool that needs python (3.8), and another tool that needs python (3.11).
|
||||
nix store will download and store the binaries for both python installations, instead of
|
||||
sharing the earliest downloaded python version for both tools
|
||||
|
||||
**what does this mean for you?**
|
||||
whatever project you're working on that can use nix for development environments and builds
|
||||
will not dirty anything else on your system. any build dependencies of surplus provided with
|
||||
`nix develop` will **not** mess up software installed for other projects or even the system.
|
||||
neat, if you ask me tbh
|
||||
|
||||
**tl;dr**: things will just werk with nix. but if you see all of this and go, "eh. i can manage
|
||||
what i install.", then power to you! i list down exactly what prerequisite software needs to be
|
||||
installed for each project anyways, so have fun! (●'◡'●)
|
||||
|
||||
## surplus (and Documentation)
|
||||
|
||||
### environment setup
|
||||
|
||||
!!! note
|
||||
all prerequisite software are available in a nix flake. if you want a reproducible environment,
|
||||
run `nix develop --impure` in the repository root
|
||||
|
||||
- for NixOS users, [nix-ld](https://github.com/Mic92/nix-ld) is needed. if you use flakes to
|
||||
declare your system, follow accordingly. else, use `/etc/nixos/configuration.nix`
|
||||
|
||||
for NixOS-on-WSL users, use [nix-ld-rs](https://github.com/nix-community/nix-ld-rs)
|
||||
|
||||
once you're done installing `nix-ld` or `nix-ld-rs`,
|
||||
don't forget to run `sudo nixos-rebuild switch`
|
||||
|
||||
prerequisite software:
|
||||
|
||||
- [Python](https://www.python.org/downloads/), 3.11 or newer
|
||||
- [Hatch](https://hatch.pypa.io/latest/): dependency management and build tool
|
||||
|
||||
to start a development environment:
|
||||
|
||||
```text
|
||||
hatch shell
|
||||
```
|
||||
|
||||
for docs:
|
||||
|
||||
```text
|
||||
hatch -e docs shell
|
||||
```
|
||||
|
||||
### workflow for python code
|
||||
|
||||
TODO
|
||||
|
||||
### workflow for markdown documentation
|
||||
|
||||
run the documentation server with:
|
||||
|
||||
```text
|
||||
hatch run docs:serve
|
||||
```
|
||||
|
||||
i personally don't use a linter for markdown files, if it looks good on my code editor, then
|
||||
whatever. if you're going to contribute back, i ask for three things:
|
||||
|
||||
- run it through a spell checker or something similar
|
||||
|
||||
- line limit of 100
|
||||
|
||||
- should be readable as-is on a code editor, **not the markdown preview pane**.
|
||||
|
||||
my stance is, if you can afford a fancy preview of the markdown file, use the nice-ned
|
||||
documentation website. else, read it as a plaintext file
|
||||
|
||||
(make it look pretty on the doc site and in plaintext)
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels
|
||||
|
||||
### environment setup
|
||||
|
||||
!!! note
|
||||
all prerequisite software are available in a nix flake. if you want a reproducible environment,
|
||||
run `nix develop` in `src/surplus-on-wheels`
|
||||
|
||||
prerequisite software:
|
||||
|
||||
- [shfmt](https://github.com/patrickvane/shfmt): formatter
|
||||
- [ShellCheck](https://www.shellcheck.net/): static analyser
|
||||
|
||||
### workflow
|
||||
|
||||
!!! note
|
||||
alternatively, run `check.sh` inside `src/surplus-on-wheels`
|
||||
|
||||
- formatting s+ow:
|
||||
- run `shfmt s+ow > s+ow.new`
|
||||
- mv `s+ow.new` into `s+ow`
|
||||
sometimes when piping shfmt's output immediately into the same file
|
||||
results in the file being empty :(
|
||||
|
||||
- checking s+ow:
|
||||
- run `shellcheck s+ow`
|
||||
if there's no output, that means it passed :)
|
||||
|
||||
- if commiting back into the repository, try it out on your Termux system for a day or two,
|
||||
just to make sure it runs correctly
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels: Telegram Bridge
|
||||
|
||||
### environment setup
|
||||
|
||||
!!! note
|
||||
all prerequisite software are available in a nix flake. if you want a reproducible environment,
|
||||
run `nix develop` in `src/spow-telegram-bridge`. it uses
|
||||
[poetry2nix](https://github.com/nix-community/poetry2nix), so you won't need to run
|
||||
`poetry shell` afterwards. if you've changed the `pyproject.toml` file,
|
||||
just exit and re-run `nix develop`
|
||||
|
||||
prerequisite software:
|
||||
|
||||
- [Python](https://www.python.org/downloads/), 3.11 or newer
|
||||
- [Poetry](https://python-poetry.org/): dependency management and build tool
|
||||
|
||||
to start a development environment:
|
||||
|
||||
```text
|
||||
poetry shell
|
||||
```
|
||||
|
||||
### workflow
|
||||
|
||||
after modifying,
|
||||
|
||||
1. check the source code:
|
||||
1. `mypy bridge.py`
|
||||
2. `ruff format bridge.py`
|
||||
3. `ruff check bridge.py`
|
||||
|
||||
!!! note
|
||||
alternatively, run `check.sh` inside `src/spow-telegram-bridge`
|
||||
|
||||
2. and then [test the binary](#workflow-for-testing-the-binary)
|
||||
|
||||
if the bridge behaves nominally, [bump the version](#versioning-surplus-on-wheels-telegram-bridge)
|
||||
and commit!
|
||||
|
||||
---
|
||||
|
||||
## surplus on wheels: WhatsApp Bridge
|
||||
|
||||
### environment setup
|
||||
|
||||
!!! note
|
||||
all prerequisite software are available in a nix flake. if you want a reproducible environment,
|
||||
run `nix develop` in `src/spow-whatsapp-bridge`
|
||||
|
||||
the flake will pull in the Android SDK and NDK for building on Termux, and as such can only be
|
||||
ran on `x86_64-linux` and `x86_64-darwin`
|
||||
|
||||
prerequisite software:
|
||||
|
||||
- [Go](https://go.dev): 1.22 or newer
|
||||
- [Android NDK](https://developer.android.com/ndk/downloads), if building for Termux
|
||||
|
||||
### workflow for modifying bridge code
|
||||
|
||||
the bridge's code is just modified [mdtest](https://github.com/tulir/whatsmeow/tree/main/mdtest)
|
||||
code, and as such, whenever in doubt, do a diff between mdtest and the bridge code
|
||||
|
||||
after modifying,
|
||||
|
||||
1. check the source code:
|
||||
1. `go fmt bridge.go`
|
||||
2. `go vet bridge.go`
|
||||
3. `golint bridge.go`
|
||||
|
||||
!!! note
|
||||
alternatively, run `check.sh` inside `src/spow-whatsapp-bridge`
|
||||
|
||||
2. [build a binary](#workflow-for-building-a-binary)
|
||||
|
||||
3. [test the binary](#workflow-for-testing-the-binary)
|
||||
|
||||
4. and if all goes well, [bump the version](#versioning-surplus-on-wheels-whatsapp-bridge)
|
||||
and commit!
|
||||
|
||||
### workflow for bumping dependencies
|
||||
|
||||
- check with your editor, plugin, or online if there's newer patch/minor (see
|
||||
[semantic versioning](https://semver.org/)) versions to update to
|
||||
|
||||
- change the `go.mod` accordingly
|
||||
|
||||
after bumping,
|
||||
|
||||
1. [build a binary](#workflow-for-building-a-binary)
|
||||
2. [test the binary](#workflow-for-testing-the-binary)
|
||||
3. and if all goes well, [bump the version](#versioning-surplus-on-wheels-whatsapp-bridge)
|
||||
and commit!
|
||||
|
||||
### workflow for building a binary
|
||||
|
||||
ensure you already have c compiler on the system (if you're using `nix develop` then yes you do), then run:
|
||||
|
||||
```text
|
||||
CGO_ENABLED=1 go build
|
||||
```
|
||||
|
||||
nix users can alternatively run:
|
||||
|
||||
```text
|
||||
nix build
|
||||
```
|
||||
|
||||
instructions to build a Termux build are located at the
|
||||
[bridges' documentation page](onwheels/whatsapp-bridge.md#anywhere-else), however nix users can run
|
||||
the following instead for a reproducible, deterministic and hermetic build command:
|
||||
|
||||
```text
|
||||
nix build .#termux
|
||||
```
|
||||
the resulting build will be in `result/spow-whatsapp-bridge`
|
||||
|
||||
### workflow for testing the binary
|
||||
|
||||
- test it out, making sure that you write dummy test text to `~/.cache/s+ow/message` before running
|
||||
the binary
|
||||
|
||||
1. run `s+ow-whatsapp-bridge login` first
|
||||
|
||||
2. run `s+ow-whatsapp-bridge list` if you don't already have a chat ID
|
||||
to send the test message to
|
||||
|
||||
3. run `s+ow-whatsapp-bridge` type or copy and paste in a `wa:`-prefixed chat ID
|
||||
after it logs in, and verify it sends
|
||||
|
||||
if the bridge behaves nominally, [bump the version](#versioning-surplus-on-wheels-whatsapp-bridge)
|
||||
and commit!
|
||||
|
||||
## workflow for versioning and tagging releases
|
||||
|
||||
### versioning surplus
|
||||
|
||||
format: `YEAR.MAJOR.MINOR[-PRERELEASE]` ([semantic versioning](https://semver.org/))
|
||||
example: `2024.0.0`, `2024.0.0-beta`
|
||||
change: update the `__version__` variable in `src/surplus/surplus.py`
|
||||
|
||||
### versioning surplus on wheels
|
||||
|
||||
i've tried to make surplus on wheels as reliable as it could be given a POSIX compliant shell and
|
||||
commands you'd find available on virtually every linux system, Termux included
|
||||
|
||||
as such, it doesn't really follow a versioning scheme as it doesn't need to. also there's no
|
||||
automatic updater for it, which would be overkill anyway
|
||||
|
||||
### versioning surplus on wheels: Telegram Bridge
|
||||
|
||||
format: `REVISION.YYYY.WW[+BUILD]` ([calendar versioning](https://calver.org/))
|
||||
example: `2.2024.24`, `2.2024.24+1`
|
||||
change: `version` key in `src/spow-telegram-bridge/pyproject.toml`
|
||||
|
||||
`REVISION` here meaning any general revision/change
|
||||
|
||||
the Telegram Bridge relies on [Telethon](https://github.com/LonamiWebs/Telethon/), which also
|
||||
follows [semantic versioning](https://semver.org/). so, as long as major isn't bumped, or
|
||||
as long as Telegram doesn't become Discord, the MTProto APIs to talk to Telegram should be
|
||||
stable.
|
||||
|
||||
however because Telethon also relies on a bunch of networking libraries, it made some sense to
|
||||
still do weekly builds to bump dependencies, getting pipx to download the newest compatible
|
||||
dependencies as compared to dubiously running some sort of script to `pipx inject` dependencies
|
||||
|
||||
under normal circumstances, a non-working version of the bridge would and **should not have a
|
||||
version bump**. but for any reason if an already tagged bridge is faulty and/or erroneous in
|
||||
normal/expected usage, add a revision number to the end after a period (see example above)
|
||||
|
||||
### versioning surplus on wheels: WhatsApp Bridge
|
||||
|
||||
format: `REVISION.YYYY.WW[+BUILD]` ([calendar versioning](https://calver.org/))
|
||||
example: `2.2024.25`, `2.2024.25+1`
|
||||
change: `version` attribute of `bridge` attribute set in `src/spow-whatsapp-bridge/flake.nix`
|
||||
|
||||
`REVISION` here meaning any general revision/change
|
||||
|
||||
the WhatsApp Bridge relies on [whatsmeow](https://github.com/tulir/whatsmeow), a rolling release
|
||||
library due to the volatile, undocumented nature of WhatsApp's multidevice API and also directly
|
||||
and indirectly relies on a bunch of networking libraries:
|
||||
|
||||
``` title="src/spow-whatsapp-bridge/go.mod"
|
||||
--8<-- "src/spow-whatsapp-bridge/go.mod"
|
||||
```
|
||||
|
||||
as such, it uses a calendar versioning scheme and is built weekly
|
||||
|
||||
under normal circumstances, a non-working version of the bridge would and **should not have a
|
||||
version bump**. but for any reason if an already tagged bridge is faulty and/or erroneous in
|
||||
normal/expected usage, add a revision number to the end after a period (see example above)
|
||||
|
||||
---
|
||||
|
||||
## i've made my changes. what now?
|
||||
|
||||
if you're contributing back to surplus and/or the sibling projects, firstly, thanks!
|
||||
see [the contributor's handbook](contributing.md) for what's next
|
BIN
docs/fonts/Geist-Regular.ttf
Normal file
BIN
docs/fonts/Geist-Regular.ttf
Normal file
Binary file not shown.
BIN
docs/fonts/GeistMono-Regular.ttf
Normal file
BIN
docs/fonts/GeistMono-Regular.ttf
Normal file
Binary file not shown.
BIN
docs/fonts/GeistMonoVF.woff2
Normal file
BIN
docs/fonts/GeistMonoVF.woff2
Normal file
Binary file not shown.
BIN
docs/fonts/GeistVF.woff2
Normal file
BIN
docs/fonts/GeistVF.woff2
Normal file
Binary file not shown.
92
docs/fonts/LICENSE.txt
Normal file
92
docs/fonts/LICENSE.txt
Normal file
|
@ -0,0 +1,92 @@
|
|||
Copyright (c) 2023 Vercel, in collaboration with basement.studio
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION AND CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
42
docs/index.md
Normal file
42
docs/index.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# surplus (s+)
|
||||
|
||||
surplus (s+) is a Python script to convert [Google Maps Plus Codes](https://maps.google.com/pluscodes/)
|
||||
to iOS Shortcuts-like shareable text
|
||||
|
||||
!!! tip
|
||||
termux users can consider [surplus on wheels](onwheels/index.md), a sibling project that allows
|
||||
you to run surplus regularly throughout the day and send it to someone on a messaging platform
|
||||
|
||||
!!! important
|
||||
python 3.11 or later is required due to a bug in earlier versions
|
||||
[(python/cpython#88089)](https://github.com/python/cpython/issues/88089)
|
||||
|
||||
install surplus with pip, or [pipx](https://pipx.pypa.io/) (recommended):
|
||||
|
||||
```text
|
||||
pipx install surplus
|
||||
```
|
||||
|
||||
then, use the `surplus` command, or its `s+` shorthand:
|
||||
|
||||
```text
|
||||
$ s+ 7RGX+GJ Singapore
|
||||
surplus version 2024.0.0
|
||||
Singapore Conference Hall
|
||||
7 Shenton Way
|
||||
068809
|
||||
Central, Singapore
|
||||
```
|
||||
|
||||
the types of queries you can pass in are:
|
||||
|
||||
- full-length Plus Codes
|
||||
`6PH58QMF+FX`
|
||||
- shortened Plus Codes / 'local codes'
|
||||
`8QMF+FX Singapore`
|
||||
- latitude and longitude coordinate pairs
|
||||
`1.3336875, 103.7749375`
|
||||
- string queries
|
||||
`Wisma Atria`
|
||||
|
||||
or, alternatively pass in `-` to read from stdin
|
113
docs/licences.md
Normal file
113
docs/licences.md
Normal file
|
@ -0,0 +1,113 @@
|
|||
# licences
|
||||
|
||||
## [surplus](index.md)
|
||||
|
||||
**The Unlicence**
|
||||
|
||||
surplus is free and unencumbered software released into the public domain:
|
||||
|
||||
``` title="src/surplus/UNLICENCE"
|
||||
--8<-- "src/surplus/UNLICENCE"
|
||||
```
|
||||
|
||||
however, the dependencies surplus relies on are licenced under different, but still permissive
|
||||
and open-source licences:
|
||||
|
||||
- [**geopy**](https://pypi.org/project/geopy/) —
|
||||
Python Geocoding Toolbox
|
||||
MIT Licence
|
||||
|
||||
- [**geographiclib**](https://pypi.org/project/geographiclib/) —
|
||||
The geodesic routines from GeographicLib
|
||||
MIT Licence
|
||||
|
||||
- [**pluscodes**](https://pypi.org/project/pluscodes/) —
|
||||
Compute Plus Codes (Open Location Codes)
|
||||
Apache 2.0
|
||||
|
||||
---
|
||||
|
||||
## [surplus on wheels](onwheels/index.md)
|
||||
|
||||
**The Unlicence**
|
||||
|
||||
surplus on wheels is free and unencumbered software released into the public domain:
|
||||
|
||||
``` title="src/surplus-on-wheels/UNLICENCE"
|
||||
--8<-- "src/surplus-on-wheels/UNLICENCE"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [surplus on wheels: WhatsApp Bridge](onwheels/whatsapp-bridge.md)
|
||||
|
||||
**Mozilla Public Licence 2.0**
|
||||
|
||||
the s+ow WhatsApp Bridge is based off of mdtest code from the
|
||||
[whatsmeow](https://github.com/tulir/whatsmeow) project, which is licenced under the Mozilla
|
||||
Public Licence 2.0:
|
||||
|
||||
``` title="src/spow-whatsapp-bridge/LICENCE"
|
||||
--8<-- "src/spow-whatsapp-bridge/LICENCE"
|
||||
```
|
||||
|
||||
the direct dependencies s+ow-whatsapp-bridge relies on are licenced under different, but still
|
||||
permissive and open-source licences:
|
||||
|
||||
- [**whatsmeow**](https://github.com/tulir/whatsmeow) —
|
||||
Go library for the WhatsApp web multidevice API
|
||||
Mozilla Public Licence 2.0
|
||||
|
||||
---
|
||||
|
||||
## [surplus on wheels: Telegram Bridge](onwheels/telegram-bridge.md)
|
||||
|
||||
**The Unlicence**
|
||||
|
||||
the s+ow Telegram Bridge is free and unencumbered software released into the public domain:
|
||||
|
||||
``` title="src/spow-telegram-bridge/UNLICENCE"
|
||||
--8<-- "src/spow-telegram-bridge/UNLICENCE"
|
||||
```
|
||||
|
||||
however, the direct dependencies surplus relies on are licenced under different, but still
|
||||
permissive and open-source licences:
|
||||
|
||||
- [**Telethon**](https://pypi.org/project/Telethon/) —
|
||||
Pure Python 3 MTProto API Telegram client library, for bots too!
|
||||
MIT Licence
|
||||
|
||||
---
|
||||
|
||||
## [surplus documentation](index.md)
|
||||
|
||||
**CC0 1.0 Universal**
|
||||
|
||||
the textual contents of surplus documentation by [Mark Joshwel](https://joshwel.co) is marked
|
||||
with [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||
to view a copy of this licence, visit <https://creativecommons.org/publicdomain/zero/1.0/>
|
||||
|
||||
``` title="docs/CC0"
|
||||
--8<-- "docs/CC0"
|
||||
```
|
||||
|
||||
the fonts the documentation website relies on are licenced under different, but still
|
||||
permissive and open-source licences:
|
||||
|
||||
- [**Geist and Geist Mono**](https://github.com/vercel/geist-font)
|
||||
SIL Open Font Licence 1.1
|
||||
|
||||
``` title="docs/fonts/LICENSE.txt"
|
||||
--8<-- "docs/fonts/LICENSE.txt"
|
||||
```
|
||||
|
||||
the direct software dependencies the documentation are also licenced under different, but still
|
||||
permissive and open-source licences:
|
||||
|
||||
- [**mkdocs-material**](https://squidfunk.github.io/mkdocs-material/) —
|
||||
Documentation that simply works
|
||||
MIT Licence
|
||||
|
||||
- [**mkdocs**](https://www.mkdocs.org/) —
|
||||
Project documentation with Markdown
|
||||
BSD-2-Clause Licence
|
69
docs/links.md
Normal file
69
docs/links.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# backup links
|
||||
|
||||
for when first-party links like <https://surplus.joshwel.co> and <https://forge.joshwel.co> are down:
|
||||
|
||||
## surplus
|
||||
|
||||
``` title="Primary Link"
|
||||
https://forge.joshwel.co/mark/surplus.git
|
||||
```
|
||||
|
||||
``` title="Alternative Link"
|
||||
https://github.com/markjoshwel/surplus.git
|
||||
```
|
||||
|
||||
## surplus on wheels
|
||||
|
||||
- shell script
|
||||
|
||||
``` title="Primary Link"
|
||||
https://surplus.joshwel.co/spow.sh
|
||||
```
|
||||
|
||||
``` title="Alternative Link"
|
||||
https://raw.githubusercontent.com/markjoshwel/surplus/main/src/surplus-on-wheels/s+ow
|
||||
```
|
||||
|
||||
- termux installation script
|
||||
|
||||
``` title="Primary Link"
|
||||
https://surplus.joshwel.co/termux.sh
|
||||
```
|
||||
|
||||
``` title="Alternative Link"
|
||||
https://raw.githubusercontent.com/markjoshwel/surplus/main/src/surplus-on-wheels/install.sh
|
||||
```
|
||||
|
||||
## surplus on wheels: Telegram Bridge
|
||||
|
||||
- install/update script:
|
||||
|
||||
``` title="Primary Link"
|
||||
https://surplus.joshwel.co/telegram.sh
|
||||
```
|
||||
|
||||
``` title="Alternative Link"
|
||||
https://raw.githubusercontent.com/markjoshwel/surplus/main/src/spow-telegram-bridge/install.sh
|
||||
```
|
||||
|
||||
- pipx target
|
||||
|
||||
``` title="Primary Link"
|
||||
git+https://forge.joshwel.co/mark/surplus.git#egg=spow-telegram-bridge&subdirectory=src/spow-telegram-bridge
|
||||
```
|
||||
|
||||
``` title="Alternative Link"
|
||||
git+https://github.com/markjoshwel/surplus.git#egg=spow-telegram-bridge&subdirectory=src/spow-telegram-bridge
|
||||
```
|
||||
|
||||
## surplus on wheels: WhatsApp Bridge
|
||||
|
||||
- install/update script:
|
||||
|
||||
``` title="Primary Link"
|
||||
https://surplus.joshwel.co/whatsapp.sh
|
||||
```
|
||||
|
||||
``` title="Alternative Link"
|
||||
https://raw.githubusercontent.com/markjoshwel/surplus/main/src/spow-whatsapp-bridge/install.sh
|
||||
```
|
58
docs/onwheels/bridges.md
Normal file
58
docs/onwheels/bridges.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
# surplus on wheel bridges
|
||||
|
||||
## official bridges
|
||||
|
||||
there are two currently “official” bridges:
|
||||
|
||||
- [surplus on wheels: WhatsApp Bridge](whatsapp-bridge.md)
|
||||
- [surplus on wheels: Telegram Bridge](telegram-bridge.md)
|
||||
|
||||
## bring your own bridge
|
||||
|
||||
### an informal specification
|
||||
|
||||
s+ow bridges are relatively simple as they are:
|
||||
|
||||
1. an executable or script
|
||||
|
||||
2. that reads in `SPOW_TARGETS` given by surplus to the bridge, using the standard input (stdin)
|
||||
stream
|
||||
|
||||
1. bridges do not need to account for the possibility of multiple lines sent to stdin
|
||||
|
||||
2. bridges should account for the possibility of comma and space (`", "` instead of just `","`)
|
||||
delimited targets, and strip each target of preceding and trailing whitespace
|
||||
|
||||
3. bridges should recognise a platform based on a prefix
|
||||
(e.g. `wa:` for WhatsApp, `tg:` for Telegram, etc.)
|
||||
|
||||
3. reads `SPOW_MESSAGE` (`~/.cache/spow/message`) for the message content
|
||||
|
||||
notes:
|
||||
|
||||
1. stderr and stdout are redirected to s+ow’s error and output logs respectively unless the
|
||||
`-p / --private` flag is passed to surplus
|
||||
|
||||
2. any errors encountered by the bridge should always result in a non-zero return. error logs will
|
||||
show the exact error code, so feel free to use other numbers than 1
|
||||
|
||||
3. persistent data such as credentials and session data storage are to be handled by the
|
||||
bridge itself. consider storing them in `$HOME/.local/share/<bridge-name>/`, or wherever
|
||||
appropriate
|
||||
|
||||
### example
|
||||
|
||||
if i were to recommend an example on a basic bridge implementation, it would be the
|
||||
[Telegram Bridge](telegram-bridge.md):
|
||||
|
||||
```python title="src/spow-telegram-bridge/bridge.py"
|
||||
--8<-- "src/spow-telegram-bridge/bridge.py"
|
||||
```
|
||||
|
||||
!!! note
|
||||
the feature of deleting the last sent message (`--delete-last`) is a non-standard feature for
|
||||
bridges, and was simply a use case i personally needed. if you're going to implement a bridge,
|
||||
all you really need is the ability to `login`, `logout`, and [send a message](#an-informal-specification)
|
||||
|
||||
you can add other features as per the needs of your platform, like how the WhatsApp Bridge has
|
||||
a `pair-phone` subcommand, or per your use case needs, like in the Telegram Bridge's `--delete-last`.
|
43
docs/onwheels/emulating-termux-location.md
Normal file
43
docs/onwheels/emulating-termux-location.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
# emulating `termux-location`
|
||||
|
||||
to bodge surplus on wheels (s+ow) on non-Termux systems
|
||||
|
||||
!!! note
|
||||
dummy admonition for colour matching
|
||||
|
||||
!!! warning
|
||||
dummy admonition for colour matching
|
||||
|
||||
`termux-location`, part of [Termux:API](https://wiki.termux.com/wiki/Termux:API), gets the device's
|
||||
location via android apis and returns a json response through stdout:
|
||||
|
||||
```text
|
||||
{
|
||||
"latitude": 1.3277513,
|
||||
"longitude": 103.678317,
|
||||
"altitude": 51.6298828125,
|
||||
"accuracy": 48.46337890625,
|
||||
"vertical_accuracy": 38.4659423828125,
|
||||
"bearing": 0.0,
|
||||
"speed": 0.0,
|
||||
"elapsedMs": 28,
|
||||
"provider": "gps"
|
||||
}
|
||||
```
|
||||
|
||||
see <https://wiki.termux.com/wiki/Termux-location> for more information
|
||||
|
||||
## implementing for surplus on wheels (s+ow)
|
||||
|
||||
s+ow will call the command a total of six times, being three pairs of parallel
|
||||
`$LOCATION_CMD -p "network"` and `$LOCATION_CMD -p "gps"` invocations, before deciding after
|
||||
exhausting all six runs on which output to choose, if any command runs were successful
|
||||
|
||||
even if somewhere in the termux-location implementation fails, it always (begrudgingly) returns
|
||||
zero. s+ow will treat the invocation of the command as successful if there is **any output** to
|
||||
the standard output (stdout) stream
|
||||
|
||||
## implementing for surplus (s+)
|
||||
|
||||
s+, when passed `--t / --using-termux-location`, will consume stdin, parse it as json and then
|
||||
attempt to retrieve the `latitude` and `longitude` keys as floating point numbers
|
46
docs/onwheels/emulating-termux-notification.md
Normal file
46
docs/onwheels/emulating-termux-notification.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
# emulating `termux-notification`
|
||||
|
||||
to bodge surplus on wheels (s+ow) on non-Termux systems
|
||||
|
||||
`termux-notification`, part of [Termux:API](https://wiki.termux.com/wiki/Termux:API), sends out an
|
||||
android notification
|
||||
|
||||
without `termux-notification`, s+ow will still run as it doesn't use `set -e` and very carefully
|
||||
handles all command invocations, with `termux-notification` being the graceful exception\
|
||||
|
||||
however, if you would like to emulate it, make an executable globally reachable with the same name
|
||||
|
||||
s+ow uses the command as such:
|
||||
|
||||
```shell
|
||||
termux-notification \
|
||||
--priority "default" \
|
||||
--title "surplus on wheels: No bridges" \
|
||||
--content "No '$SPOW_BRIDGES' file; message is not sent."
|
||||
```
|
||||
|
||||
```shell
|
||||
termux-notification \
|
||||
--priority "min" \
|
||||
--ongoing \
|
||||
--id "s+ow" \
|
||||
--title "surplus on wheels" \
|
||||
--content "s+ow has started running."
|
||||
```
|
||||
|
||||
```shell
|
||||
termux-notification \
|
||||
--priority "min" \
|
||||
--id "s+ow" \
|
||||
--title "surplus on wheels" \
|
||||
--content ...
|
||||
```
|
||||
|
||||
```shell
|
||||
termux-notification \
|
||||
--priority "default" \
|
||||
--title "surplus on wheels has errored" \
|
||||
--content ...
|
||||
```
|
||||
|
||||
see <https://wiki.termux.com/wiki/Termux-notification> for more information
|
329
docs/onwheels/index.md
Normal file
329
docs/onwheels/index.md
Normal file
|
@ -0,0 +1,329 @@
|
|||
# surplus on wheels (s+ow)
|
||||
|
||||
surplus on wheels is a pure shell script to get your location using
|
||||
[termux-location](https://wiki.termux.com/wiki/Termux-location), process it through surplus, and
|
||||
send it to messaging service or wherever using “bridges”
|
||||
|
||||
surplus was made to emulate sending your location through the iOS Shortcuts app, and surplus on
|
||||
wheels complements it by running surplus automatically using a cron job.
|
||||
(but using it manually also works!)
|
||||
|
||||
## installing
|
||||
|
||||
!!! important
|
||||
s+ow is a Termux-first script, and will not work anywhere else unless you have
|
||||
a utility that emulates [termux-location](https://wiki.termux.com/wiki/termux-location)
|
||||
on `$PATH` alongside bridges that supports your platform
|
||||
|
||||
there are two notable ways to install s+ow:
|
||||
|
||||
1. [as a standalone script](#as-a-standalone-script)
|
||||
2. or, [as a cron job](#as-a-cron-job)
|
||||
|
||||
there is also an [installation script](#using-installation-scripts) for quickly getting
|
||||
started from a _fresh_ termux installation
|
||||
|
||||
### as a standalone script
|
||||
|
||||
1. firstly install python and termux-api if you haven't already:
|
||||
|
||||
```text
|
||||
pkg install python termux-api
|
||||
```
|
||||
|
||||
also install the accompanying Termux:API app from [F-Froid](https://f-droid.org/en/packages/com.termux.api/)
|
||||
|
||||
2. install pipx if you haven't already:
|
||||
|
||||
```text
|
||||
pip install pipx
|
||||
```
|
||||
|
||||
3. install surplus:
|
||||
|
||||
```text
|
||||
pipx install surplus
|
||||
```
|
||||
|
||||
4. install surplus on wheels:
|
||||
|
||||
```text
|
||||
mkdir -p ~/.local/bin/
|
||||
wget -O ~/.local/bin/s+ow https://surplus.joshwel.co/spow.sh
|
||||
chmod +x ~/.local/bin/s+ow
|
||||
```
|
||||
|
||||
!!! note
|
||||
if `wget` throws a 404, see [backup links](../links.md)
|
||||
|
||||
if `~/.local/bin` is not in your `$PATH`, add the following to your shell's rc file:
|
||||
|
||||
```shell
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
```
|
||||
|
||||
et voilà! s+ow is now setup. to actually send the message to a messaging platform,
|
||||
[install an appropriate bridge](bridges.md)
|
||||
|
||||
### as a cron job
|
||||
|
||||
!!! important
|
||||
these instructions rely on following the [previous instructions](#as-a-standalone-script)
|
||||
|
||||
1. install necessary packages to run cron jobs:
|
||||
|
||||
```text
|
||||
pkg install cronie termux-services
|
||||
```
|
||||
|
||||
2. restart termux and start the cron service:
|
||||
|
||||
```text
|
||||
sv-enable cron
|
||||
```
|
||||
|
||||
3. set up the cron job:
|
||||
|
||||
run the following command:
|
||||
|
||||
```text
|
||||
crontab -e
|
||||
```
|
||||
|
||||
and add the following text:
|
||||
|
||||
```text
|
||||
59 * * * * bash -l -c "(SPOW_TARGETS="" SPOW_CRON=y s+ow)"
|
||||
```
|
||||
|
||||
!!! important
|
||||
minimally fill in the `SPOW_TARGETS` variable before running s+ow.
|
||||
[(see usage for more info)](#usage)
|
||||
|
||||
this will run s+ow every hour, a minute before the hour
|
||||
|
||||
modify the variables as per your needs.
|
||||
see [usage](#usage) for more information
|
||||
|
||||
et voilà! s+ow will now send a message every hour. feel free to experiment with the cron
|
||||
job to your liking. see [crontab.guru](https://crontab.guru/) if you’re new to cron jobs
|
||||
|
||||
if you haven’t already, [install an appropriate bridge](bridges.md) to actually send a
|
||||
message to a messaging platform
|
||||
|
||||
### using installation scripts
|
||||
|
||||
!!! warning
|
||||
these scripts assume you're starting from a fresh base installation of Termux.
|
||||
if you have already cron jobs, then manually carry out the instructions in
|
||||
'[as a cron job](#as-a-cron-job)'
|
||||
|
||||
!!! important
|
||||
if not installed already, install
|
||||
[Termux:API from F-Droid](https://f-droid.org/en/packages/com.termux.api/), **not the Play Store**
|
||||
|
||||
1. setup s+ow:
|
||||
|
||||
```text
|
||||
wget -O- https://surplus.joshwel.co/termux.sh | sh
|
||||
```
|
||||
|
||||
!!! note
|
||||
if `wget` throws a 404, see [backup links](../links.md)
|
||||
|
||||
2. restart termux!
|
||||
|
||||
3. and finally, [set up a cron job](#as-a-cron-job) from step 3 onwards ('set up the cron job')
|
||||
|
||||
## usage
|
||||
|
||||
### environment variables
|
||||
|
||||
s+ow's behaviour can be customised environment variables, with `SURPLUS_CMD` being the only
|
||||
required variable:
|
||||
|
||||
1. `SPOW_TARGETS`
|
||||
a single line of comma-delimited chat IDs with bridge prefixes
|
||||
|
||||
```text
|
||||
wa:000000000000000000@g.us,tg:-0000000000000000000,...
|
||||
```
|
||||
|
||||
in the example above, the WhatsApp chat ID is `wa:`-prefixed as recognised by the
|
||||
[spow-whatsapp-bridge](whatsapp-bridge.md), and the
|
||||
Telegram chat ID is `tg:`-prefixed as recognised by the
|
||||
[spow-telegram-bridge](telegram-bridge.md)
|
||||
|
||||
2. `SPOW_CRON` (optional)
|
||||
set as non-empty to declare that s+ow is being run as a cron job
|
||||
|
||||
if running as a cron job, start s+ow one minute earlier than intended to account for the time
|
||||
it takes to run `termux-location` and `surplus`. s+ow assumes this and delays itself
|
||||
appropriately
|
||||
|
||||
setting it to `n` will also be treated as if it were empty
|
||||
|
||||
3. `SPOW_PRIVATE` (optional)
|
||||
set as non-empty to discard all logs when s+ow is done:
|
||||
|
||||
- `$HOME/.cache/s+ow/out.log` will be set to `/dev/null`
|
||||
- `$HOME/.cache/s+ow/err.log` will be set to `/dev/null`
|
||||
- `$HOME/.cache/s+ow/location.net.json` will be cleared after use locating the device
|
||||
- `$HOME/.cache/s+ow/location.gps.json` will be cleared after use locating the device
|
||||
- `$HOME/.cache/s+ow/location.json` will be cleared after use locating the device
|
||||
- `$HOME/.cache/s+ow/surplus.out.log` will be cleared after use generating the message
|
||||
- `$HOME/.cache/s+ow/surplus.err.log` will be set to `/dev/null`
|
||||
- `$HOME/.cache/s+ow/message` will be cleared after all bridges has sent the message
|
||||
|
||||
!!! warning
|
||||
the only file not cleared is s+ow's last successful message file, `$HOME/.cache/s+ow/last`,
|
||||
as s+ow uses this as the first fallback message if it couldn't locate the device in time.
|
||||
if you're fine with using the `LOCATION_FALLBACK` string, feel free to modify your
|
||||
cron job to remove this file after running s+ow
|
||||
|
||||
setting it to `n` will also be treated as if it were empty
|
||||
|
||||
4. `SURPLUS_CMD` (optional)
|
||||
the custom invocation used when calling surplus, modify this if you want to add certain flags
|
||||
|
||||
this defaults to `surplus -td`
|
||||
|
||||
!!! warning
|
||||
when overriding, ensure you also have `-td` (`--using-termux-location` and `--debug`) in
|
||||
your custom invocation!
|
||||
|
||||
5. `LOCATION_CMD` (optional)
|
||||
the custom invocation used when calling `termux-location`, modify this if you want to bodge
|
||||
together surplus on wheels on non-termux systems.
|
||||
see ([emulating `termux-location`](emulating-termux-location.md)) for more information
|
||||
|
||||
this defaults to `termux-location`
|
||||
|
||||
6. `LOCATION_PRIORITISE_NETWORK` (optional)
|
||||
set as non-empty to declare that s+ow can just use network location instead of GPS
|
||||
if GPS is taking too long.
|
||||
you should only turn this on if punctuality means that much to you, or you’re in a
|
||||
country with cell towers close by or everywhere, like Singapore
|
||||
|
||||
the JIDs can be obtained by sending a message to the user/group, while running
|
||||
`s+ow mdtest`, and examining the output for your message. JIDs are email address-like
|
||||
strings
|
||||
|
||||
setting it to `n` will also be treated as if it were empty
|
||||
|
||||
7. `LOCATION_TIMEOUT` (optional)
|
||||
set as a number to override the default first location timeout of `50`
|
||||
|
||||
8. `LOCATION_FALLBACK` (optional)
|
||||
a string that can be formatted with three numbers using `%d`:
|
||||
|
||||
1. s+ow's status
|
||||
2. number of location attempts before giving up
|
||||
3. type of message sent
|
||||
|
||||
see [details on notification numbers](#details-on-notification-numbers) for the meanings of
|
||||
each number. 'a', 'b' and 'c' map to `A`, `B` and `C`
|
||||
|
||||
defaults to `%d%d%d?`
|
||||
|
||||
### faking locations
|
||||
|
||||
> sometimes you gotta do what you gotta do
|
||||
|
||||
you can fake your s+ow messages by either:
|
||||
|
||||
1. setting a dummy `last` file in s+ow cache
|
||||
|
||||
`$HOME/.cache/s+ow/last` is used as the fallback response when a part of s+ow (either
|
||||
`termux-location` or `surplus` errors out). you can set this file to whatever you want
|
||||
and just turn off location on your device
|
||||
|
||||
2. setting a `fake` file in s+ow cache
|
||||
|
||||
!!! warning
|
||||
s+ow uses the `read` command to read the file. as such, it is possible for s+ow to
|
||||
prematurely stop reading the file if the file does not contain a trailing newline.
|
||||
|
||||
you can also write text to `$HOME/.cache/s+ow/fake` to fake upcoming messages. the file
|
||||
is delimited by empty lines. as such, arrange the file like so:
|
||||
|
||||
```text
|
||||
The Clementi Mall
|
||||
3155 Commonwealth Avenue West
|
||||
Westpeak Terrace
|
||||
129588
|
||||
Southwest, Singapore
|
||||
|
||||
Westgate
|
||||
3 Gateway Drive
|
||||
Jurong East
|
||||
608532
|
||||
Southwest, Singapore
|
||||
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
on every run of s+ow, the first group of lines will be consumed, and the file will be
|
||||
updated with the remaining lines. if the file is empty, it will be deleted
|
||||
|
||||
### details on notification numbers
|
||||
|
||||
after each run, or if s+ow had to use a location fallback string, s+ow notifies you:
|
||||
|
||||
!!! abstract "surplus on wheels"
|
||||
Run has finished.
|
||||
|
||||
Singapore Conference Hall
|
||||
7 Shenton Way
|
||||
068809
|
||||
Central, Singapore
|
||||
|
||||
(A, B, C, D<E>)
|
||||
[lc:W sp:X sm:Y - Z]
|
||||
|
||||
!!! abstract "surplus on wheels has errored"
|
||||
(A, B, C, D<E>)
|
||||
[lc:W sp:X sm:Y - Z]
|
||||
|
||||
the top line denotes general statuses:
|
||||
|
||||
- `A`: s+ow's status
|
||||
- `0` is nominal
|
||||
- `1` is a termux-location error
|
||||
- `2` is a surplus error
|
||||
- `3` is a bridge/message send error
|
||||
- `B`: number of location attempts before giving up
|
||||
- `C`: type of message sent
|
||||
- `0` for freshly made sharetext
|
||||
- `1` for recycling a previous successful location sharetext (`last` file)
|
||||
- `2` for using fallback template
|
||||
- `D`: number of bridge failures
|
||||
- `E`: each bridge's return code
|
||||
|
||||
the bottom line details on how long s+ow spent on each stage:
|
||||
|
||||
- `W`: time to locate
|
||||
- `X`: time to run surplus
|
||||
- `Y`: time to send message(s)
|
||||
- `Z`: total run time
|
||||
|
||||
## help! a bridge isn't working!
|
||||
|
||||
cool. do the following:
|
||||
|
||||
1. log out and log back in and try again
|
||||
|
||||
2. if that didn't fix it, update/reinstall the bridge and try again
|
||||
|
||||
3. run the bridge's executable directly to see if there's any connection issues
|
||||
look at your bridge's installation instructions to find out where it's located at.
|
||||
or, use the `which` command
|
||||
|
||||
4. if it connected successfully, or you see no errors, try typing in one of the targets you've set
|
||||
in `SPOW_TARGETS` for the bridge, and then press the enter/return key
|
||||
|
||||
!!! failure
|
||||
on the off chance you reinstalled the bridge, and it still failed either step 3 or 4, the bridge
|
||||
itself is faulty. file a bug report/issue with the bridge's project page or maintainer and tell
|
||||
them where it failed (was it connecting to the messaging service? or failure to send a message?)
|
101
docs/onwheels/telegram-bridge.md
Normal file
101
docs/onwheels/telegram-bridge.md
Normal file
|
@ -0,0 +1,101 @@
|
|||
# surplus on wheels: Telegram Bridge
|
||||
|
||||
Telegram Bridge for surplus on wheels (s+ow)
|
||||
|
||||
s+ow bridges are defined in a file named `$HOME/.s+ow-bridges`. each command in the file is run,
|
||||
and comma-seperated target chat IDs are passed using stdin.
|
||||
|
||||
this bridge recognises targets prefixed with `tg:`.
|
||||
|
||||
```text
|
||||
tg:<chat id>,...
|
||||
```
|
||||
|
||||
## installation
|
||||
|
||||
!!! important
|
||||
the following instructions implies that [surplus](../index.md) and [surplus on wheels](bridges.md)
|
||||
have already been installed
|
||||
|
||||
1. install prerequisite software if not installed:
|
||||
|
||||
```text
|
||||
pkg install git
|
||||
```
|
||||
|
||||
```text
|
||||
pip install pipx
|
||||
```
|
||||
|
||||
2. install spow-telegram-bridge:
|
||||
|
||||
```text
|
||||
wget -O- https://surplus.joshwel.co/telegram.sh | sh
|
||||
```
|
||||
|
||||
!!! note
|
||||
if `wget` throws a 404, see [backup links](../links.md)
|
||||
|
||||
3. add the following to your `$HOME/.s+ow-bridges` file:
|
||||
|
||||
```text
|
||||
SPOW_TELEGRAM_API_HASH="" SPOW_TELEGRAM_API_ID="" s+ow-telegram-bridge
|
||||
```
|
||||
|
||||
fill in SPOW_TELEGRAM_API_HASH and SPOW_TELEGRAM_API_ID accordingly.
|
||||
see the [Telethon docs](https://docs.telethon.dev/en/stable/basic/signing-in.html) for
|
||||
more information
|
||||
|
||||
to keep up to date, look at [updating](#updating) to set up a daily update cron job:
|
||||
|
||||
## updating
|
||||
|
||||
the installation script also sets up a shell script under the `s+ow-telegram-bridge-update` command
|
||||
|
||||
```text
|
||||
s+ow-telegram-bridge-update
|
||||
```
|
||||
|
||||
to do this automatically, make a cron job with `crontab -e`
|
||||
and make a new line with the following text:
|
||||
|
||||
```text
|
||||
0 0 * * * bash -l -c "s+ow-telegram-bridge-update"
|
||||
```
|
||||
|
||||
this cron job will run the command every day at midnight
|
||||
|
||||
## usage
|
||||
|
||||
- `s+ow-telegram-bridge`
|
||||
normal usage; sends latest message to tg:-prefixed targets given in stdin
|
||||
|
||||
- `s+ow-telegram-bridge login`
|
||||
logs in to Telegram
|
||||
|
||||
- `s+ow-telegram-bridge logout`
|
||||
logs out of Telegram
|
||||
|
||||
- `s+ow-telegram-bridge list`
|
||||
lists all chats and their IDs
|
||||
|
||||
optional arguments:
|
||||
|
||||
- `--silent`
|
||||
asks telegram to send message silently
|
||||
- `--delete-last`
|
||||
deletes last location message to prevent clutter
|
||||
|
||||
## versioning scheme
|
||||
|
||||
from `v2.2024.27`, the Telegram Bridge will automatically release a new version once a week if there
|
||||
are updates to its dependencies
|
||||
|
||||
as such, the bridge is now versioned with a modified calendar versioning scheme of
|
||||
`MAJOR.YEAR.ISOWEEK`, where the `MAJOR` version segment will be bumped with codebase changes, whereas
|
||||
the `YEAR` and `ISOWEEK` segments will represent the time of which the release was built at
|
||||
|
||||
## licence
|
||||
|
||||
the s+ow Telegram Bridge is free and unencumbered software released into the public domain.
|
||||
for more information, see [licences](../licences.md).
|
210
docs/onwheels/whatsapp-bridge.md
Normal file
210
docs/onwheels/whatsapp-bridge.md
Normal file
|
@ -0,0 +1,210 @@
|
|||
# surplus on wheels: WhatsApp Bridge
|
||||
|
||||
WhatsApp Bridge for surplus on wheels (s+ow)
|
||||
|
||||
s+ow bridges are defined in a file named `$HOME/.s+ow-bridges`. each command in the file is run,
|
||||
and comma-seperated target chat IDs are passed using stdin.
|
||||
|
||||
this bridge recognises targets prefixed with `wa:`.
|
||||
|
||||
```text
|
||||
wa:<chat id>,...
|
||||
```
|
||||
|
||||
## installation
|
||||
|
||||
### from a pre-built binary
|
||||
|
||||
```text
|
||||
wget -O- https://surplus.joshwel.co/whatsapp.sh | sh
|
||||
```
|
||||
|
||||
!!! note
|
||||
if `wget` throws a 404, see [backup links](../links.md)
|
||||
|
||||
### building from source
|
||||
|
||||
#### on Termux
|
||||
|
||||
1. clone the repository at either `https://forge.joshwel.co/mark/surplus` or
|
||||
`https://github.com/markjoshwel/surplus`, and navigate to `src/spow-whatsapp-bridge` within the
|
||||
cloned repository
|
||||
|
||||
```text
|
||||
git clone https://forge.joshwel.co/mark/surplus
|
||||
cd surplus/src/spow-whatsapp-bridge
|
||||
```
|
||||
|
||||
2. build the bridge:
|
||||
|
||||
```text
|
||||
go build
|
||||
```
|
||||
|
||||
for compatibility with the documentations' instructions as-is, rename the built binary to
|
||||
`s+ow-whatsapp-bridge`
|
||||
|
||||
```text
|
||||
mv spow-whatsapp-bridge s+ow-whatsapp-bridge
|
||||
```
|
||||
|
||||
3. send the built binary over to your Termux environment, and then move it into the
|
||||
`$HOME/.local/bin/` folder. if it doesn't exist, make it with `mkdir` and ensure that the folder
|
||||
is in your `PATH` variable either using your `.profile`, `.bashrc` or whatever file is sourced
|
||||
when opening your shell
|
||||
|
||||
#### anywhere else
|
||||
|
||||
for usage on Termux, see if the [Android NDK](https://developer.android.com/ndk/downloads) supports your platform
|
||||
|
||||
1. grab a copy of the NDK, and extract it somewhere. navigate to
|
||||
`<ndk folder>/toolchains/llvm/prebuilt/<your platform>/bin` and look for a suitable `clang`
|
||||
executable, as it will be your CGO compiler
|
||||
|
||||
```
|
||||
m@csp:~/android-ndk-r26d/toolchains/llvm/prebuilt/linux-x86_64/bin$ ls *clang
|
||||
aarch64-linux-android21-clang aarch64-linux-android30-clang ...
|
||||
aarch64-linux-android22-clang aarch64-linux-android31-clang
|
||||
aarch64-linux-android23-clang aarch64-linux-android32-clang
|
||||
aarch64-linux-android24-clang aarch64-linux-android33-clang
|
||||
aarch64-linux-android25-clang aarch64-linux-android34-clang
|
||||
aarch64-linux-android26-clang armv7a-linux-androideabi21-clang
|
||||
aarch64-linux-android27-clang armv7a-linux-androideabi22-clang
|
||||
aarch64-linux-android28-clang armv7a-linux-androideabi23-clang
|
||||
aarch64-linux-android29-clang armv7a-linux-androideabi24-clang
|
||||
```
|
||||
|
||||
the example output is not exhaustive and is cut short for brevity and example, do take a look
|
||||
at your downloaded NDK archive for what executables are available to you
|
||||
|
||||
many executables are present, so choose a) what architecture you will build for (more often
|
||||
than not it's `aarch64`), and b) what target android api are you building for
|
||||
|
||||
if you're building for yourself, pick an api level/version that correlates to your devices'
|
||||
android version. as an example, my device runs on an ARM processor (`aarch64`) and runs Android 14,
|
||||
which is api level 34. (`android34`) as such, i would use the `aarch64-linux-android34-clang`
|
||||
binary
|
||||
|
||||
2. clone the repository at either `https://forge.joshwel.co/mark/surplus` or
|
||||
`https://github.com/markjoshwel/surplus`, and navigate to `src/spow-whatsapp-bridge` within the
|
||||
cloned repository
|
||||
|
||||
```text
|
||||
git clone https://forge.joshwel.co/mark/surplus
|
||||
cd surplus/src/spow-whatsapp-bridge
|
||||
```
|
||||
|
||||
3. build the bridge:
|
||||
|
||||
```text
|
||||
CC="<path to android ndk clang executable>" GOOS=android GOARCH=arm64 CGO_ENABLED=1 go build
|
||||
```
|
||||
|
||||
for compatibility with the documentations' instructions as-is, rename the built binary to
|
||||
`s+ow-whatsapp-bridge`
|
||||
|
||||
```text
|
||||
mv spow-whatsapp-bridge s+ow-whatsapp-bridge
|
||||
```
|
||||
|
||||
4. send the built binary over to your Termux environment, and then move it into the
|
||||
`$HOME/.local/bin/` folder. if it doesn't exist, make it with `mkdir` and ensure that the folder
|
||||
is in your `PATH` variable either using your `.profile`, `.bashrc` or whatever file is sourced
|
||||
when opening your shell
|
||||
|
||||
### post-installation setup
|
||||
|
||||
1. log into WhatsApp:
|
||||
|
||||
```text
|
||||
s+ow-whatsapp-bridge login
|
||||
```
|
||||
|
||||
give it a minute or two to sync your history. once the screen stops scrolling, you can safely
|
||||
exit with Ctrl+D or Ctrl+C.
|
||||
|
||||
2. find out what chats you want the bridge to target:
|
||||
|
||||
```text
|
||||
s+ow-whatsapp-bridge list
|
||||
```
|
||||
|
||||
!!! note
|
||||
for sending to individuals: their IDs are their internationalised phone numbers ending in
|
||||
`@s.whatsapp.net`
|
||||
|
||||
example: `+65 9123 4567` is `6591234567@s.whatsapp.net`
|
||||
|
||||
then, note these down, prefixed with `wa:`, to them to your `SPOW_TARGETS` variable in your
|
||||
s+ow cron job
|
||||
|
||||
3. finally, add the following to your $HOME/.s+ow-bridges file:
|
||||
|
||||
```text
|
||||
s+ow-whatsapp-bridge
|
||||
```
|
||||
|
||||
## updating
|
||||
|
||||
to keep updated as [whatsmeow](https://github.com/tulir/whatsmeow/), the library the bridge depends
|
||||
on, has to keep updated with the WhatsApp web multidevice API, you can either:
|
||||
|
||||
1. [rebuild when a weekly release comes out](#building-from-source),
|
||||
2. [or rely on the weekly continuous deployment builds](#from-a-pre-built-binary)
|
||||
|
||||
to use the weekly builds without building from scratch every time,
|
||||
|
||||
!!! note
|
||||
this will pull the latest binary, around 20 megabytes in size, every day. if your network or
|
||||
data plan may not take kindly to this, feel free to adjust the cron entry as you wish, or to
|
||||
one that runs once a week instead:
|
||||
|
||||
```text
|
||||
0 0 * * 0 bash -l -c "s+ow-whatsapp-bridge-update"
|
||||
```
|
||||
|
||||
## usage
|
||||
|
||||
- `s+ow-whatsapp-bridge`
|
||||
normal usage; sends latest message to wa:-prefixed targets given in stdin
|
||||
|
||||
- `s+ow-whatsapp-bridge login`
|
||||
logs in to WhatsApp
|
||||
|
||||
- `s+ow-whatsapp-bridge pair-phone`
|
||||
logs in to WhatsApp using a phone number
|
||||
|
||||
- `s+ow-whatsapp-bridge reconnect`
|
||||
reconnects the client
|
||||
|
||||
- `s+ow-whatsapp-bridge logout`
|
||||
logs out of WhatsApp
|
||||
|
||||
- `s+ow-whatsapp-bridge list`
|
||||
lists all group chats and their IDs.
|
||||
|
||||
for sending to individuals: their IDs are their internationalised phone numbers ending in
|
||||
`@s.whatsapp.net`
|
||||
|
||||
example: `+65 9123 4567` is `6591234567@s.whatsapp.net`
|
||||
|
||||
## verifying a pre-built binary
|
||||
|
||||
!!! note
|
||||
if you installed the bridge through an installation script, it would have already
|
||||
|
||||
and if the script or `s+ow-whatsapp-bridge-update` throws an error about failing verification,
|
||||
you can use the environment variable ``
|
||||
|
||||
TODO
|
||||
|
||||
## versioning scheme
|
||||
|
||||
from `v2.2024.25`, the bridge is now versioned with a modified calendar versioning scheme of
|
||||
`MAJOR.YEAR.ISOWEEK`, where the `MAJOR` version segment will be bumped with codebase changes, whereas
|
||||
the `YEAR` and `ISOWEEK` segments will represent the time of which the release was built at
|
||||
|
||||
## licence
|
||||
|
||||
the s+ow Telegram Bridge is free and unencumbered software released into the public domain.
|
||||
for more information, see [licences](../licences.md).
|
212
docs/stylesheets/extra.css
Normal file
212
docs/stylesheets/extra.css
Normal file
|
@ -0,0 +1,212 @@
|
|||
@font-face {
|
||||
font-family: "Geist";
|
||||
src: url('../fonts/GeistVF.woff2') format('woff2'),
|
||||
url('../fonts/Geist-Regular.ttf') format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Geist Mono";
|
||||
src: url('../fonts/GeistMonoVF.woff2') format('woff2'),
|
||||
url('../fonts/GeistMono-Regular.ttf') format('truetype');
|
||||
}
|
||||
|
||||
:root {
|
||||
--md-text-font: "Geist";
|
||||
--md-code-font: "Geist Mono";
|
||||
--md-hue: 180deg;
|
||||
}
|
||||
|
||||
* {
|
||||
text-rendering: geometricprecision !important;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
[data-md-color-scheme="default"] {
|
||||
color-scheme: light;
|
||||
|
||||
--md-sys-color-primary: rgb(51 71 65);
|
||||
--md-sys-color-surface-tint: rgb(78 99 92);
|
||||
--md-sys-color-on-primary: rgb(255 255 255);
|
||||
--md-sys-color-primary-container: rgb(95 116 109);
|
||||
--md-sys-color-on-primary-container: rgb(255 255 255);
|
||||
--md-sys-color-secondary: rgb(61 69 66);
|
||||
--md-sys-color-on-secondary: rgb(255 255 255);
|
||||
--md-sys-color-secondary-container: rgb(110 118 115);
|
||||
--md-sys-color-on-secondary-container: rgb(255 255 255);
|
||||
--md-sys-color-tertiary: rgb(0 73 93);
|
||||
--md-sys-color-on-tertiary: rgb(255 255 255);
|
||||
--md-sys-color-tertiary-container: rgb(65 124 147);
|
||||
--md-sys-color-on-tertiary-container: rgb(255 255 255);
|
||||
--md-sys-color-error: rgb(124 37 0);
|
||||
--md-sys-color-on-error: rgb(255 255 255);
|
||||
--md-sys-color-error-container: rgb(200 77 28);
|
||||
--md-sys-color-on-error-container: rgb(255 255 255);
|
||||
--md-sys-color-background: rgb(251 249 247);
|
||||
--md-sys-color-on-background: rgb(27 28 27);
|
||||
--md-sys-color-surface: rgb(251 249 247);
|
||||
--md-sys-color-on-surface: rgb(27 28 27);
|
||||
--md-sys-color-surface-variant: rgb(222 228 224);
|
||||
--md-sys-color-on-surface-variant: rgb(62 68 66);
|
||||
--md-sys-color-outline: rgb(90 96 94);
|
||||
--md-sys-color-outline-variant: rgb(118 124 121);
|
||||
--md-sys-color-shadow: rgb(0 0 0);
|
||||
--md-sys-color-scrim: rgb(0 0 0);
|
||||
--md-sys-color-inverse-surface: rgb(48 49 48);
|
||||
--md-sys-color-inverse-on-surface: rgb(242 240 239);
|
||||
--md-sys-color-inverse-primary: rgb(181 203 195);
|
||||
--md-sys-color-primary-fixed: rgb(100 121 114);
|
||||
--md-sys-color-on-primary-fixed: rgb(255 255 255);
|
||||
--md-sys-color-primary-fixed-dim: rgb(76 96 90);
|
||||
--md-sys-color-on-primary-fixed-variant: rgb(255 255 255);
|
||||
--md-sys-color-secondary-fixed: rgb(110 118 115);
|
||||
--md-sys-color-on-secondary-fixed: rgb(255 255 255);
|
||||
--md-sys-color-secondary-fixed-dim: rgb(86 94 90);
|
||||
--md-sys-color-on-secondary-fixed-variant: rgb(255 255 255);
|
||||
--md-sys-color-tertiary-fixed: rgb(65 124 147);
|
||||
--md-sys-color-on-tertiary-fixed: rgb(255 255 255);
|
||||
--md-sys-color-tertiary-fixed-dim: rgb(36 99 121);
|
||||
--md-sys-color-on-tertiary-fixed-variant: rgb(255 255 255);
|
||||
--md-sys-color-surface-dim: rgb(219 218 216);
|
||||
--md-sys-color-surface-bright: rgb(251 249 247);
|
||||
--md-sys-color-surface-container-lowest: rgb(255 255 255);
|
||||
--md-sys-color-surface-container-low: rgb(245 243 242);
|
||||
--md-sys-color-surface-container: rgb(239 238 236);
|
||||
--md-sys-color-surface-container-high: rgb(233 232 230);
|
||||
--md-sys-color-surface-container-highest: rgb(228 226 225);
|
||||
|
||||
--md-hue: 139.2deg;
|
||||
--md-default-fg-color: var(--md-sys-color-primary);
|
||||
--md-default-bg-color: var(--md-sys-color-surface);
|
||||
|
||||
/* primary colours */
|
||||
--md-primary-fg-color: var(--md-sys-color-primary);
|
||||
--md-primary-fg-color--light: var(--md-sys-color-inverse-primary);
|
||||
--md-primary-fg-color--dark: var(--md-sys-color-primary-container);
|
||||
--md-primary-bg-color: var(--md-sys-color-surface);
|
||||
--md-primary-bg-color--light: var(--md-sys-color-surface-dim);
|
||||
|
||||
/* accent (interactable) colours */
|
||||
--md-accent-fg-color: var(--md-sys-color-tertiary);
|
||||
--md-accent-bg-color: var(--md-sys-color-on-tertiary);
|
||||
--md-accent-bg-color--light: var(--md-sys-color-surface-dim);
|
||||
|
||||
/* typesetting colours */
|
||||
--md-typeset-color: var(--md-sys-color-on-surface);
|
||||
--md-typeset-a-color: var(--md-sys-color-tertiary);
|
||||
--md-typeset-del-color: var(--md-sys-color-on-error-container);
|
||||
--md-typeset-ins-color: var(--md-sys-color-on-primary-container);
|
||||
--md-typeset-kbd-color: var(--md-sys-color-surface-container-lowest);
|
||||
--md-typeset-kbd-accent-color: var(--md-sys-color-surface-container);
|
||||
--md-typeset-kbd-border-color: var(--md-sys-color-surface-container-highest);
|
||||
--md-typeset-mark-color: var(--md-sys-color-tertiary-container);
|
||||
--md-typeset-table-color: var(--md-sys-color-outline);
|
||||
--md-code-bg-color: var(--md-sys-color-surface-container-high);
|
||||
|
||||
/* admonition colours */
|
||||
--md-admonition-fg-color: var(--md-sys-color-secondary);
|
||||
--md-admonition-bg-color: var(--md-default-bg-color);
|
||||
--md-warning-fg-color: var(--md-sys-color-on-error-container);
|
||||
--md-warning-bg-color: var(--md-sys-color-error-container);
|
||||
|
||||
/* footer colours */
|
||||
--md-footer-fg-color: var(--md-sys-color-on-surface);
|
||||
--md-footer-fg-color--light: var(--md-sys-color-on-surface-variant);
|
||||
--md-footer-fg-color--lighter: var(--md-sys-color-outline);
|
||||
--md-footer-bg-color: var(--md-sys-color-surface-dim);
|
||||
--md-footer-bg-color--dark: var(--md-sys-color-surface-container-highest);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] {
|
||||
color-scheme: dark;
|
||||
|
||||
--md-sys-color-primary: rgb(185 208 199);
|
||||
--md-sys-color-surface-tint: rgb(181 203 195);
|
||||
--md-sys-color-on-primary: rgb(6 26 21);
|
||||
--md-sys-color-primary-container: rgb(128 149 142);
|
||||
--md-sys-color-on-primary-container: rgb(0 0 0);
|
||||
--md-sys-color-secondary: rgb(196 205 200);
|
||||
--md-sys-color-on-secondary: rgb(16 24 21);
|
||||
--md-sys-color-secondary-container: rgb(138 147 143);
|
||||
--md-sys-color-on-secondary-container: rgb(0 0 0);
|
||||
--md-sys-color-tertiary: rgb(153 211 236);
|
||||
--md-sys-color-on-tertiary: rgb(0 25 34);
|
||||
--md-sys-color-tertiary-container: rgb(99 157 181);
|
||||
--md-sys-color-on-tertiary-container: rgb(0 0 0);
|
||||
--md-sys-color-error: rgb(255 187 164);
|
||||
--md-sys-color-on-error: rgb(48 9 0);
|
||||
--md-sys-color-error-container: rgb(237 104 54);
|
||||
--md-sys-color-on-error-container: rgb(0 0 0);
|
||||
--md-sys-color-background: rgb(19 20 19);
|
||||
--md-sys-color-on-background: rgb(228 226 225);
|
||||
--md-sys-color-surface: rgb(19 20 19);
|
||||
--md-sys-color-on-surface: rgb(252 250 249);
|
||||
--md-sys-color-surface-variant: rgb(66 72 70);
|
||||
--md-sys-color-on-surface-variant: rgb(198 204 200);
|
||||
--md-sys-color-outline: rgb(158 164 161);
|
||||
--md-sys-color-outline-variant: rgb(126 132 129);
|
||||
--md-sys-color-shadow: rgb(0 0 0);
|
||||
--md-sys-color-scrim: rgb(0 0 0);
|
||||
--md-sys-color-inverse-surface: rgb(228 226 225);
|
||||
--md-sys-color-inverse-on-surface: rgb(41 42 41);
|
||||
--md-sys-color-inverse-primary: rgb(56 76 70);
|
||||
--md-sys-color-primary-fixed: rgb(209 232 223);
|
||||
--md-sys-color-on-primary-fixed: rgb(2 20 16);
|
||||
--md-sys-color-primary-fixed-dim: rgb(181 203 195);
|
||||
--md-sys-color-on-primary-fixed-variant: rgb(38 58 52);
|
||||
--md-sys-color-secondary-fixed: rgb(220 228 224);
|
||||
--md-sys-color-on-secondary-fixed: rgb(11 19 16);
|
||||
--md-sys-color-secondary-fixed-dim: rgb(192 200 196);
|
||||
--md-sys-color-on-secondary-fixed-variant: rgb(48 56 53);
|
||||
--md-sys-color-tertiary-fixed: rgb(186 234 255);
|
||||
--md-sys-color-on-tertiary-fixed: rgb(0 20 27);
|
||||
--md-sys-color-tertiary-fixed-dim: rgb(149 207 232);
|
||||
--md-sys-color-on-tertiary-fixed-variant: rgb(0 59 76);
|
||||
--md-sys-color-surface-dim: rgb(19 20 19);
|
||||
--md-sys-color-surface-bright: rgb(57 57 56);
|
||||
--md-sys-color-surface-container-lowest: rgb(13 14 14);
|
||||
--md-sys-color-surface-container-low: rgb(27 28 27);
|
||||
--md-sys-color-surface-container: rgb(31 32 31);
|
||||
--md-sys-color-surface-container-high: rgb(41 42 41);
|
||||
--md-sys-color-surface-container-highest: rgb(52 53 52);
|
||||
|
||||
/*--md-hue: 139.2deg;*/
|
||||
--md-default-fg-color: var(--md-sys-color-primary);
|
||||
--md-default-bg-color: var(--md-sys-color-surface);
|
||||
|
||||
/* primary colours */
|
||||
--md-primary-fg-color: var(--md-sys-color-primary);
|
||||
--md-primary-fg-color--light: var(--md-sys-color-inverse-primary);
|
||||
--md-primary-fg-color--dark: var(--md-sys-color-primary-container);
|
||||
--md-primary-bg-color: var(--md-sys-color-surface);
|
||||
--md-primary-bg-color--light: var(--md-sys-color-surface-dim);
|
||||
|
||||
/* accent (interactable) colours */
|
||||
--md-accent-fg-color: var(--md-sys-color-tertiary);
|
||||
--md-accent-bg-color: var(--md-sys-color-on-tertiary);
|
||||
--md-accent-bg-color--light: var(--md-sys-color-surface-dim);
|
||||
|
||||
/* typesetting colours */
|
||||
--md-typeset-color: var(--md-sys-color-on-surface);
|
||||
--md-typeset-a-color: var(--md-sys-color-tertiary);
|
||||
--md-typeset-del-color: var(--md-sys-color-on-error-container);
|
||||
--md-typeset-ins-color: var(--md-sys-color-on-primary-container);
|
||||
--md-typeset-kbd-color: var(--md-sys-color-surface-container-lowest);
|
||||
--md-typeset-kbd-accent-color: var(--md-sys-color-surface-container);
|
||||
--md-typeset-kbd-border-color: var(--md-sys-color-surface-container-highest);
|
||||
--md-typeset-mark-color: var(--md-sys-color-tertiary-container);
|
||||
--md-typeset-table-color: var(--md-sys-color-outline);
|
||||
--md-typeset-table-color--light: var(--md-sys-color-outline-variant);
|
||||
--md-code-bg-color: var(--md-sys-color-surface-container-high);
|
||||
|
||||
/* admonition colours */
|
||||
--md-admonition-fg-color: var(--md-sys-color-secondary);
|
||||
--md-admonition-bg-color: var(--md-default-bg-color);
|
||||
--md-warning-fg-color: var(--md-sys-color-on-error-container);
|
||||
--md-warning-bg-color: var(--md-sys-color-error-container);
|
||||
|
||||
/* footer colours */
|
||||
--md-footer-fg-color: var(--md-sys-color-on-surface);
|
||||
--md-footer-fg-color--light: var(--md-sys-color-on-surface-variant);
|
||||
--md-footer-fg-color--lighter: var(--md-sys-color-outline);
|
||||
--md-footer-bg-color: var(--md-sys-color-surface-dim);
|
||||
--md-footer-bg-color--dark: var(--md-sys-color-surface-container-highest);
|
||||
}
|
4
docs/stylesheets/pdf.scss
Normal file
4
docs/stylesheets/pdf.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
@page {
|
||||
size: A4;
|
||||
margin: 1.25cm;
|
||||
}
|
11
docs/using.md
Normal file
11
docs/using.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# the user's handbook
|
||||
|
||||
TODO
|
||||
|
||||
## as a command line tool
|
||||
|
||||
TODO
|
||||
|
||||
## as a python library
|
||||
|
||||
TODO
|
78
flake.lock
generated
Normal file
78
flake.lock
generated
Normal file
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1718318537,
|
||||
"narHash": "sha256-4Zu0RYRcAY/VWuu6awwq4opuiD//ahpc2aFHg2CWqFY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e9ee548d90ff586a6471b4ae80ae9cfcbceb3420",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-old-hatch": {
|
||||
"locked": {
|
||||
"lastModified": 1702508050,
|
||||
"narHash": "sha256-imn+/Rj+bqagOSm7GmRDbrqkxtc7QnjY3Cu85gv46BU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "fd04bea4cbf76f86f244b9e2549fca066db8ddff",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "fd04bea4cbf76f86f244b9e2549fca066db8ddff",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-old-hatch": "nixpkgs-old-hatch"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
45
flake.nix
Normal file
45
flake.nix
Normal file
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
description = "development environment for surplus";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
# https://github.com/NixOS/nixpkgs/issues/308121
|
||||
nixpkgs-old-hatch.url = "github:NixOS/nixpkgs/fd04bea4cbf76f86f244b9e2549fca066db8ddff";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, nixpkgs-old-hatch, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
pkgs-old-hatch = nixpkgs-old-hatch.legacyPackages.${system};
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShellNoCC {
|
||||
NIX_LD_LIBRARY_PATH = lib.makeLibraryPath [
|
||||
pkgs.stdenv.cc.cc
|
||||
];
|
||||
NIX_LD = lib.fileContents "${pkgs.stdenv.cc}/nix-support/dynamic-linker";
|
||||
buildInputs =
|
||||
[
|
||||
# surplus
|
||||
pkgs.python3
|
||||
pkgs-old-hatch.hatch
|
||||
|
||||
# mkdocs-exporter
|
||||
pkgs.stdenv.cc.cc.lib
|
||||
pkgs.playwright
|
||||
];
|
||||
shellHook = ''
|
||||
export LD_LIBRARY_PATH=$NIX_LD_LIBRARY_PATH
|
||||
export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers}
|
||||
export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true
|
||||
echo LD_LIBRARY_PATH=$LD_LIBRARY_PATH
|
||||
echo PLAYWRIGHT_BROWSERS_PATH=$PLAYWRIGHT_BROWSERS_PATH
|
||||
echo PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=$PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
114
mkdocs.yml
Normal file
114
mkdocs.yml
Normal file
|
@ -0,0 +1,114 @@
|
|||
site_name: surplus Documentation
|
||||
site_url: https://surplus.joshwel.co
|
||||
site_author: Mark Joshwel and surplus contributors
|
||||
site_description: documentation for the surplus and sibling projects
|
||||
|
||||
repo_name: markjoshwel/surplus
|
||||
repo_url: https://github.com/markjoshwel/surplus
|
||||
|
||||
copyright: |
|
||||
with with all our hearts, 2023-2024, mark joshwel and contributors<br>
|
||||
documentation is dedicated to the public domain with <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0</a>
|
||||
|
||||
nav:
|
||||
- about:
|
||||
- surplus: "index.md"
|
||||
- licences: "licences.md"
|
||||
- changelog: "changelog.md"
|
||||
- handbooks:
|
||||
- "using.md"
|
||||
- "developing.md"
|
||||
- "contributing.md"
|
||||
- on wheels:
|
||||
- "onwheels/index.md"
|
||||
- bridges:
|
||||
- about bridges: "onwheels/bridges.md"
|
||||
- "onwheels/telegram-bridge.md"
|
||||
- "onwheels/whatsapp-bridge.md"
|
||||
- "onwheels/emulating-termux-location.md"
|
||||
- "onwheels/emulating-termux-notification.md"
|
||||
- backup links:
|
||||
"links.md"
|
||||
|
||||
theme:
|
||||
name: material
|
||||
language: en
|
||||
|
||||
features:
|
||||
- navigation.tabs
|
||||
- navigation.tabs.sticky
|
||||
- navigation.tracking
|
||||
- navigation.expand
|
||||
- toc.integrate
|
||||
- search.suggest
|
||||
- search.highlight
|
||||
- content.tabs.link
|
||||
- content.code.annotation
|
||||
- content.code.copy
|
||||
- pymdownx.snippets
|
||||
|
||||
font: false
|
||||
|
||||
palette:
|
||||
- media: "(prefers-color-scheme)"
|
||||
toggle:
|
||||
icon: material/brightness-auto
|
||||
name: Light Theme
|
||||
primary: custom
|
||||
accent: custom
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
toggle:
|
||||
icon: material/brightness-7
|
||||
name: Dark Theme
|
||||
primary: custom
|
||||
accent: custom
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: System Theme
|
||||
primary: custom
|
||||
accent: custom
|
||||
|
||||
icon:
|
||||
admonition:
|
||||
abstract: material/text-box-outline
|
||||
tip: material/pencil-outline
|
||||
note: material/information-slab-box-outline
|
||||
warning: material/alert-outline
|
||||
danger: material/alert-octagon-outline
|
||||
|
||||
|
||||
extra_css:
|
||||
- stylesheets/extra.css
|
||||
|
||||
plugins:
|
||||
- search
|
||||
- privacy
|
||||
#- git-revision-date-localized:
|
||||
# enable_creation_date: true
|
||||
- exporter:
|
||||
formats:
|
||||
pdf:
|
||||
enabled: !ENV [MKDOCS_EXPORTER_PDF_ENABLED, true]
|
||||
stylesheets:
|
||||
- docs/stylesheets/pdf.scss
|
||||
aggregator:
|
||||
enabled: true
|
||||
output: documentation.pdf
|
||||
buttons:
|
||||
- title: Download as PDF
|
||||
icon: material-file-download-outline
|
||||
enabled: !!python/name:mkdocs_exporter.formats.pdf.buttons.download.enabled
|
||||
attributes: !!python/name:mkdocs_exporter.formats.pdf.buttons.download.attributes
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
line_spans: __span
|
||||
pygments_lang_class: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.snippets
|
||||
- pymdownx.superfences
|
726
playground.ipynb
726
playground.ipynb
|
@ -1,726 +0,0 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# surplus 2.x.y playground notebook\n",
|
||||
"\n",
|
||||
"wrangling with environments for devbox users using codium/vs code:\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"$ devbox shell # enter devbox env\n",
|
||||
"(surplus-py3.11) (devbox) $ exit # leave poetry env\n",
|
||||
"(devbox) $ codium . # open ide\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"OUTPUT_LINE_X_KEYS: Final[tuple[str, ...]] = (\"region\",\"county\",\"state\",\"state_district\",\"country\",\"continent\",)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# converting nominatim keys to OUTPUT_LINE_X_KEYS format\n",
|
||||
"\n",
|
||||
"keys = \"\"\"\n",
|
||||
"region, county, state, state_district, country, continent\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"split_keys = [f'\"{key.strip()}\"' for key in keys.strip().split(\",\")]\n",
|
||||
"\n",
|
||||
"print(f\"OUTPUT_LINE_X_KEYS: Final[tuple[str, ...]] = ({','.join(split_keys)},)\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from surplus import PlusCodeQuery, LocalCodeQuery, LatlongQuery, StringQuery\n",
|
||||
"from surplus import Latlong, Result\n",
|
||||
"from surplus import SurplusDefaultGeocoding\n",
|
||||
"\n",
|
||||
"geocoding = SurplusDefaultGeocoding()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Generic Result NamedTuple"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"True\tNone \t3\n",
|
||||
"False\tZeroDivisionError('division by zero') \tdivision by zero (ZeroDivisionError)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ename": "ZeroDivisionError",
|
||||
"evalue": "division by zero",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
||||
"\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)",
|
||||
"\u001b[1;32m/home/m/works/surplus/playground.ipynb Cell 5\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m <a href='vscode-notebook-cell://wsl%2Balpine/home/m/works/surplus/playground.ipynb#X26sdnNjb2RlLXJlbW90ZQ%3D%3D?line=7'>8</a>\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39m{}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{:<40}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{}\u001b[39;00m\u001b[39m\"\u001b[39m\u001b[39m.\u001b[39mformat(\u001b[39mbool\u001b[39m(nom_result), \u001b[39mrepr\u001b[39m(nom_result\u001b[39m.\u001b[39merror), nom_result\u001b[39m.\u001b[39mget()))\n\u001b[1;32m <a href='vscode-notebook-cell://wsl%2Balpine/home/m/works/surplus/playground.ipynb#X26sdnNjb2RlLXJlbW90ZQ%3D%3D?line=8'>9</a>\u001b[0m \u001b[39mprint\u001b[39m(\n\u001b[1;32m <a href='vscode-notebook-cell://wsl%2Balpine/home/m/works/surplus/playground.ipynb#X26sdnNjb2RlLXJlbW90ZQ%3D%3D?line=9'>10</a>\u001b[0m \u001b[39m\"\u001b[39m\u001b[39m{}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{:<40}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{}\u001b[39;00m\u001b[39m\"\u001b[39m\u001b[39m.\u001b[39mformat(\n\u001b[1;32m <a href='vscode-notebook-cell://wsl%2Balpine/home/m/works/surplus/playground.ipynb#X26sdnNjb2RlLXJlbW90ZQ%3D%3D?line=10'>11</a>\u001b[0m \u001b[39mbool\u001b[39m(exc_result), \u001b[39mrepr\u001b[39m(exc_result\u001b[39m.\u001b[39merror), exc_result\u001b[39m.\u001b[39mcry(string\u001b[39m=\u001b[39m\u001b[39mTrue\u001b[39;00m)\n\u001b[1;32m <a href='vscode-notebook-cell://wsl%2Balpine/home/m/works/surplus/playground.ipynb#X26sdnNjb2RlLXJlbW90ZQ%3D%3D?line=11'>12</a>\u001b[0m )\n\u001b[1;32m <a href='vscode-notebook-cell://wsl%2Balpine/home/m/works/surplus/playground.ipynb#X26sdnNjb2RlLXJlbW90ZQ%3D%3D?line=12'>13</a>\u001b[0m )\n\u001b[0;32m---> <a href='vscode-notebook-cell://wsl%2Balpine/home/m/works/surplus/playground.ipynb#X26sdnNjb2RlLXJlbW90ZQ%3D%3D?line=13'>14</a>\u001b[0m \u001b[39mprint\u001b[39m(\u001b[39m\"\u001b[39m\u001b[39m{}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{:<40}\u001b[39;00m\u001b[39m\\t\u001b[39;00m\u001b[39m{}\u001b[39;00m\u001b[39m\"\u001b[39m\u001b[39m.\u001b[39mformat(\u001b[39mbool\u001b[39m(exc_result), \u001b[39mrepr\u001b[39m(exc_result\u001b[39m.\u001b[39merror), exc_result\u001b[39m.\u001b[39;49mget()))\n",
|
||||
"File \u001b[0;32m~/works/surplus/surplus/surplus.py:270\u001b[0m, in \u001b[0;36mResult.get\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 268\u001b[0m \u001b[39m\"\"\"method that returns self.value if Result is non-erroneous else raises error\"\"\"\u001b[39;00m\n\u001b[1;32m 269\u001b[0m \u001b[39mif\u001b[39;00m \u001b[39misinstance\u001b[39m(\u001b[39mself\u001b[39m\u001b[39m.\u001b[39merror, \u001b[39mBaseException\u001b[39;00m):\n\u001b[0;32m--> 270\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39merror\n\u001b[1;32m 271\u001b[0m \u001b[39mreturn\u001b[39;00m \u001b[39mself\u001b[39m\u001b[39m.\u001b[39mvalue\n",
|
||||
"\u001b[1;32m/home/m/works/surplus/playground.ipynb Cell 5\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m <a href='vscode-notebook-cell://wsl%2Balpine/home/m/works/surplus/playground.ipynb#X26sdnNjb2RlLXJlbW90ZQ%3D%3D?line=0'>1</a>\u001b[0m nom_result \u001b[39m=\u001b[39m Result[\u001b[39mint\u001b[39m](\u001b[39m3\u001b[39m)\n\u001b[1;32m <a href='vscode-notebook-cell://wsl%2Balpine/home/m/works/surplus/playground.ipynb#X26sdnNjb2RlLXJlbW90ZQ%3D%3D?line=2'>3</a>\u001b[0m \u001b[39mtry\u001b[39;00m:\n\u001b[0;32m----> <a href='vscode-notebook-cell://wsl%2Balpine/home/m/works/surplus/playground.ipynb#X26sdnNjb2RlLXJlbW90ZQ%3D%3D?line=3'>4</a>\u001b[0m \u001b[39m1\u001b[39;49m \u001b[39m/\u001b[39;49m \u001b[39m0\u001b[39;49m\n\u001b[1;32m <a href='vscode-notebook-cell://wsl%2Balpine/home/m/works/surplus/playground.ipynb#X26sdnNjb2RlLXJlbW90ZQ%3D%3D?line=4'>5</a>\u001b[0m \u001b[39mexcept\u001b[39;00m \u001b[39mException\u001b[39;00m \u001b[39mas\u001b[39;00m exc:\n\u001b[1;32m <a href='vscode-notebook-cell://wsl%2Balpine/home/m/works/surplus/playground.ipynb#X26sdnNjb2RlLXJlbW90ZQ%3D%3D?line=5'>6</a>\u001b[0m exc_result \u001b[39m=\u001b[39m Result[\u001b[39mint\u001b[39m](\u001b[39m-\u001b[39m\u001b[39m1\u001b[39m, error\u001b[39m=\u001b[39mexc)\n",
|
||||
"\u001b[0;31mZeroDivisionError\u001b[0m: division by zero"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"nom_result = Result[int](3)\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" 1 / 0\n",
|
||||
"except Exception as exc:\n",
|
||||
" exc_result = Result[int](-1, error=exc)\n",
|
||||
"\n",
|
||||
"print(\"{}\\t{:<40}\\t{}\".format(bool(nom_result), repr(nom_result.error), nom_result.get()))\n",
|
||||
"print(\n",
|
||||
" \"{}\\t{:<40}\\t{}\".format(\n",
|
||||
" bool(exc_result), repr(exc_result.error), exc_result.cry(string=True)\n",
|
||||
" )\n",
|
||||
")\n",
|
||||
"print(\"{}\\t{:<40}\\t{}\".format(bool(exc_result), repr(exc_result.error), exc_result.get()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Query Types"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Result(value=Latlong(latitude=1.3336875, longitude=103.7746875), error=None)"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"PlusCodeQuery(code=\"6PH58QMF+FV\").to_lat_long_coord(geocoder=geocoding.geocoder)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Result(value=Latlong(latitude=1.3336875, longitude=103.7746875), error=None)"
|
||||
]
|
||||
},
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"plus_code = LocalCodeQuery(code=\"8QMF+FV\", locality=\"Singapore\").to_full_plus_code(\n",
|
||||
" geocoder=geocoding.geocoder\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"PlusCodeQuery(code=plus_code.get()).to_lat_long_coord(geocoder=geocoding.geocoder)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Result(value=Latlong(latitude=1.3336875, longitude=103.7746875), error=None)"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"LocalCodeQuery(code=\"8QMF+FV\", locality=\"Singapore\").to_lat_long_coord(\n",
|
||||
" geocoder=geocoding.geocoder\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Result(value=Latlong(latitude=1.33318835, longitude=103.77461234638255), error=None)"
|
||||
]
|
||||
},
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"LatlongQuery(\n",
|
||||
" latlong=Latlong(latitude=1.33318835, longitude=103.77461234638255)\n",
|
||||
").to_lat_long_coord(geocoder=geocoding.geocoder)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"Result(value=Latlong(latitude=1.33318835, longitude=103.77461234638255), error=None)"
|
||||
]
|
||||
},
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"StringQuery(query=\"Ngee Ann Polytechnic\").to_lat_long_coord(geocoder=geocoding.geocoder)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## return dictionary of `reverser` function\n",
|
||||
"\n",
|
||||
"all the necessary keys (see `SHAREABLE_TEXT_LINE_*` constants) should be at the top-level of the dictionary.\n",
|
||||
"these keys will be casted into strings for safety guarantee when shareable text lines are being generated.\n",
|
||||
"\n",
|
||||
"while not necessary, consider keeping the original response dict under the \"raw\" key.\n",
|
||||
"helps with debugging using `-d/--debug`!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"reverser_return = {\n",
|
||||
" \"amenity\": \"Ngee Ann Polytechnic\",\n",
|
||||
" \"house_number\": \"535\",\n",
|
||||
" \"road\": \"Clementi Road\",\n",
|
||||
" \"suburb\": \"Bukit Timah\",\n",
|
||||
" \"city\": \"Singapore\",\n",
|
||||
" \"county\": \"Northwest\",\n",
|
||||
" \"ISO3166-2-lvl6\": \"SG-03\",\n",
|
||||
" \"postcode\": \"599489\",\n",
|
||||
" \"country\": \"Singapore\",\n",
|
||||
" \"country_code\": \"sg\",\n",
|
||||
" \"raw\": {\n",
|
||||
" \"place_id\": 297946059,\n",
|
||||
" \"licence\": \"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright\",\n",
|
||||
" \"osm_type\": \"relation\",\n",
|
||||
" \"osm_id\": 2535118,\n",
|
||||
" \"lat\": \"1.33318835\",\n",
|
||||
" \"lon\": \"103.77461234638255\",\n",
|
||||
" \"class\": \"amenity\",\n",
|
||||
" \"type\": \"university\",\n",
|
||||
" \"place_rank\": 30,\n",
|
||||
" \"importance\": 0.34662169301918117,\n",
|
||||
" \"addresstype\": \"amenity\",\n",
|
||||
" \"name\": \"Ngee Ann Polytechnic\",\n",
|
||||
" \"display_name\": \"Ngee Ann Polytechnic, 535, Clementi Road, Bukit Timah, Singapore, Northwest, 599489, Singapore\",\n",
|
||||
" \"address\": {\n",
|
||||
" \"amenity\": \"Ngee Ann Polytechnic\",\n",
|
||||
" \"house_number\": \"535\",\n",
|
||||
" \"road\": \"Clementi Road\",\n",
|
||||
" \"suburb\": \"Bukit Timah\",\n",
|
||||
" \"city\": \"Singapore\",\n",
|
||||
" \"county\": \"Northwest\",\n",
|
||||
" \"ISO3166-2-lvl6\": \"SG-03\",\n",
|
||||
" \"postcode\": \"599489\",\n",
|
||||
" \"country\": \"Singapore\",\n",
|
||||
" \"country_code\": \"sg\",\n",
|
||||
" },\n",
|
||||
" \"boundingbox\": [\"1.3289692\", \"1.3372184\", \"103.7701481\", \"103.7783945\"],\n",
|
||||
" },\n",
|
||||
" \"latitude\": 1.33318835,\n",
|
||||
" \"longitude\": 103.77461234638255,\n",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'ISO3166-2-lvl6': 'SG-03',\n",
|
||||
" 'amenity': 'Ngee Ann Polytechnic',\n",
|
||||
" 'city': 'Singapore',\n",
|
||||
" 'country': 'Singapore',\n",
|
||||
" 'country_code': 'sg',\n",
|
||||
" 'county': 'Northwest',\n",
|
||||
" 'house_number': '535',\n",
|
||||
" 'latitude': 1.33318835,\n",
|
||||
" 'longitude': 103.77461234638255,\n",
|
||||
" 'neighbourhood': 'Ewart Park',\n",
|
||||
" 'postcode': '599489',\n",
|
||||
" 'raw': {'address': {'ISO3166-2-lvl6': 'SG-03',\n",
|
||||
" 'amenity': 'Ngee Ann Polytechnic',\n",
|
||||
" 'city': 'Singapore',\n",
|
||||
" 'country': 'Singapore',\n",
|
||||
" 'country_code': 'sg',\n",
|
||||
" 'county': 'Northwest',\n",
|
||||
" 'house_number': '535',\n",
|
||||
" 'neighbourhood': 'Ewart Park',\n",
|
||||
" 'postcode': '599489',\n",
|
||||
" 'road': 'Clementi Road',\n",
|
||||
" 'suburb': 'Bukit Timah'},\n",
|
||||
" 'addresstype': 'amenity',\n",
|
||||
" 'boundingbox': ['1.3289692',\n",
|
||||
" '1.3372184',\n",
|
||||
" '103.7701481',\n",
|
||||
" '103.7783945'],\n",
|
||||
" 'class': 'amenity',\n",
|
||||
" 'display_name': 'Ngee Ann Polytechnic, 535, Clementi Road, Ewart '\n",
|
||||
" 'Park, Bukit Timah, Singapore, Northwest, 599489, '\n",
|
||||
" 'Singapore',\n",
|
||||
" 'importance': 0.34662169301918117,\n",
|
||||
" 'lat': '1.33318835',\n",
|
||||
" 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. '\n",
|
||||
" 'http://osm.org/copyright',\n",
|
||||
" 'lon': '103.77461234638255',\n",
|
||||
" 'name': 'Ngee Ann Polytechnic',\n",
|
||||
" 'osm_id': 2535118,\n",
|
||||
" 'osm_type': 'relation',\n",
|
||||
" 'place_id': 250910125,\n",
|
||||
" 'place_rank': 30,\n",
|
||||
" 'type': 'university'},\n",
|
||||
" 'road': 'Clementi Road',\n",
|
||||
" 'suburb': 'Bukit Timah'}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import pprint\n",
|
||||
"\n",
|
||||
"latlong = LocalCodeQuery(code=\"8QMF+FV\", locality=\"Singapore\").to_lat_long_coord(\n",
|
||||
" geocoder=geocoding.geocoder\n",
|
||||
")\n",
|
||||
"if not latlong:\n",
|
||||
" latlong.cry()\n",
|
||||
"\n",
|
||||
"else:\n",
|
||||
" location = geocoding.reverser(latlong.get())\n",
|
||||
" pprint.pprint(location)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2.1.0: adventures in of shortening global/full Plus Codes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### testing rate-limited and cached default geocoding functions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"test_geocoding = SurplusDefaultGeocoding(user_agent=\"surplus/playground\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1\n",
|
||||
"2\n",
|
||||
"3\n",
|
||||
"4\n",
|
||||
"5\n",
|
||||
"\n",
|
||||
"1\n",
|
||||
"2\n",
|
||||
"3\n",
|
||||
"4\n",
|
||||
"5\n",
|
||||
"\n",
|
||||
"3.1107698050s\t->\t0.0000886890s\t\t(-3.1106811160002508s)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from timeit import timeit\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"test_stmt = \"\"\"\\\n",
|
||||
"print(1)\n",
|
||||
"test_geocoding.geocoder(\"Wisma Atria\") # instant\n",
|
||||
"print(2)\n",
|
||||
"test_geocoding.geocoder(\"Temasek Polytechnic\") # after 1 second\n",
|
||||
"print(3)\n",
|
||||
"location = test_geocoding.geocoder(\"Ngee Ann Polytechnic\") # after 1 second\n",
|
||||
"print(4)\n",
|
||||
"test_geocoding.reverser(f\"{location.latitude}, {location.longitude}\") # instant\n",
|
||||
"print(5)\n",
|
||||
"test_geocoding.reverser(f\"{location.latitude}, {location.longitude}\") # instant (cached)\n",
|
||||
"print()\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"time_cold_call = timeit(test_stmt, globals=globals(), number=1) # expecting 3-4 seconds\n",
|
||||
"time_2nd_call = timeit(test_stmt, globals=globals(), number=1) # should be instant\n",
|
||||
"\n",
|
||||
"print(\n",
|
||||
" f\"{time_cold_call:.10f}s\\t->\\t{time_2nd_call:.10f}s\\t\\t({time_2nd_call - time_cold_call}s)\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### reversing the query latlong and using the address information to form a locality"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"level = 13"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"St Lucia, St Lucia, Queensland, Australia\n",
|
||||
"Austin, Travis County, Texas, United States\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"(\n",
|
||||
" au_response := geocoding.reverser(\n",
|
||||
" (\n",
|
||||
" au_target := (\n",
|
||||
" LocalCodeQuery(\n",
|
||||
" \"G227+XF\", \"St Lucia, Queensland, Australia\"\n",
|
||||
" ).to_lat_long_coord(geocoding.geocoder)\n",
|
||||
" )\n",
|
||||
" ).get(),\n",
|
||||
" level=level,\n",
|
||||
" )\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"au_locality = f\"{au_response['suburb']}, {au_response['city_district']}, {au_response['state']}, {au_response['country']}\"\n",
|
||||
"print(au_locality)\n",
|
||||
"\n",
|
||||
"(\n",
|
||||
" us_response := geocoding.reverser(\n",
|
||||
" (\n",
|
||||
" us_target := (\n",
|
||||
" LocalCodeQuery(\"77Q4+7X\", \"Austin, Texas, USA\").to_lat_long_coord(\n",
|
||||
" geocoding.geocoder\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
" ).get(),\n",
|
||||
" level=level,\n",
|
||||
" )\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"us_locality = f\"{us_response['city']}, {us_response['county']}, {us_response['state']}, {us_response['country']}\"\n",
|
||||
"print(us_locality)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### getting boundary boxes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'addresstype': 'suburb',\n",
|
||||
" 'boundingbox': ['-27.5187362', '-27.4787362', '152.9881642', '153.0281642'],\n",
|
||||
" 'class': 'place',\n",
|
||||
" 'display_name': 'St Lucia, Brisbane City, Queensland, 4072, Australia',\n",
|
||||
" 'importance': 0.27501,\n",
|
||||
" 'lat': '-27.4987362',\n",
|
||||
" 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. '\n",
|
||||
" 'http://osm.org/copyright',\n",
|
||||
" 'lon': '153.0081642',\n",
|
||||
" 'name': 'St Lucia',\n",
|
||||
" 'osm_id': 88800268,\n",
|
||||
" 'osm_type': 'node',\n",
|
||||
" 'place_id': 54477898,\n",
|
||||
" 'place_rank': 19,\n",
|
||||
" 'type': 'suburb'}\n",
|
||||
"\n",
|
||||
"Latlong(latitude=-27.4987362, longitude=153.0081642, bounding_box=[-27.5187362, -27.4787362, 152.9881642, 153.0281642])\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from geopy.geocoders import Nominatim\n",
|
||||
"from pprint import pprint\n",
|
||||
"\n",
|
||||
"target_query: Result[Latlong] = au_target\n",
|
||||
"target_locality: str = au_locality\n",
|
||||
"\n",
|
||||
"raw_geocoding = Nominatim(user_agent=\"surplus/playground\")\n",
|
||||
"latlong = raw_geocoding.geocode(target_locality)\n",
|
||||
"pprint(latlong.raw)\n",
|
||||
"print()\n",
|
||||
"\n",
|
||||
"# done: now implmented in surplus as surplus.Latlong.bounding_box\n",
|
||||
"locality_latlong = geocoding.geocoder(target_locality)\n",
|
||||
"pprint(locality_latlong)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[-27.5187362, -27.4787362, 152.9881642, 153.0281642]\n",
|
||||
"(True, True, True, True)\n",
|
||||
"(True, True, True, True)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# based on <https://github.com/google/open-location-code/wiki/Guidance-for-shortening-codes>\n",
|
||||
"\n",
|
||||
"target_latlong = target_query.get()\n",
|
||||
"if locality_latlong.bounding_box is None:\n",
|
||||
" ... # raise some error\n",
|
||||
"\n",
|
||||
"print(locality_latlong.bounding_box)\n",
|
||||
"check1 = (\n",
|
||||
" # The center point of the feature is within 0.4 degrees latitude and 0.4 degrees longitude\n",
|
||||
" (\n",
|
||||
" (target_latlong.latitude - 0.4)\n",
|
||||
" <= locality_latlong.latitude\n",
|
||||
" <= (target_latlong.latitude + 0.4)\n",
|
||||
" ),\n",
|
||||
" (\n",
|
||||
" (target_latlong.longitude - 0.4)\n",
|
||||
" <= locality_latlong.longitude\n",
|
||||
" <= (target_latlong.longitude + 0.4)\n",
|
||||
" ),\n",
|
||||
" # The bounding box of the feature is less than 0.8 degrees high and wide.\n",
|
||||
" abs(locality_latlong.bounding_box[0] - locality_latlong.bounding_box[1]) < 0.8,\n",
|
||||
" abs(locality_latlong.bounding_box[2] - locality_latlong.bounding_box[3]) < 0.8,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"check2 = (\n",
|
||||
" # The center point of the feature is within 0.4 degrees latitude and 0.4 degrees longitude\n",
|
||||
" (\n",
|
||||
" (target_latlong.latitude - 8)\n",
|
||||
" <= locality_latlong.latitude\n",
|
||||
" <= (target_latlong.latitude + 8)\n",
|
||||
" ),\n",
|
||||
" (\n",
|
||||
" (target_latlong.longitude - 8)\n",
|
||||
" <= locality_latlong.longitude\n",
|
||||
" <= (target_latlong.longitude + 8)\n",
|
||||
" ),\n",
|
||||
" # The bounding box of the feature is less than 0.8 degrees high and wide.\n",
|
||||
" abs(locality_latlong.bounding_box[0] - locality_latlong.bounding_box[1]) < 16,\n",
|
||||
" abs(locality_latlong.bounding_box[2] - locality_latlong.bounding_box[3]) < 16,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(check1)\n",
|
||||
"print(check2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"G227+XF St Lucia, St Lucia, Queensland, Australia\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from pluscodes import encode\n",
|
||||
"\n",
|
||||
"target_plus_code = encode(\n",
|
||||
" lat=target_latlong.latitude, lon=target_latlong.longitude, code_length=10\n",
|
||||
")\n",
|
||||
"portion_plus_code = \"\"\n",
|
||||
"\n",
|
||||
"if check1:\n",
|
||||
" portion_plus_code = target_plus_code[4:]\n",
|
||||
" print(portion_plus_code, target_locality)\n",
|
||||
"\n",
|
||||
"elif check2:\n",
|
||||
" portion_plus_code = target_plus_code[2:]\n",
|
||||
" print(portion_plus_code, target_locality)\n",
|
||||
"\n",
|
||||
"else:\n",
|
||||
" print(\n",
|
||||
" \"info: could not determine a suitable geographical feature to use as locality for shortening.\"\n",
|
||||
" )\n",
|
||||
" print(plus_code)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## machine fingerprinting attempt\n",
|
||||
"\n",
|
||||
"because of nominatim's acceptable usage policy \n",
|
||||
"<https://operations.osmfoundation.org/policies/nominatim/>"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from hashlib import shake_256 as _hashlib_shake_256\n",
|
||||
"from platform import platform as _platform_platform\n",
|
||||
"from socket import gethostname as _socket_gethostname\n",
|
||||
"from uuid import getnode as _uuid_getnode\n",
|
||||
"from surplus import VERSION, VERSION_SUFFIX\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def generate_fingerprinted_user_agent() -> Result[str]:\n",
|
||||
" \"\"\"\n",
|
||||
" function that attempts to return a unique user agent string.\n",
|
||||
"\n",
|
||||
" returns Result[str]\n",
|
||||
" this result will always have a valid value as erroneous results will have a\n",
|
||||
" resulting value of 'surplus/<version>/generic-user'\n",
|
||||
" valid results will have a value of 'surplus/<version>/<fingerprint>', where\n",
|
||||
" fingerprint is a 12 character hexadecimal string\n",
|
||||
" \"\"\"\n",
|
||||
" version: str = \".\".join([str(v) for v in VERSION]) + VERSION_SUFFIX\n",
|
||||
"\n",
|
||||
" try:\n",
|
||||
" system_info: str = _platform_platform()\n",
|
||||
" hostname: str = _socket_gethostname()\n",
|
||||
" mac_address: str = \":\".join(\n",
|
||||
" [\n",
|
||||
" \"{:02x}\".format((_uuid_getnode() >> elements) & 0xFF)\n",
|
||||
" for elements in range(0, 2 * 6, 2)\n",
|
||||
" ][::-1]\n",
|
||||
" )\n",
|
||||
" unique_info: str = f\"{version}-{system_info}-{hostname}-{mac_address}\"\n",
|
||||
"\n",
|
||||
" print(f\"{version=}\")\n",
|
||||
" print(f\"{system_info=}\")\n",
|
||||
" print(f\"{hostname=}\")\n",
|
||||
" print(f\"{mac_address=}\")\n",
|
||||
"\n",
|
||||
" except Exception as exc:\n",
|
||||
" return Result[str](f\"surplus/{version} (generic-user)\", error=exc)\n",
|
||||
"\n",
|
||||
" fingerprint: str = _hashlib_shake_256(unique_info.encode()).hexdigest(5)\n",
|
||||
"\n",
|
||||
" return Result[str](f\"surplus/{version} ({fingerprint})\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.1"
|
||||
},
|
||||
"orig_nbformat": 4
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
573
poetry.lock
generated
573
poetry.lock
generated
|
@ -1,573 +0,0 @@
|
|||
# This file is automatically @generated by Poetry and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "appnope"
|
||||
version = "0.1.3"
|
||||
description = "Disable App Nap on macOS >= 10.9"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"},
|
||||
{file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asttokens"
|
||||
version = "2.4.0"
|
||||
description = "Annotate AST trees with source code positions"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "asttokens-2.4.0-py2.py3-none-any.whl", hash = "sha256:cf8fc9e61a86461aa9fb161a14a0841a03c405fa829ac6b202670b3495d2ce69"},
|
||||
{file = "asttokens-2.4.0.tar.gz", hash = "sha256:2e0171b991b2c959acc6c49318049236844a5da1d65ba2672c4880c1c894834e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.12.0"
|
||||
|
||||
[package.extras]
|
||||
test = ["astroid", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "backcall"
|
||||
version = "0.2.0"
|
||||
description = "Specifications for callback functions passed in to an API"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"},
|
||||
{file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "23.7.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"},
|
||||
{file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"},
|
||||
{file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"},
|
||||
{file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"},
|
||||
{file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"},
|
||||
{file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"},
|
||||
{file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"},
|
||||
{file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"},
|
||||
{file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"},
|
||||
{file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"},
|
||||
{file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"},
|
||||
{file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"},
|
||||
{file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"},
|
||||
{file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"},
|
||||
{file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"},
|
||||
{file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"},
|
||||
{file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"},
|
||||
{file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"},
|
||||
{file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"},
|
||||
{file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"},
|
||||
{file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"},
|
||||
{file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
ipython = {version = ">=7.8.0", optional = true, markers = "extra == \"jupyter\""}
|
||||
mypy-extensions = ">=0.4.3"
|
||||
packaging = ">=22.0"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
tokenize-rt = {version = ">=3.2.0", optional = true, markers = "extra == \"jupyter\""}
|
||||
|
||||
[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"
|
||||
category = "dev"
|
||||
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."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "decorator"
|
||||
version = "5.1.1"
|
||||
description = "Decorators for Humans"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
|
||||
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "executing"
|
||||
version = "1.2.0"
|
||||
description = "Get the currently executing AST node of a frame, and other information"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "executing-1.2.0-py2.py3-none-any.whl", hash = "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc"},
|
||||
{file = "executing-1.2.0.tar.gz", hash = "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["asttokens", "littleutils", "pytest", "rich"]
|
||||
|
||||
[[package]]
|
||||
name = "geographiclib"
|
||||
version = "2.0"
|
||||
description = "The geodesic routines from GeographicLib"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "geographiclib-2.0-py3-none-any.whl", hash = "sha256:6b7225248e45ff7edcee32becc4e0a1504c606ac5ee163a5656d482e0cd38734"},
|
||||
{file = "geographiclib-2.0.tar.gz", hash = "sha256:f7f41c85dc3e1c2d3d935ec86660dc3b2c848c83e17f9a9e51ba9d5146a15859"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "geopy"
|
||||
version = "2.4.0"
|
||||
description = "Python Geocoding Toolbox"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "geopy-2.4.0-py3-none-any.whl", hash = "sha256:d2639a46d0ce4c091e9688b750ba94348a14b898a1e55c68f4b4a07e7d1afa20"},
|
||||
{file = "geopy-2.4.0.tar.gz", hash = "sha256:a59392bf17adb486b25dbdd71fbed27733bdf24a2dac588047a619de56695e36"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
geographiclib = ">=1.52,<3"
|
||||
|
||||
[package.extras]
|
||||
aiohttp = ["aiohttp"]
|
||||
dev = ["coverage", "flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"]
|
||||
dev-docs = ["readme-renderer", "sphinx (<=4.3.2)", "sphinx-issues", "sphinx-rtd-theme (>=0.5.0)"]
|
||||
dev-lint = ["flake8 (>=5.0,<5.1)", "isort (>=5.10.0,<5.11.0)"]
|
||||
dev-test = ["coverage", "pytest (>=3.10)", "pytest-asyncio (>=0.17)", "sphinx (<=4.3.2)"]
|
||||
requests = ["requests (>=2.16.2)", "urllib3 (>=1.24.2)"]
|
||||
timezone = ["pytz"]
|
||||
|
||||
[[package]]
|
||||
name = "ipython"
|
||||
version = "8.15.0"
|
||||
description = "IPython: Productive Interactive Computing"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "ipython-8.15.0-py3-none-any.whl", hash = "sha256:45a2c3a529296870a97b7de34eda4a31bee16bc7bf954e07d39abe49caf8f887"},
|
||||
{file = "ipython-8.15.0.tar.gz", hash = "sha256:2baeb5be6949eeebf532150f81746f8333e2ccce02de1c7eedde3f23ed5e9f1e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
appnope = {version = "*", markers = "sys_platform == \"darwin\""}
|
||||
backcall = "*"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
decorator = "*"
|
||||
jedi = ">=0.16"
|
||||
matplotlib-inline = "*"
|
||||
pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""}
|
||||
pickleshare = "*"
|
||||
prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0"
|
||||
pygments = ">=2.4.0"
|
||||
stack-data = "*"
|
||||
traitlets = ">=5"
|
||||
|
||||
[package.extras]
|
||||
all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"]
|
||||
black = ["black"]
|
||||
doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"]
|
||||
kernel = ["ipykernel"]
|
||||
nbconvert = ["nbconvert"]
|
||||
nbformat = ["nbformat"]
|
||||
notebook = ["ipywidgets", "notebook"]
|
||||
parallel = ["ipyparallel"]
|
||||
qtconsole = ["qtconsole"]
|
||||
test = ["pytest (<7.1)", "pytest-asyncio", "testpath"]
|
||||
test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"]
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.12.0"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"},
|
||||
{file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.3)"]
|
||||
pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
|
||||
plugins = ["setuptools"]
|
||||
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||
|
||||
[[package]]
|
||||
name = "jedi"
|
||||
version = "0.19.0"
|
||||
description = "An autocompletion tool for Python that can be used for text editors."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "jedi-0.19.0-py2.py3-none-any.whl", hash = "sha256:cb8ce23fbccff0025e9386b5cf85e892f94c9b822378f8da49970471335ac64e"},
|
||||
{file = "jedi-0.19.0.tar.gz", hash = "sha256:bcf9894f1753969cbac8022a8c2eaee06bfa3724e4192470aaffe7eb6272b0c4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
parso = ">=0.8.3,<0.9.0"
|
||||
|
||||
[package.extras]
|
||||
docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"]
|
||||
qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"]
|
||||
testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib-inline"
|
||||
version = "0.1.6"
|
||||
description = "Inline Matplotlib backend for Jupyter"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"},
|
||||
{file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
traitlets = "*"
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.5.1"
|
||||
description = "Optional static typing for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"},
|
||||
{file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"},
|
||||
{file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"},
|
||||
{file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"},
|
||||
{file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"},
|
||||
{file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"},
|
||||
{file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"},
|
||||
{file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"},
|
||||
]
|
||||
|
||||
[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."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "23.1"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
|
||||
{file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parso"
|
||||
version = "0.8.3"
|
||||
description = "A Python Parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
|
||||
{file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
|
||||
testing = ["docopt", "pytest (<6.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.11.2"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
category = "dev"
|
||||
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 = "pexpect"
|
||||
version = "4.8.0"
|
||||
description = "Pexpect allows easy control of interactive console applications."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"},
|
||||
{file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
ptyprocess = ">=0.5"
|
||||
|
||||
[[package]]
|
||||
name = "pickleshare"
|
||||
version = "0.7.5"
|
||||
description = "Tiny 'shelve'-like database with concurrency support"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
|
||||
{file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "3.10.0"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"},
|
||||
{file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"},
|
||||
]
|
||||
|
||||
[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 = "pluscodes"
|
||||
version = "2022.1.3"
|
||||
description = "Compute Plus Codes (Open Location Codes)."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "pluscodes-2022.1.3-py3-none-any.whl", hash = "sha256:50625f472f8d4e8822e005180c2eb41bf09e45e429f362d3cded346f1169dae8"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["black (==22.3.0)", "build (==0.8.0)", "coverage (==6.4)", "isort (==5.8.0)", "pylintv (==2.15.0)", "pytest (==7.1.1)", "pytest-cov (==3.0.0)", "twine (==4.0.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.39"
|
||||
description = "Library for building powerful interactive command lines in Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"},
|
||||
{file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
wcwidth = "*"
|
||||
|
||||
[[package]]
|
||||
name = "ptyprocess"
|
||||
version = "0.7.0"
|
||||
description = "Run a subprocess in a pseudo terminal"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
|
||||
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pure-eval"
|
||||
version = "0.2.2"
|
||||
description = "Safely evaluate AST nodes without side effects"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"},
|
||||
{file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.16.1"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"},
|
||||
{file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
plugins = ["importlib-metadata"]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
files = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stack-data"
|
||||
version = "0.6.2"
|
||||
description = "Extract data from python stack frames and tracebacks for informative displays"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "stack_data-0.6.2-py3-none-any.whl", hash = "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8"},
|
||||
{file = "stack_data-0.6.2.tar.gz", hash = "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
asttokens = ">=2.1.0"
|
||||
executing = ">=1.2.0"
|
||||
pure-eval = "*"
|
||||
|
||||
[package.extras]
|
||||
tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"]
|
||||
|
||||
[[package]]
|
||||
name = "tokenize-rt"
|
||||
version = "5.2.0"
|
||||
description = "A wrapper around the stdlib `tokenize` which roundtrips."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "tokenize_rt-5.2.0-py2.py3-none-any.whl", hash = "sha256:b79d41a65cfec71285433511b50271b05da3584a1da144a0752e9c621a285289"},
|
||||
{file = "tokenize_rt-5.2.0.tar.gz", hash = "sha256:9fe80f8a5c1edad2d3ede0f37481cc0cc1538a2f442c9c2f9e4feacd2792d054"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "traitlets"
|
||||
version = "5.9.0"
|
||||
description = "Traitlets Python configuration system"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"},
|
||||
{file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
|
||||
test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.7.1"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"},
|
||||
{file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.6"
|
||||
description = "Measures the displayed width of unicode strings in a terminal"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"},
|
||||
{file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "f9270d7fb45c708e923f4afe041b26f77becb8e4adfcd517742e67f6b8f9d6b9"
|
110
pyproject.toml
110
pyproject.toml
|
@ -1,37 +1,97 @@
|
|||
[tool.poetry]
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "surplus"
|
||||
version = "2.1.1"
|
||||
description = "Python script to convert Google Maps Plus Codes to iOS Shortcuts-like shareable text."
|
||||
authors = ["Mark Joshwel <mark@joshwel.co>"]
|
||||
license = "Unlicense"
|
||||
dynamic = ["version"]
|
||||
description = 'convert Plus Codes, coordinates or location strings to shareable text'
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/markjoshwel/surplus"
|
||||
requires-python = ">=3.11"
|
||||
license = "Unlicense"
|
||||
keywords = ["pluscodes", "openlocationcode"]
|
||||
packages = [
|
||||
{include = "surplus"}
|
||||
authors = [
|
||||
{ name = "Mark Joshwel", email = "mark@joshwel.co" },
|
||||
]
|
||||
classifiers = [
|
||||
"Development Status :: 6 - Mature",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
dependencies = [
|
||||
"pluscodes~=2022.1.3",
|
||||
"geopy~=2.4.1",
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
pluscodes = "^2022.1.3"
|
||||
geopy = "^2.3.0"
|
||||
[project.scripts]
|
||||
surplus = "surplus:cli"
|
||||
"s+" = "surplus:cli"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = {extras = ["jupyter"], version = "^23.7.0"}
|
||||
mypy = "^1.5.1"
|
||||
isort = "^5.12.0"
|
||||
[tool.hatch.build.targets.sdist]
|
||||
exclude = [
|
||||
"/.github",
|
||||
"/.devbox",
|
||||
"/src/surplus-on-wheels",
|
||||
"/src/spow*",
|
||||
]
|
||||
|
||||
[tool.poetry.scripts]
|
||||
surplus = 'surplus:cli'
|
||||
"s+" = 'surplus:cli'
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src.surplus"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 90
|
||||
[project.urls]
|
||||
Documentation = "https://surplus.joshwel.co"
|
||||
Issues = "https://surplus.joshwel.co/issues"
|
||||
Source = "https://github.com/markjoshwel/surplus"
|
||||
Changelog = "https://surplus.joshwel.co/changelog"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py311"
|
||||
|
||||
[tool.isort]
|
||||
line_length = 90
|
||||
line_length = 100
|
||||
profile = "black"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
[tool.hatch.version]
|
||||
path = "src/surplus/surplus.py"
|
||||
|
||||
[[tool.hatch.envs.all.matrix]]
|
||||
python = ["3.11", "3.12"]
|
||||
|
||||
[tool.hatch.envs.default]
|
||||
description = "default development environment"
|
||||
dependencies = ["mypy", "ruff", "isort"]
|
||||
|
||||
[tool.hatch.envs.default.scripts]
|
||||
check = [
|
||||
"mypy src",
|
||||
"hatch fmt --check",
|
||||
"isort --check src"
|
||||
]
|
||||
format = [
|
||||
"hatch fmt -f",
|
||||
"isort src"
|
||||
]
|
||||
|
||||
[tool.hatch.envs.hatch-static-analysis]
|
||||
dependencies = ["ruff>=0.3.2"]
|
||||
|
||||
[tool.hatch.envs.docs]
|
||||
detached = true
|
||||
description = "env for generator documentation"
|
||||
dependencies = [
|
||||
"mkdocs",
|
||||
"mkdocs-material",
|
||||
"mkdocs-git-revision-date-localized-plugin",
|
||||
"mkdocs-exporter",
|
||||
"playwright",
|
||||
]
|
||||
[tool.hatch.envs.docs.scripts]
|
||||
build = [
|
||||
"python src/tools/docs-prebuild.py",
|
||||
"mkdocs build --clean --strict",
|
||||
]
|
||||
serve = "mkdocs serve --dev-addr localhost:8000"
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
geographiclib==2.0 ; python_version >= "3.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
|
5
src/spow-telegram-bridge/README.md
Normal file
5
src/spow-telegram-bridge/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# surplus on wheels: Telegram Bridge
|
||||
|
||||
see <https://surplus.joshwel.co/onwheels/telegram-bridge>
|
||||
or [/docs/onwheels/telegram-bridge.md](../../docs/onwheels/telegram-bridge.md)
|
||||
for more information and documentation
|
24
src/spow-telegram-bridge/UNLICENCE
Normal file
24
src/spow-telegram-bridge/UNLICENCE
Normal file
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
251
src/spow-telegram-bridge/bridge.py
Normal file
251
src/spow-telegram-bridge/bridge.py
Normal file
|
@ -0,0 +1,251 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
s+ow-telegram-bridge: add-on bridge for surplus on wheels (s+ow) to telegram
|
||||
----------------------------------------------------------------------------
|
||||
by mark <mark@joshwel.co>
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from os import environ
|
||||
from pathlib import Path
|
||||
from sys import argv, stderr, stdin
|
||||
from traceback import print_tb
|
||||
from typing import Final
|
||||
|
||||
from telethon import TelegramClient # type: ignore
|
||||
|
||||
# exit codes:
|
||||
# 1 - bad command usage or missing env vars
|
||||
# 2 - bad target
|
||||
# 3 - could not send message
|
||||
|
||||
# rundown:
|
||||
# 1. if argv[-1] is 'login', then run login() and exit
|
||||
# 2. read stdin and comma split it
|
||||
# 3. read ~/.cache/s+ow/message
|
||||
# 4. for each target in comma split stdin that starts with "tg:",
|
||||
# send ~/.cache/s+ow/message
|
||||
|
||||
|
||||
SESSION_NAME: Final[str] = "spowtg"
|
||||
|
||||
dir_data: Path = Path.home().joinpath(".local/share/s+ow-telegram-bridge")
|
||||
dir_data.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
dir_cache: Path = Path.home().joinpath(".cache/s+ow-telegram-bridge")
|
||||
dir_cache.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
api_id: str | None = environ.get("SPOW_TELEGRAM_API_ID", None)
|
||||
api_hash: str | None = environ.get("SPOW_TELEGRAM_API_HASH", None)
|
||||
message_file: Path = Path.home().joinpath(".cache/s+ow/message")
|
||||
session_file: Path = dir_data.joinpath(f"{SESSION_NAME}.session")
|
||||
|
||||
|
||||
def handle_error(
|
||||
exc: Exception | None = None,
|
||||
err_message: str = "error",
|
||||
recoverable: bool = False,
|
||||
exit_code: int = -1,
|
||||
) -> None:
|
||||
try:
|
||||
exc_details: str = ""
|
||||
if isinstance(exc, Exception):
|
||||
exc_details = f": {exc} ({exc.__class__.__name__})"
|
||||
print_tb(exc.__traceback__, file=stderr)
|
||||
|
||||
print(
|
||||
f"s+ow-telegram-bridge: {err_message}{exc_details}",
|
||||
file=stderr,
|
||||
)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not recoverable:
|
||||
exit(exit_code)
|
||||
|
||||
|
||||
def validate_vars() -> None:
|
||||
if api_id is None:
|
||||
print("s+ow-telegram-bridge: error: SPOW_TELEGRAM_API_ID not set", file=stderr)
|
||||
exit(1)
|
||||
|
||||
if api_hash is None:
|
||||
print("s+ow-telegram-bridge: error: SPOW_TELEGRAM_API_HASH not set", file=stderr)
|
||||
exit(1)
|
||||
|
||||
|
||||
async def run() -> None:
|
||||
silent: bool = "--silent" in argv
|
||||
delete_last: bool = "--delete-last" in argv
|
||||
|
||||
if silent:
|
||||
print("s+ow-telegram-bridge: info: --silent passed", file=stderr)
|
||||
|
||||
if delete_last:
|
||||
print("s+ow-telegram-bridge: info: --delete-last passed", file=stderr)
|
||||
|
||||
targets: list[int] = []
|
||||
|
||||
# "spec" point 2:
|
||||
# reads in SPOW_TARGETS given by surplus to the bridge using stdin
|
||||
# "spec" point 2(a):
|
||||
# bridges do not need to account for the possibility of multiple lines sent to stdin
|
||||
# this bridge doesn't do this because it's simpler to iterate through stdin with a 'for'
|
||||
# loop in python
|
||||
for line in stdin:
|
||||
for _target in line.split(","):
|
||||
# "spec" point 2(b):
|
||||
# bridges should account for the possibility of comma and space delimited targets
|
||||
_target = _target.strip()
|
||||
|
||||
# "spec" point 2(c):
|
||||
# bridges should recognise a platform based on a prefix
|
||||
if _target.startswith("tg:"):
|
||||
_target = _target[3:]
|
||||
if not (
|
||||
_target.isnumeric()
|
||||
or (_target.startswith("-") and _target.lstrip("-").isnumeric())
|
||||
):
|
||||
continue
|
||||
|
||||
try:
|
||||
targets.append(int(_target))
|
||||
|
||||
except Exception as exc:
|
||||
handle_error(
|
||||
exc=exc,
|
||||
err_message=f"error: could not cast '{_target}' as int",
|
||||
recoverable=True,
|
||||
exit_code=2,
|
||||
)
|
||||
continue
|
||||
|
||||
# "spec" point 3:
|
||||
# reads SPOW_MESSAGE (~/.cache/spow/message) for the message content
|
||||
if not (message_file.exists() and message_file.is_file()):
|
||||
print("s+ow-telegram-bridge: error: ~/.cache/s+ow/message not found", file=stderr)
|
||||
exit(1)
|
||||
message = message_file.read_text(encoding="utf-8")
|
||||
|
||||
async with TelegramClient(session_file, api_id, api_hash) as client:
|
||||
for target in targets:
|
||||
try:
|
||||
if delete_last is False:
|
||||
await client.send_message(
|
||||
int(target),
|
||||
message,
|
||||
silent=silent,
|
||||
)
|
||||
|
||||
else:
|
||||
target_persist: Path = dir_cache.joinpath(str(target))
|
||||
|
||||
try:
|
||||
# delete old message if persist file exists
|
||||
if target_persist.exists() and target_persist.is_file():
|
||||
await client.delete_messages(
|
||||
entity=target,
|
||||
message_ids=[int(target_persist.read_text(encoding="utf-8"))],
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
handle_error(
|
||||
exc=exc,
|
||||
err_message="error: could not delete old message",
|
||||
recoverable=True,
|
||||
exit_code=3,
|
||||
)
|
||||
continue
|
||||
|
||||
# send new message
|
||||
target_sent_message = await client.send_message(
|
||||
target,
|
||||
message,
|
||||
silent=silent,
|
||||
)
|
||||
|
||||
# persist new message id
|
||||
target_persist.write_text(str(target_sent_message.id), encoding="utf-8")
|
||||
|
||||
except Exception as exc:
|
||||
handle_error(
|
||||
exc=exc,
|
||||
err_message="error: could not send message",
|
||||
recoverable=True,
|
||||
exit_code=3,
|
||||
)
|
||||
continue
|
||||
|
||||
print("s+ow-telegram-bridge: success: message sent to", target)
|
||||
exit()
|
||||
|
||||
|
||||
def login() -> None:
|
||||
with TelegramClient(session_file, api_id, api_hash) as client:
|
||||
client.start()
|
||||
exit()
|
||||
|
||||
|
||||
def logout() -> None:
|
||||
if session_file.exists():
|
||||
session_file.unlink()
|
||||
print("s+ow-telegram-bridge: logged out successfully", file=stderr)
|
||||
else:
|
||||
print("s+ow-telegram-bridge: already logged out", file=stderr)
|
||||
|
||||
|
||||
def list_chats() -> None:
|
||||
with TelegramClient(session_file, api_id, api_hash) as client:
|
||||
for dialog in client.iter_dialogs():
|
||||
print(dialog.id, "\t", dialog.name)
|
||||
exit()
|
||||
|
||||
|
||||
def entry() -> None:
|
||||
if len(argv) < 1:
|
||||
print("s+ow-telegram-bridge: error: len(argv) < 1", file=stderr)
|
||||
exit(1)
|
||||
|
||||
if "login" in argv:
|
||||
validate_vars()
|
||||
login()
|
||||
|
||||
elif "logout" in argv:
|
||||
logout()
|
||||
|
||||
elif "list" in argv:
|
||||
validate_vars()
|
||||
list_chats()
|
||||
|
||||
else:
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
entry()
|
21
src/spow-telegram-bridge/check.sh
Normal file
21
src/spow-telegram-bridge/check.sh
Normal file
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
failures=0
|
||||
|
||||
mypy bridge.py
|
||||
failures=$((failures + $?))
|
||||
|
||||
ruff check bridge.py
|
||||
failures=$((failures + $?))
|
||||
|
||||
ruff format bridge.py
|
||||
failures=$((failures + $?))
|
||||
|
||||
isort --check bridge.py
|
||||
failures=$((failures + $?))
|
||||
|
||||
if [ $failures -eq 0 ]; then
|
||||
printf "\n\nall checks okay! (❁´◡\`❁)\n"
|
||||
else
|
||||
printf "\n\nsome checks failed...\n"
|
||||
fi
|
||||
exit $failures
|
175
src/spow-telegram-bridge/flake.lock
generated
Normal file
175
src/spow-telegram-bridge/flake.lock
generated
Normal file
|
@ -0,0 +1,175 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-github-actions": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1703863825,
|
||||
"narHash": "sha256-rXwqjtwiGKJheXB43ybM8NwWB8rO2dSRrEqes0S7F5Y=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "5163432afc817cf8bd1f031418d1869e4c9d5547",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1718530797,
|
||||
"narHash": "sha256-pup6cYwtgvzDpvpSCFh1TEUjw2zkNpk8iolbKnyFmmU=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b60ebf54c15553b393d144357375ea956f89e9a9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": "systems_3",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1718656656,
|
||||
"narHash": "sha256-/8pXTFOfb7+KrFi+g8G/dFehDkc96/O5eL8L+FjzG1w=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "2c6d07717af20e45fa5b2c823729126be91a3cdf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"poetry2nix": "poetry2nix"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_3": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "systems",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1718522839,
|
||||
"narHash": "sha256-ULzoKzEaBOiLRtjeY3YoGFJMwWSKRYOic6VNw2UyTls=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "68eb1dc333ce82d0ab0c0357363ea17c31ea1f81",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
35
src/spow-telegram-bridge/flake.nix
Normal file
35
src/spow-telegram-bridge/flake.nix
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
description = "development environment for surplus on wheels: Telegram Bridge";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
poetry2nix = {
|
||||
url = "github:nix-community/poetry2nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, poetry2nix }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
inherit (poetry2nix.lib.mkPoetry2Nix { inherit pkgs; }) mkPoetryEnv;
|
||||
|
||||
poetryEnv = mkPoetryEnv {
|
||||
python = pkgs.python311;
|
||||
projectDir = self;
|
||||
preferWheels = true;
|
||||
};
|
||||
in
|
||||
{
|
||||
# nix develop
|
||||
devShells.default = pkgs.mkShellNoCC {
|
||||
packages = [
|
||||
pkgs.poetry
|
||||
poetryEnv
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
5
src/spow-telegram-bridge/install.sh
Normal file
5
src/spow-telegram-bridge/install.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
# surplus on wheels: Telegram Bridge: installation and updater script
|
||||
set -e
|
||||
|
||||
echo TODO
|
167
src/spow-telegram-bridge/poetry.lock
generated
Normal file
167
src/spow-telegram-bridge/poetry.lock
generated
Normal file
|
@ -0,0 +1,167 @@
|
|||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.13.2"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
|
||||
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.10.1"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"},
|
||||
{file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"},
|
||||
{file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"},
|
||||
{file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"},
|
||||
{file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"},
|
||||
{file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"},
|
||||
{file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"},
|
||||
{file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"},
|
||||
{file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"},
|
||||
{file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"},
|
||||
{file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=1.0.0"
|
||||
typing-extensions = ">=4.1.0"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
install-types = ["pip"]
|
||||
mypyc = ["setuptools (>=50)"]
|
||||
reports = ["lxml"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.0.0"
|
||||
description = "Type system extensions for programs checked with the mypy type checker."
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyaes"
|
||||
version = "1.6.1"
|
||||
description = "Pure-Python Implementation of the AES block-cipher and common modes of operation"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "pyaes-1.6.1.tar.gz", hash = "sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.0"
|
||||
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"},
|
||||
{file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "4.9"
|
||||
description = "Pure-Python RSA implementation"
|
||||
optional = false
|
||||
python-versions = ">=3.6,<4"
|
||||
files = [
|
||||
{file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
|
||||
{file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyasn1 = ">=0.1.3"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.5.2"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.5.2-py3-none-linux_armv6l.whl", hash = "sha256:7bab8345df60f9368d5f4594bfb8b71157496b44c30ff035d1d01972e764d3be"},
|
||||
{file = "ruff-0.5.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1aa7acad382ada0189dbe76095cf0a36cd0036779607c397ffdea16517f535b1"},
|
||||
{file = "ruff-0.5.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:aec618d5a0cdba5592c60c2dee7d9c865180627f1a4a691257dea14ac1aa264d"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b62adc5ce81780ff04077e88bac0986363e4a3260ad3ef11ae9c14aa0e67ef"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dc42ebf56ede83cb080a50eba35a06e636775649a1ffd03dc986533f878702a3"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c15c6e9f88c67ffa442681365d11df38afb11059fc44238e71a9d9f1fd51de70"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d3de9a5960f72c335ef00763d861fc5005ef0644cb260ba1b5a115a102157251"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe5a968ae933e8f7627a7b2fc8893336ac2be0eb0aace762d3421f6e8f7b7f83"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04f54a9018f75615ae52f36ea1c5515e356e5d5e214b22609ddb546baef7132"},
|
||||
{file = "ruff-0.5.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed02fb52e3741f0738db5f93e10ae0fb5c71eb33a4f2ba87c9a2fa97462a649"},
|
||||
{file = "ruff-0.5.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3cf8fe659f6362530435d97d738eb413e9f090e7e993f88711b0377fbdc99f60"},
|
||||
{file = "ruff-0.5.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:237a37e673e9f3cbfff0d2243e797c4862a44c93d2f52a52021c1a1b0899f846"},
|
||||
{file = "ruff-0.5.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2a2949ce7c1cbd8317432ada80fe32156df825b2fd611688814c8557824ef060"},
|
||||
{file = "ruff-0.5.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:481af57c8e99da92ad168924fd82220266043c8255942a1cb87958b108ac9335"},
|
||||
{file = "ruff-0.5.2-py3-none-win32.whl", hash = "sha256:f1aea290c56d913e363066d83d3fc26848814a1fed3d72144ff9c930e8c7c718"},
|
||||
{file = "ruff-0.5.2-py3-none-win_amd64.whl", hash = "sha256:8532660b72b5d94d2a0a7a27ae7b9b40053662d00357bb2a6864dd7e38819084"},
|
||||
{file = "ruff-0.5.2-py3-none-win_arm64.whl", hash = "sha256:73439805c5cb68f364d826a5c5c4b6c798ded6b7ebaa4011f01ce6c94e4d5583"},
|
||||
{file = "ruff-0.5.2.tar.gz", hash = "sha256:2c0df2d2de685433794a14d8d2e240df619b748fbe3367346baa519d8e6f1ca2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "telethon"
|
||||
version = "1.36.0"
|
||||
description = "Full-featured Telegram client library for Python 3"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "Telethon-1.36.0.tar.gz", hash = "sha256:11db5c7ed7e37f1272d443fb7eea0f1db580d56c6949165233946fb323aaf3a7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyaes = "*"
|
||||
rsa = "*"
|
||||
|
||||
[package.extras]
|
||||
cryptg = ["cryptg"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "613e275515a3ca76e83872c16883548a10a6ad913a6be381a52acf0c4680bd1d"
|
40
src/spow-telegram-bridge/pyproject.toml
Normal file
40
src/spow-telegram-bridge/pyproject.toml
Normal file
|
@ -0,0 +1,40 @@
|
|||
[tool.poetry]
|
||||
name = "spow-telegram-bridge"
|
||||
version = "2.2024.29"
|
||||
description = "add-on bridge for surplus on wheels (s+ow) to telegram"
|
||||
authors = ["Mark Joshwel <mark@joshwel.co>"]
|
||||
license = "Unlicense"
|
||||
readme = "README.md"
|
||||
packages = [
|
||||
{include = "bridge.py"}
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
Telethon = "^1.36.0"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
spow-telegram-bridge = 'bridge:entry'
|
||||
"s+ow-telegram-bridge" = 'bridge:entry'
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
# https://github.com/nix-community/poetry2nix/blob/master/docs/edgecases.md#errors-that-are-related-to-rust-and-cargo
|
||||
# if bumping this, also update the flake.nix file
|
||||
ruff = "0.5.2"
|
||||
mypy = "^1.10.1"
|
||||
isort = "^5.13.2"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py311"
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
|
||||
[tool.isort]
|
||||
line_length = 100
|
||||
profile = "black"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
8
src/spow-whatsapp-bridge/.gitignore
vendored
Normal file
8
src/spow-whatsapp-bridge/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
spow-whatsapp-bridge
|
||||
mdtest.db
|
||||
dist/
|
||||
test/
|
||||
|
||||
# nix
|
||||
.devbox
|
||||
result
|
374
src/spow-whatsapp-bridge/LICENCE
Normal file
374
src/spow-whatsapp-bridge/LICENCE
Normal file
|
@ -0,0 +1,374 @@
|
|||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
|
5
src/spow-whatsapp-bridge/README.md
Normal file
5
src/spow-whatsapp-bridge/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# surplus on wheels: WhatsApp Bridge
|
||||
|
||||
see <https://surplus.joshwel.co/onwheels/whatsapp-bridge>
|
||||
or [/docs/onwheels/whatsapp-bridge.md](../../docs/onwheels/whatsapp-bridge.md)
|
||||
for more information and documentation
|
430
src/spow-whatsapp-bridge/bridge.go
Normal file
430
src/spow-whatsapp-bridge/bridge.go
Normal file
|
@ -0,0 +1,430 @@
|
|||
// Copyright (c) 2021 Tulir Asokan
|
||||
// Copyright (c) 2024 Mark Joshwel
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// from https://github.com/tulir/whatsmeow/commit/792d96fbe610bfbf1039ec3b8d3f37f630025aea
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"mime"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/mdp/qrterminal/v3"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"go.mau.fi/whatsmeow"
|
||||
"go.mau.fi/whatsmeow/appstate"
|
||||
waBinary "go.mau.fi/whatsmeow/binary"
|
||||
waProto "go.mau.fi/whatsmeow/binary/proto"
|
||||
"go.mau.fi/whatsmeow/store"
|
||||
"go.mau.fi/whatsmeow/store/sqlstore"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"go.mau.fi/whatsmeow/types/events"
|
||||
waLog "go.mau.fi/whatsmeow/util/log"
|
||||
)
|
||||
|
||||
var cli *whatsmeow.Client
|
||||
var log waLog.Logger
|
||||
|
||||
var logLevel = "INFO"
|
||||
var debugLogs = flag.Bool("debug", false, "Enable debug logs?")
|
||||
var dbDialect = flag.String("db-dialect", "sqlite3", "Database dialect (sqlite3 or postgres)")
|
||||
var dbAddress = flag.String("db-address", "file:mdtest.db?_foreign_keys=on", "Database address")
|
||||
var requestFullSync = flag.Bool("request-full-sync", false, "Request full (1 year) history sync when logging in?")
|
||||
var pairRejectChan = make(chan bool, 1)
|
||||
|
||||
// s+ow-whatsapp-bridge: define important paths
|
||||
var dataDir = path.Join(os.Getenv("HOME"), ".local", "share", "s+ow-whatsapp-bridge")
|
||||
var sharetextPath = path.Join(os.Getenv("HOME"), ".cache", "s+ow", "message")
|
||||
|
||||
func main() {
|
||||
waBinary.IndentXML = true
|
||||
flag.Parse()
|
||||
|
||||
if *debugLogs {
|
||||
logLevel = "DEBUG"
|
||||
}
|
||||
if *requestFullSync {
|
||||
store.DeviceProps.RequireFullSync = proto.Bool(true)
|
||||
store.DeviceProps.HistorySyncConfig = &waProto.DeviceProps_HistorySyncConfig{
|
||||
FullSyncDaysLimit: proto.Uint32(3650),
|
||||
FullSyncSizeMbLimit: proto.Uint32(102400),
|
||||
StorageQuotaMb: proto.Uint32(102400),
|
||||
}
|
||||
}
|
||||
log = waLog.Stdout("Main", logLevel, true)
|
||||
|
||||
// s+ow-whatsapp-bridge: make and change dir
|
||||
err := os.MkdirAll(dataDir, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: s+ow-whatsapp-bridge: Failed to create directory: %v", err)
|
||||
return
|
||||
}
|
||||
err = os.Chdir(dataDir)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: s+ow-whatsapp-bridge: Failed to change directory: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
dbLog := waLog.Stdout("Database", logLevel, true)
|
||||
storeContainer, err := sqlstore.New(*dbDialect, *dbAddress, dbLog)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to connect to database: %v", err)
|
||||
return
|
||||
}
|
||||
device, err := storeContainer.GetFirstDevice()
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to get device: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
cli = whatsmeow.NewClient(device, waLog.Stdout("Client", logLevel, true))
|
||||
var isWaitingForPair atomic.Bool
|
||||
cli.PrePairCallback = func(jid types.JID, platform, businessName string) bool {
|
||||
isWaitingForPair.Store(true)
|
||||
defer isWaitingForPair.Store(false)
|
||||
log.Infof("s+ow-whatsapp-bridge: Pairing %s (platform: %q, business name: %q). Type r within 3 seconds to reject pair", jid, platform, businessName)
|
||||
select {
|
||||
case reject := <-pairRejectChan:
|
||||
if reject {
|
||||
log.Infof("s+ow-whatsapp-bridge: Rejecting pair")
|
||||
return false
|
||||
}
|
||||
case <-time.After(3 * time.Second):
|
||||
}
|
||||
log.Infof("s+ow-whatsapp-bridge: Accepting pair")
|
||||
return true
|
||||
}
|
||||
|
||||
ch, err := cli.GetQRChannel(context.Background())
|
||||
if err != nil {
|
||||
// This error means that we're already logged in, so ignore it.
|
||||
if !errors.Is(err, whatsmeow.ErrQRStoreContainsID) {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to get QR channel: %v", err)
|
||||
}
|
||||
} else {
|
||||
go func() {
|
||||
for evt := range ch {
|
||||
if evt.Event == "code" {
|
||||
qrterminal.GenerateHalfBlock(evt.Code, qrterminal.L, os.Stdout)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: QR channel result: %s", evt.Event)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
cli.AddEventHandler(handler)
|
||||
err = cli.Connect()
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to connect: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
input := make(chan string)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
defer close(input)
|
||||
scan := bufio.NewScanner(os.Stdin)
|
||||
for scan.Scan() {
|
||||
line := strings.TrimSpace(scan.Text())
|
||||
if len(line) > 0 {
|
||||
input <- line
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// s+ow-whatsapp-bridge
|
||||
// - if 'login' in os.Args, we exit here
|
||||
for _, arg := range os.Args {
|
||||
if arg == "login" {
|
||||
for {
|
||||
select {
|
||||
case <-c:
|
||||
log.Infof("Interrupt received, exiting")
|
||||
cli.Disconnect()
|
||||
return
|
||||
case cmd := <-input:
|
||||
if len(cmd) == 0 {
|
||||
log.Infof("Stdin closed, exiting")
|
||||
cli.Disconnect()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// - if using as cli
|
||||
args := os.Args[1:]
|
||||
if len(args) > 0 {
|
||||
handleCmd(strings.ToLower(args[0]), args[1:])
|
||||
return
|
||||
}
|
||||
|
||||
// - else, "normal" operation:
|
||||
|
||||
// - - read file ~/.cache/s+ow/message
|
||||
sharetext, err := os.ReadFile(sharetextPath)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to open file: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// - - read JID targets from stdin
|
||||
targets := <-input
|
||||
split := strings.Split(targets, ",")
|
||||
for _, target := range split {
|
||||
// strip whitespace
|
||||
target = strings.TrimSpace(target)
|
||||
|
||||
// check if prefixed with "wa:"
|
||||
if strings.HasPrefix(target, "wa:") {
|
||||
// send message to group
|
||||
recipient, ok := parseJID(target[3:])
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// reference the "send" case in handleCmd as a single source of truth
|
||||
msg := &waProto.Message{Conversation: proto.String(strings.TrimSpace(string(sharetext)))}
|
||||
resp, err := cli.SendMessage(context.Background(), recipient, msg)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Error sending message: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Message sent (server timestamp: %s)", resp.Timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("s+ow-whatsapp-bridge: Exiting")
|
||||
cli.Disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
func parseJID(arg string) (types.JID, bool) {
|
||||
if arg[0] == '+' {
|
||||
arg = arg[1:]
|
||||
}
|
||||
|
||||
if !strings.ContainsRune(arg, '@') {
|
||||
return types.NewJID(arg, types.DefaultUserServer), true
|
||||
}
|
||||
|
||||
recipient, err := types.ParseJID(arg)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Invalid JID %s: %v", arg, err)
|
||||
return recipient, false
|
||||
} else if recipient.User == "" {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Invalid JID %s: no server specified", arg)
|
||||
return recipient, false
|
||||
}
|
||||
return recipient, true
|
||||
}
|
||||
|
||||
func handleCmd(cmd string, args []string) {
|
||||
// s+ow-whatsapp-bridge: we only need the bare minimum:
|
||||
// - account-related: pair-phone, logout, reconnect
|
||||
// - chat-related: list, send
|
||||
switch cmd {
|
||||
case "pair-phone":
|
||||
if len(args) < 1 {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Usage: pair-phone <number>")
|
||||
return
|
||||
}
|
||||
linkingCode, err := cli.PairPhone(args[0], true, whatsmeow.PairClientChrome, "Chrome (Linux)")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Linking code:", linkingCode)
|
||||
case "reconnect":
|
||||
cli.Disconnect()
|
||||
err := cli.Connect()
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to connect: %v", err)
|
||||
}
|
||||
case "logout":
|
||||
err := cli.Logout()
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Error logging out: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Successfully logged out")
|
||||
}
|
||||
case "list":
|
||||
groups, err := cli.GetJoinedGroups()
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to get group list: %v", err)
|
||||
} else {
|
||||
for _, group := range groups {
|
||||
fmt.Printf("%s\t\t%s\n", group.JID, group.Name)
|
||||
}
|
||||
}
|
||||
case "send":
|
||||
if len(args) < 2 {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Usage: send <jid> <text>")
|
||||
return
|
||||
}
|
||||
recipient, ok := parseJID(args[0])
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
msg := &waProto.Message{Conversation: proto.String(strings.Join(args[1:], " "))}
|
||||
resp, err := cli.SendMessage(context.Background(), recipient, msg)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Error sending message: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Message sent (server timestamp: %s)", resp.Timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var historySyncID int32
|
||||
var startupTime = time.Now().Unix()
|
||||
|
||||
func handler(rawEvt interface{}) {
|
||||
switch evt := rawEvt.(type) {
|
||||
case *events.AppStateSyncComplete:
|
||||
if len(cli.Store.PushName) > 0 && evt.Name == appstate.WAPatchCriticalBlock {
|
||||
err := cli.SendPresence(types.PresenceAvailable)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to send available presence: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Marked self as available")
|
||||
}
|
||||
}
|
||||
case *events.Connected, *events.PushNameSetting:
|
||||
if len(cli.Store.PushName) == 0 {
|
||||
return
|
||||
}
|
||||
// Send presence available when connecting and when the pushname is changed.
|
||||
// This makes sure that outgoing messages always have the right pushname.
|
||||
err := cli.SendPresence(types.PresenceAvailable)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to send available presence: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Marked self as available")
|
||||
}
|
||||
case *events.StreamReplaced:
|
||||
os.Exit(0)
|
||||
case *events.Message:
|
||||
metaParts := []string{fmt.Sprintf("pushname: %s", evt.Info.PushName), fmt.Sprintf("timestamp: %s", evt.Info.Timestamp)}
|
||||
if evt.Info.Type != "" {
|
||||
metaParts = append(metaParts, fmt.Sprintf("type: %s", evt.Info.Type))
|
||||
}
|
||||
if evt.Info.Category != "" {
|
||||
metaParts = append(metaParts, fmt.Sprintf("category: %s", evt.Info.Category))
|
||||
}
|
||||
if evt.IsViewOnce {
|
||||
metaParts = append(metaParts, "view once")
|
||||
}
|
||||
if evt.IsViewOnce {
|
||||
metaParts = append(metaParts, "ephemeral")
|
||||
}
|
||||
if evt.IsViewOnceV2 {
|
||||
metaParts = append(metaParts, "ephemeral (v2)")
|
||||
}
|
||||
if evt.IsDocumentWithCaption {
|
||||
metaParts = append(metaParts, "document with caption")
|
||||
}
|
||||
if evt.IsEdit {
|
||||
metaParts = append(metaParts, "edit")
|
||||
}
|
||||
|
||||
log.Infof("s+ow-whatsapp-bridge: Received message %s from %s (%s): %+v", evt.Info.ID, evt.Info.SourceString(), strings.Join(metaParts, ", "), evt.Message)
|
||||
|
||||
if evt.Message.GetPollUpdateMessage() != nil {
|
||||
decrypted, err := cli.DecryptPollVote(evt)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to decrypt vote: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Selected options in decrypted vote:")
|
||||
for _, option := range decrypted.SelectedOptions {
|
||||
log.Infof("s+ow-whatsapp-bridge: - %X", option)
|
||||
}
|
||||
}
|
||||
} else if evt.Message.GetEncReactionMessage() != nil {
|
||||
decrypted, err := cli.DecryptReaction(evt)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to decrypt encrypted reaction: %v", err)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: Decrypted reaction: %+v", decrypted)
|
||||
}
|
||||
}
|
||||
|
||||
img := evt.Message.GetImageMessage()
|
||||
if img != nil {
|
||||
data, err := cli.Download(img)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to download image: %v", err)
|
||||
return
|
||||
}
|
||||
exts, _ := mime.ExtensionsByType(img.GetMimetype())
|
||||
savePath := fmt.Sprintf("%s%s", evt.Info.ID, exts[0])
|
||||
err = os.WriteFile(savePath, data, 0600)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to save image: %v", err)
|
||||
return
|
||||
}
|
||||
log.Infof("s+ow-whatsapp-bridge: Saved image in message to %s", savePath)
|
||||
}
|
||||
case *events.Receipt:
|
||||
if evt.Type == types.ReceiptTypeRead || evt.Type == types.ReceiptTypeReadSelf {
|
||||
log.Infof("s+ow-whatsapp-bridge: %v was read by %s at %s", evt.MessageIDs, evt.SourceString(), evt.Timestamp)
|
||||
} else if evt.Type == types.ReceiptTypeDelivered {
|
||||
log.Infof("s+ow-whatsapp-bridge: %s was delivered to %s at %s", evt.MessageIDs[0], evt.SourceString(), evt.Timestamp)
|
||||
}
|
||||
case *events.Presence:
|
||||
if evt.Unavailable {
|
||||
if evt.LastSeen.IsZero() {
|
||||
log.Infof("s+ow-whatsapp-bridge: %s is now offline", evt.From)
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: %s is now offline (last seen: %s)", evt.From, evt.LastSeen)
|
||||
}
|
||||
} else {
|
||||
log.Infof("s+ow-whatsapp-bridge: %s is now online", evt.From)
|
||||
}
|
||||
case *events.HistorySync:
|
||||
id := atomic.AddInt32(&historySyncID, 1)
|
||||
fileName := fmt.Sprintf("history-%d-%d.json", startupTime, id)
|
||||
file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to open file to write history sync: %v", err)
|
||||
return
|
||||
}
|
||||
enc := json.NewEncoder(file)
|
||||
enc.SetIndent("", " ")
|
||||
err = enc.Encode(evt.Data)
|
||||
if err != nil {
|
||||
log.Errorf("s+ow-whatsapp-bridge: Failed to write history sync: %v", err)
|
||||
return
|
||||
}
|
||||
log.Infof("s+ow-whatsapp-bridge: Wrote history sync to %s", fileName)
|
||||
_ = file.Close()
|
||||
case *events.AppState:
|
||||
log.Debugf("s+ow-whatsapp-bridge: App state event: %+v / %+v", evt.Index, evt.SyncActionValue)
|
||||
case *events.KeepAliveTimeout:
|
||||
log.Debugf("s+ow-whatsapp-bridge: Keepalive timeout event: %+v", evt)
|
||||
case *events.KeepAliveRestored:
|
||||
log.Debugf("s+ow-whatsapp-bridge: Keepalive restored")
|
||||
case *events.Blocklist:
|
||||
log.Infof("s+ow-whatsapp-bridge: Blocklist event: %+v", evt)
|
||||
}
|
||||
}
|
4
src/spow-whatsapp-bridge/build-termux.sh
Normal file
4
src/spow-whatsapp-bridge/build-termux.sh
Normal file
|
@ -0,0 +1,4 @@
|
|||
mkdir -p "$HOME/.local/bin"
|
||||
pkg install golang
|
||||
go build
|
||||
mv spow_whatsapp_bridge "$HOME/.local/bin/s+ow-whatsapp-bridge"
|
24
src/spow-whatsapp-bridge/check.sh
Normal file
24
src/spow-whatsapp-bridge/check.sh
Normal file
|
@ -0,0 +1,24 @@
|
|||
#!/bin/sh
|
||||
failures=0
|
||||
|
||||
ORI_HASH=$(md5sum < bridge.go)
|
||||
FMT_HASH=$(gofmt bridge.go | md5sum)
|
||||
if ! [ "$FMT_HASH" = "$ORI_HASH" ]; then
|
||||
printf "formatted file (%s) is not the same as the original file (%s)" "$FMT_HASH" "$ORI_HASH"
|
||||
failures=$((failures + 1))
|
||||
else
|
||||
printf "formatted file is same as original file - %s (yay!)" "$FMT_HASH"
|
||||
fi
|
||||
|
||||
go vet bridge.go
|
||||
failures=$((failures + $?))
|
||||
|
||||
golint bridge.go
|
||||
failures=$((failures + $?))
|
||||
|
||||
if [ $failures -eq 0 ]; then
|
||||
printf "\n\nall checks okay! (❁´◡\`❁)\n"
|
||||
else
|
||||
printf "\n\nsome checks failed...\n"
|
||||
fi
|
||||
exit $failures
|
85
src/spow-whatsapp-bridge/flake.lock
generated
Normal file
85
src/spow-whatsapp-bridge/flake.lock
generated
Normal file
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gomod2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1722589758,
|
||||
"narHash": "sha256-sbbA8b6Q2vB/t/r1znHawoXLysCyD4L/6n6/RykiSnA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "gomod2nix",
|
||||
"rev": "4e08ca09253ef996bd4c03afa383b23e35fe28a1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "gomod2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1724224976,
|
||||
"narHash": "sha256-Z/ELQhrSd7bMzTO8r7NZgi9g5emh+aRKoCdaAv5fiO0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c374d94f1536013ca8e92341b540eba4c22f9c62",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"gomod2nix": "gomod2nix",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
116
src/spow-whatsapp-bridge/flake.nix
Normal file
116
src/spow-whatsapp-bridge/flake.nix
Normal file
|
@ -0,0 +1,116 @@
|
|||
{
|
||||
description = "development environment for surplus on wheels: WhatsApp Bridge";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
gomod2nix = {
|
||||
url = "github:nix-community/gomod2nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.flake-utils.follows = "flake-utils";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, gomod2nix }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
bridge = {
|
||||
name = "spow-whatsapp-bridge";
|
||||
version = "2.2024.34";
|
||||
};
|
||||
|
||||
pkgs = import nixpkgs {
|
||||
system = system;
|
||||
overlays = [
|
||||
gomod2nix.overlays.default
|
||||
];
|
||||
config = {
|
||||
android_sdk.accept_license = true;
|
||||
allowUnfree = true;
|
||||
};
|
||||
};
|
||||
|
||||
sdk = (pkgs.androidenv.composeAndroidPackages {
|
||||
includeSources = false;
|
||||
includeSystemImages = false;
|
||||
includeEmulator = false;
|
||||
includeNDK = true;
|
||||
ndkVersions = ["26.3.11579264"];
|
||||
}).androidsdk;
|
||||
|
||||
androidClang = (
|
||||
"${sdk}/libexec/android-sdk/ndk-bundle/toolchains/llvm/prebuilt/"
|
||||
+
|
||||
(
|
||||
{
|
||||
"x86_64-darwin" = "darwin-x86_64";
|
||||
"x86_64-linux" = "linux-x86_64";
|
||||
}."${system}" or
|
||||
(throw "the android ndk does not support your platform... (;′⌒`) (apple silicon users see https://github.com/android/ndk/issues/1299)")
|
||||
)
|
||||
+
|
||||
"/bin/aarch64-linux-android30-clang"
|
||||
);
|
||||
|
||||
bridgeBuildTermux = pkgs.buildGoApplication {
|
||||
pname = bridge.name;
|
||||
version = bridge.version;
|
||||
|
||||
go = pkgs.go_1_22;
|
||||
src = ./.;
|
||||
modules = ./gomod2nix.toml;
|
||||
|
||||
buildInputs = [ sdk ];
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
mkdir -p $out/bin
|
||||
CC="${androidClang}" CGO_ENABLED=1 GOOS=android GOARCH=arm64 go build -o $out/bin
|
||||
runHook postBuild
|
||||
'';
|
||||
};
|
||||
|
||||
bridgeBuildNative = pkgs.buildGoApplication {
|
||||
pname = bridge.name;
|
||||
version = bridge.version;
|
||||
|
||||
go = pkgs.go_1_22;
|
||||
src = ./.;
|
||||
modules = ./gomod2nix.toml;
|
||||
};
|
||||
in
|
||||
with pkgs; {
|
||||
# nix develop
|
||||
devShells.default = mkShell {
|
||||
buildInputs = [
|
||||
go
|
||||
golint
|
||||
gomod2nix.packages.${system}.default
|
||||
];
|
||||
};
|
||||
|
||||
# nix build .#termux
|
||||
packages.termux = stdenvNoCC.mkDerivation {
|
||||
pname = bridge.name;
|
||||
version = bridge.version;
|
||||
src = bridgeBuildTermux;
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp $src/bin/$pname $out/$pname
|
||||
'';
|
||||
};
|
||||
|
||||
# nix build
|
||||
packages.default = stdenvNoCC.mkDerivation {
|
||||
pname = bridge.name;
|
||||
version = bridge.version;
|
||||
src = bridgeBuildNative;
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp $src/bin/$pname $out/$pname
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
26
src/spow-whatsapp-bridge/go.mod
Normal file
26
src/spow-whatsapp-bridge/go.mod
Normal file
|
@ -0,0 +1,26 @@
|
|||
module forge.joshwel.co/mark/surplus/src/spow-whatsapp-bridge
|
||||
|
||||
go 1.22.3
|
||||
|
||||
require (
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/mdp/qrterminal/v3 v3.2.0
|
||||
go.mau.fi/whatsmeow v0.0.0-20240821142752-3d63c6fcc1a7
|
||||
google.golang.org/protobuf v1.34.2
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/rs/zerolog v1.33.0 // indirect
|
||||
go.mau.fi/libsignal v0.1.1 // indirect
|
||||
go.mau.fi/util v0.7.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
)
|
85
src/spow-whatsapp-bridge/go.sum
Normal file
85
src/spow-whatsapp-bridge/go.sum
Normal file
|
@ -0,0 +1,85 @@
|
|||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mdp/qrterminal/v3 v3.2.0 h1:qteQMXO3oyTK4IHwj2mWsKYYRBOp1Pj2WRYFYYNTCdk=
|
||||
github.com/mdp/qrterminal/v3 v3.2.0/go.mod h1:XGGuua4Lefrl7TLEsSONiD+UEjQXJZ4mPzF+gWYIJkk=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
go.mau.fi/libsignal v0.1.0 h1:vAKI/nJ5tMhdzke4cTK1fb0idJzz1JuEIpmjprueC+c=
|
||||
go.mau.fi/libsignal v0.1.0/go.mod h1:R8ovrTezxtUNzCQE5PH30StOQWWeBskBsWE55vMfY9I=
|
||||
go.mau.fi/libsignal v0.1.1 h1:m/0PGBh4QKP/I1MQ44ti4C0fMbLMuHb95cmDw01FIpI=
|
||||
go.mau.fi/libsignal v0.1.1/go.mod h1:QLs89F/OA3ThdSL2Wz2p+o+fi8uuQUz0e1BRa6ExdBw=
|
||||
go.mau.fi/util v0.4.2 h1:RR3TOcRHmCF9Bx/3YG4S65MYfa+nV6/rn8qBWW4Mi30=
|
||||
go.mau.fi/util v0.4.2/go.mod h1:PlAVfUUcPyHPrwnvjkJM9UFcPE7qGPDJqk+Oufa1Gtw=
|
||||
go.mau.fi/util v0.5.0 h1:8yELAl+1CDRrwGe9NUmREgVclSs26Z68pTWePHVxuDo=
|
||||
go.mau.fi/util v0.5.0/go.mod h1:DsJzUrJAG53lCZnnYvq9/mOyLuPScWwYhvETiTrpdP4=
|
||||
go.mau.fi/util v0.6.0 h1:W6SyB3Bm/GjenQ5iq8Z8WWdN85Gy2xS6L0wmnR7SVjg=
|
||||
go.mau.fi/util v0.6.0/go.mod h1:ljYdq3sPfpICc3zMU+/mHV/sa4z0nKxc67hSBwnrk8U=
|
||||
go.mau.fi/util v0.7.0 h1:l31z+ivrSQw+cv/9eFebEqtQW2zhxivGypn+JT0h/ws=
|
||||
go.mau.fi/util v0.7.0/go.mod h1:bWYreIoTULL/UiRbZdfddPh7uWDFW5yX4YCv5FB0eE0=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240603101645-64bc969fbe78 h1:zST/E2cOjQEjXuis0miwSd20Uf+ffdJna6QefQyxEcc=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240603101645-64bc969fbe78/go.mod h1:0+65CYaE6r4dWzr0dN8i+UZKy0gIfJ79VuSqIl0nKRM=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240619210240-329c2336a6f1 h1:gpFEqwk7WtbF/8HaOMASKE6JvxWqhTmaR0CqoPpoly8=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240619210240-329c2336a6f1/go.mod h1:0+65CYaE6r4dWzr0dN8i+UZKy0gIfJ79VuSqIl0nKRM=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240716084021-eb41d1f09552 h1:3cI+n5D79nOlS3hef6PD1D8wkXEyxSIW0mvotE8ymVE=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240716084021-eb41d1f09552/go.mod h1:BhHKalSq0qNtSCuGIUIvoJyU5KbT4a7k8DQ5yw1Ssk4=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240821142752-3d63c6fcc1a7 h1:Aa4uov0rM0SQQ7Fc/TZZpmQEGksie2SVTv/UuCJwViI=
|
||||
go.mau.fi/whatsmeow v0.0.0-20240821142752-3d63c6fcc1a7/go.mod h1:BhHKalSq0qNtSCuGIUIvoJyU5KbT4a7k8DQ5yw1Ssk4=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
54
src/spow-whatsapp-bridge/gomod2nix.toml
Normal file
54
src/spow-whatsapp-bridge/gomod2nix.toml
Normal file
|
@ -0,0 +1,54 @@
|
|||
schema = 3
|
||||
|
||||
[mod]
|
||||
[mod."filippo.io/edwards25519"]
|
||||
version = "v1.1.0"
|
||||
hash = "sha256-9ACANrgWZSd5HYPfDZHY8DVbPSC9LOMgy8deq3rDOoc="
|
||||
[mod."github.com/google/uuid"]
|
||||
version = "v1.6.0"
|
||||
hash = "sha256-VWl9sqUzdOuhW0KzQlv0gwwUQClYkmZwSydHG2sALYw="
|
||||
[mod."github.com/gorilla/websocket"]
|
||||
version = "v1.5.3"
|
||||
hash = "sha256-vTIGEFMEi+30ZdO6ffMNJ/kId6pZs5bbyqov8xe9BM0="
|
||||
[mod."github.com/mattn/go-colorable"]
|
||||
version = "v0.1.13"
|
||||
hash = "sha256-qb3Qbo0CELGRIzvw7NVM1g/aayaz4Tguppk9MD2/OI8="
|
||||
[mod."github.com/mattn/go-isatty"]
|
||||
version = "v0.0.20"
|
||||
hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ="
|
||||
[mod."github.com/mattn/go-sqlite3"]
|
||||
version = "v1.14.22"
|
||||
hash = "sha256-CWF2Hjg43658NhaePWbGzS19gHJXjuTroG5c0W3hgYQ="
|
||||
[mod."github.com/mdp/qrterminal/v3"]
|
||||
version = "v3.2.0"
|
||||
hash = "sha256-2ZcpLFu6P+a3qHH32uiFKUwzgza1NF0Bmayl41GQCEI="
|
||||
[mod."github.com/rs/zerolog"]
|
||||
version = "v1.33.0"
|
||||
hash = "sha256-jT/Y/izhZiCdrDbC/ty83FGs8UQavTU+OW03O4vKFkY="
|
||||
[mod."go.mau.fi/libsignal"]
|
||||
version = "v0.1.0"
|
||||
hash = "sha256-hSZQkw/0eV5Y0pj1N+idYuKb/jtiw/qTfaOGdYCXmn0="
|
||||
[mod."go.mau.fi/util"]
|
||||
version = "v0.4.2"
|
||||
hash = "sha256-o/d7Wd+2byFxmVxjl5o/AAUO/2d12vzItq6H5yUtcow="
|
||||
[mod."go.mau.fi/whatsmeow"]
|
||||
version = "v0.0.0-20240603101645-64bc969fbe78"
|
||||
hash = "sha256-MBDcxTHM+ZxxzIrendWWEhNdkPA7cLgkduC424+j+fU="
|
||||
[mod."golang.org/x/crypto"]
|
||||
version = "v0.24.0"
|
||||
hash = "sha256-wpxJApwSmmn9meVdpFdOU0gzeJbIXcKuFfYUUVogSss="
|
||||
[mod."golang.org/x/net"]
|
||||
version = "v0.26.0"
|
||||
hash = "sha256-WfY33QERNbcIiDkH3+p2XGrAVqvWBQfc8neUt6TH6dQ="
|
||||
[mod."golang.org/x/sys"]
|
||||
version = "v0.21.0"
|
||||
hash = "sha256-gapzPWuEqY36V6W2YhIDYR49sEvjJRd7bSuf9K1f4JY="
|
||||
[mod."golang.org/x/term"]
|
||||
version = "v0.21.0"
|
||||
hash = "sha256-zRm7uPBM1+TJkODYHkk/BtN3la5QAaSgslE2hSTm27Y="
|
||||
[mod."google.golang.org/protobuf"]
|
||||
version = "v1.34.2"
|
||||
hash = "sha256-nMTlrDEE2dbpWz50eQMPBQXCyQh4IdjrTIccaU0F3m0="
|
||||
[mod."rsc.io/qr"]
|
||||
version = "v0.2.0"
|
||||
hash = "sha256-I3fAJwwZhIrgBbCjWvIElAE9JqG2y59KRBc78EYi3RM="
|
5
src/spow-whatsapp-bridge/install.sh
Normal file
5
src/spow-whatsapp-bridge/install.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
# surplus on wheels: WhatsApp Bridge: installation and updater script
|
||||
set -e
|
||||
|
||||
echo TODO
|
2
src/surplus-on-wheels/.s+ow-bridges
Normal file
2
src/surplus-on-wheels/.s+ow-bridges
Normal file
|
@ -0,0 +1,2 @@
|
|||
SPOW_TELEGRAM_API_HASH="" SPOW_TELEGRAM_API_ID="" s+ow-telegram-bridge
|
||||
s+ow-whatsapp-bridge
|
13
src/surplus-on-wheels/README.md
Normal file
13
src/surplus-on-wheels/README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# surplus on wheels
|
||||
|
||||
surplus on wheels (s+ow) is a pure shell script to get your location using
|
||||
[termux-location](https://wiki.termux.com/wiki/Termux-location), process it through surplus, and
|
||||
send it to messaging service or wherever using “bridges”
|
||||
|
||||
surplus was made to emulate sending your location through the iOS Shortcuts app, and surplus on
|
||||
wheels complements it by running surplus automatically using a cron job.
|
||||
(but using it manually also works!)
|
||||
|
||||
see <https://surplus.joshwel.co/onwheels/>
|
||||
or [/docs/onwheels/index.md](../../docs/onwheels/index.md)
|
||||
for more information and documentation
|
24
src/surplus-on-wheels/UNLICENCE
Normal file
24
src/surplus-on-wheels/UNLICENCE
Normal file
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
21
src/surplus-on-wheels/check.sh
Normal file
21
src/surplus-on-wheels/check.sh
Normal file
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
failures=0
|
||||
|
||||
FMT_HASH=$(shfmt s+ow | md5sum)
|
||||
ORI_HASH=$(md5sum < s+ow)
|
||||
if ! [ "$FMT_HASH" = "$ORI_HASH" ]; then
|
||||
printf "formatted file (%s) is not the same as the original file (%s)" "$FMT_HASH" "$ORI_HASH"
|
||||
failures=$((failures + 1))
|
||||
else
|
||||
printf "formatted file is same as original file - %s (yay!)" "$FMT_HASH"
|
||||
fi
|
||||
|
||||
shellcheck s+ow
|
||||
failures=$((failures + $?))
|
||||
|
||||
if [ $failures -eq 0 ]; then
|
||||
printf "\n\nall checks okay! (❁´◡\`❁)\n"
|
||||
else
|
||||
printf "\n\nsome checks failed...\n"
|
||||
fi
|
||||
exit $failures
|
61
src/surplus-on-wheels/flake.lock
generated
Normal file
61
src/surplus-on-wheels/flake.lock
generated
Normal file
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1718318537,
|
||||
"narHash": "sha256-4Zu0RYRcAY/VWuu6awwq4opuiD//ahpc2aFHg2CWqFY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e9ee548d90ff586a6471b4ae80ae9cfcbceb3420",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
20
src/surplus-on-wheels/flake.nix
Normal file
20
src/surplus-on-wheels/flake.nix
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
description = "development environment for surplus on wheels";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
with pkgs; {
|
||||
devShells.default = mkShellNoCC {
|
||||
buildInputs = [ shfmt shellcheck ];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
37
src/surplus-on-wheels/install.sh
Normal file
37
src/surplus-on-wheels/install.sh
Normal file
|
@ -0,0 +1,37 @@
|
|||
#!/bin/sh
|
||||
# surplus on wheels: termux installation script
|
||||
set -e
|
||||
|
||||
# get packages
|
||||
yes | pkg upgrade
|
||||
yes | pkg install python cronie termux-api termux-services wget
|
||||
|
||||
# install pipx and surplus
|
||||
pip install pipx
|
||||
pipx install surplus
|
||||
|
||||
# install s+ow
|
||||
mkdir -p ~/.local/bin/
|
||||
if ping -c 1 surplus.joshwel.co ; then
|
||||
wget -O ~/.local/bin/s+ow https://surplus.joshwel.co/spow.sh
|
||||
else
|
||||
wget -O ~/.local/bin/s+ow https://raw.githubusercontent.com/markjoshwel/surplus/main/src/surplus-on-wheels/s+ow
|
||||
fi
|
||||
chmod +x ~/.local/bin/s+ow
|
||||
|
||||
# setup path
|
||||
echo "export PATH=\$PATH:\$HOME/.local/bin/" >> ~/.profile
|
||||
|
||||
printf "
|
||||
----- done! -----
|
||||
|
||||
if you're going to set a cron job up:
|
||||
|
||||
1. restart termux
|
||||
2. run crontab -e
|
||||
3. add \"59 * * * * bash -l -c \"(SPOW_TARGETS="" SPOW_CRON=y s+ow)\"\"
|
||||
(remember to minimally fill in the SPOW_TARGETS variable)
|
||||
|
||||
else, surplus on wheels has been set up!
|
||||
|
||||
"
|
550
src/surplus-on-wheels/s+ow
Normal file
550
src/surplus-on-wheels/s+ow
Normal file
|
@ -0,0 +1,550 @@
|
|||
#!/bin/sh
|
||||
|
||||
# surplus on wheels (s+ow) - a pure shell script to run surplus with mdtest using the termux-api
|
||||
# ------------------------
|
||||
# This is free and unencumbered software released into the public domain.
|
||||
#
|
||||
# Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
# distribute this software, either in source code form or as a compiled
|
||||
# binary, for any purpose, commercial or non-commercial, and by any
|
||||
# means.
|
||||
#
|
||||
# In jurisdictions that recognize copyright laws, the author or authors
|
||||
# of this software dedicate any and all copyright interest in the
|
||||
# software to the public domain. We make this dedication for the benefit
|
||||
# of the public at large and to the detriment of our heirs and
|
||||
# successors. We intend this dedication to be an overt act of
|
||||
# relinquishment in perpetuity of all present and future rights to this
|
||||
# software under copyright law.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# For more information, please refer to <http://unlicense.org/>
|
||||
|
||||
SURPLUS_CMD_DEFAULT="surplus -td"
|
||||
SURPLUS_CMD=${SURPLUS_CMD:-$SURPLUS_CMD_DEFAULT}
|
||||
LOCATION_CMD_DEFAULT="termux-location"
|
||||
LOCATION_CMD=${LOCATION_CMD:-$LOCATION_CMD_DEFAULT}
|
||||
|
||||
# shellcheck disable=SC2059
|
||||
LOCATION_FALLBACK=${LOCATION_FALLBACK:-"%d%d%d\nSingapore?"}
|
||||
# shellcheck disable=SC2269
|
||||
LOCATION_PRIORITISE_NETWORK="$LOCATION_PRIORITISE_NETWORK"
|
||||
LOCATION_TIMEOUT=${LOCATION_TIMEOUT:-50}
|
||||
|
||||
# shellcheck disable=SC2269
|
||||
SPOW_TARGETS="$SPOW_TARGETS"
|
||||
SPOW_CACHE_DIR="$HOME/.cache/s+ow"
|
||||
SPOW_BRIDGES="$HOME/.s+ow-bridges"
|
||||
SPOW_CRON=${SPOW_CRON:-n}
|
||||
SPOW_PRIVATE=${SPOW_PRIVATE:-n}
|
||||
|
||||
# per-tool session logs
|
||||
SPOW_NETLC_OUT="$SPOW_CACHE_DIR/location.net.json" # honours SPOW_PRIVATE (cleared after use)
|
||||
SPOW_GPSLC_OUT="$SPOW_CACHE_DIR/location.gps.json" # honours SPOW_PRIVATE (cleared after use)
|
||||
SPOW_LOCTN_OUT="$SPOW_CACHE_DIR/location.json" # honours SPOW_PRIVATE (cleared after use)
|
||||
SPOW_SPLUS_OUT="$SPOW_CACHE_DIR/surplus.out.log" # honours SPOW_PRIVATE (cleared after use)
|
||||
SPOW_SPLUS_ERR="$SPOW_CACHE_DIR/surplus.err.log" # honours SPOW_PRIVATE (set to /dev/null + cleared after use)
|
||||
|
||||
# per-session collated logs
|
||||
SPOW_SESH_OUT="$SPOW_CACHE_DIR/out.log" # honours SPOW_PRIVATE (set to /dev/null)
|
||||
SPOW_SESH_ERR="$SPOW_CACHE_DIR/err.log" # honours SPOW_PRIVATE (set to /dev/null)
|
||||
|
||||
# per-week collated logs
|
||||
SPOW_WEEK_PRE="$SPOW_CACHE_DIR/$(date +%Y)W$(date +"%V")"
|
||||
SPOW_WEEK_OUT="$SPOW_WEEK_PRE.out.log"
|
||||
SPOW_WEEK_ERR="$SPOW_WEEK_PRE.err.log"
|
||||
|
||||
# last successful surplus output
|
||||
SPOW_LAST_OUT="$SPOW_CACHE_DIR/last"
|
||||
|
||||
# message to be sent
|
||||
SPOW_MESSAGE="$SPOW_CACHE_DIR/message" # honours SPOW_PRIVATE (cleared after use)
|
||||
|
||||
# list of fakes
|
||||
SPOW_FAKE_OUT="$SPOW_CACHE_DIR/fake"
|
||||
|
||||
# check for network location priority
|
||||
if [ "$LOCATION_PRIORITISE_NETWORK" = "n" ]; then
|
||||
LOCATION_PRIORITISE_NETWORK=""
|
||||
fi
|
||||
|
||||
# check for cron status
|
||||
if [ "$SPOW_CRON" = "n" ]; then
|
||||
SPOW_CRON=""
|
||||
fi
|
||||
|
||||
# check if running in 'private' mode
|
||||
if [ "$SPOW_PRIVATE" = "n" ]; then
|
||||
SPOW_PRIVATE=""
|
||||
fi
|
||||
|
||||
# extract command names for checking
|
||||
TERMUX_EXE=$(echo "$TERMUX_CMD" | awk '{print $1}')
|
||||
SURPLUS_EXE=$(echo "$SURPLUS_CMD" | awk '{print $1}')
|
||||
|
||||
# ensure commands exist
|
||||
if ! command -v "$SURPLUS_EXE" >/dev/null 2>&1; then
|
||||
if [ "$SURPLUS_EXE" = "surplus" ]; then
|
||||
printf "s+ow: error: surplus is not installed.\ninstall it with 'pip install surplus'. see <https://surplus.joshwel.co> for more information.\n"
|
||||
else
|
||||
printf "s+ow: error: custom surplus command '%s' is not accessible" "$SURPLUS_EXE"
|
||||
fi
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! command -v "$TERMUX_EXE" >/dev/null 2>&1; then
|
||||
if [ "$TERMUX_EXE" = $LOCATION_CMD_DEFAULT ]; then
|
||||
printf "s+ow: error: termux-location is not installed.\ninstall it with 'pkg install termux-api' and with installing the termux:api app from the play store or f-droid.\n"
|
||||
else
|
||||
printf "s+ow: error: custom location command '%s' is not accessible" "$TERMUX_EXE"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ensure directories
|
||||
mkdir -p "$SPOW_CACHE_DIR"
|
||||
|
||||
# create new session logs
|
||||
rm -f "$SPOW_LOCTN_OUT" "$SPOW_SPLUS_OUT" "$SPOW_SPLUS_ERR" \
|
||||
"$SPOW_SESH_OUT" "$SPOW_SESH_ERR"
|
||||
touch "$SPOW_NETLC_OUT" "$SPOW_GPSLC_OUT" "$SPOW_LOCTN_OUT" \
|
||||
"$SPOW_SPLUS_OUT" "$SPOW_SPLUS_ERR" \
|
||||
"$SPOW_SESH_OUT" "$SPOW_SESH_ERR" \
|
||||
"$SPOW_WEEK_OUT" "$SPOW_WEEK_ERR" \
|
||||
"$SPOW_BRIDGES" "$SPOW_FAKE_OUT"
|
||||
|
||||
# disable logs if private
|
||||
if [ -n "$SPOW_PRIVATE" ]; then
|
||||
SPOW_SESH_OUT="/dev/null"
|
||||
SPOW_SESH_ERR="/dev/null"
|
||||
SPOW_SPLUS_ERR="/dev/null"
|
||||
fi
|
||||
|
||||
# 0 is nominal
|
||||
# 1 is an termux-location error
|
||||
# 2 is a surplus error
|
||||
# 3 is a bridge/message send error
|
||||
status=0
|
||||
|
||||
bridge_failures=0
|
||||
bridge_returns=""
|
||||
|
||||
locate() {
|
||||
# spawn termux-location processes
|
||||
(
|
||||
$LOCATION_CMD -p "network" >"$SPOW_NETLC_OUT"
|
||||
if [ -s "$SPOW_NETLC_OUT" ]; then
|
||||
printf "net" | tee -a "$SPOW_SESH_ERR"
|
||||
else
|
||||
printf "net?" | tee -a "$SPOW_SESH_ERR"
|
||||
fi
|
||||
cat "$SPOW_NETLC_OUT" >>"$SPOW_SESH_OUT"
|
||||
) &
|
||||
tl_net_pid="$!"
|
||||
sleep 1
|
||||
(
|
||||
$LOCATION_CMD -p "gps" >"$SPOW_GPSLC_OUT"
|
||||
if [ -s "$SPOW_GPSLC_OUT" ]; then
|
||||
printf "gps" | tee -a "$SPOW_SESH_ERR"
|
||||
else
|
||||
printf "gps?" | tee -a "$SPOW_SESH_ERR"
|
||||
fi
|
||||
cat "$SPOW_GPSLC_OUT" >>"$SPOW_SESH_OUT"
|
||||
) &
|
||||
tl_gps_pid="$!"
|
||||
|
||||
# wait until timeout or both finished
|
||||
printf "running '%s'" "$LOCATION_CMD" | tee -a "$SPOW_SESH_ERR"
|
||||
while [ "$LOCATION_TIMEOUT" -gt 0 ]; do
|
||||
# get process statuses
|
||||
kill -0 "$tl_net_pid" >/dev/null 2>&1
|
||||
tl_net_status="$?"
|
||||
kill -0 "$tl_gps_pid" >/dev/null 2>&1
|
||||
tl_gps_status="$?"
|
||||
|
||||
# break if both finished
|
||||
if [ "$tl_net_status" -eq 1 ] && [ "$tl_gps_status" -eq 1 ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
# exception: if network is prioritised: just use that
|
||||
if [ "$tl_net_status" -eq 1 ] && [ -n "$LOCATION_PRIORITISE_NETWORK" ]; then
|
||||
# break only if theres an actual response
|
||||
if [ -s "$SPOW_NETLC_OUT" ]; then
|
||||
break
|
||||
fi
|
||||
# else just keep on waiting for gps to finish
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
printf "." | tee -a "$SPOW_SESH_ERR"
|
||||
LOCATION_TIMEOUT=$((LOCATION_TIMEOUT - 1))
|
||||
done
|
||||
if [ "$LOCATION_TIMEOUT" -eq 0 ]; then
|
||||
printf " errored (timeout)\n" | tee -a "$SPOW_SESH_ERR"
|
||||
else
|
||||
printf " nominal\n" | tee -a "$SPOW_SESH_ERR"
|
||||
fi
|
||||
|
||||
# check outputs
|
||||
printf "determining output: " | tee -a "$SPOW_SESH_ERR"
|
||||
if [ -s "$SPOW_NETLC_OUT" ] && [ -s "$SPOW_GPSLC_OUT" ]; then
|
||||
printf "both succeeded, "
|
||||
acc_net="$(grep "\"accuracy\"" <"$SPOW_NETLC_OUT" | awk -F ': ' '{print $2}' | tr -d ',')"
|
||||
acc_gps="$(grep "\"accuracy\"" <"$SPOW_GPSLC_OUT" | awk -F ': ' '{print $2}' | tr -d ',')"
|
||||
|
||||
# compare accuracy
|
||||
if awk -v n1="$acc_net" -v n2="$acc_gps" 'BEGIN { if (n1 < n2) exit 0; else exit 1; }'; then
|
||||
printf "choosing network (%s < %s)" "$acc_net" "$acc_gps" | tee -a "$SPOW_SESH_ERR"
|
||||
cat "$SPOW_NETLC_OUT" >"$SPOW_LOCTN_OUT"
|
||||
else
|
||||
printf "choosing gps (%s < %s)" "$acc_gps" "$acc_net" | tee -a "$SPOW_SESH_ERR"
|
||||
cat "$SPOW_GPSLC_OUT" >"$SPOW_LOCTN_OUT"
|
||||
fi
|
||||
|
||||
cat "$SPOW_GPSLC_OUT" >"$SPOW_LOCTN_OUT"
|
||||
else
|
||||
# one or none succeeded
|
||||
if [ -s "$SPOW_NETLC_OUT" ]; then
|
||||
if [ -n "$LOCATION_PRIORITISE_NETWORK" ]; then
|
||||
printf "using network (prioritised)" | tee -a "$SPOW_SESH_ERR"
|
||||
else
|
||||
printf "using network" | tee -a "$SPOW_SESH_ERR"
|
||||
fi
|
||||
cat "$SPOW_NETLC_OUT" >"$SPOW_LOCTN_OUT"
|
||||
fi
|
||||
if [ -s "$SPOW_GPSLC_OUT" ]; then
|
||||
printf "using gps" | tee -a "$SPOW_SESH_ERR"
|
||||
cat "$SPOW_GPSLC_OUT" >"$SPOW_LOCTN_OUT"
|
||||
fi
|
||||
fi
|
||||
if [ ! -s "$SPOW_LOCTN_OUT" ]; then
|
||||
printf "none (error)" | tee -a "$SPOW_SESH_ERR"
|
||||
fi
|
||||
printf "\n" | tee -a "$SPOW_SESH_ERR"
|
||||
}
|
||||
|
||||
gensharetext() {
|
||||
$SURPLUS_CMD "$1" >"$SPOW_SPLUS_OUT" 2>"$SPOW_SPLUS_ERR"
|
||||
ret="$?"
|
||||
cat "$SPOW_SPLUS_OUT" >>"$SPOW_SESH_OUT"
|
||||
cat "$SPOW_SPLUS_ERR" >>"$SPOW_SESH_ERR"
|
||||
return "$ret"
|
||||
}
|
||||
|
||||
send() {
|
||||
# $1 is sharetext
|
||||
|
||||
# use fake if any
|
||||
fake_first=""
|
||||
fake_rest=""
|
||||
|
||||
if [ -n "$(cat "$SPOW_FAKE_OUT")" ]; then
|
||||
inside_first_group=false
|
||||
first_group_done=false
|
||||
|
||||
while IFS= read -r line; do
|
||||
# skip preceding empty lines
|
||||
if [ -z "$line" ] && [ "$inside_first_group" = false ] && [ -z "$fake_first" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# if not empty and not inside first group, then we are inside first group
|
||||
if [ -n "$line" ] && [ "$inside_first_group" = false ] && [ "$first_group_done" = false ]; then
|
||||
inside_first_group=true
|
||||
|
||||
# if empty and inside first group, then we are outside first group
|
||||
elif [ -z "$line" ] && [ "$inside_first_group" = true ]; then
|
||||
inside_first_group=false
|
||||
first_group_done=true
|
||||
|
||||
# if empty and outside first group but first group is done, add newline to fake_rest
|
||||
elif [ -z "$line" ] && [ "$inside_first_group" = false ] && [ "$first_group_done" = true ]; then
|
||||
fake_rest="$fake_rest\n"
|
||||
fi
|
||||
|
||||
# append to fake_first (if message_fake is not empty)
|
||||
if [ "$inside_first_group" = true ]; then
|
||||
if [ -z "$fake_first" ]; then
|
||||
fake_first="$line"
|
||||
else
|
||||
fake_first="$(printf "%s\n%s" "$fake_first" "$line")"
|
||||
fi
|
||||
|
||||
# append to fake_rest (if message_fake is not empty)
|
||||
else
|
||||
if [ -z "$fake_rest" ]; then
|
||||
fake_rest="$line"
|
||||
else
|
||||
fake_rest="$(printf "%s\n%s" "$fake_rest" "$line")"
|
||||
fi
|
||||
fi
|
||||
|
||||
done <"$SPOW_FAKE_OUT"
|
||||
|
||||
if [ -n "$fake_first" ]; then
|
||||
printf "%s\n" "$fake_rest" >"$SPOW_FAKE_OUT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# choose what message to use
|
||||
message=""
|
||||
if [ -n "$fake_first" ]; then
|
||||
echo using fake
|
||||
message="$fake_first"
|
||||
else
|
||||
echo using message
|
||||
message="$1"
|
||||
fi
|
||||
|
||||
# store message
|
||||
printf "%s\n" "$message" >"$SPOW_MESSAGE"
|
||||
cat "$SPOW_MESSAGE" >>"$SPOW_SESH_OUT"
|
||||
cat "$SPOW_MESSAGE" >>"$SPOW_SESH_ERR"
|
||||
|
||||
# run bridges
|
||||
if [ -f "$SPOW_BRIDGES" ]; then
|
||||
# run commands in bridge file
|
||||
while IFS= read -r command; do
|
||||
# skip command if its actually a comment
|
||||
if [ "$(printf "%s" "$command" | head -c 1)" = "#" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# run bridge
|
||||
echo "$command"
|
||||
echo "$SPOW_TARGETS" | eval "$command" >>"$SPOW_SESH_ERR" 2>>"$SPOW_SESH_ERR"
|
||||
bridge_return="$?"
|
||||
|
||||
# check if bridge failed
|
||||
if [ ! "$bridge_return" -eq 0 ]; then
|
||||
bridge_failures=$((bridge_failures + 1))
|
||||
fi
|
||||
|
||||
# store return value
|
||||
if [ -z "$bridge_returns" ]; then
|
||||
bridge_returns="$bridge_return"
|
||||
else
|
||||
bridge_returns="$bridge_returns, $bridge_return"
|
||||
fi
|
||||
done <"$SPOW_BRIDGES"
|
||||
else
|
||||
printf "s+ow: warning: no '%s' file; message is not sent.\n" "$SPOW_BRIDGES"
|
||||
termux-notification \
|
||||
--priority "default" \
|
||||
--title "surplus on wheels: No bridges" \
|
||||
--content "No '$SPOW_BRIDGES' file; message is not sent."
|
||||
fi
|
||||
|
||||
echo "$bridge_returns" >>"$SPOW_SESH_ERR"
|
||||
}
|
||||
|
||||
notify_start() {
|
||||
termux-notification \
|
||||
--priority "min" \
|
||||
--ongoing \
|
||||
--id "s+ow" \
|
||||
--title "surplus on wheels" \
|
||||
--content "s+ow has started running."
|
||||
}
|
||||
|
||||
notify() {
|
||||
# $1 is text
|
||||
# $2 is attempt number (if any)
|
||||
attempt_text="$1"
|
||||
if [ $# -eq 2 ]; then
|
||||
attempt_text="$1 (attempt $2)"
|
||||
fi
|
||||
termux-notification \
|
||||
--priority "min" \
|
||||
--ongoing \
|
||||
--id "s+ow" \
|
||||
--title "surplus on wheels" \
|
||||
--content "$attempt_text"
|
||||
}
|
||||
|
||||
notify_end() {
|
||||
# $1 is done tuple
|
||||
# $2 is sharetext
|
||||
termux-notification \
|
||||
--priority "min" \
|
||||
--id "s+ow" \
|
||||
--title "surplus on wheels" \
|
||||
--content "$(printf 'Run has finished. %s\n\n%s' "$1" "$2")"
|
||||
}
|
||||
|
||||
# program functions
|
||||
|
||||
run() {
|
||||
notify_start
|
||||
printf "[run! stdout (%s)]\n" "$(date)" >>"$SPOW_SESH_OUT"
|
||||
printf "[run! stderr (%s)]\n" "$(date)" >>"$SPOW_SESH_ERR"
|
||||
|
||||
time_run_start="$(date +%s)"
|
||||
|
||||
# if cron: wait until its the new hour
|
||||
if [ -n "$SPOW_CRON" ]; then
|
||||
notify "Waiting for the 30th second to pass..."
|
||||
printf "waiting for the 30th second to pass...\n"
|
||||
while [ "$(date +'%S')" -lt 30 ]; do
|
||||
printf " %s\n" "$(date)"
|
||||
sleep 1
|
||||
done
|
||||
printf "proceeding\n"
|
||||
fi
|
||||
|
||||
time_locate_start="$(date +%s)"
|
||||
|
||||
# termux-location
|
||||
location=""
|
||||
for locate_run in 1 2 3; do # run three times in case :p
|
||||
notify "Running '%s'" "$LOCATION_CMD" "$locate_run"
|
||||
|
||||
if [ "$locate_run" -gt "1" ]; then
|
||||
LOCATION_TIMEOUT=75 locate
|
||||
else
|
||||
locate
|
||||
fi
|
||||
if [ ! -s "$SPOW_LOCTN_OUT" ]; then
|
||||
# erroneous: is empty
|
||||
echo "s+ow: error: failed to get location" | tee -a "$SPOW_SESH_ERR"
|
||||
status=1
|
||||
else
|
||||
# nominal: is not empty
|
||||
location="$(cat "$SPOW_LOCTN_OUT")"
|
||||
status=0
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$SPOW_PRIVATE" ]; then
|
||||
cat /dev/null >"$SPOW_GPSLC_OUT"
|
||||
cat /dev/null >"$SPOW_NETLC_OUT"
|
||||
cat /dev/null >"$SPOW_LOCTN_OUT"
|
||||
fi
|
||||
|
||||
time_locate_end="$(date +%s)"
|
||||
time_locate=$((time_locate_end - time_locate_start))
|
||||
|
||||
time_surplus_start="$(date +%s)"
|
||||
|
||||
# surplus
|
||||
printf "running '%s'... " "$SURPLUS_CMD"
|
||||
notify "Running $SURPLUS_CMD $location"
|
||||
if [ "$status" -eq 0 ]; then
|
||||
if gensharetext "$location"; then
|
||||
# surplus ran nominally
|
||||
cp "$SPOW_SPLUS_OUT" "$SPOW_LAST_OUT"
|
||||
status=0
|
||||
printf "nominal\n"
|
||||
else
|
||||
# something happened :^)
|
||||
status=2
|
||||
printf "errored\n"
|
||||
fi
|
||||
else
|
||||
printf "skipped\n"
|
||||
fi
|
||||
|
||||
time_surplus_end="$(date +%s)"
|
||||
time_surplus=$((time_surplus_end - time_surplus_start))
|
||||
|
||||
# if cron: wait until its the new hour
|
||||
if [ -n "$SPOW_CRON" ]; then
|
||||
notify "Waiting for the new hour..."
|
||||
printf "waiting until the new hour...\n"
|
||||
while [ "$(date +'%M')" -eq 59 ]; do
|
||||
printf " %s\n" "$(date)"
|
||||
sleep 1
|
||||
done
|
||||
printf "proceeding\n"
|
||||
fi
|
||||
|
||||
time_sendmsg_start="$(date +%s)"
|
||||
|
||||
# send message
|
||||
printf "sending message(s)... "
|
||||
notify "Sending message(s)"
|
||||
# 0 for freshly made sharetext
|
||||
# 1 for recycling a last location
|
||||
# 2 for using fallback template
|
||||
sent_type=0
|
||||
bridge_failures=0
|
||||
sharetext=""
|
||||
send_error_notif=false
|
||||
if [ "$status" -eq 0 ]; then
|
||||
# s+ow has behaved nominally until now, send as per normal
|
||||
sharetext="$(cat "$SPOW_SPLUS_OUT")"
|
||||
printf "\n"
|
||||
|
||||
send "$sharetext"
|
||||
else
|
||||
# something has gone wrong, send an appropriate fallback
|
||||
sharetext=""
|
||||
if [ -s "$SPOW_LAST_OUT" ]; then
|
||||
# use last successful location
|
||||
sharetext="$(cat "$SPOW_LAST_OUT")"
|
||||
sent_type=1
|
||||
printf "using last...\n"
|
||||
|
||||
else
|
||||
# no last location, use fallback
|
||||
sent_type=2
|
||||
# shellcheck disable=SC2059
|
||||
sharetext="$(printf "$LOCATION_FALLBACK" "$status" "$locate_run" "$sent_type")"
|
||||
printf "using fallback... \n"
|
||||
|
||||
fi
|
||||
|
||||
send "$sharetext"
|
||||
|
||||
# send done info except
|
||||
printf "(%d, %d, %d, %s<%s>) [lc:%ds sp:%ds]\n" "$status" "$locate_run" "$sent_type" "$bridge_failures" "$bridge_returns" "$time_locate" "$time_surplus" >>"$SPOW_SESH_ERR"
|
||||
|
||||
send_error_notif=true
|
||||
fi
|
||||
|
||||
# delete s+ logs
|
||||
if [ -n "$SPOW_PRIVATE" ]; then
|
||||
cat /dev/null >"$SPOW_SPLUS_OUT"
|
||||
cat /dev/null >"$SPOW_SPLUS_ERR"
|
||||
cat /dev/null >"$SPOW_MESSAGE"
|
||||
fi
|
||||
|
||||
time_sendmsg_end="$(date +%s)"
|
||||
time_sendmsg=$((time_sendmsg_end - time_sendmsg_start))
|
||||
|
||||
time_run_end="$(date +%s)"
|
||||
time_run=$((time_run_end - time_run_start))
|
||||
|
||||
done_info="$(printf "(%d, %d, %d, %s<%s>) [lc:%ds sp:%ds sm:%ds - %ds]\n" "$status" "$locate_run" "$sent_type" "$bridge_failures" "$bridge_returns" "$time_locate" "$time_surplus" "$time_sendmsg" "$time_run")"
|
||||
echo "done $done_info" | tee -a "$SPOW_SESH_ERR"
|
||||
|
||||
# finish
|
||||
notify_end "$done_info" "$sharetext"
|
||||
if [ ! "$send_error_notif" = false ]; then
|
||||
# error notification
|
||||
termux-notification \
|
||||
--priority "default" \
|
||||
--title "surplus on wheels has errored" \
|
||||
--content "$(printf "\n(%d, %d, %d, %s<%s>)\n[lc:%ds sp:%ds sm:%ds - %ds]\n" "$status" "$locate_run" "$sent_type" "$bridge_failures" "$bridge_returns" "$time_locate" "$time_surplus" "$time_sendmsg" "$time_run")"
|
||||
fi
|
||||
}
|
||||
|
||||
# script entry
|
||||
|
||||
if [ -z "$SPOW_TARGETS" ]; then
|
||||
echo "s+ow: error: SPOW_TARGETS are not set"
|
||||
exit 3
|
||||
fi
|
||||
run
|
||||
|
||||
printf "%s\n\n" "$(cat "$SPOW_SESH_OUT")" >>"$SPOW_WEEK_OUT"
|
||||
printf "%s\n\n" "$(cat "$SPOW_SESH_ERR")" >>"$SPOW_WEEK_ERR"
|
26
src/surplus-on-wheels/test.sh
Normal file
26
src/surplus-on-wheels/test.sh
Normal file
|
@ -0,0 +1,26 @@
|
|||
#!/bin/sh
|
||||
|
||||
SURPLUS_CMD_DEFAULT="surplus --debugp -tp"
|
||||
SURPLUS_CMD=${SURPLUS_CMD:-$SURPLUS_CMD_DEFAULT}
|
||||
|
||||
# parse SURPLUS_CMD to see if "-p" or "--private" is in the args
|
||||
set -f
|
||||
# shellcheck disable=SC2086
|
||||
set -- $SURPLUS_CMD
|
||||
for arg; do
|
||||
case "$arg" in
|
||||
--private)
|
||||
echo YAY
|
||||
break
|
||||
;;
|
||||
--*)
|
||||
;;
|
||||
-*)
|
||||
if echo "$arg" | grep -q "p"; then
|
||||
echo YAY
|
||||
break
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
set +f
|
8
src/surplus/README.md
Normal file
8
src/surplus/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# surplus
|
||||
|
||||
surplus (s+) is a Python script to convert [Google Maps Plus Codes](https://maps.google.com/pluscodes/)
|
||||
to iOS Shortcuts-like shareable text
|
||||
|
||||
see <https://surplus.joshwel.co/>
|
||||
or [/docs/index.md](../../docs/index.md)
|
||||
for more information and documentation
|
24
src/surplus/UNLICENCE
Normal file
24
src/surplus/UNLICENCE
Normal file
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
|
@ -32,24 +32,13 @@ For more information, please refer to <http://unlicense.org/>
|
|||
# surplus was and would've been a single-file module, but typing is in the way :(
|
||||
# https://github.com/python/typing/issues/1333
|
||||
|
||||
from .surplus import default_geocoder # deprecated, emulation function
|
||||
from .surplus import default_reverser # deprecated, emulation function
|
||||
from .surplus import (
|
||||
from .surplus import ( # noqa: F401, TID252
|
||||
BUILD_BRANCH,
|
||||
BUILD_COMMIT,
|
||||
BUILD_DATETIME,
|
||||
CONNECTION_MAX_RETRIES,
|
||||
CONNECTION_WAIT_SECONDS,
|
||||
EMPTY_LATLONG,
|
||||
SHAREABLE_TEXT_LINE_0_KEYS,
|
||||
SHAREABLE_TEXT_LINE_1_KEYS,
|
||||
SHAREABLE_TEXT_LINE_2_KEYS,
|
||||
SHAREABLE_TEXT_LINE_3_KEYS,
|
||||
SHAREABLE_TEXT_LINE_4_KEYS,
|
||||
SHAREABLE_TEXT_LINE_5_KEYS,
|
||||
SHAREABLE_TEXT_LINE_6_KEYS,
|
||||
SHAREABLE_TEXT_LOCALITY,
|
||||
SHAREABLE_TEXT_NAMES,
|
||||
VERSION,
|
||||
VERSION_SUFFIX,
|
||||
Behaviour,
|
||||
|
@ -68,12 +57,12 @@ from .surplus import (
|
|||
ResultType,
|
||||
StringQuery,
|
||||
SurplusDefaultGeocoding,
|
||||
SurplusException,
|
||||
SurplusError,
|
||||
SurplusGeocoderProtocol,
|
||||
SurplusReverserProtocol,
|
||||
__version__,
|
||||
cli,
|
||||
generate_fingerprinted_user_agent,
|
||||
handle_args,
|
||||
parse_query,
|
||||
surplus,
|
||||
)
|
File diff suppressed because it is too large
Load diff
24
src/tools/docs-prebuild.py
Normal file
24
src/tools/docs-prebuild.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
"""
|
||||
script to copy shell scripts into the docs folder for publishing
|
||||
|
||||
src/surplus-on-wheels/s+ow -> docs/spow.sh
|
||||
src/surplus-on-wheels/termux-s+ow-setup -> docs/termux.sh
|
||||
src/spow-whatsapp-bridge/install.sh -> docs/whatsapp.sh
|
||||
src/spow-telegram-bridge/install.sh -> docs/telegram.sh
|
||||
"""
|
||||
from pathlib import Path
|
||||
from shutil import copyfile
|
||||
|
||||
repo_root: Path = Path(__file__).parent.parent.parent
|
||||
docs_path: Path = repo_root.joinpath("docs")
|
||||
|
||||
copy_map: dict[Path, Path] = {
|
||||
repo_root.joinpath("src/surplus-on-wheels/s+ow"): docs_path.joinpath("spow.sh"),
|
||||
repo_root.joinpath("src/surplus-on-wheels/install.sh"): docs_path.joinpath("termux.sh"),
|
||||
repo_root.joinpath("src/spow-whatsapp-bridge/install.sh"): docs_path.joinpath("whatsapp.sh"),
|
||||
repo_root.joinpath("src/spow-telegram-bridge/install.sh"): docs_path.joinpath("telegram.sh"),
|
||||
}
|
||||
|
||||
for target, destination in copy_map.items():
|
||||
copyfile(target, destination)
|
||||
print(f"{target}\t->\t{destination}")
|
|
@ -33,29 +33,37 @@ from datetime import datetime, timedelta, timezone
|
|||
from os import getenv
|
||||
from pathlib import Path
|
||||
from subprocess import run
|
||||
from sys import exit as sysexit
|
||||
|
||||
# NOTE: change this if surplus has moved
|
||||
path_surplus = Path(__file__).parent.joinpath("./surplus/surplus.py")
|
||||
path_surplus = Path(__file__).parent.joinpath("./src/surplus/surplus.py")
|
||||
|
||||
build_time = datetime.now(timezone(timedelta(hours=8))) # using SGT
|
||||
|
||||
_insert_build_branch = getenv(
|
||||
"SURPLUS_BUILD_BRANCH",
|
||||
run(
|
||||
"git branch --show-current",
|
||||
"git branch --show-current".split(),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
shell=True,
|
||||
).stdout.strip("\n"),
|
||||
check=False,
|
||||
)
|
||||
.stdout.strip("\n")
|
||||
.strip(),
|
||||
)
|
||||
insert_build_branch = _insert_build_branch if _insert_build_branch != "" else "unknown"
|
||||
|
||||
insert_build_commit: str = run(
|
||||
"git rev-parse HEAD",
|
||||
capture_output=True,
|
||||
text=True,
|
||||
shell=True,
|
||||
).stdout.strip("\n")
|
||||
_insert_build_commit: str = (
|
||||
run(
|
||||
"git rev-parse HEAD".split(),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
.stdout.strip("\n")
|
||||
.strip()
|
||||
)
|
||||
insert_build_commit = _insert_build_commit if _insert_build_commit != "" else "unknown"
|
||||
|
||||
insert_build_datetime: str = repr(build_time).replace("datetime.", "")
|
||||
|
||||
|
@ -63,7 +71,7 @@ insert_build_datetime: str = repr(build_time).replace("datetime.", "")
|
|||
targets: list[tuple[str, str]] = [
|
||||
(
|
||||
'VERSION_SUFFIX: Final[str] = "-local"',
|
||||
'VERSION_SUFFIX: Final[str] = ""',
|
||||
'VERSION_SUFFIX: Final[str] = "-alpha"',
|
||||
),
|
||||
(
|
||||
'BUILD_BRANCH: Final[str] = "future"',
|
||||
|
@ -81,18 +89,19 @@ targets: list[tuple[str, str]] = [
|
|||
|
||||
|
||||
def main() -> int:
|
||||
assert path_surplus.is_file() and path_surplus.exists(), f"{path_surplus} not found"
|
||||
if not (path_surplus.is_file() and path_surplus.exists()):
|
||||
raise FileNotFoundError(path_surplus)
|
||||
|
||||
source_surplus: str = path_surplus.read_text(encoding="utf-8")
|
||||
|
||||
for old, new in targets:
|
||||
print(f"new: {new}\nold: {old}\n")
|
||||
print(f" old: {old}\n-> new: {new}\n") # noqa: T201
|
||||
source_surplus = source_surplus.replace(old, new)
|
||||
|
||||
path_surplus.write_text(source_surplus, encoding="utf-8")
|
||||
# path_surplus.write_text(source_surplus, encoding="utf-8")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
||||
sysexit(main())
|
239
test.py
239
test.py
|
@ -1,239 +0,0 @@
|
|||
# type: ignore
|
||||
|
||||
"""
|
||||
surplus test runner
|
||||
-------------------
|
||||
by mark <mark@joshwel.co>
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <http://unlicense.org/>
|
||||
"""
|
||||
|
||||
from io import StringIO
|
||||
from sys import stderr
|
||||
from textwrap import indent
|
||||
from traceback import format_exception
|
||||
from typing import Final, NamedTuple
|
||||
|
||||
import surplus
|
||||
|
||||
INDENT: Final[int] = 3
|
||||
MINIMUM_PASS_RATE: Final[float] = 0.7 # because results can be flaky
|
||||
|
||||
|
||||
class ContinuityTest(NamedTuple):
|
||||
query: str
|
||||
expected: list[str]
|
||||
|
||||
|
||||
class TestFailure(NamedTuple):
|
||||
test: ContinuityTest
|
||||
exception: Exception
|
||||
output: str
|
||||
stderr: StringIO
|
||||
|
||||
|
||||
tests: list[ContinuityTest] = [
|
||||
ContinuityTest(
|
||||
query="8R3M+F8 Singapore",
|
||||
expected=("Wisma Atria\n" "435 Orchard Road\n" "238877\n" "Central, Singapore"),
|
||||
),
|
||||
ContinuityTest(
|
||||
query="9R3J+R9 Singapore",
|
||||
expected=[
|
||||
(
|
||||
"Thomson Plaza\n"
|
||||
"301 Upper Thomson Road\n"
|
||||
"Sin Ming, Bishan\n"
|
||||
"574408\n"
|
||||
"Central, Singapore"
|
||||
)
|
||||
],
|
||||
),
|
||||
ContinuityTest(
|
||||
query="3RQ3+HW3 Pemping, Batam City, Riau Islands, Indonesia",
|
||||
expected=[
|
||||
("Batam\n" "Kepulauan Riau, Indonesia"),
|
||||
("Batam\n" "Sumatera, Kepulauan Riau, Indonesia"),
|
||||
],
|
||||
),
|
||||
ContinuityTest(
|
||||
query="St Lucia, Queensland, Australia G227+XF",
|
||||
expected=[
|
||||
(
|
||||
"The University of Queensland\n"
|
||||
"Macquarie Street\n"
|
||||
"St Lucia, Greater Brisbane\n"
|
||||
"4072\n"
|
||||
"Queensland, Australia"
|
||||
),
|
||||
(
|
||||
"The University of Queensland\n"
|
||||
"Eleanor Schonell Bridge\n"
|
||||
"St Lucia, Greater Brisbane, Dutton Park\n"
|
||||
"4072\n"
|
||||
"Queensland, Australia"
|
||||
),
|
||||
],
|
||||
),
|
||||
ContinuityTest(
|
||||
query="Ngee Ann Polytechnic, Singapore",
|
||||
expected=[
|
||||
(
|
||||
"Ngee Ann Polytechnic\n"
|
||||
"535 Clementi Road\n"
|
||||
"Bukit Timah\n"
|
||||
"599489\n"
|
||||
"Northwest, Singapore"
|
||||
)
|
||||
],
|
||||
),
|
||||
ContinuityTest(
|
||||
query="1.3521, 103.8198",
|
||||
expected=[
|
||||
(
|
||||
"MacRitchie Nature Trail\n"
|
||||
"Central Water Catchment\n"
|
||||
"574325\n"
|
||||
"Central, Singapore"
|
||||
)
|
||||
],
|
||||
),
|
||||
ContinuityTest(
|
||||
query="8WWJ+4P, Singapore", # a comma!
|
||||
expected=[
|
||||
(
|
||||
"Temasek Polytechnic\n"
|
||||
"21 Tampines Avenue 1\n"
|
||||
"Tampines West\n"
|
||||
"529757\n"
|
||||
"Northeast, Singapore"
|
||||
),
|
||||
(
|
||||
"Temasek Polytechnic\n"
|
||||
"21 Tampines Avenue 1\n"
|
||||
"529757\n"
|
||||
"Southeast, Singapore"
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class SurplusFailure(Exception):
|
||||
...
|
||||
|
||||
|
||||
class QueryParseFailure(Exception):
|
||||
...
|
||||
|
||||
|
||||
class ContinuityFailure(Exception):
|
||||
...
|
||||
|
||||
|
||||
def main() -> int:
|
||||
failures: list[TestFailure] = []
|
||||
|
||||
for idx, test in enumerate(tests, start=1):
|
||||
print(f"[{idx}/{len(tests)}] {test.query}")
|
||||
|
||||
test_stderr = StringIO()
|
||||
|
||||
output: str = ""
|
||||
behaviour = surplus.Behaviour(test.query, stderr=test_stderr, debug=True)
|
||||
|
||||
try:
|
||||
query = surplus.parse_query(behaviour)
|
||||
|
||||
if not query:
|
||||
raise QueryParseFailure(query.cry())
|
||||
|
||||
result = surplus.surplus(query.get(), behaviour)
|
||||
|
||||
if not result:
|
||||
raise SurplusFailure(result.cry())
|
||||
|
||||
output = result.get()
|
||||
|
||||
if output not in test.expected:
|
||||
raise ContinuityFailure("did not match any expected outputs")
|
||||
|
||||
except Exception as exc:
|
||||
failures.append(
|
||||
TestFailure(test=test, exception=exc, output=output, stderr=test_stderr)
|
||||
)
|
||||
stderr.write(indent(text="(fail)", prefix=INDENT * " ") + "\n\n")
|
||||
|
||||
else:
|
||||
stderr.write(indent(text="(pass)", prefix=INDENT * " ") + "\n\n")
|
||||
|
||||
if len(failures) > 0:
|
||||
print(f"\n--- failures ---\n")
|
||||
|
||||
for fail in failures:
|
||||
print(
|
||||
f"[{tests.index(fail.test) + 1}/{len(tests)}] {fail.test.query}\n"
|
||||
+ (
|
||||
indent("\n".join(format_exception(fail.exception)), prefix=INDENT * " ")
|
||||
+ "\n"
|
||||
)
|
||||
+ (indent(text="Expected:", prefix=INDENT * " "))
|
||||
)
|
||||
|
||||
for expected_output in fail.test.expected:
|
||||
print(
|
||||
indent(text=repr(expected_output), prefix=(2 * INDENT) * " ")
|
||||
+ "\n"
|
||||
+ (indent(text=expected_output, prefix=(2 * INDENT) * " ") + "\n")
|
||||
)
|
||||
|
||||
print(
|
||||
indent(text="Actual:", prefix=INDENT * " ")
|
||||
+ "\n"
|
||||
+ (indent(text=repr(fail.output), prefix=(2 * INDENT) * " ") + "\n")
|
||||
+ (indent(text=fail.output, prefix=(2 * INDENT) * " ") + "\n\n")
|
||||
+ (indent(text="stderr:", prefix=INDENT * " ") + "\n")
|
||||
+ (indent(text=fail.stderr.getvalue(), prefix=(2 * INDENT) * " "))
|
||||
)
|
||||
|
||||
passes = len(tests) - len(failures)
|
||||
pass_rate = passes / len(tests)
|
||||
|
||||
print(
|
||||
f"complete: {passes} passed, {len(failures)} failed "
|
||||
f"({pass_rate * 100:.0f}%/{MINIMUM_PASS_RATE * 100:.0f}%)"
|
||||
)
|
||||
|
||||
if pass_rate < MINIMUM_PASS_RATE:
|
||||
print("continuity pass rate is under minimum, test suite failed ;<")
|
||||
return 1
|
||||
|
||||
print("continuity tests passed :>")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit(main())
|
Loading…
Add table
Reference in a new issue