mindflakes

Sep 12, 2020 - 6 minute read

The short lived life of Breakout Bots for Zoom Meetings

With the pandemic happening, it’s been tough for many organizations to adapt. We’re all supposed to be together! One way organizations have been staying together is by using Zoom Meetings.

At work, we’ve been using a knock-off reseller version called RingCentral Meetings. From looking at their competitors, Zoom has pulled out all the stops to make their meeting experience the most efficient, resiliant, rather cheapest, and reliable experience.

One of these features is “Breakout Rooms”. With breakout rooms, users can subdivide their meetings to make mini-meetings from a bigger meeting. For most of the year, there has been an odd restriction on Breakout Meetings though.

You cannot go to another breakout room as an attendee without the host reassigning you unless you are a Host or a Co-Host.

Obviously, this can result in a lot of load upon the poor user who is desginated the host. Even the Co-Hosts can’t assign users to another breakout room.

So I made a bot:

https://github.com/nelsonjchen/BreakoutRoomsBotForZoomMeetings

the bot rename in action

It’s quite a hack, but it basically controls a web client that is a Host and assigns users based on chat commands and on attendees renaming themselves.

Last night, I just finished finally optimizing the bot. It should be able to handle hundreds of users and piles of chat commands.

This morning I learned that Zoom will add native support for switching:

https://www.reddit.com/r/Zoom/comments/irkm82/selfselect_breakout_room/

It was fun, but as brief as the bot existed, the main purposes are numbered. Maybe someone can reuse the code to make other Zoom Meeting add-on/bots?

Technical and Learnings

  • I knew about this issue for many months but I never made a bot because there wasn’t an API. I waited until I was very disatisified with the situation and then made the bot. I should have just done it, without the API.

  • Thankfully, by doing this quickly, I only really spent like a week of time on this total. With some of the tooling I used, I didn’t have to spend too long debugging the issues.

  • The original implementation of this tool was a copy and paste into Chrome’s Console. This is a much worse experience for users than a Chrome extension. Kyle Husmann contributed a fork that extensionized the bot and it is totally something I’ll be stealing for future hacks like this.

  • Zoom got to where they are by doing things their competitors did not and rather quickly. This feature/feedback response is great and is why they’ve been crushing. I feel this is a feature that would have taken their competitors possibly a year to implement. At this time, Microsoft Teams is also looking to implement Breakout Rooms.

  • Zoom’s web client uses React+Redux. They also use the Redux-Thunk middleware. They do stick some side-effects in places where things shouldn’t but whatever, it works. It did make integration very hard if not impossible at this layer for my bot. However, subscribing to state changes was and is very reliable.

  • RxJS is a nice implementation of the Reactive Pattern with Observables and stuff like with RX.NET. Without this, I could not fit the bot in so few lines of code with the reliability and usability that it currently has.

  • With RxJS and Redux’s store, I was able to subscribe to internal Redux state changes and transform them into streams of events. For example, the bot transform the streams of renames and chat message requests into a common structure that it then merges to be processed by later operators. Lots of code is shared and I’m able to rearrange and transform at will.

  • The local override functionality is great if you want to inject Redux Web Tools into an existing minified application. If you beautify the code, you can inject dev tools too. Here’s the line of code I replaced to inject Redux Dev Tools into Zoom’s web client.

    Previous:

    , s = Object(r.compose)(Object(r.applyMiddleware)(i.a));
    

    After:

    , s = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__(Object(r.applyMiddleware)(i.a));
    

    This was super helpful in seeing how the Redux state changes when performing actions in a nice GUI.

  • fuzzysort is great! I’m quite surprised at how unsurprising the results can be when looking up rooms by a partial or cut up name.

  • The underlying websocket connection is available globally as commandSocket. If you observe the commands, they are just JSON and you can inject your own commands to control the meeting programatically.

  • Since Breakout Room status is sent back via WebSocket, the UI will automatically update. I did not have to go through the Redux store to update the Breakout Room UI.

  • UI Automation of Breakout Room UI: ~100ms vs webSocket command: 2ms. Wow!

  • Chat messages are encrypted and decrypted client-side before going out on the Websocket interface. This probably doesn’t stop Zoom from reading the chat messages since they are the ones who gave you the keys. I could have reimplemented the encryption in the bot but after running some replay attacks, the UI did not update. Without the UI updating, I didn’t feel it was safe for the host to not know what the bot sent out on their behalf.

  • Even if I had implemented the chat message encryption, the existing UI automation of chat messages hovered around 30ms. The profiler also showed that the overhead of automation wasn’t that much and much of the chat message encryption contributed to the 30ms. It wouldn’t have been much of a performance win if I implemented it myself.

  • Possible Security-ish Issue - Since it isn’t an acceptable bounty issue, and hosts can simply disable chat, I will disclose is possible to DOS the native client by simply spamming chat. If there is too much text, the native clients will lock up and require a restart. Native clients don’t drop old chat logs. Web clients don’t have this issue as they will drop old chat automatically. Last but not least, it is possible for the Host to disable chat in many ways to alleviate such an attack. I wasn’t sure if to let my load_test bot out there or not because of this but after reflection, I think it’s OK.

  • RxJS is MVP. Or rather the Reactive stuff is MVP. If you can get it into a stream, it’s super powerful. Best yet, you can reuse your experience from other ecosystems. I was previously using Rx.NET for some stuff at work. I’ll be definitely looking at using the Python version of this in the future.

    • Though it did seem the differing Reactive stuff have different operators. The baselines are still great though and you can always port operators from one to another implementation.
  • Rx is really helped if you can use TypeScript. Unfortuntately, I never got to this stage.

