89 Commits

Author SHA1 Message Date
Drew Short 19ca104ae3 Changing logging format 4 years ago
Drew Short 435af1be6a Moving SzuruBooruClient to typescript module export and import 4 years ago
Drew Short 12074a8179 Adding version command to the admin module 4 years ago
Drew Short 3680d875ab Updating log location 4 years ago
Drew Short a0639d874a Updating logging to use rolling files 4 years ago
Drew Short 148bf30385 Adding separate mount for logs 4 years ago
Drew Short 527bb1b637 Fixing broken secrets resolution 4 years ago
Drew Short 21a304cb15 Telling concourse not to download the created image 4 years ago
Drew Short 9647ac3383 Switching to the secrets controlled resource location 4 years ago
Drew Short f9051099db Adding credentials to pull from docker repository 4 years ago
Drew Short 609d5746f4 Updating pipeline to post messages to matrix 4 years ago
Drew Short 01c69f9002 Adding tag file 4 years ago
Drew Short 736645d29a Adding to startup and shutdown messages 4 years ago
Drew Short ce60ee5534 Fix module namespace issues 4 years ago
Drew Short 69508fd2d7 Fix pipeline for packaging the application 4 years ago
Drew Short 7867114665 Disable js source allowance 4 years ago
Drew Short 51be142c13 Converted the remaining source files to TS 4 years ago
Drew Short 5f75745256 Converting the rest of the modules 4 years ago
Drew Short 96d84a8a85 Converting the base message and module to TS 4 years ago
Drew Short ab767f6c67 Start converting files to typescript 4 years ago
Drew Short 900cab5e59 Starting conversion to typescript 4 years ago
Drew Short 87907b065e MVP for szurubooru plugin 4 years ago
Drew Short 0a6b571e9a Adding Szurubooru module scaffold 4 years ago
Drew Short fc37049b1e Adding event to the module message handling 4 years ago
Drew Short c13c8fdc64 Additional error handling and code cleanup 4 years ago
Drew Short 9146f98793 Converted login to promise based resolution 4 years ago
Drew Short 3e331f5607 Adding executable flags to required scripts 4 years ago
Drew Short cf4d29b338 Master builds should also reprocess on updated pipeline 4 years ago
Drew Short 08cebff6e2 Enabling pipeline updates to trigger builds 4 years ago
Drew Short 1c9afda81b reverting to microk8s hostpath storage for now 4 years ago
Drew Short 1e087bf7f1 Updating the storage controller 4 years ago
Drew Short 4a3d209e52 Started work on admin module 4 years ago
Drew Short 34fbb90b5b Disable initial sync 4 years ago
Drew Short 21e0b7f902 engine method naming cleanup 4 years ago
Drew Short 641746b05d cleanup of dockerignore 4 years ago
Drew Short c28fb72fc3 Code formatting cleanup 4 years ago
Drew Short 2a6d685a24 Adding ignores for version information 4 years ago
Drew Short 00d3d05883 Allowing the bot to upload an avatar 4 years ago
Drew Short 4364bd298c Adding development environment settings 4 years ago
Drew Short 46b5287a2c Added a flag to force re-reading configs 4 years ago
Drew Short de0978575b Adding dynamic build.info to development docker 4 years ago
Drew Short 32fa3fe4df Added local development script 4 years ago
Drew Short 2100f54451 Improved bot login flow with optional password auth 4 years ago
Drew Short 9b823da608 Passing sigterm commands to node 4 years ago
Drew Short fda5a6ee56 Updating pipeline to deploy with helm 4 years ago
Drew Short a5a407ae8f Adding helm charts for deployment 4 years ago
Drew Short cc5c09442a fix a bad logging command 4 years ago
Drew Short 15f9a196ff Fixing logging and build version parsing 4 years ago
Drew Short 01f90c8aae Adding pipeline pieces deploy release images 4 years ago
Drew Short 6fb88057b5 Added some useful output to pipeline steps 4 years ago
Drew Short b12fa7eba1 Completed development image deployment pipeline 4 years ago
Drew Short 7becc77175 Added develop package and deploy step to pipeline 4 years ago
Drew Short 63d67034a0 Cleanup pipeline part naming 4 years ago
Drew Short aa3e60a7ad Working on updated pipeline with deployment 4 years ago
Drew Short 1a7eed5bff Adding path ignores to build pipeline 4 years ago
Drew Short fcaed0fe17 Fixing spacing in markdown 4 years ago
Drew Short 7cb47dbb2e Updating readme with development and build badges 4 years ago
Drew Short be7f2d19ba Updating docker configuration 4 years ago
Drew Short 827f9eb3ba Implemented a working Giphy module 4 years ago
Drew Short 4c16d8161e Fix broken command lookup 4 years ago
Drew Short 06cf7aa19f Working delegation to defaultCommand 4 years ago
Drew Short 4cce06e2db Making module respones a callback 4 years ago
Drew Short cdac7609fd Handle RECONNECTING and reduce initial sync 4 years ago
Drew Short eb2f2a006b Adding help message support 4 years ago
Drew Short 7e03d89dec Improving module support 4 years ago
Drew Short 41a63558fc Moved config into a seperate module 4 years ago
Drew Short 08ea447add Functionality improvements and code cleanup 4 years ago
Drew Short f866db1fad Additional code cleanup for engine and bot classes 4 years ago
Drew Short fa91dc468b Code cleanup 4 years ago
Drew Short c6b4d7d3c4 Adding admin module and changing startup to notice 4 years ago
Drew Short e92ebf6208 Add module scaffold and anonymous function cleanup 4 years ago
Drew Short 738a3cbc51 Improved the logging configuration 4 years ago
Drew Short b2c33f5ab9 Adding a logging library 4 years ago
Drew Short c8f86ae762 Adding shutdown hook 4 years ago
Drew Short b38212abf4 Fixing the pipeline 4 years ago
Drew Short 3c52c38b1d Merge branch 'develop' of warricksothr/baphomet-js into master 4 years ago
Drew Short d558de736e Merge remote-tracking branch 'origin/master' into develop 4 years ago
Drew Short 2bf725822f Removing the public flags from the pipeline 4 years ago
Drew Short 14a896ac10 Fixing the broken pipeline 4 years ago
Drew Short bef3f50017 Merge branch 'develop' of warricksothr/baphomet-js into master 4 years ago
Drew Short ac185367a7 Updating the pipeline to use scripts for versioning 4 years ago
Drew Short 282a227ac7 Fixing pipeline 4 years ago
Drew Short e69ef6df88 Adding initial Docker setup files 4 years ago
Drew Short 1fdfa87dee Pointing pipeline at update repository 4 years ago
Drew Short 8aa569f847 Fixing pipeline packaging 4 years ago
Drew Short c1b694fd5a Fixing pipeline typo 4 years ago
Drew Short 88b6716299 condensing pipeline 4 years ago
Drew Short f6933f85ad Updating pipeline 4 years ago
Drew Short 1fc5f74c5e Starting to break bot out 4 years ago
  1. 23
      .dockerignore
  2. 20
      .gitignore
  3. 5
      .helm/Chart.yaml
  4. 21
      .helm/templates/NOTES.txt
  5. 56
      .helm/templates/_helpers.tpl
  6. 68
      .helm/templates/deployment.yaml
  7. 41
      .helm/templates/ingress.yaml
  8. 23
      .helm/templates/persistentVolumeClaim.yaml
  9. 16
      .helm/templates/service.yaml
  10. 8
      .helm/templates/serviceaccount.yaml
  11. 15
      .helm/templates/tests/test-connection.yaml
  12. 79
      .helm/values.yaml
  13. 26
      Dockerfile
  14. 28
      README.md
  15. 4
      data/giphy-config.json.example
  16. 9
      data/szurubooru-config.json.example
  17. 20
      entrypoint.sh
  18. 54
      index.js
  19. 442
      package-lock.json
  20. 16
      package.json
  21. 266
      pipeline.yml
  22. 2
      scripts/get_build.sh
  23. 2
      scripts/get_tag.sh
  24. 2
      scripts/get_version.sh
  25. 27
      scripts/run_development_docker.sh
  26. 21
      scripts/run_development_local.sh
  27. 195
      src/bot.ts
  28. 29
      src/config.ts
  29. 172
      src/engine.ts
  30. 55
      src/logging.ts
  31. 28
      src/message.ts
  32. 260
      src/module/abstract.ts
  33. 65
      src/module/admin.ts
  34. 39
      src/module/example.ts
  35. 50
      src/module/giphy.ts
  36. 52
      src/module/help.ts
  37. 19
      src/module/index.ts
  38. 135
      src/module/szurubooru/client.ts
  39. 65
      src/module/szurubooru/index.ts
  40. 85
      src/utility.ts
  41. 14
      tsconfig.json

