mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 13:17:08 +00:00
Merge pull request #78 from Half-Shot/hs/initial-rust
Initial support for Rust
This commit is contained in:
commit
25fb04fe86
@ -1,10 +1,12 @@
|
|||||||
node_modules/
|
|
||||||
lib/
|
|
||||||
tests/
|
|
||||||
config.yml
|
|
||||||
public/
|
|
||||||
.github/
|
.github/
|
||||||
logging.txt
|
|
||||||
tsconfig.tsbuildinfo
|
|
||||||
*.pem
|
*.pem
|
||||||
registration.yml
|
config.yml
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
|
public/
|
||||||
|
registration.yml
|
||||||
|
tests/
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
/target
|
@ -1,4 +1,3 @@
|
|||||||
// eslint-disable-next-line no-undef
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
|
24
.github/workflows/main.yml
vendored
24
.github/workflows/main.yml
vendored
@ -9,16 +9,26 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint-node:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 16
|
||||||
- run: yarn
|
- run: yarn --ignore-scripts
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
|
lint-rust:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
profile: minimal
|
||||||
|
components: rustfmt
|
||||||
|
- run: cargo fmt --all -- --check
|
||||||
config:
|
config:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
@ -26,7 +36,7 @@ jobs:
|
|||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 12
|
node-version: 16
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: node lib/Config/Defaults.js --config > expected-config.sample.yml
|
- run: node lib/Config/Defaults.js --config > expected-config.sample.yml
|
||||||
- run: cmp --silent config.sample.yml expected-config.sample.yml
|
- run: cmp --silent config.sample.yml expected-config.sample.yml
|
||||||
@ -34,12 +44,16 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node_version: ['14', '12']
|
node_version: [12, 14, 16]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js ${{ matrix.node_version }}
|
- name: Use Node.js ${{ matrix.node_version }}
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node_version }}
|
node-version: ${{ matrix.node_version }}
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
profile: minimal
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn test
|
- run: yarn test
|
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,9 +1,10 @@
|
|||||||
node_modules/
|
|
||||||
lib/
|
|
||||||
|
|
||||||
config.yml
|
|
||||||
*.pem
|
*.pem
|
||||||
|
config.yml
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
|
public/
|
||||||
registration.yml
|
registration.yml
|
||||||
tsconfig.tsbuildinfo
|
tsconfig.tsbuildinfo
|
||||||
|
|
||||||
public/
|
# Added by cargo
|
||||||
|
/target
|
767
Cargo.lock
generated
Normal file
767
Cargo.lock
generated
Normal file
@ -0,0 +1,767 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "contrast"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e9e2e6885a8c59c03522edaa351a7ad5b6d47ed2632fcfbc0f2b00fcce520eb1"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"rgb",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||||
|
dependencies = [
|
||||||
|
"matches",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"proc-macro-hack",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"futures-core",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
"proc-macro-hack",
|
||||||
|
"proc-macro-nested",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h2"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"indexmap",
|
||||||
|
"slab",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"itoa",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-body"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httparse"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpdate"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper"
|
||||||
|
version = "0.14.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "436ec0091e4f20e655156a30a0df3770fe2900aa301e548e08446ec794b6953c"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"httparse",
|
||||||
|
"httpdate",
|
||||||
|
"itoa",
|
||||||
|
"pin-project-lite",
|
||||||
|
"socket2",
|
||||||
|
"tokio",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
"want",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||||
|
dependencies = [
|
||||||
|
"matches",
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "instant"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "0.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.108"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matches"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matrix-github"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"contrast",
|
||||||
|
"futures-util",
|
||||||
|
"hyper",
|
||||||
|
"napi",
|
||||||
|
"napi-build",
|
||||||
|
"napi-derive",
|
||||||
|
"rgb",
|
||||||
|
"routerify",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.7.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"miow",
|
||||||
|
"ntapi",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miow"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi"
|
||||||
|
version = "1.7.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9050238b713b3c5dd5ae1613da1ccefe4061c03992f9e9bbe43b7d473ba4bd3c"
|
||||||
|
dependencies = [
|
||||||
|
"napi-sys",
|
||||||
|
"once_cell",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi-build"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87375bacff0768dd606ccf870eae936efd21e3245af9e7b37ae44f969d48be8a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi-derive"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ee880798e942fc785e2e234544b9db578019a1d7676f45dad7f38d432ab0fe4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi-sys"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67cf20e0081fea04e044aa4adf74cfea8ddc0324eec2894b1c700f4cafc72a56"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ntapi"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||||
|
dependencies = [
|
||||||
|
"instant",
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"instant",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-hack"
|
||||||
|
version = "0.5.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-nested"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rgb"
|
||||||
|
version = "0.8.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a27fa03bb1e3e2941f52d4a555a395a72bf79b0a85fbbaab79447050c97d978c"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "routerify"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c6bb49594c791cadb5ccfa5f36d41b498d40482595c199d10cd318800280bd9"
|
||||||
|
dependencies = [
|
||||||
|
"http",
|
||||||
|
"hyper",
|
||||||
|
"lazy_static",
|
||||||
|
"percent-encoding",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.130"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.130"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.71"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.81"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"bytes",
|
||||||
|
"libc",
|
||||||
|
"memchr",
|
||||||
|
"mio",
|
||||||
|
"num_cpus",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"tokio-macros",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-util"
|
||||||
|
version = "0.6.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"log",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-service"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing"
|
||||||
|
version = "0.1.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-core"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "try-lock"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"matches",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "want"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"try-lock",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
20
Cargo.toml
Normal file
20
Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "matrix-github"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
napi = {version="1", features=["serde-json"]}
|
||||||
|
napi-derive = "1"
|
||||||
|
url = "2"
|
||||||
|
serde_json = "1"
|
||||||
|
serde = "1"
|
||||||
|
serde_derive = "1"
|
||||||
|
contrast = "0"
|
||||||
|
rgb = "0"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
napi-build = "1"
|
16
Dockerfile
16
Dockerfile
@ -1,14 +1,22 @@
|
|||||||
# Stage 0: Build the thing
|
# Stage 0: Build the thing
|
||||||
FROM node:16-alpine AS builder
|
# Need debian based image to build the native rust module
|
||||||
|
# as musl doesn't support cdylib
|
||||||
|
FROM node:16 AS builder
|
||||||
|
|
||||||
COPY . /src
|
COPY . /src
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
# will also build
|
# We need rustup so we have a sensible rust version, the version packed with bullsye is too old
|
||||||
RUN yarn
|
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --profile minimal --target x86_64-unknown-linux-gnu
|
||||||
|
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||||
|
|
||||||
|
# Workaround: Need to install esbuild manually https://github.com/evanw/esbuild/issues/462#issuecomment-771328459
|
||||||
|
RUN yarn --ignore-scripts
|
||||||
|
RUN node node_modules/esbuild/install.js
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
# Stage 1: The actual container
|
# Stage 1: The actual container
|
||||||
FROM node:16-alpine
|
FROM node:16
|
||||||
|
|
||||||
COPY --from=builder /src/lib/ /bin/matrix-github/
|
COPY --from=builder /src/lib/ /bin/matrix-github/
|
||||||
COPY --from=builder /src/public/ /bin/matrix-github/public/
|
COPY --from=builder /src/public/ /bin/matrix-github/public/
|
||||||
|
@ -8,15 +8,17 @@ This bridge enables users to join Github issues and PRs through Matrix and colla
|
|||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
This bridge requires at least Node 12, and Rust installed. If you do not have rust, https://rustup.rs/ is the quickest way to get it.
|
||||||
|
|
||||||
To set up the bridge, simply clone this repository.
|
To set up the bridge, simply clone this repository.
|
||||||
|
|
||||||
` git clone git@github.com:Half-Shot/matrix-github.git`
|
` git clone git@github.com:Half-Shot/matrix-github.git`
|
||||||
|
|
||||||
then you will need to install dependencies
|
then you will need to install the dependencies
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
cd matrix-github
|
cd matrix-github
|
||||||
npm i # Or "yarn"
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you will need to copy the `config.sample.yml` to a new file called `config.yml`. You should fill this in. Pay **close** attention to settings like `passkey` which are required for the bridge to function.
|
Then you will need to copy the `config.sample.yml` to a new file called `config.yml`. You should fill this in. Pay **close** attention to settings like `passkey` which are required for the bridge to function.
|
||||||
|
5
build.rs
Normal file
5
build.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
extern crate napi_build;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
napi_build::setup();
|
||||||
|
}
|
20
package.json
20
package.json
@ -7,27 +7,34 @@
|
|||||||
"author": "Half-Shot",
|
"author": "Half-Shot",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": false,
|
"private": false,
|
||||||
|
"napi": {
|
||||||
|
"name": "matrix-github-rs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:web": "snowpack build",
|
"build:web": "snowpack build",
|
||||||
"build:app": "tsc --project tsconfig.json",
|
"build:app": "tsc --project tsconfig.json",
|
||||||
|
"build:app:rs": "napi build --release ./lib",
|
||||||
"dev:web": "snowpack dev",
|
"dev:web": "snowpack dev",
|
||||||
"build": "yarn run build:web && yarn run build:app",
|
"build": "yarn run build:web && yarn run build:app:rs && yarn run build:app",
|
||||||
"prepare": "yarn build",
|
"prepare": "yarn build",
|
||||||
"start": "node --require source-map-support/register lib/App/BridgeApp.js",
|
"start": "node --require source-map-support/register lib/App/BridgeApp.js",
|
||||||
"start:app": "node --require source-map-support/register lib/App/BridgeApp.js",
|
"start:app": "node --require source-map-support/register lib/App/BridgeApp.js",
|
||||||
"start:webhooks": "node --require source-map-support/register lib/App/GithubWebhookApp.js",
|
"start:webhooks": "node --require source-map-support/register lib/App/GithubWebhookApp.js",
|
||||||
"start:matrixsender": "node --require source-map-support/register lib/App/MatrixSenderApp.js",
|
"start:matrixsender": "node --require source-map-support/register lib/App/MatrixSenderApp.js",
|
||||||
"test": "mocha -r ts-node/register tests/*.ts",
|
"test": "mocha -r ts-node/register tests/*.ts tests/**/*.ts",
|
||||||
"lint": "eslint -c .eslintrc.js src/**/*.ts",
|
"lint": "eslint -c .eslintrc.js src/**/*.ts",
|
||||||
"generate-default-config": "node lib/Config/Defaults.js --config > config.sample.yml"
|
"generate-default-config": "node lib/Config/Defaults.js --config > config.sample.yml"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@node-rs/helper": "^1.2.1",
|
||||||
"@octokit/auth-app": "^3.3.0",
|
"@octokit/auth-app": "^3.3.0",
|
||||||
"@octokit/auth-token": "^2.4.5",
|
"@octokit/auth-token": "^2.4.5",
|
||||||
"@octokit/rest": "^18.10.0",
|
"@octokit/rest": "^18.10.0",
|
||||||
"@octokit/webhooks": "^9.1.2",
|
"@octokit/webhooks": "^9.1.2",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"contrast-color": "^1.0.1",
|
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"ioredis": "^4.26.0",
|
"ioredis": "^4.26.0",
|
||||||
@ -47,6 +54,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fontsource/open-sans": "^4.2.2",
|
"@fontsource/open-sans": "^4.2.2",
|
||||||
|
"@napi-rs/cli": "^1.3.5",
|
||||||
"@prefresh/snowpack": "^3.1.2",
|
"@prefresh/snowpack": "^3.1.2",
|
||||||
"@snowpack/plugin-typescript": "^1.2.1",
|
"@snowpack/plugin-typescript": "^1.2.1",
|
||||||
"@types/chai": "^4.2.16",
|
"@types/chai": "^4.2.16",
|
||||||
@ -57,17 +65,17 @@
|
|||||||
"@types/micromatch": "^4.0.1",
|
"@types/micromatch": "^4.0.1",
|
||||||
"@types/mime": "^2.0.3",
|
"@types/mime": "^2.0.3",
|
||||||
"@types/mocha": "^8.2.2",
|
"@types/mocha": "^8.2.2",
|
||||||
"@types/node": "^12",
|
|
||||||
"@types/node-emoji": "^1.8.1",
|
"@types/node-emoji": "^1.8.1",
|
||||||
|
"@types/node": "^12",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.21.0",
|
"@typescript-eslint/eslint-plugin": "^4.21.0",
|
||||||
"@typescript-eslint/parser": "^4.21.0",
|
"@typescript-eslint/parser": "^4.21.0",
|
||||||
"chai": "^4.3.4",
|
"chai": "^4.3.4",
|
||||||
"eslint": "^7.24.0",
|
|
||||||
"eslint-plugin-mocha": "^8.1.0",
|
"eslint-plugin-mocha": "^8.1.0",
|
||||||
|
"eslint": "^7.24.0",
|
||||||
"mini.css": "^3.0.1",
|
"mini.css": "^3.0.1",
|
||||||
"preact": "^10.5.13",
|
"preact": "^10.5.13",
|
||||||
"snowpack": "^3.2.2",
|
"snowpack": "^3.8.8",
|
||||||
"tailwind": "^4.0.0",
|
"tailwind": "^4.0.0",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "^9.1.1",
|
||||||
"typescript": "^4.2.4"
|
"typescript": "^4.2.4"
|
||||||
|
@ -5,7 +5,6 @@ import { BridgeConfig, parseRegistrationFile } from "../Config/Config";
|
|||||||
import { Webhooks } from "../Webhooks";
|
import { Webhooks } from "../Webhooks";
|
||||||
import { MatrixSender } from "../MatrixSender";
|
import { MatrixSender } from "../MatrixSender";
|
||||||
import { UserNotificationWatcher } from "../Notifications/UserNotificationWatcher";
|
import { UserNotificationWatcher } from "../Notifications/UserNotificationWatcher";
|
||||||
|
|
||||||
LogWrapper.configureLogging("debug");
|
LogWrapper.configureLogging("debug");
|
||||||
const log = new LogWrapper("App");
|
const log = new LogWrapper("App");
|
||||||
|
|
||||||
|
254
src/ConnectionManager.ts
Normal file
254
src/ConnectionManager.ts
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages connections between Matrix rooms and the remote side.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Appservice, StateEvent } from "matrix-bot-sdk";
|
||||||
|
import { CommentProcessor } from "./CommentProcessor";
|
||||||
|
import { BridgeConfig, GitLabInstance } from "./Config/Config";
|
||||||
|
import { GitHubDiscussionConnection, GitHubDiscussionSpace, GitHubIssueConnection, GitHubProjectConnection, GitHubRepoConnection, GitHubUserSpace, GitLabIssueConnection, GitLabRepoConnection, IConnection } from "./Connections";
|
||||||
|
import { GenericHookConnection } from "./Connections/GenericHook";
|
||||||
|
import { JiraProjectConnection } from "./Connections/JiraProject";
|
||||||
|
import { GithubInstance } from "./Github/GithubInstance";
|
||||||
|
import { GitLabClient } from "./Gitlab/Client";
|
||||||
|
import LogWrapper from "./LogWrapper";
|
||||||
|
import { MessageSenderClient } from "./MatrixSender";
|
||||||
|
import { UserTokenStore } from "./UserTokenStore";
|
||||||
|
|
||||||
|
const log = new LogWrapper("ConnectionManager");
|
||||||
|
|
||||||
|
export class ConnectionManager {
|
||||||
|
private connections: IConnection[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly as: Appservice,
|
||||||
|
private readonly config: BridgeConfig,
|
||||||
|
private readonly tokenStore: UserTokenStore,
|
||||||
|
private readonly commentProcessor: CommentProcessor,
|
||||||
|
private readonly messageClient: MessageSenderClient,
|
||||||
|
private readonly github?: GithubInstance) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a new connection to the manager, if this connection already
|
||||||
|
* exists then this will no-op.
|
||||||
|
* NOTE: The comparison only checks that the same object instance isn't present,
|
||||||
|
* but not if two instances exist with the same type/state.
|
||||||
|
* @param connection The connection instance to push.
|
||||||
|
*/
|
||||||
|
public push(...connections: IConnection[]) {
|
||||||
|
// NOTE: Double loop
|
||||||
|
for (const connection of connections) {
|
||||||
|
if (this.connections.find((c) => c !== connection)) {
|
||||||
|
this.connections.push(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Already exists, noop.
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createConnectionForState(roomId: string, state: StateEvent<any>) {
|
||||||
|
log.debug(`Looking to create connection for ${roomId}`);
|
||||||
|
if (state.content.disabled === false) {
|
||||||
|
log.debug(`${roomId} has disabled state for ${state.type}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GitHubRepoConnection.EventTypes.includes(state.type)) {
|
||||||
|
if (!this.github) {
|
||||||
|
throw Error('GitHub is not configured');
|
||||||
|
}
|
||||||
|
return new GitHubRepoConnection(roomId, this.as, state.content, this.tokenStore, state.stateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GitHubDiscussionConnection.EventTypes.includes(state.type)) {
|
||||||
|
if (!this.github) {
|
||||||
|
throw Error('GitHub is not configured');
|
||||||
|
}
|
||||||
|
return new GitHubDiscussionConnection(
|
||||||
|
roomId, this.as, state.content, state.stateKey, this.tokenStore, this.commentProcessor,
|
||||||
|
this.messageClient,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GitHubDiscussionSpace.EventTypes.includes(state.type)) {
|
||||||
|
if (!this.github) {
|
||||||
|
throw Error('GitHub is not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GitHubDiscussionSpace(
|
||||||
|
await this.as.botClient.getSpace(roomId), state.content, state.stateKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GitHubIssueConnection.EventTypes.includes(state.type)) {
|
||||||
|
if (!this.github) {
|
||||||
|
throw Error('GitHub is not configured');
|
||||||
|
}
|
||||||
|
const issue = new GitHubIssueConnection(roomId, this.as, state.content, state.stateKey || "", this.tokenStore, this.commentProcessor, this.messageClient, this.github);
|
||||||
|
await issue.syncIssueState();
|
||||||
|
return issue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GitHubUserSpace.EventTypes.includes(state.type)) {
|
||||||
|
if (!this.github) {
|
||||||
|
throw Error('GitHub is not configured');
|
||||||
|
}
|
||||||
|
return new GitHubUserSpace(
|
||||||
|
await this.as.botClient.getSpace(roomId), state.content, state.stateKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GitLabRepoConnection.EventTypes.includes(state.type)) {
|
||||||
|
if (!this.config.gitlab) {
|
||||||
|
throw Error('GitLab is not configured');
|
||||||
|
}
|
||||||
|
const instance = this.config.gitlab.instances[state.content.instance];
|
||||||
|
if (!instance) {
|
||||||
|
throw Error('Instance name not recognised');
|
||||||
|
}
|
||||||
|
return new GitLabRepoConnection(roomId, this.as, state.content, this.tokenStore, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GitLabIssueConnection.EventTypes.includes(state.type)) {
|
||||||
|
if (!this.config.gitlab) {
|
||||||
|
throw Error('GitLab is not configured');
|
||||||
|
}
|
||||||
|
const instance = this.config.gitlab.instances[state.content.instance];
|
||||||
|
return new GitLabIssueConnection(
|
||||||
|
roomId,
|
||||||
|
this.as,
|
||||||
|
state.content,
|
||||||
|
state.stateKey as string,
|
||||||
|
this.tokenStore,
|
||||||
|
this.commentProcessor,
|
||||||
|
this.messageClient,
|
||||||
|
instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JiraProjectConnection.EventTypes.includes(state.type)) {
|
||||||
|
if (!this.config.jira) {
|
||||||
|
throw Error('JIRA is not configured');
|
||||||
|
}
|
||||||
|
return new JiraProjectConnection(roomId, this.as, state.content, state.stateKey, this.commentProcessor, this.messageClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GenericHookConnection.EventTypes.includes(state.type) && this.config.generic?.enabled) {
|
||||||
|
return new GenericHookConnection(
|
||||||
|
roomId,
|
||||||
|
state.content,
|
||||||
|
state.stateKey,
|
||||||
|
this.messageClient,
|
||||||
|
this.config.generic.allowJsTransformationFunctions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createConnectionsForRoomId(roomId: string): Promise<IConnection[]> {
|
||||||
|
const state = await this.as.botClient.getRoomState(roomId);
|
||||||
|
const connections: IConnection[] = [];
|
||||||
|
for (const event of state) {
|
||||||
|
const conn = await this.createConnectionForState(roomId, new StateEvent(event));
|
||||||
|
if (conn) { connections.push(conn); }
|
||||||
|
}
|
||||||
|
return connections;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnectionsForGithubIssue(org: string, repo: string, issueNumber: number): (GitHubIssueConnection|GitHubRepoConnection)[] {
|
||||||
|
org = org.toLowerCase();
|
||||||
|
repo = repo.toLowerCase();
|
||||||
|
return this.connections.filter((c) => (c instanceof GitHubIssueConnection && c.org === org && c.repo === repo && c.issueNumber === issueNumber) ||
|
||||||
|
(c instanceof GitHubRepoConnection && c.org === org && c.repo === repo)) as (GitHubIssueConnection|GitHubRepoConnection)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnectionsForGithubRepo(org: string, repo: string): GitHubRepoConnection[] {
|
||||||
|
org = org.toLowerCase();
|
||||||
|
repo = repo.toLowerCase();
|
||||||
|
return this.connections.filter((c) => (c instanceof GitHubRepoConnection && c.org === org && c.repo === repo)) as GitHubRepoConnection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnectionsForGithubRepoDiscussion(owner: string, repo: string): GitHubDiscussionSpace[] {
|
||||||
|
owner = owner.toLowerCase();
|
||||||
|
repo = repo.toLowerCase();
|
||||||
|
return this.connections.filter((c) => (c instanceof GitHubDiscussionSpace && c.owner === owner && c.repo === repo)) as GitHubDiscussionSpace[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnectionForGithubUser(user: string): GitHubUserSpace {
|
||||||
|
return this.connections.find(c => c instanceof GitHubUserSpace && c.owner === user.toLowerCase()) as GitHubUserSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnectionsForGithubDiscussion(owner: string, repo: string, discussionNumber: number) {
|
||||||
|
owner = owner.toLowerCase();
|
||||||
|
repo = repo.toLowerCase();
|
||||||
|
return this.connections.filter(
|
||||||
|
c => (
|
||||||
|
c instanceof GitHubDiscussionConnection &&
|
||||||
|
c.owner === owner &&
|
||||||
|
c.repo === repo &&
|
||||||
|
c.discussionNumber === discussionNumber
|
||||||
|
)
|
||||||
|
) as GitHubDiscussionConnection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getForGitHubProject(projectId: number): GitHubProjectConnection[] {
|
||||||
|
return this.connections.filter(
|
||||||
|
c => (
|
||||||
|
c instanceof GitHubProjectConnection &&
|
||||||
|
c.projectId === projectId
|
||||||
|
)
|
||||||
|
) as GitHubProjectConnection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnectionsForGitLabIssueWebhook(repoHome: string, issueId: number) {
|
||||||
|
if (!this.config.gitlab) {
|
||||||
|
throw Error('GitLab configuration missing, cannot handle note');
|
||||||
|
}
|
||||||
|
const res = GitLabClient.splitUrlIntoParts(this.config.gitlab.instances, repoHome);
|
||||||
|
if (!res) {
|
||||||
|
throw Error('No instance found for note');
|
||||||
|
}
|
||||||
|
const instance = this.config.gitlab.instances[res[0]];
|
||||||
|
return this.getConnectionsForGitLabIssue(instance, res[1], issueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnectionsForGitLabIssue(instance: GitLabInstance, projects: string[], issueNumber: number): GitLabIssueConnection[] {
|
||||||
|
return this.connections.filter((c) => (
|
||||||
|
c instanceof GitLabIssueConnection &&
|
||||||
|
c.issueNumber == issueNumber &&
|
||||||
|
c.instanceUrl == instance.url &&
|
||||||
|
c.projectPath == projects.join("/")
|
||||||
|
)) as GitLabIssueConnection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnectionsForGitLabRepo(pathWithNamespace: string): GitLabRepoConnection[] {
|
||||||
|
pathWithNamespace = pathWithNamespace.toLowerCase();
|
||||||
|
return this.connections.filter((c) => (c instanceof GitLabRepoConnection && c.path === pathWithNamespace)) as GitLabRepoConnection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnectionsForJiraProject(projectId: string, eventName: string): JiraProjectConnection[] {
|
||||||
|
return this.connections.filter((c) => (c instanceof JiraProjectConnection && c.projectId === projectId && c.isInterestedInHookEvent(eventName))) as JiraProjectConnection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getConnectionsForGenericWebhook(hookId: string): GenericHookConnection[] {
|
||||||
|
return this.connections.filter((c) => (c instanceof GenericHookConnection && c.hookId === hookId)) as GenericHookConnection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
public getAllConnectionsOfType<T extends IConnection>(typeT: new (...params : any[]) => T): T[] {
|
||||||
|
return this.connections.filter((c) => (c instanceof typeT)) as T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public isRoomConnected(roomId: string): boolean {
|
||||||
|
return !!this.connections.find(c => c.roomId === roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAllConnectionsForRoom(roomId: string): IConnection[] {
|
||||||
|
return this.connections.filter(c => c.roomId === roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getInterestedForRoomState(roomId: string, eventType: string, stateKey: string): IConnection[] {
|
||||||
|
return this.connections.filter(c => c.roomId === roomId && c.isInterestedInStateEvent(eventType, stateKey));
|
||||||
|
}
|
||||||
|
}
|
@ -48,6 +48,10 @@ export class GitHubProjectConnection implements IConnection {
|
|||||||
return new GitHubProjectConnection(roomId, as, state, project.url)
|
return new GitHubProjectConnection(roomId, as, state, project.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get projectId() {
|
||||||
|
return this.state.project_id;
|
||||||
|
}
|
||||||
|
|
||||||
constructor(public readonly roomId: string,
|
constructor(public readonly roomId: string,
|
||||||
as: Appservice,
|
as: Appservice,
|
||||||
private state: GitHubProjectConnectionState,
|
private state: GitHubProjectConnectionState,
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
import { IConnection } from "./IConnection";
|
|
||||||
import { Appservice } from "matrix-bot-sdk";
|
import { Appservice } from "matrix-bot-sdk";
|
||||||
import { MatrixMessageContent, MatrixEvent, MatrixReactionContent } from "../MatrixEvent";
|
|
||||||
import markdown from "markdown-it";
|
|
||||||
import { UserTokenStore } from "../UserTokenStore";
|
|
||||||
import LogWrapper from "../LogWrapper";
|
|
||||||
import { CommentProcessor } from "../CommentProcessor";
|
|
||||||
import { Octokit } from "@octokit/rest";
|
|
||||||
import { MessageSenderClient } from "../MatrixSender";
|
|
||||||
import { FormatUtil } from "../FormatUtil";
|
|
||||||
import axios from "axios";
|
|
||||||
import { BotCommands, handleCommand, botCommand, compileBotCommands } from "../BotCommands";
|
import { BotCommands, handleCommand, botCommand, compileBotCommands } from "../BotCommands";
|
||||||
import { ReposGetResponseData } from "../Github/Types";
|
import { CommentProcessor } from "../CommentProcessor";
|
||||||
import { IssuesOpenedEvent, IssuesEditedEvent, PullRequestOpenedEvent, PullRequestClosedEvent, PullRequestReadyForReviewEvent, PullRequestReviewSubmittedEvent, ReleaseCreatedEvent } from "@octokit/webhooks-types";
|
import { FormatUtil } from "../FormatUtil";
|
||||||
import emoji from "node-emoji";
|
import { IConnection } from "./IConnection";
|
||||||
|
import { IssuesOpenedEvent, IssuesReopenedEvent, IssuesEditedEvent, PullRequestOpenedEvent, IssuesClosedEvent, PullRequestClosedEvent, PullRequestReadyForReviewEvent, PullRequestReviewSubmittedEvent, ReleaseCreatedEvent } from "@octokit/webhooks-types";
|
||||||
|
import { MatrixMessageContent, MatrixEvent, MatrixReactionContent } from "../MatrixEvent";
|
||||||
|
import { MessageSenderClient } from "../MatrixSender";
|
||||||
import { NotLoggedInError } from "../errors";
|
import { NotLoggedInError } from "../errors";
|
||||||
|
import { Octokit } from "@octokit/rest";
|
||||||
|
import { ReposGetResponseData } from "../Github/Types";
|
||||||
|
import { UserTokenStore } from "../UserTokenStore";
|
||||||
|
import axios from "axios";
|
||||||
|
import emoji from "node-emoji";
|
||||||
|
import LogWrapper from "../LogWrapper";
|
||||||
|
import markdown from "markdown-it";
|
||||||
const log = new LogWrapper("GitHubRepoConnection");
|
const log = new LogWrapper("GitHubRepoConnection");
|
||||||
const md = new markdown();
|
const md = new markdown();
|
||||||
|
|
||||||
@ -289,18 +289,18 @@ export class GitHubRepoConnection implements IConnection {
|
|||||||
const orgRepoName = event.repository.full_name;
|
const orgRepoName = event.repository.full_name;
|
||||||
|
|
||||||
const content = emoji.emojify(`${event.issue.user?.login} created new issue [${orgRepoName}#${event.issue.number}](${event.issue.html_url}): "${event.issue.title}"`);
|
const content = emoji.emojify(`${event.issue.user?.login} created new issue [${orgRepoName}#${event.issue.number}](${event.issue.html_url}): "${event.issue.title}"`);
|
||||||
const { labelsHtml, labelsStr } = FormatUtil.formatLabels(event.issue.labels);
|
const labels = FormatUtil.formatLabels(event.issue.labels?.map(l => ({ name: l.name, description: l.description || undefined, color: l.color || undefined })));
|
||||||
await this.as.botIntent.sendEvent(this.roomId, {
|
await this.as.botIntent.sendEvent(this.roomId, {
|
||||||
msgtype: "m.notice",
|
msgtype: "m.notice",
|
||||||
body: content + (labelsStr.length > 0 ? ` with labels ${labelsStr}`: ""),
|
body: content + (labels.plain.length > 0 ? ` with labels ${labels.plain}`: ""),
|
||||||
formatted_body: md.renderInline(content) + (labelsHtml.length > 0 ? ` with labels ${labelsHtml}`: ""),
|
formatted_body: md.renderInline(content) + (labels.html.length > 0 ? ` with labels ${labels.html}`: ""),
|
||||||
format: "org.matrix.custom.html",
|
format: "org.matrix.custom.html",
|
||||||
// TODO: Fix types.
|
// TODO: Fix types.
|
||||||
...FormatUtil.getPartialBodyForIssue(event.repository, event.issue as any),
|
...FormatUtil.getPartialBodyForIssue(event.repository, event.issue as any),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onIssueStateChange(event: IssuesEditedEvent) {
|
public async onIssueStateChange(event: IssuesEditedEvent|IssuesReopenedEvent|IssuesClosedEvent) {
|
||||||
if (this.shouldSkipHook('issue.changed', 'issue')) {
|
if (this.shouldSkipHook('issue.changed', 'issue')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -358,11 +358,11 @@ export class GitHubRepoConnection implements IConnection {
|
|||||||
const orgRepoName = event.repository.full_name;
|
const orgRepoName = event.repository.full_name;
|
||||||
const verb = event.pull_request.draft ? 'drafted' : 'opened';
|
const verb = event.pull_request.draft ? 'drafted' : 'opened';
|
||||||
const content = emoji.emojify(`**${event.sender.login}** ${verb} a new PR [${orgRepoName}#${event.pull_request.number}](${event.pull_request.html_url}): "${event.pull_request.title}"`);
|
const content = emoji.emojify(`**${event.sender.login}** ${verb} a new PR [${orgRepoName}#${event.pull_request.number}](${event.pull_request.html_url}): "${event.pull_request.title}"`);
|
||||||
const { labelsHtml, labelsStr } = FormatUtil.formatLabels(event.pull_request.labels);
|
const labels = FormatUtil.formatLabels(event.pull_request.labels?.map(l => ({ name: l.name, description: l.description || undefined, color: l.color || undefined })));
|
||||||
await this.as.botIntent.sendEvent(this.roomId, {
|
await this.as.botIntent.sendEvent(this.roomId, {
|
||||||
msgtype: "m.notice",
|
msgtype: "m.notice",
|
||||||
body: content + (labelsStr.length > 0 ? ` with labels ${labelsStr}`: ""),
|
body: content + (labels.plain.length > 0 ? ` with labels ${labels}`: ""),
|
||||||
formatted_body: md.renderInline(content) + (labelsHtml.length > 0 ? ` with labels ${labelsHtml}`: ""),
|
formatted_body: md.renderInline(content) + (labels.html.length > 0 ? ` with labels ${labels.html}`: ""),
|
||||||
format: "org.matrix.custom.html",
|
format: "org.matrix.custom.html",
|
||||||
// TODO: Fix types.
|
// TODO: Fix types.
|
||||||
...FormatUtil.getPartialBodyForIssue(event.repository, event.pull_request as any),
|
...FormatUtil.getPartialBodyForIssue(event.repository, event.pull_request as any),
|
||||||
|
@ -25,5 +25,10 @@ export interface IConnection {
|
|||||||
|
|
||||||
isInterestedInStateEvent: (eventType: string, stateKey: string) => boolean;
|
isInterestedInStateEvent: (eventType: string, stateKey: string) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the connection interested in the event that is being sent from the remote side?
|
||||||
|
*/
|
||||||
|
isInterestedInHookEvent?: (eventType: string) => boolean;
|
||||||
|
|
||||||
toString(): string;
|
toString(): string;
|
||||||
}
|
}
|
@ -6,10 +6,13 @@ import { MessageSenderClient } from "../MatrixSender"
|
|||||||
import { JiraIssueEvent } from "../Jira/WebhookTypes";
|
import { JiraIssueEvent } from "../Jira/WebhookTypes";
|
||||||
import { FormatUtil } from "../FormatUtil";
|
import { FormatUtil } from "../FormatUtil";
|
||||||
import markdownit from "markdown-it";
|
import markdownit from "markdown-it";
|
||||||
import { generateWebLinkFromIssue } from "../Jira/Utils";
|
import { generateJiraWebLinkFromIssue } from "../Jira";
|
||||||
|
|
||||||
|
type JiraAllowedEventsNames = "issue.created";
|
||||||
|
const JiraAllowedEvents: JiraAllowedEventsNames[] = ["issue.created"];
|
||||||
export interface JiraProjectConnectionState {
|
export interface JiraProjectConnectionState {
|
||||||
id: string;
|
id: string;
|
||||||
|
events?: JiraAllowedEventsNames[],
|
||||||
}
|
}
|
||||||
|
|
||||||
const log = new LogWrapper("JiraProjectConnection");
|
const log = new LogWrapper("JiraProjectConnection");
|
||||||
@ -33,6 +36,10 @@ export class JiraProjectConnection implements IConnection {
|
|||||||
return this.state.id;
|
return this.state.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isInterestedInHookEvent(eventName: string) {
|
||||||
|
return !this.state.events || this.state.events?.includes(eventName as JiraAllowedEventsNames);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(public readonly roomId: string,
|
constructor(public readonly roomId: string,
|
||||||
private readonly as: Appservice,
|
private readonly as: Appservice,
|
||||||
private state: JiraProjectConnectionState,
|
private state: JiraProjectConnectionState,
|
||||||
@ -47,11 +54,12 @@ export class JiraProjectConnection implements IConnection {
|
|||||||
|
|
||||||
public async onJiraIssueCreated(data: JiraIssueEvent) {
|
public async onJiraIssueCreated(data: JiraIssueEvent) {
|
||||||
log.info(`onIssueCreated ${this.roomId} ${this.projectId} ${data.issue.id}`);
|
log.info(`onIssueCreated ${this.roomId} ${this.projectId} ${data.issue.id}`);
|
||||||
|
|
||||||
const creator = data.issue.fields.creator;
|
const creator = data.issue.fields.creator;
|
||||||
if (!creator) {
|
if (!creator) {
|
||||||
throw Error('No creator field');
|
throw Error('No creator field');
|
||||||
}
|
}
|
||||||
const url = generateWebLinkFromIssue(data.issue);
|
const url = generateJiraWebLinkFromIssue(data.issue);
|
||||||
const content = `${creator.displayName} created a new JIRA issue [${data.issue.key}](${url}): "${data.issue.fields.summary}"`;
|
const content = `${creator.displayName} created a new JIRA issue [${data.issue.key}](${url}): "${data.issue.fields.summary}"`;
|
||||||
await this.as.botIntent.sendEvent(this.roomId, {
|
await this.as.botIntent.sendEvent(this.roomId, {
|
||||||
msgtype: "m.notice",
|
msgtype: "m.notice",
|
||||||
|
158
src/FormatUtil.rs
Normal file
158
src/FormatUtil.rs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
use crate::Jira;
|
||||||
|
use crate::Jira::types::{JiraIssue, JiraIssueLight};
|
||||||
|
use contrast;
|
||||||
|
use napi::{CallContext, Env, Error as NapiError, JsObject, JsUnknown, Status};
|
||||||
|
use rgb::RGB;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Deserialize)]
|
||||||
|
struct IssueLabelDetail {
|
||||||
|
color: Option<String>,
|
||||||
|
name: String,
|
||||||
|
description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_module(env: Env) -> Result<JsObject, NapiError> {
|
||||||
|
let mut root_module = env.create_object()?;
|
||||||
|
root_module.create_named_method(
|
||||||
|
"get_partial_body_for_jira_issue",
|
||||||
|
get_partial_body_for_jira_issue,
|
||||||
|
)?;
|
||||||
|
root_module.create_named_method("format_labels", format_labels)?;
|
||||||
|
Ok(root_module)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_rgb(input_color: String) -> Result<rgb::RGB8, NapiError> {
|
||||||
|
let chunk_size;
|
||||||
|
let color;
|
||||||
|
if input_color.starts_with('#') {
|
||||||
|
let mut chars = input_color.chars();
|
||||||
|
chars.next();
|
||||||
|
color = String::from_iter(chars);
|
||||||
|
} else {
|
||||||
|
color = input_color;
|
||||||
|
}
|
||||||
|
match color.len() {
|
||||||
|
6 => {
|
||||||
|
chunk_size = 2;
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
chunk_size = 1;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(NapiError::new(
|
||||||
|
Status::InvalidArg,
|
||||||
|
format!("color '{}' is invalid", color).to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let rgb = color
|
||||||
|
.as_bytes()
|
||||||
|
.chunks(chunk_size)
|
||||||
|
.map(std::str::from_utf8)
|
||||||
|
.collect::<Result<Vec<&str>, _>>()
|
||||||
|
.unwrap();
|
||||||
|
let r = u8::from_str_radix(rgb[0], 16).unwrap();
|
||||||
|
let g = u8::from_str_radix(rgb[1], 16).unwrap();
|
||||||
|
let b = u8::from_str_radix(rgb[2], 16).unwrap();
|
||||||
|
Ok(RGB::new(r, g, b))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[js_function(1)]
|
||||||
|
pub fn format_labels(ctx: CallContext) -> Result<JsObject, NapiError> {
|
||||||
|
let array: JsObject = ctx.get::<JsObject>(0)?;
|
||||||
|
if array.is_array()? != true {
|
||||||
|
return Err(NapiError::new(
|
||||||
|
Status::InvalidArg,
|
||||||
|
"labels is not an array".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut plain = String::new();
|
||||||
|
let mut html = String::new();
|
||||||
|
let mut i = 0;
|
||||||
|
while array.has_element(i)? {
|
||||||
|
let label: IssueLabelDetail = ctx
|
||||||
|
.env
|
||||||
|
.from_js_value(array.get_element_unchecked::<JsUnknown>(i)?)?;
|
||||||
|
|
||||||
|
if i != 0 {
|
||||||
|
plain.push_str(", ");
|
||||||
|
html.push_str(" ");
|
||||||
|
}
|
||||||
|
plain.push_str(&label.name);
|
||||||
|
|
||||||
|
// HTML
|
||||||
|
html.push_str("<span");
|
||||||
|
match label.color {
|
||||||
|
Some(color) => {
|
||||||
|
write!(html, " data-mx-bg-color=\"{}\"", color).unwrap();
|
||||||
|
// Determine the constrast
|
||||||
|
let color_rgb = parse_rgb(color)?;
|
||||||
|
let contrast_color;
|
||||||
|
if contrast::contrast::<u8, f32>(color_rgb, RGB::new(0, 0, 0)) > 4.5 {
|
||||||
|
contrast_color = "#000000";
|
||||||
|
} else {
|
||||||
|
contrast_color = "#FFFFFF";
|
||||||
|
}
|
||||||
|
write!(html, " data-mx-color=\"{}\"", contrast_color).unwrap();
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
match label.description {
|
||||||
|
Some(description) => {
|
||||||
|
write!(html, " title=\"{}\"", description).unwrap();
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
html.push_str(">");
|
||||||
|
html.push_str(&label.name);
|
||||||
|
html.push_str("</span>");
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut body = ctx.env.create_object()?;
|
||||||
|
body.set_named_property("plain", ctx.env.create_string_from_std(plain)?)?;
|
||||||
|
body.set_named_property("html", ctx.env.create_string_from_std(html)?)?;
|
||||||
|
Ok(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a URL for a given Jira Issue object.
|
||||||
|
#[js_function(1)]
|
||||||
|
pub fn get_partial_body_for_jira_issue(ctx: CallContext) -> Result<JsObject, NapiError> {
|
||||||
|
let jira_issue: JiraIssue = ctx.env.from_js_value(ctx.get::<JsUnknown>(0)?)?;
|
||||||
|
let light = JiraIssueLight {
|
||||||
|
_self: jira_issue._self,
|
||||||
|
key: jira_issue.key,
|
||||||
|
};
|
||||||
|
let mut body = ctx.env.create_object()?;
|
||||||
|
let url = Jira::utils::generate_jira_web_link_from_issue(&light)?;
|
||||||
|
body.set_named_property("external_url", ctx.env.create_string_from_std(url)?)?;
|
||||||
|
|
||||||
|
let mut jira_issue_result = ctx.env.create_object()?;
|
||||||
|
let mut jira_project = ctx.env.create_object()?;
|
||||||
|
|
||||||
|
jira_issue_result.set_named_property("id", ctx.env.create_string_from_std(jira_issue.id)?)?;
|
||||||
|
jira_issue_result.set_named_property("key", ctx.env.create_string_from_std(light.key)?)?;
|
||||||
|
jira_issue_result
|
||||||
|
.set_named_property("api_url", ctx.env.create_string_from_std(light._self)?)?;
|
||||||
|
|
||||||
|
jira_project.set_named_property(
|
||||||
|
"id",
|
||||||
|
ctx.env
|
||||||
|
.create_string_from_std(jira_issue.fields.project.id)?,
|
||||||
|
)?;
|
||||||
|
jira_project.set_named_property(
|
||||||
|
"key",
|
||||||
|
ctx.env
|
||||||
|
.create_string_from_std(jira_issue.fields.project.key)?,
|
||||||
|
)?;
|
||||||
|
jira_project.set_named_property(
|
||||||
|
"api_url",
|
||||||
|
ctx.env
|
||||||
|
.create_string_from_std(jira_issue.fields.project._self)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
body.set_named_property("uk.half-shot.matrix-github.jira.issue", jira_issue_result)?;
|
||||||
|
body.set_named_property("uk.half-shot.matrix-github.jira.project", jira_project)?;
|
||||||
|
Ok(body)
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import { ProjectsListResponseData } from './Github/Types';
|
import { ProjectsListResponseData } from './Github/Types';
|
||||||
import emoji from "node-emoji";
|
import emoji from "node-emoji";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { contrastColor } from "contrast-color";
|
import { contrastColor } from "contrast-color";
|
||||||
import { JiraIssue } from './Jira/Types';
|
import { JiraIssue } from './Jira/Types';
|
||||||
import { generateWebLinkFromIssue } from './Jira/Utils';
|
import { format_util } from "./libRs";
|
||||||
|
|
||||||
interface IMinimalRepository {
|
interface IMinimalRepository {
|
||||||
id: number;
|
id: number;
|
||||||
full_name: string;
|
full_name: string;
|
||||||
@ -21,6 +23,12 @@ interface IMinimalIssue {
|
|||||||
pull_request?: any;
|
pull_request?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ILabel {
|
||||||
|
color?: string,
|
||||||
|
name: string,
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
export class FormatUtil {
|
export class FormatUtil {
|
||||||
public static formatIssueRoomName(issue: IMinimalIssue) {
|
public static formatIssueRoomName(issue: IMinimalIssue) {
|
||||||
const orgRepoName = issue.repository_url.substr("https://api.github.com/repos/".length);
|
const orgRepoName = issue.repository_url.substr("https://api.github.com/repos/".length);
|
||||||
@ -84,39 +92,11 @@ export class FormatUtil {
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static formatLabels(labels: Array<{color?: string|null, name?: string, description?: string|null}|string> = []) {
|
public static formatLabels(labels: ILabel[] = []): { plain: string, html: string } {
|
||||||
const labelsHtml = labels.map((label: {color?: string|null, name?: string, description?: string|null}|string) => {
|
return format_util.format_labels(labels);
|
||||||
if (typeof(label) === "string") {
|
|
||||||
return `<span>${label}</span>`;
|
|
||||||
}
|
|
||||||
const fontColor = contrastColor(label.color);
|
|
||||||
return `<span title="${label.description}" data-mx-color="${fontColor}" data-mx-bg-color="#${label.color}">${label.name}</span>`
|
|
||||||
}
|
|
||||||
|
|
||||||
).join(" ") || "";
|
|
||||||
const labelsStr = labels.map((label: {name?: string}|string) =>
|
|
||||||
typeof(label) === "string" ? label : label.name
|
|
||||||
).join(", ") || "";
|
|
||||||
return {
|
|
||||||
labelsStr,
|
|
||||||
labelsHtml,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getPartialBodyForJiraIssue(issue: JiraIssue) {
|
public static getPartialBodyForJiraIssue(issue: JiraIssue) {
|
||||||
const url = generateWebLinkFromIssue(issue);
|
return format_util.get_partial_body_for_jira_issue(issue);
|
||||||
return {
|
|
||||||
"external_url": url,
|
|
||||||
"uk.half-shot.matrix-github.jira.issue": {
|
|
||||||
id: issue.id,
|
|
||||||
key: issue.key,
|
|
||||||
api_url: issue.self,
|
|
||||||
},
|
|
||||||
"uk.half-shot.matrix-github.jira.project": {
|
|
||||||
id: issue.fields.project.id,
|
|
||||||
key: issue.fields.project.key,
|
|
||||||
api_url: issue.fields.project.self,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,17 @@ import { Appservice, IAppserviceRegistration, RichRepliesPreprocessor, IRichRepl
|
|||||||
import { BridgeConfig, GitLabInstance } from "./Config/Config";
|
import { BridgeConfig, GitLabInstance } from "./Config/Config";
|
||||||
import { BridgeWidgetApi } from "./Widgets/BridgeWidgetApi";
|
import { BridgeWidgetApi } from "./Widgets/BridgeWidgetApi";
|
||||||
import { CommentProcessor } from "./CommentProcessor";
|
import { CommentProcessor } from "./CommentProcessor";
|
||||||
|
import { ConnectionManager } from "./ConnectionManager";
|
||||||
import { GetIssueResponse, GetIssueOpts } from "./Gitlab/Types"
|
import { GetIssueResponse, GetIssueOpts } from "./Gitlab/Types"
|
||||||
import { GenericHookConnection } from "./Connections/GenericHook";
|
|
||||||
import { GithubInstance } from "./Github/GithubInstance";
|
import { GithubInstance } from "./Github/GithubInstance";
|
||||||
import { GitHubIssueConnection } from "./Connections/GithubIssue";
|
import { GitHubIssueConnection } from "./Connections/GithubIssue";
|
||||||
import { GitHubProjectConnection } from "./Connections/GithubProject";
|
import { GitHubProjectConnection } from "./Connections/GithubProject";
|
||||||
import { GitHubRepoConnection } from "./Connections/GithubRepo";
|
import { GitHubRepoConnection } from "./Connections/GithubRepo";
|
||||||
import { GitLabClient } from "./Gitlab/Client";
|
|
||||||
import { GitLabIssueConnection } from "./Connections/GitlabIssue";
|
import { GitLabIssueConnection } from "./Connections/GitlabIssue";
|
||||||
import { GitLabRepoConnection } from "./Connections/GitlabRepo";
|
|
||||||
import { IBridgeStorageProvider } from "./Stores/StorageProvider";
|
import { IBridgeStorageProvider } from "./Stores/StorageProvider";
|
||||||
import { IConnection, GitHubDiscussionSpace, GitHubDiscussionConnection, GitHubUserSpace } from "./Connections";
|
import { IConnection, GitHubDiscussionSpace, GitHubDiscussionConnection, GitHubUserSpace } from "./Connections";
|
||||||
import { IGitLabWebhookIssueStateEvent, IGitLabWebhookMREvent, IGitLabWebhookNoteEvent } from "./Gitlab/WebhookTypes";
|
import { IGitLabWebhookIssueStateEvent, IGitLabWebhookMREvent, IGitLabWebhookNoteEvent } from "./Gitlab/WebhookTypes";
|
||||||
import { JiraIssueEvent } from "./Jira/WebhookTypes";
|
import { JiraIssueEvent } from "./Jira/WebhookTypes";
|
||||||
import { JiraProjectConnection } from "./Connections/JiraProject";
|
|
||||||
import { MatrixEvent, MatrixMemberContent, MatrixMessageContent } from "./MatrixEvent";
|
import { MatrixEvent, MatrixMemberContent, MatrixMessageContent } from "./MatrixEvent";
|
||||||
import { MemoryStorageProvider } from "./Stores/MemoryStorageProvider";
|
import { MemoryStorageProvider } from "./Stores/MemoryStorageProvider";
|
||||||
import { MessageQueue, createMessageQueue } from "./MessageQueue/MessageQueue";
|
import { MessageQueue, createMessageQueue } from "./MessageQueue/MessageQueue";
|
||||||
@ -31,216 +28,54 @@ import { UserNotificationsEvent } from "./Notifications/UserNotificationWatcher"
|
|||||||
import { UserTokenStore } from "./UserTokenStore";
|
import { UserTokenStore } from "./UserTokenStore";
|
||||||
import * as GitHubWebhookTypes from "@octokit/webhooks-types";
|
import * as GitHubWebhookTypes from "@octokit/webhooks-types";
|
||||||
import LogWrapper from "./LogWrapper";
|
import LogWrapper from "./LogWrapper";
|
||||||
|
|
||||||
const log = new LogWrapper("GithubBridge");
|
const log = new LogWrapper("GithubBridge");
|
||||||
|
|
||||||
export class GithubBridge {
|
export class GithubBridge {
|
||||||
|
private readonly as: Appservice;
|
||||||
|
private readonly storage: IBridgeStorageProvider;
|
||||||
|
private readonly messageClient: MessageSenderClient;
|
||||||
|
private readonly queue: MessageQueue;
|
||||||
|
private readonly commentProcessor: CommentProcessor;
|
||||||
|
private readonly notifProcessor: NotificationProcessor;
|
||||||
|
private readonly tokenStore: UserTokenStore;
|
||||||
|
private connectionManager?: ConnectionManager;
|
||||||
private github?: GithubInstance;
|
private github?: GithubInstance;
|
||||||
private as!: Appservice;
|
|
||||||
private encryptedMatrixClient?: MatrixClient;
|
private encryptedMatrixClient?: MatrixClient;
|
||||||
private adminRooms: Map<string, AdminRoom> = new Map();
|
private adminRooms: Map<string, AdminRoom> = new Map();
|
||||||
private commentProcessor!: CommentProcessor;
|
private widgetApi: BridgeWidgetApi = new BridgeWidgetApi(this.adminRooms);
|
||||||
private notifProcessor!: NotificationProcessor;
|
|
||||||
private queue!: MessageQueue;
|
|
||||||
private tokenStore!: UserTokenStore;
|
|
||||||
private messageClient!: MessageSenderClient;
|
|
||||||
private widgetApi!: BridgeWidgetApi;
|
|
||||||
private connections: IConnection[] = [];
|
|
||||||
|
|
||||||
private ready = false;
|
private ready = false;
|
||||||
|
|
||||||
constructor(private config: BridgeConfig, private registration: IAppserviceRegistration) { }
|
constructor(private config: BridgeConfig, private registration: IAppserviceRegistration) {
|
||||||
|
if (this.config.queue.host && this.config.queue.port) {
|
||||||
private async createConnectionForState(roomId: string, state: StateEvent<any>) {
|
log.info(`Initialising Redis storage (on ${this.config.queue.host}:${this.config.queue.port})`);
|
||||||
log.debug(`Looking to create connection for ${roomId}`);
|
this.storage = new RedisStorageProvider(this.config.queue.host, this.config.queue.port);
|
||||||
if (state.content.disabled === false) {
|
} else {
|
||||||
log.debug(`${roomId} has disabled state for ${state.type}`);
|
log.info('Initialising memory storage');
|
||||||
return;
|
this.storage = new MemoryStorageProvider();
|
||||||
}
|
}
|
||||||
|
this.as = new Appservice({
|
||||||
if (GitHubRepoConnection.EventTypes.includes(state.type)) {
|
homeserverName: this.config.bridge.domain,
|
||||||
if (!this.github) {
|
homeserverUrl: this.config.bridge.url,
|
||||||
throw Error('GitHub is not configured');
|
port: this.config.bridge.port,
|
||||||
}
|
bindAddress: this.config.bridge.bindAddress,
|
||||||
return new GitHubRepoConnection(roomId, this.as, state.content, this.tokenStore, state.stateKey);
|
registration: this.registration,
|
||||||
}
|
storage: this.storage,
|
||||||
|
});
|
||||||
if (GitHubDiscussionConnection.EventTypes.includes(state.type)) {
|
this.queue = createMessageQueue(this.config);
|
||||||
if (!this.github) {
|
this.messageClient = new MessageSenderClient(this.queue);
|
||||||
throw Error('GitHub is not configured');
|
this.commentProcessor = new CommentProcessor(this.as, this.config.bridge.mediaUrl || this.config.bridge.url);
|
||||||
}
|
this.notifProcessor = new NotificationProcessor(this.storage, this.messageClient);
|
||||||
return new GitHubDiscussionConnection(
|
this.tokenStore = new UserTokenStore(this.config.passFile || "./passkey.pem", this.as.botIntent);
|
||||||
roomId, this.as, state.content, state.stateKey, this.tokenStore, this.commentProcessor,
|
|
||||||
this.messageClient,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GitHubDiscussionSpace.EventTypes.includes(state.type)) {
|
|
||||||
if (!this.github) {
|
|
||||||
throw Error('GitHub is not configured');
|
|
||||||
}
|
|
||||||
|
|
||||||
return new GitHubDiscussionSpace(
|
|
||||||
await this.as.botClient.getSpace(roomId), state.content, state.stateKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GitHubIssueConnection.EventTypes.includes(state.type)) {
|
|
||||||
if (!this.github) {
|
|
||||||
throw Error('GitHub is not configured');
|
|
||||||
}
|
|
||||||
const issue = new GitHubIssueConnection(roomId, this.as, state.content, state.stateKey || "", this.tokenStore, this.commentProcessor, this.messageClient, this.github);
|
|
||||||
await issue.syncIssueState();
|
|
||||||
return issue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GitHubUserSpace.EventTypes.includes(state.type)) {
|
|
||||||
if (!this.github) {
|
|
||||||
throw Error('GitHub is not configured');
|
|
||||||
}
|
|
||||||
return new GitHubUserSpace(
|
|
||||||
await this.as.botClient.getSpace(roomId), state.content, state.stateKey
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GitLabRepoConnection.EventTypes.includes(state.type)) {
|
|
||||||
if (!this.config.gitlab) {
|
|
||||||
throw Error('GitLab is not configured');
|
|
||||||
}
|
|
||||||
const instance = this.config.gitlab.instances[state.content.instance];
|
|
||||||
if (!instance) {
|
|
||||||
throw Error('Instance name not recognised');
|
|
||||||
}
|
|
||||||
return new GitLabRepoConnection(roomId, this.as, state.content, this.tokenStore, instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GitLabIssueConnection.EventTypes.includes(state.type)) {
|
|
||||||
if (!this.config.gitlab) {
|
|
||||||
throw Error('GitLab is not configured');
|
|
||||||
}
|
|
||||||
const instance = this.config.gitlab.instances[state.content.instance];
|
|
||||||
return new GitLabIssueConnection(
|
|
||||||
roomId,
|
|
||||||
this.as,
|
|
||||||
state.content,
|
|
||||||
state.stateKey as string,
|
|
||||||
this.tokenStore,
|
|
||||||
this.commentProcessor,
|
|
||||||
this.messageClient,
|
|
||||||
instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JiraProjectConnection.EventTypes.includes(state.type)) {
|
|
||||||
if (!this.config.jira) {
|
|
||||||
throw Error('JIRA is not configured');
|
|
||||||
}
|
|
||||||
return new JiraProjectConnection(roomId, this.as, state.content, state.stateKey, this.commentProcessor, this.messageClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GenericHookConnection.EventTypes.includes(state.type) && this.config.generic?.enabled) {
|
|
||||||
return new GenericHookConnection(
|
|
||||||
roomId,
|
|
||||||
state.content,
|
|
||||||
state.stateKey,
|
|
||||||
this.messageClient,
|
|
||||||
this.config.generic.allowJsTransformationFunctions
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createConnectionsForRoomId(roomId: string): Promise<IConnection[]> {
|
|
||||||
const state = await this.as.botClient.getRoomState(roomId);
|
|
||||||
const connections: IConnection[] = [];
|
|
||||||
for (const event of state) {
|
|
||||||
const conn = await this.createConnectionForState(roomId, new StateEvent(event));
|
|
||||||
if (conn) { connections.push(conn); }
|
|
||||||
}
|
|
||||||
return connections;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getConnectionsForGithubIssue(org: string, repo: string, issueNumber: number): (GitHubIssueConnection|GitLabRepoConnection)[] {
|
|
||||||
org = org.toLowerCase();
|
|
||||||
repo = repo.toLowerCase();
|
|
||||||
return this.connections.filter((c) => (c instanceof GitHubIssueConnection && c.org === org && c.repo === repo && c.issueNumber === issueNumber) ||
|
|
||||||
(c instanceof GitHubRepoConnection && c.org === org && c.repo === repo)) as (GitHubIssueConnection|GitLabRepoConnection)[];
|
|
||||||
}
|
|
||||||
|
|
||||||
private getConnectionsForGithubRepo(org: string, repo: string): GitHubRepoConnection[] {
|
|
||||||
org = org.toLowerCase();
|
|
||||||
repo = repo.toLowerCase();
|
|
||||||
return this.connections.filter((c) => (c instanceof GitHubRepoConnection && c.org === org && c.repo === repo)) as GitHubRepoConnection[];
|
|
||||||
}
|
|
||||||
|
|
||||||
private getConnectionsForGithubRepoDiscussion(owner: string, repo: string): GitHubDiscussionSpace[] {
|
|
||||||
owner = owner.toLowerCase();
|
|
||||||
repo = repo.toLowerCase();
|
|
||||||
return this.connections.filter((c) => (c instanceof GitHubDiscussionSpace && c.owner === owner && c.repo === repo)) as GitHubDiscussionSpace[];
|
|
||||||
}
|
|
||||||
|
|
||||||
private getConnectionForGithubUser(user: string): GitHubUserSpace {
|
|
||||||
return this.connections.find(c => c instanceof GitHubUserSpace && c.owner === user.toLowerCase()) as GitHubUserSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private getConnectionsForGithubDiscussion(owner: string, repo: string, discussionNumber: number) {
|
|
||||||
owner = owner.toLowerCase();
|
|
||||||
repo = repo.toLowerCase();
|
|
||||||
return this.connections.filter(
|
|
||||||
(c) => (
|
|
||||||
c instanceof GitHubDiscussionConnection &&
|
|
||||||
c.owner === owner &&
|
|
||||||
c.repo === repo &&
|
|
||||||
c.discussionNumber === discussionNumber
|
|
||||||
)
|
|
||||||
) as GitHubDiscussionConnection[];
|
|
||||||
}
|
|
||||||
|
|
||||||
private getConnectionsForGitLabIssueWebhook(repoHome: string, issueId: number) {
|
|
||||||
if (!this.config.gitlab) {
|
|
||||||
throw Error('GitLab configuration missing, cannot handle note');
|
|
||||||
}
|
|
||||||
const res = GitLabClient.splitUrlIntoParts(this.config.gitlab.instances, repoHome);
|
|
||||||
if (!res) {
|
|
||||||
throw Error('No instance found for note');
|
|
||||||
}
|
|
||||||
const instance = this.config.gitlab.instances[res[0]];
|
|
||||||
return this.getConnectionsForGitLabIssue(instance, res[1], issueId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getConnectionsForGitLabIssue(instance: GitLabInstance, projects: string[], issueNumber: number): GitLabIssueConnection[] {
|
|
||||||
return this.connections.filter((c) => (
|
|
||||||
c instanceof GitLabIssueConnection &&
|
|
||||||
c.issueNumber == issueNumber &&
|
|
||||||
c.instanceUrl == instance.url &&
|
|
||||||
c.projectPath == projects.join("/")
|
|
||||||
)) as GitLabIssueConnection[];
|
|
||||||
}
|
|
||||||
|
|
||||||
private getConnectionsForGitLabRepo(pathWithNamespace: string): GitLabRepoConnection[] {
|
|
||||||
pathWithNamespace = pathWithNamespace.toLowerCase();
|
|
||||||
return this.connections.filter((c) => (c instanceof GitLabRepoConnection && c.path === pathWithNamespace)) as GitLabRepoConnection[];
|
|
||||||
}
|
|
||||||
|
|
||||||
private getConnectionsForJiraProject(projectId: string): JiraProjectConnection[] {
|
|
||||||
return this.connections.filter((c) => (c instanceof JiraProjectConnection && c.projectId === projectId)) as JiraProjectConnection[];
|
|
||||||
}
|
|
||||||
|
|
||||||
private getConnectionsForGenericWebhook(hookId: string): GenericHookConnection[] {
|
|
||||||
return this.connections.filter((c) => (c instanceof GenericHookConnection && c.hookId === hookId)) as GenericHookConnection[];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public stop() {
|
public stop() {
|
||||||
this.as.stop();
|
this.as.stop();
|
||||||
if(this.queue.stop) this.queue.stop();
|
if (this.queue.stop) this.queue.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async start() {
|
public async start() {
|
||||||
log.info('Starting up');
|
log.info('Starting up');
|
||||||
this.queue = createMessageQueue(this.config);
|
|
||||||
this.messageClient = new MessageSenderClient(this.queue);
|
|
||||||
|
|
||||||
if (!this.config.github && !this.config.gitlab) {
|
if (!this.config.github && !this.config.gitlab) {
|
||||||
log.error("You haven't configured support for GitHub or GitLab!");
|
log.error("You haven't configured support for GitHub or GitLab!");
|
||||||
@ -252,27 +87,6 @@ export class GithubBridge {
|
|||||||
await this.github.start();
|
await this.github.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
let storage: IBridgeStorageProvider;
|
|
||||||
if (this.config.queue.host && this.config.queue.port) {
|
|
||||||
log.info(`Initialising Redis storage (on ${this.config.queue.host}:${this.config.queue.port})`);
|
|
||||||
storage = new RedisStorageProvider(this.config.queue.host, this.config.queue.port);
|
|
||||||
} else {
|
|
||||||
log.info('Initialising memory storage');
|
|
||||||
storage = new MemoryStorageProvider();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
this.notifProcessor = new NotificationProcessor(storage, this.messageClient);
|
|
||||||
|
|
||||||
this.as = new Appservice({
|
|
||||||
homeserverName: this.config.bridge.domain,
|
|
||||||
homeserverUrl: this.config.bridge.url,
|
|
||||||
port: this.config.bridge.port,
|
|
||||||
bindAddress: this.config.bridge.bindAddress,
|
|
||||||
registration: this.registration,
|
|
||||||
storage,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.as.expressAppInstance.get("/live", (_, res) => res.send({ok: true}));
|
this.as.expressAppInstance.get("/live", (_, res) => res.send({ok: true}));
|
||||||
this.as.expressAppInstance.get("/ready", (_, res) => res.status(this.ready ? 200 : 500).send({ready: this.ready}));
|
this.as.expressAppInstance.get("/ready", (_, res) => res.status(this.ready ? 200 : 500).send({ready: this.ready}));
|
||||||
|
|
||||||
@ -280,7 +94,7 @@ export class GithubBridge {
|
|||||||
log.info(`Loading pantalaimon client`);
|
log.info(`Loading pantalaimon client`);
|
||||||
const pan = new PantalaimonClient(
|
const pan = new PantalaimonClient(
|
||||||
this.config.bridge.pantalaimon.url,
|
this.config.bridge.pantalaimon.url,
|
||||||
storage,
|
this.storage,
|
||||||
);
|
);
|
||||||
this.encryptedMatrixClient = await pan.createClientWithCredentials(
|
this.encryptedMatrixClient = await pan.createClientWithCredentials(
|
||||||
this.config.bridge.pantalaimon.username,
|
this.config.bridge.pantalaimon.username,
|
||||||
@ -294,12 +108,10 @@ export class GithubBridge {
|
|||||||
log.info(`Pan client is syncing`);
|
log.info(`Pan client is syncing`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.widgetApi = new BridgeWidgetApi(this.adminRooms);
|
|
||||||
|
|
||||||
this.commentProcessor = new CommentProcessor(this.as, this.config.bridge.mediaUrl || this.config.bridge.url);
|
|
||||||
|
|
||||||
this.tokenStore = new UserTokenStore(this.config.passFile || "./passkey.pem", this.as.botIntent);
|
|
||||||
await this.tokenStore.load();
|
await this.tokenStore.load();
|
||||||
|
const connManager = this.connectionManager = new ConnectionManager(this.as,
|
||||||
|
this.config, this.tokenStore, this.commentProcessor, this.messageClient, this.github);
|
||||||
|
|
||||||
this.as.on("query.room", async (roomAlias, cb) => {
|
this.as.on("query.room", async (roomAlias, cb) => {
|
||||||
try {
|
try {
|
||||||
@ -347,7 +159,7 @@ export class GithubBridge {
|
|||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.IssueCommentCreatedEvent>("github.issue_comment.created", async ({ data }) => {
|
this.queue.on<GitHubWebhookTypes.IssueCommentCreatedEvent>("github.issue_comment.created", async ({ data }) => {
|
||||||
const { repository, issue, owner } = validateRepoIssue(data);
|
const { repository, issue, owner } = validateRepoIssue(data);
|
||||||
const connections = this.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
const connections = connManager.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
if (c instanceof GitHubIssueConnection)
|
if (c instanceof GitHubIssueConnection)
|
||||||
@ -360,7 +172,7 @@ export class GithubBridge {
|
|||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.IssuesOpenedEvent>("github.issues.opened", async ({ data }) => {
|
this.queue.on<GitHubWebhookTypes.IssuesOpenedEvent>("github.issues.opened", async ({ data }) => {
|
||||||
const { repository, owner } = validateRepoIssue(data);
|
const { repository, owner } = validateRepoIssue(data);
|
||||||
const connections = this.getConnectionsForGithubRepo(owner, repository.name);
|
const connections = connManager.getConnectionsForGithubRepo(owner, repository.name);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
await c.onIssueCreated(data);
|
await c.onIssueCreated(data);
|
||||||
@ -372,7 +184,7 @@ export class GithubBridge {
|
|||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.IssuesEditedEvent>("github.issues.edited", async ({ data }) => {
|
this.queue.on<GitHubWebhookTypes.IssuesEditedEvent>("github.issues.edited", async ({ data }) => {
|
||||||
const { repository, issue, owner } = validateRepoIssue(data);
|
const { repository, issue, owner } = validateRepoIssue(data);
|
||||||
const connections = this.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
const connections = connManager.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
// TODO: Needs impls
|
// TODO: Needs impls
|
||||||
@ -386,7 +198,7 @@ export class GithubBridge {
|
|||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.IssuesClosedEvent>("github.issues.closed", async ({ data }) => {
|
this.queue.on<GitHubWebhookTypes.IssuesClosedEvent>("github.issues.closed", async ({ data }) => {
|
||||||
const { repository, issue, owner } = validateRepoIssue(data);
|
const { repository, issue, owner } = validateRepoIssue(data);
|
||||||
const connections = this.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
const connections = connManager.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
if (c instanceof GitHubIssueConnection || c instanceof GitHubRepoConnection)
|
if (c instanceof GitHubIssueConnection || c instanceof GitHubRepoConnection)
|
||||||
@ -399,11 +211,12 @@ export class GithubBridge {
|
|||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.IssuesReopenedEvent>("github.issues.reopened", async ({ data }) => {
|
this.queue.on<GitHubWebhookTypes.IssuesReopenedEvent>("github.issues.reopened", async ({ data }) => {
|
||||||
const { repository, issue, owner } = validateRepoIssue(data);
|
const { repository, issue, owner } = validateRepoIssue(data);
|
||||||
const connections = this.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
const connections = connManager.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
if (c instanceof GitHubIssueConnection || c instanceof GitHubRepoConnection)
|
if (c.onIssueStateChange) {
|
||||||
await c.onIssueStateChange(data);
|
await c.onIssueStateChange(data);
|
||||||
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
log.warn(`Connection ${c.toString()} failed to handle github.issues.reopened:`, ex);
|
log.warn(`Connection ${c.toString()} failed to handle github.issues.reopened:`, ex);
|
||||||
}
|
}
|
||||||
@ -411,8 +224,8 @@ export class GithubBridge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.IssuesEditedEvent>("github.issues.edited", async ({ data }) => {
|
this.queue.on<GitHubWebhookTypes.IssuesEditedEvent>("github.issues.edited", async ({ data }) => {
|
||||||
const { repository, issue, owner } = validateRepoIssue(data);
|
const { repository, owner } = validateRepoIssue(data);
|
||||||
const connections = this.getConnectionsForGithubRepo(owner, repository.name);
|
const connections = connManager.getConnectionsForGithubRepo(owner, repository.name);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
await c.onIssueEdited(data);
|
await c.onIssueEdited(data);
|
||||||
@ -423,7 +236,7 @@ export class GithubBridge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.PullRequestOpenedEvent>("github.pull_request.opened", async ({ data }) => {
|
this.queue.on<GitHubWebhookTypes.PullRequestOpenedEvent>("github.pull_request.opened", async ({ data }) => {
|
||||||
const connections = this.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
const connections = connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
await c.onPROpened(data);
|
await c.onPROpened(data);
|
||||||
@ -434,7 +247,7 @@ export class GithubBridge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.PullRequestClosedEvent>("github.pull_request.closed", async ({ data }) => {
|
this.queue.on<GitHubWebhookTypes.PullRequestClosedEvent>("github.pull_request.closed", async ({ data }) => {
|
||||||
const connections = this.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
const connections = connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
await c.onPRClosed(data);
|
await c.onPRClosed(data);
|
||||||
@ -445,7 +258,7 @@ export class GithubBridge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.PullRequestReadyForReviewEvent>("github.pull_request.ready_for_review", async ({ data }) => {
|
this.queue.on<GitHubWebhookTypes.PullRequestReadyForReviewEvent>("github.pull_request.ready_for_review", async ({ data }) => {
|
||||||
const connections = this.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
const connections = connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
await c.onPRReadyForReview(data);
|
await c.onPRReadyForReview(data);
|
||||||
@ -456,7 +269,7 @@ export class GithubBridge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.PullRequestReviewSubmittedEvent>("github.pull_request_review.submitted", async ({ data }) => {
|
this.queue.on<GitHubWebhookTypes.PullRequestReviewSubmittedEvent>("github.pull_request_review.submitted", async ({ data }) => {
|
||||||
const connections = this.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
const connections = connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
await c.onPRReviewed(data);
|
await c.onPRReviewed(data);
|
||||||
@ -467,7 +280,7 @@ export class GithubBridge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.ReleaseCreatedEvent>("github.release.created", async ({ data }) => {
|
this.queue.on<GitHubWebhookTypes.ReleaseCreatedEvent>("github.release.created", async ({ data }) => {
|
||||||
const connections = this.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
const connections = connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
await c.onReleaseCreated(data);
|
await c.onReleaseCreated(data);
|
||||||
@ -478,7 +291,7 @@ export class GithubBridge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<IGitLabWebhookMREvent>("gitlab.merge_request.open", async (msg) => {
|
this.queue.on<IGitLabWebhookMREvent>("gitlab.merge_request.open", async (msg) => {
|
||||||
const connections = this.getConnectionsForGitLabRepo(msg.data.project.path_with_namespace);
|
const connections = connManager.getConnectionsForGitLabRepo(msg.data.project.path_with_namespace);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
await c.onMergeRequestOpened(msg.data);
|
await c.onMergeRequestOpened(msg.data);
|
||||||
@ -489,7 +302,7 @@ export class GithubBridge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<IGitLabWebhookMREvent>("gitlab.tag_push", async (msg) => {
|
this.queue.on<IGitLabWebhookMREvent>("gitlab.tag_push", async (msg) => {
|
||||||
const connections = this.getConnectionsForGitLabRepo(msg.data.project.path_with_namespace);
|
const connections = connManager.getConnectionsForGitLabRepo(msg.data.project.path_with_namespace);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
await c.onMergeRequestOpened(msg.data);
|
await c.onMergeRequestOpened(msg.data);
|
||||||
@ -529,7 +342,7 @@ export class GithubBridge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<IGitLabWebhookNoteEvent>("gitlab.note.created", async ({data}) => {
|
this.queue.on<IGitLabWebhookNoteEvent>("gitlab.note.created", async ({data}) => {
|
||||||
const connections = this.getConnectionsForGitLabIssueWebhook(data.repository.homepage, data.issue.iid);
|
const connections = connManager.getConnectionsForGitLabIssueWebhook(data.repository.homepage, data.issue.iid);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
if (c.onCommentCreated)
|
if (c.onCommentCreated)
|
||||||
@ -541,7 +354,7 @@ export class GithubBridge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<IGitLabWebhookIssueStateEvent>("gitlab.issue.reopen", async ({data}) => {
|
this.queue.on<IGitLabWebhookIssueStateEvent>("gitlab.issue.reopen", async ({data}) => {
|
||||||
const connections = this.getConnectionsForGitLabIssueWebhook(data.repository.homepage, data.object_attributes.iid);
|
const connections = connManager.getConnectionsForGitLabIssueWebhook(data.repository.homepage, data.object_attributes.iid);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
await c.onIssueReopened();
|
await c.onIssueReopened();
|
||||||
@ -552,7 +365,7 @@ export class GithubBridge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<IGitLabWebhookIssueStateEvent>("gitlab.issue.close", async ({data}) => {
|
this.queue.on<IGitLabWebhookIssueStateEvent>("gitlab.issue.close", async ({data}) => {
|
||||||
const connections = this.getConnectionsForGitLabIssueWebhook(data.repository.homepage, data.object_attributes.iid);
|
const connections = connManager.getConnectionsForGitLabIssueWebhook(data.repository.homepage, data.object_attributes.iid);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
await c.onIssueClosed();
|
await c.onIssueClosed();
|
||||||
@ -563,7 +376,7 @@ export class GithubBridge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.DiscussionCommentCreatedEvent>("github.discussion_comment.created", async ({data}) => {
|
this.queue.on<GitHubWebhookTypes.DiscussionCommentCreatedEvent>("github.discussion_comment.created", async ({data}) => {
|
||||||
const connections = this.getConnectionsForGithubDiscussion(data.repository.owner.login, data.repository.name, data.discussion.number);
|
const connections = connManager.getConnectionsForGithubDiscussion(data.repository.owner.login, data.repository.name, data.discussion.number);
|
||||||
connections.map(async (c) => {
|
connections.map(async (c) => {
|
||||||
try {
|
try {
|
||||||
await c.onDiscussionCommentCreated(data);
|
await c.onDiscussionCommentCreated(data);
|
||||||
@ -577,13 +390,13 @@ export class GithubBridge {
|
|||||||
if (!this.github) {
|
if (!this.github) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const spaces = this.getConnectionsForGithubRepoDiscussion(data.repository.owner.login, data.repository.name);
|
const spaces = connManager.getConnectionsForGithubRepoDiscussion(data.repository.owner.login, data.repository.name);
|
||||||
if (spaces.length === 0) {
|
if (spaces.length === 0) {
|
||||||
log.info(`Not creating discussion ${data.discussion.id} ${data.repository.owner.login}/${data.repository.name}, no target spaces`);
|
log.info(`Not creating discussion ${data.discussion.id} ${data.repository.owner.login}/${data.repository.name}, no target spaces`);
|
||||||
// We don't want to create any discussions if we have no target spaces.
|
// We don't want to create any discussions if we have no target spaces.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let [discussionConnection] = this.getConnectionsForGithubDiscussion(data.repository.owner.login, data.repository.name, data.discussion.id);
|
let [discussionConnection] = connManager.getConnectionsForGithubDiscussion(data.repository.owner.login, data.repository.name, data.discussion.id);
|
||||||
if (!discussionConnection) {
|
if (!discussionConnection) {
|
||||||
try {
|
try {
|
||||||
// If we don't have an existing connection for this discussion (likely), then create one.
|
// If we don't have an existing connection for this discussion (likely), then create one.
|
||||||
@ -597,7 +410,7 @@ export class GithubBridge {
|
|||||||
this.commentProcessor,
|
this.commentProcessor,
|
||||||
this.messageClient,
|
this.messageClient,
|
||||||
);
|
);
|
||||||
this.connections.push(discussionConnection);
|
connManager.push(discussionConnection);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
log.error(ex);
|
log.error(ex);
|
||||||
throw Error('Failed to create discussion room');
|
throw Error('Failed to create discussion room');
|
||||||
@ -616,7 +429,7 @@ export class GithubBridge {
|
|||||||
this.queue.on<JiraIssueEvent>("jira.issue_created", async ({data}) => {
|
this.queue.on<JiraIssueEvent>("jira.issue_created", async ({data}) => {
|
||||||
log.info(`JIRA issue created for project ${data.issue.fields.project.id}, issue id ${data.issue.id}`);
|
log.info(`JIRA issue created for project ${data.issue.fields.project.id}, issue id ${data.issue.id}`);
|
||||||
const projectId = data.issue.fields.project.id;
|
const projectId = data.issue.fields.project.id;
|
||||||
const connections = this.getConnectionsForJiraProject(projectId);
|
const connections = connManager.getConnectionsForJiraProject(projectId, "jira.issue_created");
|
||||||
|
|
||||||
connections.forEach(async (c) => {
|
connections.forEach(async (c) => {
|
||||||
try {
|
try {
|
||||||
@ -629,7 +442,7 @@ export class GithubBridge {
|
|||||||
|
|
||||||
this.queue.on<GenericWebhookEvent>("generic-webhook.event", async ({data}) => {
|
this.queue.on<GenericWebhookEvent>("generic-webhook.event", async ({data}) => {
|
||||||
log.info(`Incoming generic hook ${data.hookId}`);
|
log.info(`Incoming generic hook ${data.hookId}`);
|
||||||
const connections = this.getConnectionsForGenericWebhook(data.hookId);
|
const connections = connManager.getConnectionsForGenericWebhook(data.hookId);
|
||||||
|
|
||||||
connections.forEach(async (c) => {
|
connections.forEach(async (c) => {
|
||||||
try {
|
try {
|
||||||
@ -678,14 +491,14 @@ export class GithubBridge {
|
|||||||
log.debug("Fetching state for " + roomId);
|
log.debug("Fetching state for " + roomId);
|
||||||
let connections: IConnection[];
|
let connections: IConnection[];
|
||||||
try {
|
try {
|
||||||
connections = await this.createConnectionsForRoomId(roomId);
|
connections = await connManager.createConnectionsForRoomId(roomId);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
log.error(`Unable to create connection for ${roomId}`, ex);
|
log.error(`Unable to create connection for ${roomId}`, ex);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (connections.length) {
|
if (connections.length) {
|
||||||
log.info(`Room ${roomId} is connected to: ${connections.join(',')}`);
|
log.info(`Room ${roomId} is connected to: ${connections.join(',')}`);
|
||||||
this.connections.push(...connections);
|
connManager.push(...connections);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,8 +530,8 @@ export class GithubBridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle spaces
|
// Handle spaces
|
||||||
for (const discussion of this.connections.filter((c) => c instanceof GitHubDiscussionSpace) as GitHubDiscussionSpace[]) {
|
for (const discussion of connManager.getAllConnectionsOfType(GitHubDiscussionSpace)) {
|
||||||
const user = this.getConnectionForGithubUser(discussion.owner);
|
const user = connManager.getConnectionForGithubUser(discussion.owner);
|
||||||
if (user) {
|
if (user) {
|
||||||
await user.ensureDiscussionInSpace(discussion);
|
await user.ensureDiscussionInSpace(discussion);
|
||||||
}
|
}
|
||||||
@ -753,6 +566,10 @@ export class GithubBridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async onRoomMessage(roomId: string, event: MatrixEvent<MatrixMessageContent>) {
|
private async onRoomMessage(roomId: string, event: MatrixEvent<MatrixMessageContent>) {
|
||||||
|
if (!this.connectionManager) {
|
||||||
|
// Not ready yet.
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.as.isNamespacedUser(event.sender)) {
|
if (this.as.isNamespacedUser(event.sender)) {
|
||||||
/* We ignore messages from our users */
|
/* We ignore messages from our users */
|
||||||
return;
|
return;
|
||||||
@ -762,7 +579,6 @@ export class GithubBridge {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.info(`Got message roomId=${roomId} type=${event.type} from=${event.sender}`);
|
log.info(`Got message roomId=${roomId} type=${event.type} from=${event.sender}`);
|
||||||
console.log(event);
|
|
||||||
log.debug("Content:", JSON.stringify(event));
|
log.debug("Content:", JSON.stringify(event));
|
||||||
const adminRoom = this.adminRooms.get(roomId);
|
const adminRoom = this.adminRooms.get(roomId);
|
||||||
|
|
||||||
@ -784,7 +600,7 @@ export class GithubBridge {
|
|||||||
const issueNumber = ev.content["uk.half-shot.matrix-github.issue"]?.number;
|
const issueNumber = ev.content["uk.half-shot.matrix-github.issue"]?.number;
|
||||||
if (splitParts && issueNumber) {
|
if (splitParts && issueNumber) {
|
||||||
log.info(`Handling reply for ${splitParts}${issueNumber}`);
|
log.info(`Handling reply for ${splitParts}${issueNumber}`);
|
||||||
const connections = this.getConnectionsForGithubIssue(splitParts[0], splitParts[1], issueNumber);
|
const connections = this.connectionManager.getConnectionsForGithubIssue(splitParts[0], splitParts[1], issueNumber);
|
||||||
await Promise.all(connections.map(async c => {
|
await Promise.all(connections.map(async c => {
|
||||||
if (c instanceof GitHubIssueConnection) {
|
if (c instanceof GitHubIssueConnection) {
|
||||||
return c.onMatrixIssueComment(processedReply);
|
return c.onMatrixIssueComment(processedReply);
|
||||||
@ -806,7 +622,7 @@ export class GithubBridge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const connection of this.connections.filter((c) => c.roomId === roomId)) {
|
for (const connection of this.connectionManager.getAllConnectionsForRoom(roomId)) {
|
||||||
try {
|
try {
|
||||||
if (connection.onMessageEvent) {
|
if (connection.onMessageEvent) {
|
||||||
await connection.onMessageEvent(event);
|
await connection.onMessageEvent(event);
|
||||||
@ -822,44 +638,59 @@ export class GithubBridge {
|
|||||||
// Only act on bot joins
|
// Only act on bot joins
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!this.connectionManager) {
|
||||||
const isRoomConnected = !!this.connections.find(c => c.roomId === roomId);
|
// Not ready yet.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Only fetch rooms we have no connections in yet.
|
// Only fetch rooms we have no connections in yet.
|
||||||
if (!isRoomConnected) {
|
if (!this.connectionManager.isRoomConnected(roomId)) {
|
||||||
const connections = await this.createConnectionsForRoomId(roomId);
|
const connections = await this.connectionManager.createConnectionsForRoomId(roomId);
|
||||||
this.connections.push(...connections);
|
this.connectionManager.push(...connections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onRoomEvent(roomId: string, event: MatrixEvent<unknown>) {
|
private async onRoomEvent(roomId: string, event: MatrixEvent<unknown>) {
|
||||||
|
if (!this.connectionManager) {
|
||||||
|
// Not ready yet.
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.state_key) {
|
if (event.state_key) {
|
||||||
// A state update, hurrah!
|
// A state update, hurrah!
|
||||||
const existingConnection = this.connections.find((c) => c.roomId === roomId && c.isInterestedInStateEvent(event.type, event.state_key || ""));
|
const existingConnections = this.connectionManager.getInterestedForRoomState(roomId, event.type, event.state_key);
|
||||||
if (existingConnection?.onStateUpdate) {
|
for (const connection of existingConnections) {
|
||||||
existingConnection.onStateUpdate(event);
|
try {
|
||||||
} else if (!existingConnection) {
|
if (connection?.onStateUpdate) {
|
||||||
|
connection.onStateUpdate(event);
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
log.warn(`Connection ${connection.toString()} failed to handle onStateUpdate:`, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!existingConnections.length) {
|
||||||
// Is anyone interested in this state?
|
// Is anyone interested in this state?
|
||||||
const connection = await this.createConnectionForState(roomId, new StateEvent(event));
|
const connection = await this.connectionManager.createConnectionForState(roomId, new StateEvent(event));
|
||||||
if (connection) {
|
if (connection) {
|
||||||
log.info(`New connected added to ${roomId}: ${connection.toString()}`);
|
log.info(`New connected added to ${roomId}: ${connection.toString()}`);
|
||||||
this.connections.push(connection);
|
this.connectionManager.push(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We still want to react to our own state events.
|
||||||
if (event.sender === this.as.botUserId) {
|
if (event.sender === this.as.botUserId) {
|
||||||
// It's us
|
// It's us
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const connection of this.connections.filter((c) => c.roomId === roomId)) {
|
for (const connection of this.connectionManager.getAllConnectionsForRoom(roomId)) {
|
||||||
try {
|
try {
|
||||||
if (connection.onEvent) {
|
if (connection.onEvent) {
|
||||||
await connection.onEvent(event);
|
await connection.onEvent(event);
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
log.warn(`Connection ${connection.toString()} failed to handle event:`, ex);
|
log.warn(`Connection ${connection.toString()} failed to handle onEvent:`, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -922,7 +753,6 @@ export class GithubBridge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
res = GitHubUserSpace.QueryRoomRegex.exec(roomAlias);
|
res = GitHubUserSpace.QueryRoomRegex.exec(roomAlias);
|
||||||
if (res) {
|
if (res) {
|
||||||
if (!this.github) {
|
if (!this.github) {
|
||||||
@ -1018,8 +848,13 @@ export class GithubBridge {
|
|||||||
);
|
);
|
||||||
adminRoom.on("settings.changed", this.onAdminRoomSettingsChanged.bind(this));
|
adminRoom.on("settings.changed", this.onAdminRoomSettingsChanged.bind(this));
|
||||||
adminRoom.on("open.project", async (project: ProjectsGetResponseData) => {
|
adminRoom.on("open.project", async (project: ProjectsGetResponseData) => {
|
||||||
const connection = await GitHubProjectConnection.onOpenProject(project, this.as, adminRoom.userId);
|
const [connection] = this.connectionManager?.getForGitHubProject(project.id) || [];
|
||||||
this.connections.push(connection);
|
if (!connection) {
|
||||||
|
const connection = await GitHubProjectConnection.onOpenProject(project, this.as, adminRoom.userId);
|
||||||
|
this.connectionManager?.push(connection);
|
||||||
|
} else {
|
||||||
|
await this.as.botClient.inviteUser(adminRoom.userId, connection.roomId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// adminRoom.on("open.discussion", async (owner: string, repo: string, discussions: Discussion) => {
|
// adminRoom.on("open.discussion", async (owner: string, repo: string, discussions: Discussion) => {
|
||||||
// const connection = await GitHubDiscussionConnection.createDiscussionRoom(
|
// const connection = await GitHubDiscussionConnection.createDiscussionRoom(
|
||||||
@ -1028,7 +863,7 @@ export class GithubBridge {
|
|||||||
// this.connections.push(connection);
|
// this.connections.push(connection);
|
||||||
// });
|
// });
|
||||||
adminRoom.on("open.gitlab-issue", async (issueInfo: GetIssueOpts, res: GetIssueResponse, instanceName: string, instance: GitLabInstance) => {
|
adminRoom.on("open.gitlab-issue", async (issueInfo: GetIssueOpts, res: GetIssueResponse, instanceName: string, instance: GitLabInstance) => {
|
||||||
const [ connection ] = this.getConnectionsForGitLabIssue(instance, issueInfo.projects, issueInfo.issue);
|
const [ connection ] = this.connectionManager?.getConnectionsForGitLabIssue(instance, issueInfo.projects, issueInfo.issue) || [];
|
||||||
if (connection) {
|
if (connection) {
|
||||||
return this.as.botClient.inviteUser(adminRoom.userId, connection.roomId);
|
return this.as.botClient.inviteUser(adminRoom.userId, connection.roomId);
|
||||||
}
|
}
|
||||||
@ -1042,7 +877,7 @@ export class GithubBridge {
|
|||||||
this.commentProcessor,
|
this.commentProcessor,
|
||||||
this.messageClient
|
this.messageClient
|
||||||
);
|
);
|
||||||
this.connections.push(newConnection);
|
this.connectionManager?.push(newConnection);
|
||||||
return this.as.botClient.inviteUser(adminRoom.userId, newConnection.roomId);
|
return this.as.botClient.inviteUser(adminRoom.userId, newConnection.roomId);
|
||||||
});
|
});
|
||||||
this.adminRooms.set(roomId, adminRoom);
|
this.adminRooms.set(roomId, adminRoom);
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
import { JiraIssue } from "./Types";
|
|
||||||
|
|
||||||
export function generateWebLinkFromIssue(issue: JiraIssue) {
|
|
||||||
const { origin } = new URL(issue.self);
|
|
||||||
return `${origin}/browse/${issue.key}`
|
|
||||||
}
|
|
3
src/Jira/index.ts
Normal file
3
src/Jira/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { jira } from "../libRs";
|
||||||
|
|
||||||
|
export const generateJiraWebLinkFromIssue = jira.utils.generate_jira_web_link_from_issue;
|
14
src/Jira/mod.rs
Normal file
14
src/Jira/mod.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use napi::{Env, Error as NapiError, JsObject};
|
||||||
|
pub mod types;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
pub fn get_module(env: Env) -> Result<JsObject, NapiError> {
|
||||||
|
let mut root_module = env.create_object()?;
|
||||||
|
let mut utils_module = env.create_object()?;
|
||||||
|
utils_module.create_named_method(
|
||||||
|
"generate_jira_web_link_from_issue",
|
||||||
|
utils::js_generate_jira_web_link_from_issue,
|
||||||
|
)?;
|
||||||
|
root_module.set_named_property("utils", utils_module)?;
|
||||||
|
Ok(root_module)
|
||||||
|
}
|
29
src/Jira/types.rs
Normal file
29
src/Jira/types.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#[derive(Serialize, Debug, Deserialize)]
|
||||||
|
pub struct JiraProject {
|
||||||
|
#[serde(rename = "self")]
|
||||||
|
pub _self: String,
|
||||||
|
pub id: String,
|
||||||
|
pub key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Deserialize)]
|
||||||
|
|
||||||
|
pub struct JiraIssue {
|
||||||
|
#[serde(rename = "self")]
|
||||||
|
pub _self: String,
|
||||||
|
pub id: String,
|
||||||
|
pub key: String,
|
||||||
|
pub fields: JiraIssueFields,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Deserialize)]
|
||||||
|
pub struct JiraIssueFields {
|
||||||
|
pub project: JiraProject,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Deserialize)]
|
||||||
|
pub struct JiraIssueLight {
|
||||||
|
#[serde(rename = "self")]
|
||||||
|
pub _self: String,
|
||||||
|
pub key: String,
|
||||||
|
}
|
27
src/Jira/utils.rs
Normal file
27
src/Jira/utils.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use super::types::JiraIssueLight;
|
||||||
|
use napi::{CallContext, Error as NapiError, JsString, JsUnknown, Status};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
/// Generate a URL for a given Jira Issue object.
|
||||||
|
#[js_function(1)]
|
||||||
|
pub fn js_generate_jira_web_link_from_issue(ctx: CallContext) -> Result<JsString, NapiError> {
|
||||||
|
let jira_issue: JiraIssueLight = ctx.env.from_js_value(ctx.get::<JsUnknown>(0)?)?;
|
||||||
|
match generate_jira_web_link_from_issue(&jira_issue) {
|
||||||
|
Ok(url) => ctx.env.create_string_from_std(url),
|
||||||
|
Err(err) => Err(NapiError::new(Status::Unknown, err.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a URL for a given Jira Issue object.
|
||||||
|
pub fn generate_jira_web_link_from_issue(jira_issue: &JiraIssueLight) -> Result<String, NapiError> {
|
||||||
|
let result = Url::parse(&jira_issue._self);
|
||||||
|
match result {
|
||||||
|
Ok(url) => Ok(format!(
|
||||||
|
"{}://{}/browse/{}",
|
||||||
|
url.scheme(),
|
||||||
|
url.host_str().unwrap(),
|
||||||
|
jira_issue.key
|
||||||
|
)),
|
||||||
|
Err(err) => Err(NapiError::new(Status::Unknown, err.to_string())),
|
||||||
|
}
|
||||||
|
}
|
16
src/lib.rs
Normal file
16
src/lib.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use napi::{Env, Error as NapiError, JsObject};
|
||||||
|
mod FormatUtil;
|
||||||
|
mod Jira;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate napi_derive;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
#[module_exports]
|
||||||
|
fn init(mut exports: JsObject, env: Env) -> Result<(), NapiError> {
|
||||||
|
exports.set_named_property("jira", Jira::get_module(env)?)?;
|
||||||
|
exports.set_named_property("format_util", FormatUtil::get_module(env)?)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
28
src/libRs.ts
Normal file
28
src/libRs.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/* eslint-disable camelcase */
|
||||||
|
import { ILabel } from "./FormatUtil";
|
||||||
|
import { JiraIssue } from "./Jira/Types";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
let rootModule;
|
||||||
|
try {
|
||||||
|
// In production, we expect it co-located
|
||||||
|
rootModule = require('./matrix-github-rs.node');
|
||||||
|
} catch (ex) {
|
||||||
|
// When running under ts-node, it may not be co-located.
|
||||||
|
rootModule = require('../lib/matrix-github-rs.node');
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FormatUtil {
|
||||||
|
get_partial_body_for_jira_issue: (issue: JiraIssue) => Record<string, unknown>
|
||||||
|
format_labels: (labels: ILabel[]) => { plain: string, html: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JiraModule {
|
||||||
|
utils: {
|
||||||
|
generate_jira_web_link_from_issue: (issue: {self: string, key: string}) => string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const format_util = rootModule.format_util as FormatUtil;
|
||||||
|
export const jira = rootModule.jira as JiraModule;
|
@ -1,5 +1,6 @@
|
|||||||
import { FormatUtil } from "../src/FormatUtil";
|
import { FormatUtil } from "../src/FormatUtil";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
import { JiraIssue, JiraProject } from "../src/Jira/Types";
|
||||||
|
|
||||||
const SIMPLE_ISSUE = {
|
const SIMPLE_ISSUE = {
|
||||||
id: 123,
|
id: 123,
|
||||||
@ -19,21 +20,85 @@ const SIMPLE_REPO = {
|
|||||||
html_url: "https://github.com/evilcorp/lab/issues/123",
|
html_url: "https://github.com/evilcorp/lab/issues/123",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SIMPLE_JIRA_ISSUE = {
|
||||||
|
id: "test-issue",
|
||||||
|
self: "http://example-api.url.com/issue-url",
|
||||||
|
key: "TEST-001",
|
||||||
|
fields: {
|
||||||
|
summary: "summary",
|
||||||
|
issuetype: "foo",
|
||||||
|
project: {
|
||||||
|
self: "http://example-api.url.com/project-url",
|
||||||
|
id: "test-project",
|
||||||
|
key: "TEST",
|
||||||
|
name: "Test Project",
|
||||||
|
projectTypeKey: "project-type-key",
|
||||||
|
simplified: false,
|
||||||
|
avatarUrls: {}
|
||||||
|
} as JiraProject,
|
||||||
|
assignee: null,
|
||||||
|
priority: "1",
|
||||||
|
status: "open",
|
||||||
|
},
|
||||||
|
} as JiraIssue;
|
||||||
|
|
||||||
describe("FormatUtilTest", () => {
|
describe("FormatUtilTest", () => {
|
||||||
it("correctly formats a repo room name", () => {
|
it("should correctly formats a repo room name", () => {
|
||||||
expect(FormatUtil.formatRepoRoomName(SIMPLE_REPO)).to.equal(
|
expect(FormatUtil.formatRepoRoomName(SIMPLE_REPO)).to.equal(
|
||||||
"evilcorp/lab: A simple description",
|
"evilcorp/lab: A simple description",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it("correctly formats a issue room name", () => {
|
it("should correctly formats a issue room name", () => {
|
||||||
expect(FormatUtil.formatIssueRoomName(SIMPLE_ISSUE)).to.equal(
|
expect(FormatUtil.formatIssueRoomName(SIMPLE_ISSUE)).to.equal(
|
||||||
"evilcorp/lab#123: A simple title",
|
"evilcorp/lab#123: A simple title",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it("correctly formats a room topic", () => {
|
it("should correctly formats a room topic", () => {
|
||||||
expect(FormatUtil.formatRoomTopic(SIMPLE_ISSUE)).to.equal(
|
expect(FormatUtil.formatRoomTopic(SIMPLE_ISSUE)).to.equal(
|
||||||
"Status: open | https://github.com/evilcorp/lab/issues/123",
|
"Status: open | https://github.com/evilcorp/lab/issues/123",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it("should correctly format one simple label", () => {
|
||||||
|
expect(FormatUtil.formatLabels([{name: "foo"}])).to.deep.equal({
|
||||||
|
plain: "foo",
|
||||||
|
html: "<span>foo</span>"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("should correctly format many simple labels", () => {
|
||||||
|
expect(FormatUtil.formatLabels([{name: "foo"},{name: "bar"}])).to.deep.equal({
|
||||||
|
plain: "foo, bar",
|
||||||
|
html: "<span>foo</span> <span>bar</span>"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("should correctly format one detailed label", () => {
|
||||||
|
expect(FormatUtil.formatLabels([{name: "foo", color: '#FFFFFF', description: 'My label'}])).to.deep.equal({
|
||||||
|
plain: "foo",
|
||||||
|
html: "<span data-mx-bg-color=\"#FFFFFF\" data-mx-color=\"#000000\" title=\"My label\">foo</span>"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("should correctly format many detailed labels", () => {
|
||||||
|
expect(FormatUtil.formatLabels([
|
||||||
|
{name: "foo", color: '#FFFFFF', description: 'My label'},
|
||||||
|
{name: "bar", color: '#AACCEE', description: 'My other label'},
|
||||||
|
])).to.deep.equal({
|
||||||
|
plain: "foo, bar",
|
||||||
|
html: "<span data-mx-bg-color=\"#FFFFFF\" data-mx-color=\"#000000\" title=\"My label\">foo</span> "
|
||||||
|
+ "<span data-mx-bg-color=\"#AACCEE\" data-mx-color=\"#000000\" title=\"My other label\">bar</span>"
|
||||||
|
},);
|
||||||
|
});
|
||||||
|
it("should correctly format a JIRA issue", () => {
|
||||||
|
expect(FormatUtil.getPartialBodyForJiraIssue(SIMPLE_JIRA_ISSUE)).to.deep.equal({
|
||||||
|
"external_url": "http://example-api.url.com/browse/TEST-001",
|
||||||
|
"uk.half-shot.matrix-github.jira.issue": {
|
||||||
|
"api_url": "http://example-api.url.com/issue-url",
|
||||||
|
"id": "test-issue",
|
||||||
|
"key": "TEST-001",
|
||||||
|
},
|
||||||
|
"uk.half-shot.matrix-github.jira.project": {
|
||||||
|
"api_url": "http://example-api.url.com/project-url",
|
||||||
|
"id": "test-project",
|
||||||
|
"key": "TEST",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,6 @@ const mq = createMessageQueue({
|
|||||||
queue: {
|
queue: {
|
||||||
monolithic: true,
|
monolithic: true,
|
||||||
},
|
},
|
||||||
// tslint:disable-next-line: no-any
|
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
describe("MessageQueueTest", () => {
|
describe("MessageQueueTest", () => {
|
||||||
|
13
tests/jira/Utils.ts
Normal file
13
tests/jira/Utils.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { expect } from "chai";
|
||||||
|
import { generateJiraWebLinkFromIssue } from "../../src/Jira";
|
||||||
|
|
||||||
|
describe("Jira", () => {
|
||||||
|
describe("Utils", () => {
|
||||||
|
it("processes a jira issue into a URL", () => {
|
||||||
|
expect(generateJiraWebLinkFromIssue({
|
||||||
|
self: "https://my-test-jira/",
|
||||||
|
key: "TEST-111",
|
||||||
|
})).to.equal("https://my-test-jira/browse/TEST-111");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user