That said, this was fun! Now onward to the next thing.

Oct 2, 2019 - 1 minute read

Stop Sims 2 Purple Soup or Pink Flashing Crashes on Windows 10 and Modern Hardware with D9VK

Wrote a guide on how to fix the Purple Soup or Pink Flashing Crashes for The Sims 2 on Windows 10 1903+ on powerful gaming computers. It’s great to make this accessible again to gamers with powerful gaming computers.

Guide Link: https://docs.google.com/document/d/19JMr-FQSU3AlF7Kyvcrr7awTiHBDQSUNLG6qfaI6rOs/edit#

General idea: Replace Microsoft DirectX 9 with D9VK.

D9VK is actually actively developed and fixes bugs. It also translates graphics to a stack that is more open and stable.

So this:

sims 2 purple soup

Turns to this:

Fixed and proof of running

Jun 2, 2019 - 2 minute read

Fast and lightweight headless Qt Installer from Qt Mirrors: aqtinstall

As part of my work on the Barrier project, we needed a way to build it with the [Qt installer]from an online CI/CD service. On some platforms, such as Appveyor, Qt may be preinstalled. But on some other platforms such as Azure Pipelines, Qt may not be pre-installed. Nor on Travis. Nor on CircleCI. For Linux and macOS, there is a saving grace. The Qt libraries can be installed headlessly from their operating system repositories such as apt or brew. But this luxury does not exist as well on Windows. While Chocolatey does offer some Qt packages, they tend to ancient or non-official.

There is a toolkit called CuteCI that let you use the Qt Installer and script the GUI headlessly. It’s janky and big though. Additionally, to pin versions, you must download 3 to 5 GB of Qt Installer to install a pinned version of Qt.

So, I found this: https://lnj.gitlab.io/post/qli-installer/

Apparently it’s possible to mimic the Qt Installer from a Python script.

To save it from possible bit-rot, I forked it to GitHub. I added parallel downloading, Windows support, and a CI system. I pushed Azure-Pipelines and each commit could cause 100 jobs to run. Across all the various platforms. Azure Pipelines did not break a sweat.

CuteCI is still extremely useful if you must use the official installer with official support. But the speeds with my fork were insane. It would normally take many minutes to install Qt with CuteCI. My QLI-Installer fork? 10 or 20 seconds.

But it turns out there’s an even better installer than mine!

https://github.com/miurahr/aqtinstall/