23
.dockerignore

@ -0,0 +1,23 @@
# Installed node files
node_modules/
# Compiled Files
dist/
# Logging files
log/
# Files open as swp in an editor
*.swp
# Development / CICD / Deployment files
pipeline.yml
.helm/
Dockerfile
# Development documentation
README.md
CONTRIBUTING.md
# Testing
test/

20
.gitignore

@ -1,8 +1,24 @@
# Node Files
node_modules/
# Compiled Files
dist/
# Config files
data/config.json
data/*config.json
# Log files
log/
*.log
# Mac Files
.DS_Store
.DS_Store
# Swap files
*.swp
# Version files
build.info
version.info
tag.info
tag

5
.helm/Chart.yaml

@ -0,0 +1,5 @@
apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: baphomet-js
version: 0.1.2

21
.helm/templates/NOTES.txt

@ -0,0 +1,21 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "baphomet-js.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "baphomet-js.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "baphomet-js.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "baphomet-js.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl port-forward $POD_NAME 8080:80
{{- end }}

56
.helm/templates/_helpers.tpl

@ -0,0 +1,56 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "baphomet-js.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "baphomet-js.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "baphomet-js.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Common labels
*/}}
{{- define "baphomet-js.labels" -}}
app.kubernetes.io/name: {{ include "baphomet-js.name" . }}
helm.sh/chart: {{ include "baphomet-js.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}
{{/*
Create the name of the service account to use
*/}}
{{- define "baphomet-js.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "baphomet-js.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}

68
.helm/templates/deployment.yaml

@ -0,0 +1,68 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "baphomet-js.fullname" . }}
labels:
{{ include "baphomet-js.labels" . | indent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "baphomet-js.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "baphomet-js.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ template "baphomet-js.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
- name: LOG_LEVEL
value: {{ .Values.app.env.log_level }}
- name: NODE_ENV
value: {{ .Values.app.env.node_env }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- mountPath: /opt/baphomet/data
name: data
subPath: data
- mountPath: /opt/baphomet/log
name: data
subPath: log
volumes:
- name: config
configMap:
name: {{ include "baphomet-js.fullname" . }}
- name: data
{{- if .Values.persistence.enabled }}
persistentVolumeClaim:
claimName: {{ .Values.persistence.existingClaim | default (include "baphomet-js.fullname" .) }}
{{- else }}
emptyDir: {}
{{- end -}}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

41
.helm/templates/ingress.yaml

@ -0,0 +1,41 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "baphomet-js.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{ include "baphomet-js.labels" . | indent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ . }}
backend:
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}

23
.helm/templates/persistentVolumeClaim.yaml

@ -0,0 +1,23 @@
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: {{ include "baphomet-js.fullname" . }}
labels:
app: {{ include "baphomet-js.fullname" . }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: "{{ .Release.Name }}"
heritage: "{{ .Release.Service }}"
annotations:
{{- if .Values.persistence.storageClass }}
volume.beta.kubernetes.io/storage-class: {{ .Values.persistence.storageClass | quote }}
{{- else }}
volume.alpha.kubernetes.io/storage-class: default
{{- end }}
spec:
accessModes:
- {{ .Values.persistence.accessMode | quote }}
resources:
requests:
storage: {{ .Values.persistence.size | quote }}
{{- end }}

16
.helm/templates/service.yaml

@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "baphomet-js.fullname" . }}
labels:
{{ include "baphomet-js.labels" . | indent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: {{ include "baphomet-js.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}

8
.helm/templates/serviceaccount.yaml

@ -0,0 +1,8 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ template "baphomet-js.serviceAccountName" . }}
labels:
{{ include "baphomet-js.labels" . | indent 4 }}
{{- end -}}

15
.helm/templates/tests/test-connection.yaml

@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "baphomet-js.fullname" . }}-test-connection"
labels:
{{ include "baphomet-js.labels" . | indent 4 }}
annotations:
"helm.sh/hook": test-success
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "baphomet-js.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

79
.helm/values.yaml

@ -0,0 +1,79 @@
# Default values for baphomet-js.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: nexus.nulloctet.com:5000/nulloctet/baphomet-js
tag: stable
pullPolicy: IfNotPresent
app:
env:
log_level: "info"
node_env: "production"
persistence:
accessMode: ReadWriteOnce
enabled: true
size: 10Mi
storageClass: storage
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name:
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 80
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths: []
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}

26
Dockerfile

@ -0,0 +1,26 @@
FROM node:12.14-stretch AS builder
WORKDIR /opt/baphomet
COPY . /opt/baphomet
RUN cd /opt/baphomet \
&& npm install \
&& npm run build
FROM node:12.14-stretch
WORKDIR /opt/baphomet
COPY . /opt/baphomet
ENV NODE_ENV=production
ENV LOG_LEVEL=warn
RUN rm -rf /opt/baphomet/src \
&& mkdir /opt/baphomet/log
COPY --from=builder /opt/baphomet/dist /opt/baphomet/dist
RUN cd /opt/baphomet \
&& npm install --only=prod \
&& chmod +x entrypoint.sh
ENTRYPOINT [ "./entrypoint.sh" ]
CMD [ "run" ]

28
README.md

@ -1,5 +1,9 @@
# Baphomet
Development Branch Tests: [![Concourse](https://concourse.nulloctet.com/api/v1/teams/nulloctet/pipelines/baphomet-js/jobs/test-develop/badge)](https://concourse.nulloctet.com/teams/nulloctet/pipelines/baphomet-js)
Master Branch Tests: [![Concourse](https://concourse.nulloctet.com/api/v1/teams/nulloctet/pipelines/baphomet-js/jobs/test-release/badge)](https://concourse.nulloctet.com/teams/nulloctet/pipelines/baphomet-js)
Baphomet is a bot to provide extended functionality to a matrix server
## Configuration
@ -10,12 +14,32 @@ Copy data/config.json.example to data/config.json and replace the relevent confi
```bash
npm install
node run index.js
./entrypoint.sh run
```
## Development
TBD
### Requirements:
NodeJS 12 LTS
OR
Docker
#### Local:
```bash
npm install
#<make changes>
./scripts/run_development_local.sh
```
#### Docker:
```bash
#<make changes>
./scripts/run_development_docker.sh
```
## Contributing

4
data/giphy-config.json.example

@ -0,0 +1,4 @@
{
"endpoint": "api.giphy.com/v1",
"apiKey": "<Your API Key Here>"
}

9
data/szurubooru-config.json.example

@ -0,0 +1,9 @@
{
"url": "<api endpoint>",
"username": "<connecting username>",
"token": "<connection api token>",
"random_image_cache": {},
"upload_rooms": {
"<authorized room to upload from>"
}
}

20
entrypoint.sh

@ -0,0 +1,20 @@
#! /usr/bin/env sh
DIR="$( cd "$( dirname "${0}" )" >/dev/null 2>&1 && pwd )"
export NODE_PATH="${DIR}"
echo "NODE_ENV: ${NODE_ENV}"
echo "NODE_PATH: ${NODE_PATH}"
echo "LOG_LEVEL: ${LOG_LEVEL}"
echo "Running build version: $(cat build.info)"
case $1 in
run)
echo "Starting Baphomet..."
exec node index.js
;;
*)
echo "\"$1\" is an unrecognized command"
;;
esac

54
index.js

@ -1,48 +1,10 @@
let sdk = require("matrix-js-sdk");
let config = require("./data/config.json");
let bot = require('./dist/bot');
let { getConfig } = require('./dist/config');
let engine = require('./dist/engine');
console.log("Running with config:");
console.log(config);
let config = getConfig(process.env.NODE_PATH + "/data/config.json", ['accessToken'])
let client = sdk.createClient({
baseUrl: config.baseUrl,
accessToken: config.accessToken,
userId: config.userId
});
function createBasicTextMessage(body) {
return {
"body": body,
"msgtype": "m.text"
}
}
function sendClientStatusUpdate() {
config.statusRooms.forEach(roomId => {
console.log("Notifying %s", roomId);
client.sendMessage(roomId, createBasicTextMessage("Started!")).done(function() {
console.log("Notified %s", roomId);
})
});
}
// Prep the bot
client.on("sync", function (state, previousState, data) {
switch (state) {
case "PREPARED":
sendClientStatusUpdate();
break;
}
});
// auto join rooms that an admin user has invited the bot to
client.on("RoomMember.membership", function (event, member) {
if (member.membership === "invite"
&& config.admin.indexOf(ember.userId) >= 0) {
client.joinRoom(member.roomId).done(function () {
console.log("Auto-joined %s", member.roomId);
});
}
});
client.startClient()
engine.create(
config,
bot.create(config)
).init().run();

442
package-lock.json

@ -4,6 +4,12 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "13.1.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.1.6.tgz",
"integrity": "sha512-Jg1F+bmxcpENHP23sVKkNuU3uaxPnsBMW0cLjleiikFKomJQbsn0Cqk2yDvQArqzZN6ABfBkZ0To7pQ8sLdWDg==",
"dev": true
},
"ajv": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
@ -36,7 +42,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
@ -63,6 +68,14 @@
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"requires": {
"lodash": "^4.17.14"
}
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -78,6 +91,15 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.0.tgz",
"integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A=="
},
"axios": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
"integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
"requires": {
"follow-redirects": "1.5.10",
"is-buffer": "^2.0.2"
}
},
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
@ -146,8 +168,7 @@
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"caseless": {
"version": "0.12.0",
@ -180,7 +201,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true,
"requires": {
"string-width": "^3.1.0",
"strip-ansi": "^5.2.0",
@ -190,14 +210,12 @@
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
@ -208,18 +226,25 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
}
}
},
"color": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz",
"integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==",
"requires": {
"color-convert": "^1.9.1",
"color-string": "^1.5.2"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
@ -227,8 +252,35 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"color-string": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz",
"integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==",
"requires": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"colornames": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz",
"integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y="
},
"colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
},
"colorspace": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz",
"integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==",
"requires": {
"color": "3.0.x",
"text-hex": "1.0.x"
}
},
"combined-stream": {
"version": "1.0.8",
@ -279,8 +331,7 @@
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"define-properties": {
"version": "1.1.3",
@ -296,6 +347,16 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"diagnostics": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz",
"integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==",
"requires": {
"colorspace": "1.1.x",
"enabled": "1.0.x",
"kuler": "1.0.x"
}
},
"diff": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
@ -314,8 +375,20 @@
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
},
"enabled": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz",
"integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=",
"requires": {
"env-variable": "0.0.x"
}
},
"env-variable": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz",
"integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA=="
},
"es-abstract": {
"version": "1.16.3",
@ -374,15 +447,32 @@
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
},
"fast-safe-stringify": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
"integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA=="
},
"fecha": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz",
"integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg=="
},
"file-stream-rotator": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.5.5.tgz",
"integrity": "sha512-XzvE1ogpxUbARtZPZLICaDRAeWxoQLFMKS3ZwADoCQmurKEwuDD2jEfDVPm/R1HeKYsRYEl9PzVIezjQ3VTTPQ==",
"requires": {
"moment": "^2.11.2"
}
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true,
"requires": {
"locate-path": "^3.0.0"
}
@ -396,6 +486,29 @@
"is-buffer": "~2.0.3"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@ -426,8 +539,7 @@
"get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"getpass": {
"version": "0.1.7",
@ -521,14 +633,17 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"is-buffer": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
"dev": true
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
},
"is-callable": {
"version": "1.1.4",
@ -545,8 +660,7 @@
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"is-regex": {
"version": "1.0.4",
@ -557,6 +671,11 @@
"has": "^1.0.1"
}
},
"is-stream": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"is-symbol": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
@ -571,6 +690,11 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -623,11 +747,18 @@
"verror": "1.10.0"
}
},
"kuler": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz",
"integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==",
"requires": {
"colornames": "^1.1.1"
}
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true,
"requires": {
"p-locate": "^3.0.0",
"path-exists": "^3.0.0"
@ -636,8 +767,7 @@
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"log-symbols": {
"version": "2.2.0",
@ -648,6 +778,18 @@
"chalk": "^2.0.1"
}
},
"logform": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz",
"integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==",
"requires": {
"colors": "^1.2.1",
"fast-safe-stringify": "^2.0.4",
"fecha": "^2.3.3",
"ms": "^2.1.1",
"triple-beam": "^1.3.0"
}
},
"loglevel": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.6.tgz",
@ -738,11 +880,15 @@
"yargs-unparser": "1.6.0"
}
},
"moment": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
},
"node-environment-flags": {
"version": "1.0.5",
@ -759,6 +905,11 @@
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
},
"object-hash": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.1.tgz",
"integrity": "sha512-HgcGMooY4JC2PBt9sdUdJ6PMzpin+YtY3r/7wg0uTifP+HJWW8rammseSEHuyt0UeShI183UGssCJqm1bJR7QA=="
},
"object-inspect": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
@ -802,11 +953,15 @@
"wrappy": "1"
}
},
"one-time": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz",
"integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4="
},
"p-limit": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz",
"integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
@ -815,7 +970,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
"p-limit": "^2.0.0"
}
@ -823,14 +977,12 @@
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"path-exists": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
"dev": true
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
},
"path-is-absolute": {
"version": "1.0.1",
@ -843,10 +995,15 @@
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"psl": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz",
"integrity": "sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA=="
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz",
"integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ=="
},
"punycode": {
"version": "2.1.1",
@ -858,6 +1015,16 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz",
"integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA=="
},
"readable-stream": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
"integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
@ -900,14 +1067,12 @@
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"safe-buffer": {
"version": "5.2.0",
@ -928,8 +1093,75 @@
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"showdown": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/showdown/-/showdown-1.9.1.tgz",
"integrity": "sha512-9cGuS382HcvExtf5AHk7Cb4pAeQQ+h0eTr33V1mu+crYWV4KvWAw6el92bDrqGEk5d46Ai/fhbEUwqJ/mTCNEA==",
"requires": {
"yargs": "^14.2"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"requires": {
"ansi-regex": "^4.1.0"
}
},
"yargs": {
"version": "14.2.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.2.tgz",
"integrity": "sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA==",
"requires": {
"cliui": "^5.0.0",
"decamelize": "^1.2.0",
"find-up": "^3.0.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^3.0.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^15.0.0"
}
},
"yargs-parser": {
"version": "15.0.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.0.tgz",
"integrity": "sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ==",
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
}
}
},
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
"requires": {
"is-arrayish": "^0.3.1"
}
},
"sprintf-js": {
"version": "1.0.3",
@ -953,6 +1185,11 @@
"tweetnacl": "~0.14.0"
}
},
"stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA="
},
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
@ -983,6 +1220,14 @@
"function-bind": "^1.1.1"
}
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
}
},
"strip-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
@ -1007,6 +1252,11 @@
"has-flag": "^3.0.0"
}
},
"text-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
@ -1023,6 +1273,16 @@
}
}
},
"trie-prefix-tree": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/trie-prefix-tree/-/trie-prefix-tree-1.5.1.tgz",
"integrity": "sha512-Jjvj/dA97wXnabG/NLJUgo4IQMj6vucH+Qxm7of/omfWSmZlPqdRU6Ta4GmQqCZH+n3/iYZUwfvUoEhB0Hs83Q=="
},
"triple-beam": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@ -1036,6 +1296,12 @@
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
"typescript": {
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.4.tgz",
"integrity": "sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==",
"dev": true
},
"unhomoglyph": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.3.tgz",
@ -1049,6 +1315,11 @@
"punycode": "^2.1.0"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"uuid": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
@ -1076,8 +1347,7 @@
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
},
"wide-align": {
"version": "1.1.3",
@ -1088,11 +1358,75 @@
"string-width": "^1.0.2 || 2"
}
},
"winston": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz",
"integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==",
"requires": {
"async": "^2.6.1",
"diagnostics": "^1.1.1",
"is-stream": "^1.1.0",
"logform": "^2.1.1",
"one-time": "0.0.4",
"readable-stream": "^3.1.1",
"stack-trace": "0.0.x",
"triple-beam": "^1.3.0",
"winston-transport": "^4.3.0"
}
},
"winston-daily-rotate-file": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.4.1.tgz",
"integrity": "sha512-516bL4IDjgX5mPEsTPXNVNzZtJkrUFY2IvPhj8n5xSKyy804xadp4TUlhxEZLL/Jbs8CF+rESfq95QXFLFTzKA==",
"requires": {
"file-stream-rotator": "^0.5.5",
"object-hash": "^2.0.1",
"triple-beam": "^1.3.0",
"winston-transport": "^4.2.0"
}
},
"winston-transport": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz",
"integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==",
"requires": {
"readable-stream": "^2.3.6",
"triple-beam": "^1.2.0"
},
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
},
"wrap-ansi": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
@ -1102,14 +1436,12 @@
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true,
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
@ -1120,7 +1452,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
}
@ -1136,8 +1467,7 @@
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
"dev": true
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
},
"yargs": {
"version": "13.3.0",

16
package.json

@ -4,13 +4,23 @@
"main": "index.js",
"author": "Drew Short <warrick@sothr.com>",
"license": "MIT",
"description": "A Matrix bot written on top of the matrix-js-sdk",
"repository": "https://git.nulloctet.com/warricksothr/baphomet-js",
"scripts": {
"test": "mocha"
"test": "mocha",
"build": "tsc"
},
"dependencies": {
"matrix-js-sdk": "2.4.5"
"axios": "^0.19.0",
"matrix-js-sdk": "2.4.5",
"showdown": "^1.9.1",
"trie-prefix-tree": "^1.5.1",
"winston": "^3.2.1",
"winston-daily-rotate-file": "^4.4.1"
},
"devDependencies": {
"mocha": "^6.2.2"
"@types/node": "^13.1.6",
"mocha": "^6.2.2",
"typescript": "^3.7.4"
}
}

