Browse Source

Initial commit with 0.1.0 version

miles 2 years ago
parent
commit
2e2dd025c2

+ 128 - 1
README.md

@@ -1,3 +1,130 @@
 # docker-matrixbot-chatgpt
 
-Maubot (Matrix/Synapse Python Bot) plus chatGPT /OpenAI integration with docker-compose.
+Maubot (Matrix/Synapse Python Bot) plus chatGPT /OpenAI integration with docker-compose.
+
+#### Run after setup correctly Matrix/Synapse server also with docker and traefik
+```
+cd ./dockers/ && docker-compose up -d
+```
+
+#### Tweak Maubot configs inside 
+```
+./data/maubot/data/config.yaml
+```
+
+#### Compile and load chatGPT echo plugin
+exec to maubot docker with:
+```
+docker exec -ti maubot /bin/sh
+```
+and run:
+```
+mbc build /path/to/plugin-code-directory
+```
+If first time upload plugin need login to maubot:
+```
+mbc login
+```
+with:
+```
+? Username admin
+? Password <YOUR_MAUBOT_PASSWORD>
+? Server http://localhost:29316
+? Alias maubot
+```
+Then upload plugin:
+```
+mbc upload /path/to/plugin-mbc-directory
+```
+
+#### Workaround for self-signed certs
+turning of SSL cert check:
+vi /usr/lib/python3.9/site-packages/aiohttp/connector.py
+
+class TCPConnector(BaseConnector):
+    """TCP connector.
+                                                                                        
+    verify_ssl - Set to True to check ssl certifications.
+    fingerprint - Pass the binary sha256
+        digest of the expected certificate in DER format to verify              
+        that the certificate the server presents matches. See also                 
+        https://en.wikipedia.org/wiki/Transport_Layer_Security#Certificate_pinning    
+    resolver - Enable DNS lookups and use this
+        resolver                                                                        
+    use_dns_cache - Use memory cache for DNS lookups.
+    ttl_dns_cache - Max seconds having cached a DNS entry, None forever.
+    family - socket address family
+    local_addr - local tuple of (host, port) to bind socket to
+                                                                                        
+    keepalive_timeout - (optional) Keep-alive timeout.
+    force_close - Set to True to force close and do reconnect
+        after each request (and between redirects).                             
+    limit - The total number of simultaneous connections.exec to maubot docker with:
+```
+docker exec -ti maubot /bin/sh
+```
+and run:
+```
+mbc build /path/to/plugin-code-directory
+```
+If first time upload plugin need login to maubot:
+```
+mbc login
+```
+with:
+```
+? Username admin
+? Password **********
+? Server http://localhost:29316
+? Alias maubot
+```
+Then upload plugin:
+```
+mbc upload /path/to/plugin-mbc-directory
+```
+
+turning of SSL cert check:
+vi /usr/lib/python3.9/site-packages/aiohttp/connector.py
+
+class TCPConnector(BaseConnector):
+    """TCP connector.
+                                                                                        
+    verify_ssl - Set to True to check ssl certifications.
+    fingerprint - Pass the binary sha256
+        digest of the expected certificate in DER format to verify              
+        that the certificate the server presents matches. See also                 
+        https://en.wikipedia.org/wiki/Transport_Layer_Security#Certificate_pinning    
+    resolver - Enable DNS lookups and use this
+        resolver                                                                        
+    use_dns_cache - Use memory cache for DNS lookups.
+    ttl_dns_cache - Max seconds having cached a DNS entry, None forever.
+    family - socket address family
+    local_addr - local tuple of (host, port) to bind socket to
+                                                                                        
+    keepalive_timeout - (optional) Keep-alive timeout.
+    force_close - Set to True to force close and do reconnect
+        after each request (and between redirects).                             
+    limit - The total number of simultaneous connections.
+    limit_per_host - Number of simultaneous connections to one host.
+    enable_cleanup_closed - Enables clean-up closed ssl transports.
+                            Disabled by default.                                     
+    loop - Optional event loop.
+    """
+                                                                                      
+    def __init__(
+        self,                                                             
+        *,                                                                     
+        verify_ssl: bool = False, ##mmi 
+    limit_per_host - Number of simultaneous connections to one host.
+    enable_cleanup_closed - Enables clean-up closed ssl transports.
+                            Disabled by default.                                     
+    loop - Optional event loop.
+    """
+                                                                                      
+    def __init__(
+        self,                                                             
+        *,                                                                     
+        verify_ssl: bool = False, ##miles
+
+
+

+ 22 - 0
data/maubot/code/echo/LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2020 Tulir Asokan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

+ 9 - 0
data/maubot/code/echo/README.md

@@ -0,0 +1,9 @@
+# echo
+Code based on:
+A simple [maubot](https://github.com/maubot/maubot) that echoes pings and other stuff.
+
+Modified by: miles (git.milecki.it) for chatGPT feature
+## Usage
+* `!ping` - Reply with "Pong!" and the time it took for the message to reach the bot.
+* `!echo <message>` - Reply with the given message
+* `!gpt <question>` - chatGPT reply for the given question

+ 17 - 0
data/maubot/code/echo/base-config.yaml

@@ -0,0 +1,17 @@
+gpt_on: True
+gpt_room_id: ['<your_matrix_room_id>']
+gpt_apikey: <your_openai__apikey>
+gpt_model_engine: text-davinci-003
+gpt_max_tokens: 1000
+gpt_temperature: 0
+gpt_top_p: 1
+gpt_presence_penalty: 0
+gpt_frequency_penalty: 0
+gpt_echo: False
+gpt_stop: None
+gpt_n: 1
+gpt_stream: False
+gpt_logprobs: None #not working/not initiated
+gpt_best_of: 1
+gpt_logit_bias: {} #not working/hardcoded
+

+ 152 - 0
data/maubot/code/echo/echo.py

@@ -0,0 +1,152 @@
+from typing import Type, Optional
+from time import time
+from html import escape
+
+from mautrix.types import MediaMessageEventContent, TextMessageEventContent, MessageType, Format, RelatesTo, RelationType, RoomID
+from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper
+
+from maubot import Plugin, MessageEvent
+from maubot.handlers import command
+
+import json
+import openai
+
+class Config(BaseProxyConfig):
+    def do_update(self, helper: ConfigUpdateHelper) -> None:
+     helper.copy("gpt_on")
+     helper.copy("gpt_room_id")
+     helper.copy("gpt_apikey")
+     helper.copy("gpt_model_engine")
+     helper.copy("gpt_temperature")
+     helper.copy("gpt_top_p")
+     helper.copy("gpt_presence_penalty")
+     helper.copy("gpt_frequency_penalty")
+     helper.copy("gpt_echo")
+     helper.copy("gpt_stop")
+     helper.copy("gpt_n")
+     helper.copy("gpt_stream")
+     helper.copy("gpt_logprobs")
+     helper.copy("gpt_best_of")
+     helper.copy("gpt_logit_bias")
+
+
+class EchoBot(Plugin):
+    
+    @classmethod
+    def get_config_class(cls) -> Type[BaseProxyConfig]:
+        return Config
+
+    async def start(self) -> None:
+     await super().start()
+     self.config.load_and_update()
+     self.http = self.client.api.session
+		
+
+    async def stop(self) -> None:
+        await super().stop()
+
+    @staticmethod
+    def plural(num: float, unit: str, decimals: Optional[int] = None) -> str:
+        num = round(num, decimals)
+        if num == 1:
+            return f"{num} {unit}"
+        else:
+            return f"{num} {unit}s"
+
+    @classmethod
+    def prettify_diff(cls, diff: int) -> str:
+        if abs(diff) < 10 * 1_000:
+            return f"{diff} ms"
+        elif abs(diff) < 60 * 1_000:
+            return cls.plural(diff / 1_000, 'second', decimals=1)
+        minutes, seconds = divmod(diff / 1_000, 60)
+        if abs(minutes) < 60:
+            return f"{cls.plural(minutes, 'minute')} and {cls.plural(seconds, 'second')}"
+        hours, minutes = divmod(minutes, 60)
+        if abs(hours) < 24:
+            return (f"{cls.plural(hours, 'hour')}, {cls.plural(minutes, 'minute')}"
+                    f" and {cls.plural(seconds, 'second')}")
+        days, hours = divmod(hours, 24)
+        return (f"{cls.plural(days, 'day')}, {cls.plural(hours, 'hour')}, "
+                f"{cls.plural(minutes, 'minute')} and {cls.plural(seconds, 'second')}")
+
+    @command.new("ping", help="Ping")
+    @command.argument("message", pass_raw=True, required=False)
+    async def ping_handler(self, evt: MessageEvent, message: str = "") -> None:
+        diff = int(time() * 1000) - evt.timestamp
+        pretty_diff = self.prettify_diff(diff)
+        text_message = f'"{message[:20]}" took' if message else "took"
+        html_message = f'"{escape(message[:20])}" took' if message else "took"
+        content = TextMessageEventContent(
+            msgtype=MessageType.NOTICE, format=Format.HTML,
+            body=f"{evt.sender}: Pong! (ping {text_message} {pretty_diff} to arrive)",
+            formatted_body=f"<a href='https://matrix.example.pl/#/{evt.sender}'>{evt.sender}</a>: Pong! "
+            f"(<a href='https://matrix.example.pl/#/{evt.room_id}/{evt.event_id}'>ping</a> {html_message} "
+            f"{pretty_diff} to arrive)",
+            relates_to=RelatesTo(
+                rel_type=RelationType("xyz.maubot.gpt.echo"),
+                event_id=evt.event_id,
+            ))
+        pong_from = evt.sender.split(":", 1)[1]
+        content.relates_to["from"] = pong_from
+        content.relates_to["ms"] = diff
+        content["pong"] = {
+            "ms": diff,
+            "from": pong_from,
+            "ping": evt.event_id,
+        }
+        await evt.respond(content)
+
+    @command.new("echo", help="Repeat a message")
+    @command.argument("message", pass_raw=True)
+    async def echo_handler(self, evt: MessageEvent, message: str) -> None:
+        await evt.respond(message)
+
+    @command.new("gpt", help="ChatGPT response")
+    @command.argument("message", pass_raw=True, required=False)
+    async def gpt_handler(self, evt: MessageEvent, message: str = "") -> None:
+     if self.config["gpt_on"] and evt.room_id in self.config["gpt_room_id"]:
+      openai.api_key = self.config["gpt_apikey"]
+      resp = openai.Completion.create(engine=self.config["gpt_model_engine"],
+                                        prompt=message,
+                                        max_tokens=int(self.config["gpt_max_tokens"]),
+                                        temperature=float(self.config["gpt_temperature"]),
+                                        top_p=int(self.config["gpt_top_p"]),
+                                        presence_penalty=int(self.config["gpt_presence_penalty"]),
+                                        frequency_penalty=int(self.config["gpt_frequency_penalty"]),
+                                        echo=self.config["gpt_echo"],
+                                        stop=self.config["gpt_stop"],
+                                        n=int(self.config["gpt_n"]),
+                                        stream=self.config["gpt_stream"],
+#                                        logprobs=self.config["gpt_logprobs"],
+                                        best_of=int(self.config["gpt_best_of"]),
+                                        logit_bias={}
+       )
+
+      n = len(resp.choices)
+      if n == 1:
+       html_message = resp.choices[0].text
+       text_message = resp.choices[0].text
+      else:
+       texts = []
+       for idx in range(0, n):
+        html_message.append(resp.choices[idx].text)
+        text_message.append(resp.choices[idx].text)
+     else:
+      html_message = f'chatGPT jest wyłączony.'
+      text_message = 'chatGPT jest wyłączony.'
+   
+     content = TextMessageEventContent(
+     msgtype=MessageType.NOTICE, format=Format.HTML,
+     body=f"{evt.sender}: {text_message}",
+            formatted_body=f"{evt.sender}: "
+            f"{html_message}",
+            relates_to=RelatesTo(
+                rel_type=RelationType("xyz.maubot.gpt.echo"),
+                event_id=evt.event_id,
+       ))
+     pong_from = evt.sender.split(":", 1)[1]
+     content.relates_to["from"] = pong_from
+
+     await evt.respond(content)
+

+ 12 - 0
data/maubot/code/echo/maubot.yaml

@@ -0,0 +1,12 @@
+maubot: 0.1.0
+id: xyz.maubot.gpt.echo
+version: 0.1.0
+license: MIT
+modules:
+- echo
+main_class: EchoBot
+extra_files:
+- base-config.yaml
+dependencies:
+- openai
+

+ 103 - 0
data/maubot/data/config.yaml

@@ -0,0 +1,103 @@
+# The full URI to the database. SQLite and Postgres are fully supported.
+# Other DBMSes supported by SQLAlchemy may or may not work.
+# Format examples:
+#   SQLite:   sqlite:///filename.db
+#   Postgres: postgresql://username:password@hostname/dbname
+database: sqlite:////data/maubot.db
+
+# Separate database URL for the crypto database. "default" means use the same database as above.
+# Due to concurrency issues, you should use a separate file when using SQLite rather than the same as above.
+# When using postgres, using the same database for both is safe.
+crypto_database: default
+
+plugin_directories:
+    # The directory where uploaded new plugins should be stored.
+    upload: /data/plugins
+    # The directories from which plugins should be loaded.
+    # Duplicate plugin IDs will be moved to the trash.
+    load:
+    - /data/plugins
+    trash: /data/trash
+    # The directory where plugin databases should be stored.
+    db: /data/dbs
+
+server:
+    # The IP and port to listen to.
+    hostname: 0.0.0.0
+    port: 29316
+    # Public base URL where the server is visible.
+    public_url: http://matrix.example.pl
+    # The base management API path.
+    base_path: /_matrix/maubot/v1
+    # The base path for the UI.
+    ui_base_path: /_matrix/maubot
+    # The base path for plugin endpoints. The instance ID will be appended directly.
+    plugin_base_path: /_matrix/maubot/plugin/
+    # Override path from where to load UI resources.
+    # Set to false to using pkg_resources to find the path.
+    override_resource_path: /opt/maubot/frontend
+    # The base appservice API path. Use / for legacy appservice API and /_matrix/app/v1 for v1.
+    appservice_base_path: /_matrix/app/v1
+    # The shared secret to sign API access tokens.
+    # Set to "generate" to generate and save a new token at startup.
+    unshared_secret: <YOUR_SECRET>
+
+# Known homeservers. This is required for the `mbc auth` command and also allows
+# more convenient access from the management UI. This is not required to create
+# clients in the management UI, since you can also just type the homeserver URL
+# into the box there.
+homeservers:
+    matrix.example.pl:
+    # Client-server API URL
+        url: https://synapse.matrix.example.pl
+    # registration_shared_secret from synapse config
+    # You can leave this empty if you don't have access to the homeserver.
+    # When this is empty, `mbc auth --register` won't work, but `mbc auth` (login) will.
+        secret:
+admins:
+    admin: <YOUR_ACCESS_HASH>
+api_features:
+    login: true
+    plugin: true
+    plugin_upload: true
+    instance: true
+    instance_database: true
+    client: true
+    client_proxy: true
+    client_auth: true
+    dev_open: true
+    log: true
+
+# Python logging configuration.
+#
+# See section 16.7.2 of the Python documentation for more info:
+# https://docs.python.org/3.6/library/logging.config.html#configuration-dictionary-schema
+logging:
+    version: 1
+    formatters:
+        colored:
+            (): maubot.lib.color_log.ColorFormatter
+            format: '[%(asctime)s] [%(levelname)s@%(name)s] %(message)s'
+        normal:
+            format: '[%(asctime)s] [%(levelname)s@%(name)s] %(message)s'
+    handlers:
+        file:
+            class: logging.handlers.RotatingFileHandler
+            formatter: normal
+            filename: /var/log/maubot.log
+            maxBytes: 10485760
+            backupCount: 10
+        console:
+            class: logging.StreamHandler
+            formatter: colored
+    loggers:
+        maubot:
+            level: DEBUG
+        mau:
+            level: DEBUG
+        aiohttp:
+            level: INFO
+    root:
+        level: DEBUG
+        handlers: [file, console]
+

+ 0 - 0
data/maubot/data/dbs/.gitkeep


+ 0 - 0
data/maubot/data/plugins/.gitkeep


+ 0 - 0
data/maubot/data/trash/.gitkeep


+ 24 - 0
dockers/Dockerfile

@@ -0,0 +1,24 @@
+FROM dock.mau.dev/maubot/maubot:latest
+
+## With openai for chatGPT:
+RUN apk upgrade
+RUN apk upgrade --available
+
+RUN pip install --upgrade pip
+RUN pip install --upgrade setuptools
+
+RUN apk add --no-cache \
+        --virtual=.build-dependencies \
+        g++ file binutils \
+        musl-dev python3-dev cython && \
+    apk add libstdc++ openblas && \
+    ln -s locale.h /usr/include/xlocale.h && \
+    pip install -I numpy==1.24.1
+#    rm -r /root/.cache && \
+#    find /usr/lib/python3.*/ -name 'tests' -exec rm -r '{}' + && \
+#    find /usr/lib/python3.*/site-packages/ -name '*.so' -print -exec sh -c 'file "{}" | grep -q "not stripped" && strip -s "{}"' \; && \
+#    rm /usr/include/xlocale.h && \
+#    apk del .build-dependencies
+
+RUN pip install -I openai
+

+ 35 - 0
dockers/docker-compose.yml

@@ -0,0 +1,35 @@
+version: '3.3'
+
+services:
+ app:
+  build: .
+  container_name: maubot
+  restart: unless-stopped
+  volumes:
+    - /matrix/data/maubot/data:/data
+    - /matrix/data/maubot/code:/code
+  networks:
+    - proxy
+  ports:
+    - 29316:29316
+  labels:
+   - "traefik.enable=true"
+   - "traefik.docker.network=proxy"
+   - "traefik.http.routers.maubot.rule=Host(`maubot.matrix.example.pl`)"
+   - "traefik.http.routers.maubot.entrypoints=websecure"
+   - "traefik.http.services.maubot.loadbalancer.server.port=29316"
+   - "traefik.http.routers.maubot.service=maubot"
+   - "traefik.http.routers.maubot.tls.certresolver=letscrypt"
+   - "traefik.http.middlewares.maubot-ratelimit.ratelimit.average=1000"
+   - "traefik.http.middlewares.maubot-ratelimit.ratelimit.burst=500"
+   - "traefik.http.middlewares.maubot-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.0.0/16"
+  logging:
+      driver: "json-file"
+      options:
+        max-size: "100m"
+        max-file: "10"
+
+networks:
+ proxy:
+  external: true
+