aqtinstall

  • Classy reusable Python structure
  • On PyPi. You can do pip install aqtinstall and install aqtinstall for immediate use.

This is also another fork of qli-installer. The CI system was a in a bit of a sorry state though. So I contributed matrix generated jobs to test aqtinstall across Mac, Windows, and Linux. @miurahr was able to dial down the installation and testing madness too and ramp it up in some places. Actual Qt applications are built to test if aqtinstall work. Additionally, there was the insight that mobile platforms generally always require the latest Qt version.

So, if you want a fast and lightweight headless Qt installer that installs from Qt Mirrors and is continually tested, use aqtinstall!

Jun 1, 2019 - 1 minute read

Non-encumbered Windows Bonjour SDK DLLs and Libs

This was one of those adventures where you find a problem and it snowballs from there.

As part of my work on the Barrier project, we needed a way to build it with the Bonjour SDK from an online CI/CD service. Bonjour is used by Barrier to discover other barrier hosts on the same network. On Windows, the SDK files are needed. Other platforms natively come with the library or similar APIs already preinstalled.

But there’s an issue. The SDK is behind a login wall on Apple’s site. Rehosting it might be possible but it might violate some agreement within the installer. Luckily, the sources are open and are under very permissive licenses.

To save some work, I reused the XBMC project’s fork and made my fork of the SDK. I didn’t change any of XBMC’s changes, if any. I added a CI system to build the necessary libraries in the same manner that Apple did.

So, if you need a free, Cloud CI system accessible Apple Windows Bonjour SDK, look at the releases in my repo! The library files are there and users can fork my build to build the stubs their own way as well.

May 30, 2019 - 1 minute read

Barrier CI System WIP

This was one of those adventures where you find a problem and it snowballs from there.

I wanted to use barrier which is a tool for sharing mouses between computers. It is a fork of Synergy, which had gone commercial.

So, I noticed on their GitHub repo that the builds for various platforms was inconsistent. OK, maybe I can help fix their CI systems.

More worringly, there was some Appveyor CI system configured. It was not configured with the appveyor.yml method and the build process was non-transparent. It was also failing.

My current CI system of choice for open source projects is Microsoft’s Azure Pipelines. For open source projects on GitHub, they’ll provide 10 parallel jobs of Mac, Linux, and Windows. Mac and Windows usually only show up as premium options or are severely rate-limited. As far as I can tell, whatever cluster Microsoft has created for their service, it’s really not limited by capacity.

The good news is that they were fans of it and I was able to move them. It’s still a work in progress. The work done here has draw in some general “improve the ecosystem” enhancements which I will elaborate on in future posts.

Mar 15, 2019 - 2 minute read

An Android Accessibility Service for Eventbrite Organizers: PassShout

The Scene:

  • It’s Saturday. And it’s like so many people have free time on that date.
  • You’re manning a convention center ticket check-in booth.
  • Your eyesight just isn’t the best. After looking at hundreds, if not thousands of them, it can be a blur.
  • You’re using Android.
  • There’s a long line and people have different pass or ticket types, sometimes even if they are in the same group.
  • You’re processing thousands of people.
  • You might not even have a table.
  • You’re using Eventbrite Organizer.

I have a tool for you! It’s called PassShout.

In addition to a chime, it’ll read out loud the pass/ticket type.

This greatly reduces operational mistakes. PassShout is part of an implementation of a method in occupational safety called Pointing and Calling. PAC has been adopted widely thoughout Asia and the NYC Metro to reduce incidents on trains systems. By calling out the type, things move faster and mistakes are reduced.

Implementation

This is the first Android app I’ve written in Kotlin. It’s amazing what the Android Accessibility API offers for third party programs to make things more accessible. Unfortunately, there does not seem to be an equivalent for iOS.

Kotlin is really easy to read and write. I can see how it’s not Scala and is closer to a usable modern Java. It’s basically the Swift of Android.

Dec 15, 2018 - 2 minute read

Gooey and PyInstaller for easy to write, distribute, and use scripts!