266
pipeline.yml

@ -1,27 +1,200 @@
---
resource_types:
- name: helm
type: docker-image
source:
repository: linkyard/concourse-helm-resource
- name: matrix-notification-resource
type: docker-image
source:
repository: ((nexus_docker_read.host))/sothr/matrix-notification-resource
username: ((nexus_docker_read.username))
password: ((nexus_docker_read.password))
resources:
- name: baphomet-js-git
- name: git-develop
type: git
icon: git
source:
uri: ssh://git@git.nulloctet.com:8437/warricksothr/baphomet-js.git
private_key: |
((pull_key))
branch: develop
ignore_paths:
- scripts/upload_pipeline.sh
- README.md
- CONTRIBUTING.md
- LICENSE.md
- name: git-master
type: git
icon: git
source:
uri: ssh://git@git.nulloctet.com:8437/warricksothr/basphomet-js.git
uri: ssh://git@git.nulloctet.com:8437/warricksothr/baphomet-js.git
private_key: |
((pull_key))
branch: master
ignore_paths:
- scripts/upload_pipeline.sh
- README.md
- CONTRIBUTING.md
- LICENSE.md
- name: docker-image
type: docker-image
icon: docker
source:
repository: ((nexus_docker_write.host))/nulloctet/baphomet-js
username: ((nexus_docker_write.username))
password: ((nexus_docker_write.password))
- name: helm
type: helm
source:
cluster_url: ((helm.cluster_url))
cluster_ca: ((helm.cluster_ca))
token: ((helm.token))
- name: matrix-notification
type: matrix-notification-resource
source:
matrix_server_url: ((matrix.url))
token: ((matrix.token))
room_id: ((matrix.room_id))
jobs:
- name: test
public: true
# Development Pipeline
- name: test-develop
plan:
- get: git-develop
trigger: true
- task: run-tests
config:
platform: linux
image_resource:
type: registry-image
source: { repository: node, tag: "12.14-stretch" }
inputs:
- name: git-develop
run:
path: /bin/sh
args:
- -c
- |
echo "Node Version: $(node --version)"
echo "NPM Version: $(npm --version)"
cd git-develop
npm install
npm test
on_failure:
do:
- put: matrix-notification
params:
msgtype: m.notice
text: |
A test of baphomet-js-dev has failed. Check it out at:
https://concourse.nulloctet.com/pipelines/$BUILD_PIPELINE_NAME/jobs/$BUILD_JOB_NAME/builds/$BUILD_NAME
or at:
https://concourse.nulloctet.com/builds/$BUILD_ID
- name: deploy-develop-image
plan:
- get: git-develop
passed: [test-develop]
trigger: true
- task: capture-version
config:
platform: linux
image_resource:
type: registry-image
source: { repository: bitnami/git, tag: "2-debian-9" }
inputs:
- name: git-develop
outputs:
- name: version
run:
path: /bin/sh
args:
- -c
- |
cd git-develop
chmod +x ././scripts/get_*.sh
echo $(./scripts/get_build.sh) > ../version/build.info
echo $(./scripts/get_version.sh) > ../version/version.info
echo $(./scripts/get_tag.sh) > ../version/tag.info
echo "dev" > ../version/tag
echo "Build Information: $(cat ../version/build.info)"
echo "Version Information: $(cat ../version/version.info)"
echo "Tag Information: $(cat ../version/tag.info)"
echo "Docker Image Tag: $(cat ../version/tag)"
- task: package
config:
platform: linux
image_resource:
type: registry-image
source: { repository: debian, tag: "stretch-slim" }
inputs:
- name: git-develop
- name: version
outputs:
- name: package
run:
path: /bin/sh
args:
- -c
- |
cd package
cp ../version/* .
cp ../git-develop/package*.json .
cp ../git-develop/tsconfig.json .
cp ../git-develop/index.js .
cp -r ../git-develop/assets .
cp -r ../git-develop/src .
cp -r ../git-develop/data .
cp ../git-develop/entrypoint.sh .
cp ../git-develop/Dockerfile .
cp ../git-develop/README.md .
cp ../git-develop/LICENSE.md .
ls -al .
- put: docker-image
params:
build: package
tag_file: package/tag
tag_as_latest: false
get_params: {skip_download: true}
- put: matrix-notification
params:
msgtype: m.notice
text: |
Successfully deployed baphomet-js-dev image to repository
- name: deploy-develop-helm
plan:
- get: git-develop
passed: [deploy-develop-image]
trigger: true
- put: helm
params:
chart: git-develop/.helm
values: git-develop/.helm/values.yaml
release: baphomet-js-dev
override_values:
- key: image.tag
value: dev
- key: image.pullPolicy
value: Always
- key: app.env.node_env
value: development
- key: persistence.storageClass
value: microk8s-hostpath
recreate_pods: true
# Release Pipeline
- name: test-release
plan:
- get: baphomet-js-git
- get: git-master
trigger: true
- task: run-tests
config:
platform: linux
image_resource:
type: registry-image
source: { repository: node, tag: "13" }
source: { repository: node, tag: "12.14-stretch" }
inputs:
- name: baphomet-js-git
- name: git-master
run:
path: /bin/sh
args:
@ -29,6 +202,81 @@ jobs:
- |
echo "Node Version: $(node --version)"
echo "NPM Version: $(npm --version)"
cd baphomet-js-git
cd git-master
npm install
npm test
npm test
on_failure:
do:
- put: matrix-notification
params:
msgtype: m.notice
text: |
A test of baphomet-js has failed. Check it out at:
https://concourse.nulloctet.com/pipelines/$BUILD_PIPELINE_NAME/jobs/$BUILD_JOB_NAME/builds/$BUILD_NAME
or at:
https://concourse.nulloctet.com/builds/$BUILD_ID
- name: deploy-release-image
plan:
- get: git-master
passed: [test-release]
trigger: true
- task: capture-version
config:
platform: linux
image_resource:
type: registry-image
source: { repository: bitnami/git, tag: "2-debian-9" }
inputs:
- name: git-master
outputs:
- name: version
run:
path: /bin/sh
args:
- -c
- |
cd git-master
chmod +x ././scripts/get_*.sh
echo $(./scripts/get_build.sh) > ../version/build.info
echo $(./scripts/get_version.sh) > ../version/version.info
echo $(./scripts/get_tag.sh) > ../version/tag.info
cp ../version/tag.info ../version/tag
echo "Build Information: $(cat ../version/build.info)"
echo "Version Information: $(cat ../version/version.info)"
echo "Tag Information: $(cat ../version/tag.info)"
echo "Docker Image Tag: $(cat ../version/tag)"
- task: package
config:
platform: linux
image_resource:
type: registry-image
source: { repository: debian, tag: "stretch-slim" }
inputs:
- name: git-master
- name: version
outputs:
- name: package
run:
path: /bin/sh
args:
- -c
- |
cd package
cp ../version/* .
cp ../git-master/package*.json .
cp ../git-master/tsconfig.json .
cp ../git-master/index.js .
cp -r ../git-master/assets .
cp -r ../git-master/src .
cp -r ../git-master/data .
cp ../git-master/entrypoint.sh .
cp ../git-master/Dockerfile .
cp ../git-master/README.md .
cp ../git-master/LICENSE.md .
ls -al .
- put: docker-image
params:
build: package
tag_file: package/tag
tag_as_latest: true
get_params: {skip_download: true}

2
scripts/get_build.sh

@ -0,0 +1,2 @@
#!/usr/bin/env sh
echo "$(git describe --tags --long)_$(date --rfc-3339=seconds | sed 's/ /T/g')"

2
scripts/get_tag.sh

@ -0,0 +1,2 @@
#!/usr/bin/env sh
echo "$(git describe --tags | awk '{split($0,a,"-"); print a[1]}')"

2
scripts/get_version.sh

@ -0,0 +1,2 @@
#!/usr/bin/env sh
echo "$(git describe --tags --long)"

27
scripts/run_development_docker.sh

@ -0,0 +1,27 @@
#!/usr/bin/env bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
pushd "${DIR}/.."
echo "Writing local testing build.info"
./scripts/get_build.sh > build.info
echo "DOCKER_DEVELOPMENT_"$(cat build.info) > build.info
CONTAINER_NAME=baphomet-dev
IMAGE_NAME=baphomet-js
IMAGE_TAG=dev
IMAGE_BUILD_DIR=.
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_BUILD_DIR}
rm build.info
docker rm ${CONTAINER_NAME}
docker run -it \
-e NODE_ENV=development \
-e LOG_LEVEL=debug \
--name ${CONTAINER_NAME} \
${IMAGE_NAME}:${IMAGE_TAG}
popd

21
scripts/run_development_local.sh

@ -0,0 +1,21 @@
#!/usr/bin/env bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
pushd "${DIR}/.."
echo "Writing local testing build.info"
./scripts/get_build.sh > build.info
echo "LOCAL_DEVELOPMENT_"$(cat build.info) > build.info
export NODE_ENV=development
export LOG_LEVEL=debug
set -e
npm run build
set +e
./entrypoint.sh run
rm build.info
popd

195
src/bot.ts

@ -0,0 +1,195 @@
let fs = require('fs');
let sdk = require('matrix-js-sdk');
let message = require('./message');
let utility = require('./utility');
let { logger } = require('./logging');
class Bot {
client: any;
config: any;
buildInfo: string;
connected: boolean;
startTime: Date;
constructor(config, buildInfo) {
this.config = config;
this.buildInfo = buildInfo;
this.connected = false;
this.startTime = new Date();
}
/**
* Initialize the bot connection
*/
init(messageCallback: any) {
logger.info("Creating Matrix Client")
this.client = sdk.createClient({
baseUrl: this.config.baseUrl,
userId: this.config.userId
});
this.client.on("sync", async (state: any, previousState: any, data: any) => {
switch (state) {
case "PREPARED":
this.connected = true;
await this.sendStatusStartup();
await this.updateAvatar(process.env.NODE_PATH + '/assets/avatar.jpg');
this.client.getJoinedRooms()
.done((rooms) => {
logger.info("Connected to: %o", rooms)
});
break;
case "SYNCING":
logger.debug("Syncing")
break;
case "RECONNECTING":
logger.debug("Reconnecting");
break;
default:
logger.error("Unexpected sync state: %s", state);
process.exit(1);
}
});
this.client.on("RoomMember.membership", (event: any, member: any) => {
if (member.membership === "invite"
&& this.config.admin.indexOf(member.userId) >= 0) {
this.client.joinRoom(member.roomId).done(() => {
logger.info("Auto-joined %s", member.roomId);
});
}
});
this.client.on("Room.timeline", messageCallback);
return this;
}
async connect() {
// logger.info("Initializing Crypto");
// await bot.client.initCrypto();
let botClient = this.client;
let botConfig = this.config;
let startServerConnection = () => {
logger.info("Starting Matrix SDK Client");
return this.client.startClient({
initialSyncLimit: 0
});
}
let attemptPasswordLogin = (botClient, botConfig) => {
return botClient.loginWithPassword(botConfig.userId, botConfig.userPassword)
.then((data) => {
logger.info("Successfully authenticated with password %o", data);
return startServerConnection();
}, (err) => {
logger.error("Failed to authenticate with password %o", err);
});
}
logger.info("Authenticating With Server");
if (typeof botConfig.accessToken !== 'undefined') {
await botClient.loginWithToken(this.config.accessToken)
.then((data) => {
logger.info("Successfully authenticated with access token %o", data);
return startServerConnection();
}, (err) => {
logger.error("Failed to authenticate with access token %o", err);
if (typeof botConfig.userPassword !== 'undefined') {
return attemptPasswordLogin(botClient, botConfig);
} else {
logger.error("No fallback password provided!");
}
});
} else if (typeof botConfig.userPassword !== 'undefined') {
await attemptPasswordLogin(botClient, botConfig);
} else {
logger.error("No authentication credentials available!");
process.exit(1);
}
}
updateAvatar(avatarFile, overwrite = false) {
let matrixClient = this.client;
let botConfig = this.config;
let promises = [Promise.resolve(true)];
if (this.connected) {
promises.push(this.client.getProfileInfo(this.config.userId, "avatar_url")
.then((existingAvatarUrl) => {
logger.info("Recieved avatar_url: %o", existingAvatarUrl);
if (typeof existingAvatarUrl !== 'undefined' && typeof existingAvatarUrl == 'object'
&& existingAvatarUrl.constructor === Object && Object.keys(existingAvatarUrl).length !== 0
&& !overwrite) {
logger.info("Avatar already set");
} else {
logger.info("Setting avatar content from %s", avatarFile);
let avatarFileBuffer = fs.readFileSync(avatarFile);
logger.debug("Avatar Image Data %o", avatarFileBuffer);
matrixClient.uploadContent(avatarFileBuffer, {
name: botConfig.userId + " avatar",
type: "image/jpeg",
rawResponse: false
}).then((uploadedAvatar) => {
logger.info("Uploaded avatar %o", uploadedAvatar);
matrixClient.setAvatarUrl(uploadedAvatar.content_uri)
.then(() => {
logger.info("Updated %s avatar to %s", botConfig.userId, uploadedAvatar.content_uri);
return true;
});
});
}
}));
} else {
logger.warn("Attempting to update avatar while disconnected");
}
return Promise.all(promises);
}
sendStatusStartup() {
let promises = [Promise.resolve(true)]
if (this.connected) {
this.config.statusRooms.forEach(roomId => {
logger.debug("Notifying %s of startup", roomId);
promises.push(this.client.sendMessage(
roomId, message.createBasic(`Started at ${utility.toISODateString(this.startTime)} with version: ${this.buildInfo}`, message.types.NOTICE)
).then(() => {
logger.debug("Notified %s of startup", roomId);
}, (err) => {
logger.error("Unable to send message to room %s because %s", roomId, err.errcode);
}));
});
} else {
logger.warn("Attempting to send startup message while disconnected");
}
return Promise.all(promises);
}
sendStatusShutdown() {
let promises = [Promise.resolve(true)]
if (this.connected) {
this.config.statusRooms.forEach(roomId => {
logger.debug("Notifying %s of shutdown", roomId);
promises.push(this.client.sendMessage(
roomId, message.createBasic(`Shutting down at ${utility.toISODateString(new Date())} with version: ${this.buildInfo}`, message.types.NOTICE)
).then(() => {
logger.debug("Notified %s of shutdown", roomId);
}, (err) => {
logger.error("Unable to send message to room %s because %s", roomId, err.errcode);
}));
});
} else {
logger.warn("Attempting to send shutdown message while disconnected");
}
return Promise.all(promises);
}
}
function create(config) {
let buildInfo = utility.getBuildInfo();
logger.info("Running version: %s", buildInfo);
return new Bot(config, buildInfo);
}
export { create };

