[Plugin] BetterChat v3.0.1

BetterChat

BetterChat is an unofficial addon for TeamSpeak 6 and aims to provide a better chat experience for the TeamSpeak 6 client. It enables support for BBCodes, improving messages sent by TeamSpeak 3 users, and automatic rich embeds for video, audio, image and twitter content. It works in both compact and detailed view.

BBCode support

BetterChat readds support for BBCodes in chat, just like in TeamSpeak 3. Currently, the following tags are supported:

Name Syntax Example
bold [b]bold[/b] bold
code [code]code[/code] code
color [color=#FFA500]hexcode[/color] or [color=orange]css color[/color] color
italic [i]italic[/i] italic
spoiler [spoiler]spoiler[/spoiler] spoiler
strike [s]strike[/s] strike
underline [u]underline[/u] underline
url [u]https://example.com[/u] or [url=https://example.com]text[/url] url
pre [pre]inline code[/pre] pre

Rich Embeds

BetterChat supports automatic embedding of any audio or video content. Due to technical limitations, not all video and audio formats are supported at the moment.

Video Embed

Audio Embed

Twitter Embed

Twitter Embed

Generic Embed

Generic Embed

A list of supported audio and video formats can be found here.

Styling

Custom styling for the image preview can be changed in BetterChat.zip/src/style.css. Rich embeds use the already existing css classes of the TeamSpeak client and should be compatible with already existing themes.

Download and Installation

Note:
The addon needs to be reinstalled after every TeamSpeak update

A download and installation instructions can be found on GitHub including an installer. An installer for TeamSpeak 5 can be found here. Note that this addon modifies your existing TeamSpeak 6 installation.

Configuration

BetterChat can be enabled and disabled while TeamSpeak is running. Just go to the settings menu inside TeamSpeak and navigate to Chats (previously Behavior). There you can toggle specific features, like BBCode support or Rich Embeds, or enable and disable entire addon.

Acknowledgements

This addon is inspired by the TeamSpeak UNOFFICIAL Plugin Installer by Gamer9200

10 Likes

So, I didn’t look too deep at your code, but it looks really well written!
It seems you manipulate the actual DOM the same way I do.
For me, this results in some problems with the virtual scroller of the chat when rendering Twitter embeds.
Technically you can access the vuejs virtual scroller content and manipulate the render function directly. This would even eliminate the need for the MutationObserver and greatly improve performance. Seeing your dedication to this project, this would be an awesome next step. :stuck_out_tongue:

Also, your README suggests that MP4 (and others) are supported while in fact, it can’t be, as TeamSpeak’s CEF is compiled without proprietary codec support.

5 Likes

Thank you!

I would be very thankful if you would show me a proof of concept!

Good catch! I thought that the website would only list the available codecs. Technically you can get mp4 support in ts5 if you recompile cef with propietary codecs enabled, although its pretty complicated (but it works).

1 Like

It took some considerable time to figure all this vuejs stuff out, but I got there!
By modifying the component loader in the vendors.js file you can manipulate components before they are created or mounted. In this POC I opted to manipulate the tsv-virtual-list-item component. Keep in mind that the component IDs generated by webpack sadly change per update and thus you will need to reverse the main.js after each update.

POC

vendor.js after formatting

--- a/vendor.js
+++ b/vendor.js
@@ -2,7 +2,125 @@

-function e(r) { 
-  var o = n[r];
-  if (void 0 !== o) return o.exports;
-  var i = (n[r] = { id: r, loaded: !1, exports: {} });
-  return t[r].call(i.exports, i, i.exports, e), (i.loaded = !0), i.exports;
-}
+function oldE(r) {
+  var o = n[r];
+  if (void 0 !== o) return o.exports;
+  var i = (n[r] = { id: r, loaded: !1, exports: {} });
+  return t[r].call(i.exports, i, i.exports, e), (i.loaded = !0), i.exports;
+}
+
+const linkRegex =
+  /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
+
+const contentTypeMap = {
+  jpg: "image/jpeg",
+  jpeg: "image/jpeg",
+  png: "image/png",
+  gif: "image/gif",
+  webp: "image/webp",
+  svg: "image/svg+xml",
+};
+
+function e(r) {
+  let result = oldE(r);
+  if (r === 661163) {
+    result = {
+      s: function () {
+        var e = this,
+          t = e._self._c;
+        e._self._setupProxy;
+
+        const item = e.item;
+
+        const children = [
+          e.item
+            ? t(
+                e.component,
+                e._g(
+                  e._b(
+                    {
+                      key: e.uniqueId,
+                      tag: "component",
+                      style: { opacity: e.finalHeight ? "1" : "0" },
+                      attrs: { role: "listitem" },
+                    },
+                    "component",
+                    e.filteredProps,
+                    !1
+                  ),
+                  e.wrappedListeners
+                )
+              )
+            : e._e(),
+        ];
+
+        try {
+          if (
+            !item.isSystem &&
+            !item.parsedItem?.hideText &&
+            !(item.parsedItem?.attachments?.length || false)
+          ) {
+            const text = item.original || item.body;
+            if (text.match(linkRegex)) {
+              console.error(this);
+              const extension = text.split(".").pop().split("?")[0];
+              const filename = text.split("/").pop().split("?")[0];
+              if (
+                ["jpg", "jpeg", "png", "gif", "webp", "svg"].includes(extension)
+              ) {
+                children.push(
+                  t(
+                    "div",
+                    {
+                      staticClass: "test",
+                      style: { "padding-left": "70px" },
+                    },
+                    [
+                      t("img", {
+                        attrs: { src: text },
+                        style: {
+                          width: "100%",
+                          "max-width": "350px",
+                          cursor: "pointer",
+                        },
+                        on: {
+                          click() {
+                            e.$root.appController.events.onInvokeLightboxEmitter.fire(
+                              {
+                                invocation: {
+                                  url: text,
+                                  thumbnailUrl: text,
+                                  contentType: contentTypeMap[extension],
+                                  contentSize: 0,
+                                  footerActions: [],
+                                  headerLabel: filename,
+                                  bodyLabel: "Ꮁᔐᔇᔉᔈ á”–á”’Ê·á”‰Êłá”‰á”ˆ ᔇʞ áŽłá”ƒá”á”‰ÊłâčÂČ⁰⁰⁰",
+                                },
+                              }
+                            );
+                          },
+                        },
+                      }),
+                    ]
+                  )
+                );
+              }
+            }
+          }
+        } catch (e) {
+          console.error(e);
+        }
+
+        return t(
+          "div",
+          {
+            ref: "itemRef",
+            staticClass: "tsv-virtual-list-item",
+            style: e.itemStyles,
+          },
+          children
+        );
+      },
+      x: result.x,
+    };
+  }
+  return result;
+}

This will crudely embed images and allow you to click on them just like other shared images.
TeamSpeak_FP3MfLvkhp

I actually did this once. But sadly you can neither distribute such a self-compiled version nor can you expect the users to do so. Also I wouldn’t want to compile this for a windows target


EDIT: changed the POC to do something actually useful.
EDIT2: I just couldn’t stop myself

Now the embed uses the native overlay to display a larger version of the image when clicked on.

4 Likes

I think this could also be done without modifying the vendor.js. You can access the vue instance with document.body.querySelector("#app").__vue__ or call $0.__vue__ on any other component.

2 Likes

Well, I’m not that proficient with vuejs + webpack compiled and bundled code.
So I don’t think you could patch the component loader this way.
Nonetheless, whether you modify the index.html or vendor.js is not much of a difference, is it?

My main problem was that I needed to modify the component loader before the vendor.js was actually loaded in order for it to actually use the patched version. Otherwise, the original version was just everywhere in the call stack and referenced from everywhere directly.

2 Likes

awesome work!
sadly i couldn’t make it work with channel description, only chats.

We updated the Installer and BetterChat to be compatible with the latest version of TeamSpeak (5.0.0-beta72). We also fixed the issue with the vuejs virtual scroller (hopefully).

3 Likes

We updated the Installer and BetterChat to be compatible with the latest version of TeamSpeak (5.0.0-beta73).

1 Like

We updated the Installer and BetterChat to be compatible with the latest version of TeamSpeak (5.0.0-beta74).

1 Like

We updated the Installer and BetterChat to be compatible with the latest version of TeamSpeak (5.0.0-beta75).

1 Like

We updated the Installer and BetterChat to be compatible with the latest version of TeamSpeak (5.0.0-beta76).

2 Likes

You said you updated the installer to version 76 but the installer is not releasing the new version, can you send us a new link so we can get the correct installer?
image
image
image

1 Like

I see that I have now installed the installer, it is working, everything is OK, but can you make the Plugin read these Chat effects in the descriptions of a server’s rooms?
Follow the example below:

We updated the Installer and BetterChat to be compatible with the latest version of TeamSpeak (5.0.0-beta77).

Sorry for the late reply, I didn’t see your message! When I post about a version update in this thread, it is available for download. I noticed that GitHub does some strange ordering with the releases, where the latest version is not always at the top. I will update description to link to the latest release.

EDIT: I added them to the update notice.

Thanks for the suggestion. I will look into it.

BBCodes are supported natively by TS5 in server and channel descriptions, on both TS5 servers and TS3 servers. However, your description looks like it is not properly formatted. For example, the headline is missing a closing [/b] tag. Additionally, tables are not supported by TS5 and neither by BetterChat.

The Installer is now available for TeamSpeak 6, along with a new version of BetterChat.

2 Likes

Hey, thank you! tiktok also?