What is:

  • Easy to write
  • Easy to distribute and portable
  • Super Powerful
  • Cross Platform
  • Puts computer novices and professionals on the same level?

I think it’s a combination of Gooey and PyInstaller. If you put those two together, you get all those above.

  • It’s Python, so it’s easy to write. There are so many guides online and a wide assortment of libraries. Most importantly, you get libraries that have a strong eye towards ergonomics. Python is a wonderful beginner’s language that some people even call an executable psuedolanguage.
  • PyInstaller bundles applications into a single folder or exe so it’s easy to distribute.
  • You get the full force of Python’s language and its ecosystem behind you.
  • Python is cross platform, but Gooey’s GUI is also as well. Don’t be expecting to make “Destiny”-style GUIs with it. But do be expecting a surprising amount of native-ness.
  • While PyInstaller does package the application up into a portable Python script, the existing command line invokation is still left for professionals. Simply add --ignore-gooey to the list of arguments. Now it’s just a plain old script.

The Gooey PyInstaller Demo was presented at a DesertPy meetup in August of 2018. It’s a project showing the development of a simple argument parsing script all the way up to a portable GUI. Future improvements to the demo include some CI scripts and configurations. This was completed on 2018-12-15.

Demo Project Link: https://github.com/nelsonjchen/gooey-pyinstaller-demo

Nov 29, 2015 - 2 minute read

Switched to Hugo

Wow! I think it has been nearly two years since I last updated this blog. I have decided to change the blog a bit and port it over to hugo which is a static site generator written in Go. It is very fast compared to nanoc which I was using. I do not know if I quite agree that this is just as customizable as nanoc but it is clearly faster. I was not taking too much advantage of the previous Foundation based flexibility of the nanoc site and I believe it may had become a burden. Also, bitrot was beginning to seriously set in. Luckily, it is a static site but it does make some tears shed when you have to pin some really old gems and use a really old version of Ruby to build and deploy the site. Unless the kernel syscalls change dramatically, I will probably be able to compile with this version of hugo indefinitely.

Anyway, the nanoc versions troubles me no more. I am using a hugo version of the site now. As an improvement, I have also setup automatic deployment of my master branch to Google’s storage and configured CloudFlare to provide TLS and all the goodies that come with that. I guess I could also start using something like prose.io to write my posts as well. I doubt I will go that far! But, it is very nice to know that is possible.

I am now just using a theme called hyde-y which is off-the-shelf. Of course, I did customize it a little bit. hugo’s overriding system is plain and simple to use.

  • Why would anyone want summaries on the front page? Just give the complete content.
  • The titles of the posts on the front page should have the same information.

I addressed all that, and while it was a bit of a hunt, it was fairly clear what needed to be done.

I do not think I will go the full mile with customizing hugo like I did with nanoc. The complexity was hurting more than it helped. I guess I have switched over to being more minimalist since I concieved the original site. Foundation is great if you need the kitchen sink and everything but it is really overkill. It was also another maintenance burden with its rather relentless upgrade schedule. I am taking it slow from here on out.

Jan 6, 2014 - 1 minute read

Running Windows XP using Libvirt inside Ubuntu inside VMWare Fusion

So I’m investigating running Windows XP inside libvirt in Ubuntu on my MacBook for continuous integration testing purposes with Jenkins. I tried for many hours to get it working but Windows would not boot up or it would get stuck on the NTLDR cannot be found issue. As it turns out, I did not enable “Enable hypervisor applications in this virtual machine”. I was under the assumption that I would not need to check that box as I wrongly assumed qemu would handle all the needed machine translation and that an error with bootup is not the fault of lacking the ability to use a CPU feature to emulate something or the disk emulation being borked. With five different ISOs of various pedigrees, I tried installing Windows XP with virt-manager and all of them failed to bootup with various disk image formats like qcow2 or raw. It was only until I checked that checkbox did it then work.

That’s my surprise for today. I really wished I checked that checkbox earlier! It determines if your machine can bootup and only using qemu without kvm will not cut it for booting Windows XP inside libvirt inside Ubuntu inside VMWare Fusion.