29
src/config.ts

@ -0,0 +1,29 @@
let fs = require('fs');
let { logger } = require('./logging');
let loadedConfigs = new Map();
function sanitizeConfig(config: any, fields = []) : any {
let clonedConfig = { ...config };
fields.forEach((field) => {
clonedConfig[field] = '******'
})
return clonedConfig;
}
function getConfig(configFile: string, sanitizedFields: Array<string> = [], reload: Boolean = false) : any {
if (loadedConfigs.has(configFile) && !reload) {
return loadedConfigs.get(configFile);
} else {
logger.info("Reading config: %s", configFile);
let rawConfigData = fs.readFileSync(configFile);
let config = JSON.parse(rawConfigData);
logger.info("Loaded config: %s", configFile);
logger.debug("%o", sanitizeConfig(config, sanitizedFields));
loadedConfigs.set(configFile, config);
return config;
}
}
export { getConfig };
export { sanitizeConfig };

172
src/engine.ts

@ -0,0 +1,172 @@
import { AbstractModule } from "./module/abstract";
let { logger } = require('./logging');
let { modules } = require('./module/index');
let trie = require('trie-prefix-tree');
let { getShortestPrefix } = require('./utility');
let help = require('./module/help');
let message = require('./message');
let utility = require('./utility');
let { initModule } = require('./module/abstract');
let { sanitizeConfig } = require('./config');
let commandPrefix = '!';
class Engine {
config: any;
bot: any;
modules: Array<AbstractModule>;
moduleMap: Map<string, AbstractModule>;
commands: Array<string>;
commandMap: Map<string, AbstractModule>;
commandRadixTree: any;
helpModule: any;
constructor(config, bot, modules) {
this.config = config;
this.bot = bot;
this.modules = modules;
this.moduleMap = new Map();
this.commands = [];
this.commandMap = new Map();
this.commandRadixTree = trie([]);
}
initModules() {
let sanitizedGlobalConfig = sanitizeConfig(
this.config,
[
'userPassword',
'accessToken'
])
this.modules.forEach((mod) => {
logger.info("Loading module: %s", mod.name);
initModule(mod, sanitizedGlobalConfig);
logger.info("Recognized commands: %s", mod.getRecognizedCommands())
this.moduleMap.set(mod.command, mod);
this.commandMap.set(mod.command, mod);
this.commandRadixTree.addWord(mod.command);
});
this.modules.forEach((mod) => {
let shortCharCommand = getShortestPrefix(this.commandRadixTree, mod.command, 1);
let short3CharCommand = getShortestPrefix(this.commandRadixTree, mod.command, 3);
let shortCommandAliases = [shortCharCommand, short3CharCommand];
logger.info("Adding short command %s for module: %s", shortCommandAliases, mod.name);
shortCommandAliases.forEach((commandAlias) => {
this.commandMap.set(commandAlias, mod);
})
});
this.helpModule = help.create(this.moduleMap)
initModule(this.helpModule);
this.moduleMap.set(this.helpModule.command, this.helpModule);
this.commandMap.set('help', this.helpModule);
this.commands = Array.from(this.commandMap.keys()).sort()
logger.info("Bound modules to keywords: %o", this.moduleMap);
}
init() {
logger.info("Initializing modules");
this.initModules();
/* Bind Message Parsing */
let engine = this;
let handleEvent = (event, room, toStartOfTimeline) => {
/* Don't process messages from self */
if (event.sender.userId !== this.config.userId) {
/* don't process messages that aren't of type m.room.message */
if (event.getType() !== "m.room.message") {
logger.debug("Recieved message of type: %s", event.getType());
return;
} else {
let messageBody = event.event.content.body;
logger.debug("[%s] %s", room.name, messageBody);
if (messageBody.indexOf(commandPrefix) === 0) {
let command = messageBody.split(' ')[0].substring(1);
if (engine.commandMap.has(command)) {
engine.commandMap.get(command).handleMessage(event, room, sendResponseMessageCallback(engine.bot));
} else {
let responseMessage = "The following commands are recognized"
responseMessage += "\n" + engine.commands.join(", ")
responseMessage += "\nAdditional information can be discovered with !help <command>"
sendResponseMessage(engine.bot, room, responseMessage);
}
}
}
}
}
this.bot.init(handleEvent);
/* Capture Exit Conditions */
let signals: NodeJS.Signals[] = ["SIGINT", "SIGTERM"];
signals.forEach((signature: NodeJS.Signals) => {
process.on(signature, async () => {
await this.bot.sendStatusShutdown()
.then(() => {
logger.info("Gracefully stopping Matrix SDK Client")
this.bot.client.stopClient();
});
process.exit(0);
});
});
process.on('exit', () => {
logger.info("Shutting Down");
});
return this;
}
run() {
this.bot.connect();
return this;
}
}
/**
* Handle the callback sending messages via the bot
*
* @param {*} bot
* @param {*} room
* @param {*} responseMessage
*/
function sendResponseMessage(bot, room, responseMessage) {
logger.debug("Responding to room: %s with %o", room.roomId, responseMessage);
Promise.resolve(responseMessage).then((promisedMessage) => {
if (responseMessage !== null) {
logger.debug("Sending message: %s", promisedMessage);
if (promisedMessage instanceof Object) {
bot.client.sendMessage(room.roomId, promisedMessage);
} else if (utility.isString(promisedMessage)) {
bot.client.sendMessage(room.roomId, message.createBasic(promisedMessage));
} else {
logger.error("Unable to process response message: %s", promisedMessage);
}
} else {
logger.warn("No response message offered");
}
})
}
/**
* Wrapper to produce a callback function that can be passed to the modules
*
* @param {*} bot
*/
function sendResponseMessageCallback(bot) {
return (room, responseMessage) => {
sendResponseMessage(bot, room, responseMessage);
}
}
function create(config, bot) {
return new Engine(config, bot, modules)
}
export { create };

