Skip to content

Commit

Permalink
Merge pull request #11 from uzair-ashraf/dev
Browse files Browse the repository at this point in the history
1.0.0 Release
  • Loading branch information
shadorki authored Jan 15, 2023
2 parents 4e546bb + df17842 commit dbd0b7f
Show file tree
Hide file tree
Showing 17 changed files with 91 additions and 65 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ jobs:
- name: Deploy Unity Package
uses: actions/upload-artifact@v3
with:
name: bracelet
path: ./bracelet.unitypackage
name: OSC_Bracelet
path: ./OSC_Bracelet.unitypackage
if-no-files-found: error


Expand All @@ -60,8 +60,8 @@ jobs:

- uses: actions/download-artifact@v3
with:
name: bracelet
path: artifact/bracelet
name: OSC_Bracelet
path: artifact/OSC_Bracelet

- name: Display structure of downloaded files
run: ls -R
Expand All @@ -72,4 +72,4 @@ jobs:
with:
files: |
artifact/vrc-discord-osc/vrc-discord-osc.exe
artifact/bracelet/bracelet.unitypackage
artifact/OSC_Bracelet/OSC_Bracelet.unitypackage
Binary file added OSC_Bracelet.unitypackage
Binary file not shown.
34 changes: 7 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ A python application for VRChat players to receive discord notifications on thei
- Application - Shadoki
- Bracelet Model - Sorru
- Shader Configuration - Harumodoki
- [Avatars 3.0 Manager](https://github.com/VRLabs/Avatars-3.0-Manager) - For the animator merging script

## Table of Contents

Expand Down Expand Up @@ -72,7 +73,7 @@ This doc assumes you have a Unity Package with an avatar already set up to be pu

1. Import the unity package from the latest release.

1. If the materials in `Assets/OSC Braclet/MAT` are purple, set them all to Poiyomi and the settings should transfer
1. If the materials in `Assets/OSC Bracelet/MAT` are purple, set them all to Poiyomi and the settings should transfer

1. Place the prefab in your scene

Expand All @@ -82,39 +83,18 @@ This doc assumes you have a Unity Package with an avatar already set up to be pu
<img src="./img/screenshot-bone.png">
</p>

1. In your avatar's expression parameters, create a new parameter with the following name: `osc_discord_band` of type `Bool`.

1. In your tool bar click `Shadoki > Discord Bracelet`

<p align="center">
<img src="./img/screenshot-avatar-parameters.png">
<img src="./img/screenshot-unity-tool.png">
</p>

1. In your avatar's FX layer animator, create a new animation parameter with the following name: `osc_discord_band` of type `Bool`.
1. Add the Discord Bracelet, Controllers, and Animations into the editor window and press `Apply`. Keep an eye in the debug console for possible errors.

<p align="center">
<img src="./img/screenshot-animator-parameters.png">
<img src="./img/screenshot-bracelet-tool.png">
</p>

1. In your avatar's FX layer animator, under the `Layers` tab create a new layer and set the `Weight` to 1:


<p align="center">
<img src="./img/screenshot-animator-layer.png">
</p>

At the time of writing this readme I haven't been able to figure out how to add an animation into the unity package that is easily transferable to your avatar. In the meantime till I figure out how to automate that, you will have to create your own animation.

1. Create an animation that is enabled when the `osc_discord_band` parameter is set true. Here are some screenshot examples of my setup:
<p align="center">
<img src="./img/screenshot-animator-example-1.png">
</p>
<p align="center">
<img src="./img/screenshot-animator-example-2.png">
</p>
<p align="center">
<img src="./img/screenshot-animator-example-3.png">
</p>
1. Your new FX layer should be present in `OSC Bracelet/Animations`. Drag and drop it into your avatar's FX Layer.

1. If you are having trouble getting the OSC program to communicate with VRChat, checkout this troubleshooting doc that Wizard wrote for their TTS App: https://github.com/VRCWizard/TTS-Voice-Wizard/wiki/OSC-Troubleshooting


Binary file removed bracelet.unitypackage
Binary file not shown.
57 changes: 47 additions & 10 deletions discord_band.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from winsdk.windows.ui.notifications import UserNotification
from pythonosc.udp_client import SimpleUDPClient
from threading import Thread
import time
import osc_parameters


class DiscordBand:
Expand All @@ -12,7 +14,35 @@ def __init__(self):
self.thread = Thread(target=self.run)
self.thread.start()

def run(self):
def is_call_notification(self, notification: UserNotification) -> bool:
try:
binding = notification.notification.visual.get_binding(
"ToastGeneric")
if binding is None:
return False
elements = binding.get_text_elements()
if elements is None:
return False
if elements.size != 2:
return False
username = elements.get_at(0)
message = elements.get_at(1)
expected = f'{username.text} started a call.'
return message.text == expected
except AttributeError:
return False

def is_discord_notification(self, notification: UserNotification) -> bool:
try:
if hasattr(notification, "app_info"):
app_name = notification.app_info.display_info.display_name
if app_name == "Discord":
return True
return False
except AttributeError:
return False

def run(self) -> None:
while True:
if self.is_disposing:
return
Expand All @@ -21,21 +51,28 @@ def run(self):
self.disable_timer -= 1
time.sleep(1)

def dispose(self):
def dispose(self) -> None:
self.is_disposing = True
self.thread.join()

def enable(self):
print("Enabling discord notification")
self.osc_client.send_message(
"/avatar/parameters/osc_discord_band", True)
def enable_band_notification(self) -> None:
print("Enabling band notification")
self.osc_client.send_message(osc_parameters.DISCORD_BAND, True)
self.is_enabled = True
self.disable_timer = 5

def enable_call_notification(self) -> None:
print("Enabling call notification")
# Forcibly disabling the band notification because call takes higher priority
self.osc_client.send_message(osc_parameters.DISCORD_BAND, False)
self.osc_client.send_message(osc_parameters.DISCORD_CALL, True)
self.is_enabled = True
self.disable_timer = 5

def disable(self):
print("Disabling discord notification")
self.osc_client.send_message(
"/avatar/parameters/osc_discord_band", False)
def disable(self) -> None:
print("Disabling discord notifications")
self.osc_client.send_message(osc_parameters.DISCORD_BAND, False)
self.osc_client.send_message(osc_parameters.DISCORD_CALL, False)
self.is_enabled = False
self.disable_timer = 0

Expand Down
Binary file removed img/screenshot-animator-example-1.png
Binary file not shown.
Binary file removed img/screenshot-animator-example-2.png
Binary file not shown.
Binary file removed img/screenshot-animator-example-3.png
Binary file not shown.
Binary file removed img/screenshot-animator-layer.png
Binary file not shown.
Binary file removed img/screenshot-animator-parameters.png
Binary file not shown.
Binary file removed img/screenshot-avatar-parameters.png
Binary file not shown.
Binary file modified img/screenshot-bone.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/screenshot-bracelet-tool.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/screenshot-unity-tool.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 8 additions & 17 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
import asyncio
from discord_band import DiscordBand
from windows import Windows
from winsdk.windows.ui.notifications.management import UserNotificationListener, UserNotificationListenerAccessStatus
from winsdk.windows.foundation.metadata import ApiInformation
from winsdk.windows.ui.notifications import UserNotification


def handler(discord_band: DiscordBand):
def closure(notification):
if hasattr(notification, "app_info"):
app_name = notification.app_info.display_info.display_name
if app_name == "Discord":
discord_band.enable()
def closure(notification: UserNotification) -> None:
if discord_band.is_discord_notification(notification):
if discord_band.is_call_notification(notification):
discord_band.enable_call_notification()
else:
discord_band.enable_band_notification()
return closure


async def init(discord_band: DiscordBand, windows: Windows):
if not ApiInformation.is_type_present("Windows.UI.Notifications.Management.UserNotificationListener"):
print("UserNotificationListener is not supported on this device.")
if not await windows.can_read_notifications():
exit()

listener = UserNotificationListener.get_current()
accessStatus = await listener.request_access_async()

if accessStatus != UserNotificationListenerAccessStatus.ALLOWED:
print("Access to UserNotificationListener is not allowed.")
exit()

windows.add_notification_listener(handler(discord_band))
await windows.run()

Expand Down
2 changes: 2 additions & 0 deletions osc_parameters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DISCORD_BAND = "/avatar/parameters/osc_discord_band"
DISCORD_CALL = "/avatar/parameters/osc_discord_call"
28 changes: 22 additions & 6 deletions windows.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
from winsdk.windows.ui.notifications.management import UserNotificationListener
from winsdk.windows.ui.notifications import NotificationKinds
from winsdk.windows.ui.notifications.management import UserNotificationListener, UserNotificationListenerAccessStatus
from winsdk.windows.ui.notifications import NotificationKinds, UserNotification
from winsdk.windows.foundation.metadata import ApiInformation
import time


class Windows:
def __init__(self):
self.seen_ids = set()
self.listeners = []

async def run(self):
async def can_read_notifications(self) -> bool:
if not ApiInformation.is_type_present("Windows.UI.Notifications.Management.UserNotificationListener"):
print("UserNotificationListener is not supported on this device.")
return False

listener = UserNotificationListener.get_current()
accessStatus = await listener.request_access_async()

if accessStatus != UserNotificationListenerAccessStatus.ALLOWED:
print("Access to UserNotificationListener is not allowed.")
return False

return True

async def run(self) -> None:
# Call here before loop in order to fill seen_ids
await self.get_new_notifications()
while True:
Expand All @@ -16,7 +32,7 @@ async def run(self):
self.dispatch(notification)
time.sleep(.3)

async def get_new_notifications(self):
async def get_new_notifications(self) -> list[UserNotification]:
listener = UserNotificationListener.get_current()
notifications = await listener.get_notifications_async(NotificationKinds.TOAST)
new_notifications = []
Expand All @@ -27,9 +43,9 @@ async def get_new_notifications(self):
self.seen_ids.add(notification.id)
return new_notifications

def dispatch(self, notification):
def dispatch(self, notification) -> None:
for listener in self.listeners:
listener(notification)

def add_notification_listener(self, listener):
def add_notification_listener(self, listener) -> None:
self.listeners.append(listener)

0 comments on commit dbd0b7f

Please sign in to comment.