55
src/logging.ts

@ -0,0 +1,55 @@
let winston = require('winston');
require('winston-daily-rotate-file');
let errorTransport = new (winston.transports.DailyRotateFile)({
level: 'error',
filename: 'log/error-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxFiles: '7d'
});
let combinedTransport = new (winston.transports.DailyRotateFile)({
filename: 'log/combined-%DATE%.log',
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxFiles: '7d'
});
let logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.splat(),
winston.format.simple()
),
defaultMeta: { service: 'baphomet-js' },
transports: [
errorTransport,
combinedTransport
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
} else {
logger.add(new winston.transports.Console({
level: 'error',
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
}
if ('LOG_LEVEL' in process.env) {
logger.info('LOG_LEVEL: %s', process.env.LOG_LEVEL)
logger.level = process.env.LOG_LEVEL
}
export { logger };

28
src/message.ts

@ -0,0 +1,28 @@
let showdown = require('showdown');
let converter = new showdown.Converter();
enum MessageTypes {
TEXT = 'm.text',
NOTICE = 'm.notice'
}
function createBasicMessage(body: string, msgtype: MessageTypes = MessageTypes.TEXT) {
return {
"body": body,
"msgtype": msgtype
};
}
function createMarkdownMessage(body: string, markdown: string, msgtype: MessageTypes = MessageTypes.TEXT) {
return {
"body": body,
"msgtype": msgtype,
"format": "org.matrix.custom.html",
"formatted_body": converter.makeHtml(markdown)
};
}
export { MessageTypes as types };
export { createBasicMessage as createBasic };
export { createMarkdownMessage as createMarkdown };

260
src/module/abstract.ts

@ -0,0 +1,260 @@
/**
* Base module that all modules extend
*/
let { logger } = require('../logging');
let message = require('../message');
let { isFunction, getObjectKeysToPrototype } = require('../utility');
let { getConfig, sanitizeConfig } = require('../config');
class AbstractModule {
/*
Name of the module used in help documentation and logging.
*/
name: string = "AbstractModule";
/*
Short description of the module functionality.
*/
description: string = "Base Module That All Other Modules Extend";
/*
A helpful multiline string that defines the module usage
*/
helpAndUsage: string = `Example: !abstract_module <command>
!abstract_module <boo> : scares people`
/*
The exported command used to invoke the module directly.
*/
command: string = "abstract_module";
/*
The default method to call when a command word is not recognized.
*/
defaultCommand?: string = null;
/*
The module should be hidden from help and command dialogs.
*/
hidden: boolean = false;
/*
This module should receive all messages, regardless of whether
the module was directly referenced with a command.
*/
canHandleIndirectMessages: boolean = false;
/*
Indicates if the modules needs access to the global config
*/
needGlobalConfig: boolean = false;
/*
Indicates if the module requires a readable config file.
*/
needConfig: boolean = false;
/* internal */
/*
The global config passed in
*/
_global_config: any = null;
/*
The loaded config file, if it exists.
*/
_config: any = null;
/**
* The recognized commands for the module
*/
_recognizedCommands: Array<string> = [];
/**
* The map of commands to functions
*/
_recognizedCommandMap: Map<string, string> = new Map();
constructor(name: string, description: string, command: string) {
this.name = name;
this.description = description;
this.command = command;
}
/**
* Called after the module is initialized.
*/
postInit() : any {
return this;
}
/**
* Adds a recognized command and method to the module.
*
* @param {*} command
* @param {*} methodName
*/
addRecognizedCommand(command: string, methodName: string) {
this._recognizedCommands.push(command);
this._recognizedCommandMap.set(command, methodName);
}
/**
* Returns the list of recognized commands.
*/
getRecognizedCommands() : Iterable<string> {
return this._recognizedCommandMap.keys();
}
/**
* The file path that the module configuration file is expected at.
*/
getConfigFilePath() : string {
return process.env.NODE_PATH + '/data/' + this.name.toLowerCase().replace(' ', '_') + '-config.json';
}
/**
* Fields that should be sanitized before printing the config information for the user.
*/
getConfigSensitiveFields() : Array<string> {
return [];
}
/**
* Return a global config value or a default.
*
* @param {*} key
* @param {*} defaultValue
*/
getGlobal(key: string, defaultValue: string = null) {
if (this._global_config !== null && typeof this._global_config[key] !== 'undefined') {
return this._global_config[key];
}
return defaultValue
}
/**
* Return a module config value or a default.
*
* @param {*} key
* @param {*} defaultValue
*/
get(key: string, defaultValue: string = null) {
if (this._config !== null && typeof this._config[key] !== 'undefined') {
return this._config[key];
}
return defaultValue
}
/**
* Default functionality for receiving and processing a message.
*
* Override this if the module needs to do more complicated message processing.
*/
handleMessage(event: any, room: any, callback: CallableFunction) {
logger.debug("[%s] [%s] [%s]", this.name, room.name, event.event.content.body);
let messageBody = event.event.content.body;
let bodyParts = messageBody.split(' ');
let trigger = bodyParts[0];
let command = bodyParts[1];
var args = [];
if (bodyParts.length > 2) {
args = bodyParts.slice(2);
}
logger.debug("[%s] Attempting to call %s with %s", trigger, command, args);
let responseMessage = this.processMessage(event, command, ...args);
callback(
room,
responseMessage
);
}
/*
Call the command method with the args
*/
processMessage(event: any, command: string, ...args: Array<string>) {
if (this._recognizedCommands.includes(command)) {
logger.debug("Calling %s with %s", this._recognizedCommandMap.get(command), args);
return this[this._recognizedCommandMap.get(command)](event, ...args);
} else {
if (this.defaultCommand != null) {
logger.debug("Attempting to use default command %s", this.defaultCommand);
let newArgs = [command].concat(...args);
try {
logger.debug("Calling %s with %s", this._recognizedCommandMap.get(this.defaultCommand), newArgs);
return this[this._recognizedCommandMap.get(this.defaultCommand)](event, ...newArgs);
} catch (e) {
logger.error("Error while calling default command %s %s", this.defaultCommand, e);
return this.cmd_help(event, ...newArgs);
}
} else {
logger.debug("Unrecognized command %s", command);
return this.cmd_help(event, ...[command].concat(...args));
}
}
}
/* Basic cmd methods */
/*
return basic help information,.
*/
cmd_help(event: any, ...args: Array<string>) {
return message.createBasic(this.helpAndUsage);
}
/**
* Return the basic config file
*
* @param {...any} args
*/
cmd_config(event: any, ...args: Array<string>) {
if (this._config != null) {
let configBody = JSON.stringify(sanitizeConfig(this._config, this.getConfigSensitiveFields()), null, 2)
return message.createMarkdown(configBody, "```" + configBody + "```");
}
return null;
}
}
let abstractModulePrototype = Object.getPrototypeOf(new AbstractModule('', '', ''));
/*
Initialization of a module.
*/
function init(mod: AbstractModule, globalConfig: any) {
logger.debug("Initializing module %s", mod.name);
if (mod.needConfig) {
logger.debug("Loading config file %s", mod.getConfigFilePath());
try {
mod._config = getConfig(mod.getConfigFilePath(), mod.getConfigSensitiveFields());
} catch (e) {
logger.error("Module %s needs a valid config file at %s", mod.name, mod.getConfigFilePath());
process.exit(1);
}
}
if (mod.needGlobalConfig) {
logger.debug("Bound global config to module %s", mod.name);
mod._global_config = globalConfig;
}
logger.debug("Detecting command methods.");
let commandMethods = getObjectKeysToPrototype(mod, abstractModulePrototype, (key) => {
return key.startsWith('cmd_') && isFunction(mod[key]);
})
// let commandMethods = objectKeys.filter();
logger.debug("Identified command methods: %s", commandMethods);
commandMethods.forEach((commandMethodName) => {
let command = commandMethodName.substring(4);
mod.addRecognizedCommand(command, commandMethodName);
})
logger.debug("Bound command methods for %s as %s", mod.name, mod.getRecognizedCommands());
mod.postInit();
}
export { AbstractModule };
export { init as initModule };

65
src/module/admin.ts

@ -0,0 +1,65 @@
/**
* Administration module
*/
let { AbstractModule } = require('./abstract');
let { logger } = require('../logging');
let { sanitizeConfig } = require('../config');
let message = require('../message');
import { getBuildInfo } from '../utility';
class AdminModule extends AbstractModule {
constructor() {
super(
"Administration",
"Support administration tasks",
"admin"
);
this.hidden = true;
this.helpAndUsage = `Usage: admin <command>
admin config - print the bot configuration`;
this.needGlobalConfig = true;
}
/**
* Override to only permit recognized admin users to access the plugin
*/
handleMessage(event: any, room: any, callback: CallableFunction) {
if (this.getGlobal("admins", []).includes(event.sender.userId)) {
logger.debug("Authorized %s for admin action", event.sender.userId);
super.handleMessage(event, room, callback);
} else {
logger.warn("User %s tried to access admin functionality", event.sender.userId);
return event.sender.userId + " is not a recognized admin!"
}
}
/* Commands
* All methods starting with cmd_ will be parsed at initialization to expose those methods as commdands to the user
*/
/**
* Print the current config information
*
* @param {...any} args
*/
cmd_config(event: any, ...args: Array<string>) {
if (this._global_config != null) {
let configBody = JSON.stringify(sanitizeConfig(this._global_config), null, 2)
return message.createMarkdown(configBody, "```" + configBody + "```");
}
return null;
}
/**
* Return the verion of the running bot
*
* @param event
* @param args
*/
cmd_version(event: any, ...args: Array<string>) {
return getBuildInfo();
}
}
export { AdminModule as Module };

39
src/module/example.ts

@ -0,0 +1,39 @@
/**
* Example module
* Copy this, replace the relevent parts, add it to the list in index.js
*/
let { AbstractModule } = require('./abstract');
class ExampleModule extends AbstractModule {
constructor() {
super(
"Example",
"Example tasks",
"example"
);
this.helpAndUsage = `Usage: example <command>
...`;
this.hidden = false; //default value
this.needGlobalConfig = false; // default value
this.needConfig = false; // default value
this.defaultCommand = 'search'; // name of the command to call when no recognized command name is present
}
/* Commands
* All methods starting with cmd_ will be parsed at initialization to expose those methods as commdands to the user
*/
/**
* Processed when !example blah is processed
*
* @param {...any} args the individual words passed after the command
*/
cmd_blah(event, ...args) {
return "Bla blah bla"
}
}
let module = new ExampleModule();
export { module };

50
src/module/giphy.ts

@ -0,0 +1,50 @@
/**
* Giphy module
*/
let { AbstractModule } = require('./abstract');
let axios = require('axios');
let { logger } = require('../logging');
class GiphyModule extends AbstractModule {
constructor() {
super(
"Giphy",
"Insert Giphy Links/Media",
"giphy"
);
this.helpAndUsage = `Usage: !giphy itsworking
...`;
this.needConfig = true;
this.defaultCommand = 'search';
}
getConfigSensitiveFields(): Array<string> {
return ["apiKey"];
}
getGiphySearch(term: string) {
let url = this.get("endpoint") + '/gifs/search?api_key=' + this.get("apiKey") + '&q=' + term + '&limit=1';
logger.debug("Requesting: %s", url.replace(this.get("apiKey"), '******'));
return axios.get(url);
}
/* Commands
* All methods starting with cmd_ will be parsed at initialization to expose those methods as commdands to the user
*/
/**
* Return the top item for the search terms.
*
* @param {...any} args
*/
cmd_search(event: any, ...args: Array<string>) {
return this.getGiphySearch(args[0])
.then((response) => {
// logger.debug("Giphy response: %o", response.data.data[0].url);
return response.data.data[0].embed_url;
})
}
}
export { GiphyModule as Module };

52
src/module/help.ts

@ -0,0 +1,52 @@
/**
* Help module
*/
let { AbstractModule } = require('./abstract');
let { logger } = require('../logging');
class HelpModule extends AbstractModule {
constructor(commandMap: Map<string, CallableFunction>) {
super(
"Help",
"Provide helpful information about other modules.",
"help"
);
this._commandMap = commandMap;
this._commandList = Array.from(commandMap.keys()).sort();
this.defaultCommand = 'help';
}
_default_help_message(): string {
let help = `!help <command>`;
for (let command of this._commandList) {
help += "\n!help " + command + " : " + this._commandMap.get(command).description;
}
return help;
}
/* Commands */
cmd_help(event: any, ...args: Array<string>) {
logger.debug("%o", args)
if (args.length < 1) {
return this._default_help_message();
} else {
let command = args[0];
logger.debug("Looking up help for %s from %o", command, this._commandMap);
if (this._commandList.includes(command)) {
return this._commandMap.get(command).cmd_help();
} else {
let help = command + " is an unrecognized module\n";
help += this._default_help_message();
return help;
}
}
}
}
function create(commandMap: Map<string, CallableFunction>) {
return new HelpModule(commandMap);
}
export { create };

19
src/module/index.ts

@ -0,0 +1,19 @@
/**
* Manage the registered modules
*/
let admin = require('./admin');
let giphy = require('./giphy')
let szurubooru = require('./szurubooru/index');
function getModules() {
return [
new admin.Module(),
new giphy.Module(),
new szurubooru.Module()
];
}
let modules = getModules();
export { modules };

135
src/module/szurubooru/client.ts

@ -0,0 +1,135 @@
/**
* Szurubooru api client
*/
let axios = require('axios');
let { logger } = require('../../logging');
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
}
export class SzurubooruClient {
url: string;
baseUrl: string;
username: string;
token: string;
randomImageCount: number;
_randomSearchQueryValues: string = "type:image,animated,video sort:random";
_randomSearchUrlTemplate: string = "%s/posts/?offset=%s&limit=%d&query=%s";
constructor(url: string, username: string, token: string) {
this.url = url;
this.baseUrl = url.replace('/api', '');
this.username = username;
this.token = token;
this.updateRandomScope();
}
getRandomSearchUrl(offset: number, limit: number, tags: Array<string> = []) {
var searchQuery = this._randomSearchQueryValues;
if (tags !== null && tags.length > 0) {
searchQuery = this._randomSearchQueryValues + tags.join(" ");
}
return this.url + '/posts/?offset=' + offset + '&limit=' + limit + '&query=' + searchQuery;
}
/**
* Determine a sane range of offset values
*
* @param {*} limit The content limit of a single page
* @param {*} total The number of objects to search through
*/
getValidOffsetRange(limit: number, total: number): Array<number> {
let offsetOverflow = total % limit;
var offsetCeiling = (Math.round(total / limit));
if (offsetOverflow === 0) {
offsetCeiling -= 1;
}
return [0, offsetCeiling];
}
/**
* Determine a random offset based on the potential random search size
*
* Because Szurubooru doesn't randomize each sort:random search request, we must
* manually calculate a random offset for each request to simulate randomness in the
* posts returned.
*
* @param {*} self
* @param {*} imageCount
*/
getRandomSearchParameters(imageCount?: number) {
let resultLimit = 10;
var offsetRange = [0, 1];
if (imageCount === null) {
offsetRange = this.getValidOffsetRange(resultLimit, this.randomImageCount);
} else {
offsetRange = this.getValidOffsetRange(resultLimit, imageCount);
}
return [resultLimit, getRandomInt(offsetRange[0], offsetRange[1])];
}
async updateRandomScope() {
let client = this;
let target = this.getRandomSearchUrl(0, 1);
await this.makeGetRequest(target).then((result: any) => {
client.randomImageCount = result.total;
});
}
makeGetRequest(url: string) {
logger.debug("Making get request to %s", url);
return axios.get(url, {
headers: {
"Authorization": "Token " + Buffer.from(this.username + ':' + this.token).toString('base64'),
"Accept": "application/json"
}
}).then((response: any) => {
logger.debug("Recieved: %o", response.data);
return response.data;
}, (err: any) => {
logger.error("Unexpected client error: %o", err);
return null;
});
}
/**
* Return a random post matching the optionally provided tags.
*
* @param {*} self
* @param {*} tags
*/
getRandomPost(tags: Array<string> = []) {
let client = this;
var resultLimit = 0;
var randomSearchOffset = 0;
if (tags.length === 0) {
let randomSearchParameters = this.getRandomSearchParameters();
resultLimit = randomSearchParameters[0];
randomSearchOffset = randomSearchParameters[1];
}
let searchUrl = this.getRandomSearchUrl(randomSearchOffset, resultLimit, tags);
return this.makeGetRequest(searchUrl)
.then((data: any) => {
if (data !== null) {
let limitSpace = Math.min(resultLimit, data.results.length - 1);
let randomPost = data.results[getRandomInt(0, limitSpace)];
return client.baseUrl + '/' + randomPost.contentUrl;
} else {
return null
}
}, (err: any) => {
logger.error("Unexpected error: %o", err);
return null;
})
}
}

65
src/module/szurubooru/index.ts

@ -0,0 +1,65 @@
/**
* Module for interacting with a Szurubooru instance
*/
let { AbstractModule } = require('../abstract');
let { logger } = require('../../logging');
import { SzurubooruClient } from './client';
class SzurubooruModule extends AbstractModule {
client: SzurubooruClient;
constructor() {
super(
"Szurubooru",
"Interact with a Szurubooru instance",
"szurubooru"
);
this.helpAndUsage = `Usage:
'!szurubooru <tag> [<tag>...]' display a random post that matches tags
'!szurubooru config' prints config information for the room
'!szurubooru config upload' toggles uploading images from room messages
'!szurubooru iqdb' get an IQDB link for the last displayed post
'!szurubooru need [<max tag count (5)>]' show a random post that needs additional tags
'!szurubooru post <post_id>' retrieve a post and publish the image to the room
'!szurubooru random [<tag>...]' display a random image from szurubooru that matches tags if preset
'!szurubooru random update' update the random counts
'!szurubooru recent [<days> (1)]' display a recent random image within the last <days>
'!szurubooru related' display related posts to the last displayed post
'!szurubooru tag <tag> [<tag>...]' add tags to the last displayed post
'!szurubooru tag clone <post_id>' clone tags from <post_id> to the last displayed post
'!szurubooru tag remove <tag> [<tag>...]' remove tags from the last displayed post
'!szurubooru tags' list of all tags by popularity
'!szurubooru tags <tag_stub> [<tag_stub>...]' list tags similar to each <tag_stub>
'!szurubooru upload <url>' upload a remote file to szurubooru`;
this.needConfig = true;
this.defaultCommand = 'random';
}
postInit() {
super.postInit();
this.client = new SzurubooruClient(
this.get("url"),
this.get("username"),
this.get("token")
)
}
getConfigSensitiveFields() {
return ["token"];
}
/* Commands
* All methods starting with cmd_ will be parsed at initialization to expose those methods as commdands to the user
*/
cmd_random(event: any, ...args: Array<string>) {
return this.client.getRandomPost().then((data) => {
logger.info("Random post: %o", data);
return data;
});
}
}
export { SzurubooruModule as Module };

85
src/utility.ts

@ -0,0 +1,85 @@
let fs = require('fs');
let { logger } = require('./logging');
function getShortestPrefix(radixTree: any, key: string, sliceSize: number) {
let shortKey = key.substring(0, sliceSize);
let keyCount = radixTree.countPrefix(shortKey);
if (keyCount < 1) {
return null;
}
if (key.length === sliceSize && radixTree.getPrefix(shortKey).includes(key)) {
return null;
}
if (keyCount === 1) {
return shortKey;
}
return getShortestPrefix(radixTree, key, sliceSize + 1);
}
function toISODateString(d: Date) {
function pad(n: number) { return n < 10 ? '0' + n : n }
return d.getUTCFullYear() + '-'
+ pad(d.getUTCMonth() + 1) + '-'
+ pad(d.getUTCDate()) + 'T'
+ pad(d.getUTCHours()) + ':'
+ pad(d.getUTCMinutes()) + ':'
+ pad(d.getUTCSeconds()) + 'Z'
}
function getBuildInfo() {
let buildInfoPath = process.env.NODE_PATH + '/build.info';
try {
return fs.readFileSync(buildInfoPath, "utf8").trim();
} catch (err) {
if (err.code === 'ENOENT') {
return "UNKNOWN_" + toISODateString(new Date());
} else {
logger.error("Unexpected Error!", err);
}
}
}
function sleep(ms: number) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
function isString(s: any) {
return typeof (s) === 'string' || s instanceof String;
}
function isFunction(f: any) {
return f && {}.toString.call(f) === '[object Function]';
}
/**
* Parse the prototype tree to return all accessible properties till
* reaching a sentinelPrototype.
*
* Optionally provide a filtering function to return only the names that match.
*
* @param {*} initialObj The starting object to derive the from
* @param {*} sentinelPrototype The prototype that represents the end of the line
* @param {*} filterFunc A filtering function for the return names
*/
function getObjectKeysToPrototype(initialObj: any, sentinelPrototype: any, filterFunc: any = (e: any) => true) {
let prototypeChain = []
var targetPrototype = initialObj;
while (Object.getPrototypeOf(targetPrototype) && targetPrototype !== sentinelPrototype) {
targetPrototype = Object.getPrototypeOf(targetPrototype);
prototypeChain.push(targetPrototype);
}
let completePropertyNames = prototypeChain.map((obj) => {
return Object.getOwnPropertyNames(obj);
})
return [Object.getOwnPropertyNames(initialObj)].concat.apply([], completePropertyNames).filter(filterFunc);
}
export { getShortestPrefix };
export { toISODateString };
export { getBuildInfo };
export { sleep };
export { isString };
export { isFunction };
export { getObjectKeysToPrototype };

14
tsconfig.json

@ -0,0 +1,14 @@
{
"compilerOptions": {
"outDir": "./dist",
"allowJs": false,
"target": "ES2015",
"module": "CommonJS",
"typeRoots": [
"node_modules/@types"
]
},
"include": [
"./src/**/*"
]
}
Loading…
Cancel
Save