Add boop support

This commit is contained in:
Natsumi
2024-06-08 01:46:45 +12:00
parent 831e83061d
commit f8b6396e04
7 changed files with 243 additions and 19 deletions

View File

@@ -3562,6 +3562,18 @@ speechSynthesis.getVoices();
} else if (json.title) {
json.message = json.title;
}
if (json.type === 'boop') {
if (!json.imageUrl && json.details?.emojiId?.startsWith('file_')) {
// JANK: create image url from fileId
json.imageUrl = `https://api.vrchat.cloud/api/1/file/${json.details.emojiId}/${json.details.emojiVersion}`;
}
if (!json.details?.emojiId?.startsWith('file_')) {
// JANK: get emoji name from emojiId
json.message = `${json.senderUsername} Booped you! with ${$app.getEmojiName(json.details.emojiId)}`;
} else {
json.message = `${json.senderUsername} Booped you! with custom emoji`;
}
}
this.$emit('NOTIFICATION', {
json,
params: {
@@ -3604,14 +3616,20 @@ speechSynthesis.getVoices();
return this.call(`notifications/${params.notificationId}/respond`, {
method: 'POST',
params
}).then((json) => {
var args = {
json,
params
};
this.$emit('NOTIFICATION:RESPONSE', args);
return args;
});
})
.then((json) => {
var args = {
json,
params
};
this.$emit('NOTIFICATION:RESPONSE', args);
return args;
})
.catch((err) => {
// something went wrong, lets assume it's already expired
this.$emit('NOTIFICATION:HIDE', { params });
throw err;
});
};
API.$on('NOTIFICATION:RESPONSE', function (args) {
@@ -6806,6 +6824,9 @@ speechSynthesis.getVoices();
`${noty.previousDisplayName} changed their name to ${noty.displayName}`
);
break;
case 'boop':
this.speak(noty.message);
break;
case 'groupChange':
this.speak(`${noty.senderUsername} ${noty.message}`);
break;
@@ -7037,6 +7058,9 @@ speechSynthesis.getVoices();
image
);
break;
case 'boop':
AppApi.XSNotification('VRCX', noty.message, timeout, image);
break;
case 'groupChange':
AppApi.XSNotification(
'VRCX',
@@ -7372,6 +7396,16 @@ speechSynthesis.getVoices();
image
);
break;
case 'boop':
AppApi.OVRTNotification(
playOvrtHudNotifications,
playOvrtWristNotifications,
'VRCX',
noty.message,
timeout,
image
);
break;
case 'groupChange':
AppApi.OVRTNotification(
playOvrtHudNotifications,
@@ -7737,6 +7771,13 @@ speechSynthesis.getVoices();
image
);
break;
case 'boop':
AppApi.DesktopNotification(
noty.senderUsername,
noty.message,
image
);
break;
case 'groupChange':
AppApi.DesktopNotification(
noty.senderUsername,
@@ -15827,6 +15868,7 @@ speechSynthesis.getVoices();
Unfriend: 'On',
DisplayName: 'VIP',
TrustLevel: 'VIP',
boop: 'Off',
groupChange: 'On',
'group.announcement': 'On',
'group.informative': 'On',
@@ -15868,6 +15910,7 @@ speechSynthesis.getVoices();
Unfriend: 'On',
DisplayName: 'Friends',
TrustLevel: 'Friends',
boop: 'On',
groupChange: 'On',
'group.announcement': 'On',
'group.informative': 'On',
@@ -15941,6 +15984,10 @@ speechSynthesis.getVoices();
$app.data.sharedFeedFilters.noty['group.transfer'] = 'On';
$app.data.sharedFeedFilters.wrist['group.transfer'] = 'On';
}
if (!$app.data.sharedFeedFilters.noty.boop) {
$app.data.sharedFeedFilters.noty.boop = 'Off';
$app.data.sharedFeedFilters.wrist.boop = 'On';
}
$app.data.trustColor = JSON.parse(
await configRepository.getString(
@@ -18539,6 +18586,8 @@ speechSynthesis.getVoices();
this.showGalleryDialog();
} else if (command === 'Invite To Group') {
this.showInviteGroupDialog('', D.id);
} else if (command === 'Send Boop') {
this.showSendBoopDialog(D.id);
} else if (command === 'Hide Avatar') {
if (D.isHideAvatar) {
this.setPlayerModeration(D.id, 0);
@@ -20527,11 +20576,8 @@ speechSynthesis.getVoices();
D.shortName = '';
D.secureOrShortName = '';
API.getGroupPermissions({ userId: API.currentUser.id });
if (D.selectedTab === '0') {
this.buildInstance();
} else {
this.buildLegacyInstance();
}
this.buildInstance();
this.buildLegacyInstance();
this.updateNewInstanceDialog();
D.visible = true;
};
@@ -24219,8 +24265,6 @@ speechSynthesis.getVoices();
// YouTube API
$app.data.youTubeApiKey = '';
$app.data.youTubeApiDialog = {
visible: false
};
@@ -24875,11 +24919,22 @@ speechSynthesis.getVoices();
$app.galleryTable = [];
});
$app.methods.showGalleryDialog = function () {
$app.methods.showGalleryDialog = function (pageNum) {
this.$nextTick(() => adjustDialogZ(this.$refs.galleryDialog.$el));
this.galleryDialogVisible = true;
this.refreshGalleryTable();
this.refreshVRCPlusIconsTable();
this.refreshEmojiTable();
workerTimers.setTimeout(() => this.setGalleryTab(pageNum), 100);
};
$app.methods.setGalleryTab = function (pageNum) {
if (
typeof pageNum !== 'undefined' &&
typeof this.$refs.galleryTabs !== 'undefined'
) {
this.$refs.galleryTabs.setCurrentName(`${pageNum}`);
}
};
$app.methods.refreshGalleryTable = function () {
@@ -32588,6 +32643,7 @@ speechSynthesis.getVoices();
});
// #endregion
// #region | Settings: Zoom
$app.data.zoomLevel = ((await AppApi.GetZoom()) + 10) * 10;
@@ -32599,6 +32655,80 @@ speechSynthesis.getVoices();
AppApi.SetZoom(this.zoomLevel / 10 - 10);
};
// #endregion
// #region | Boops
API.sendBoop = function (params) {
return this.call(`users/${params.userId}/boop`, {
method: 'POST',
params
}).then((json) => {
var args = {
json,
params
};
this.$emit('BOOP:SEND', args);
return args;
});
};
$app.methods.sendBoop = function () {
var D = this.sendBoopDialog;
this.dismissBoop(D.userId);
API.sendBoop({ userId: D.userId, emojiId: D.fileId });
D.visible = false;
};
$app.methods.dismissBoop = function (userId) {
// JANK: This is a hack to remove boop notifications when responding
var array = this.notificationTable.data;
for (var i = array.length - 1; i >= 0; i--) {
var ref = array[i];
if (
ref.type !== 'boop' ||
ref.$isExpired ||
ref.senderUserId !== userId
) {
continue;
}
API.sendNotificationResponse({
notificationId: ref.id,
responseType: 'delete',
responseData: ''
});
}
};
$app.data.sendBoopDialog = {
visible: false,
userId: '',
fileId: ''
};
$app.methods.showSendBoopDialog = function (userId) {
this.$nextTick(() => adjustDialogZ(this.$refs.sendBoopDialog.$el));
var D = this.sendBoopDialog;
D.userId = userId;
D.visible = true;
if (this.emojiTable.length === 0 && API.currentUser.$isVRCPlus) {
this.refreshEmojiTable();
}
};
$app.methods.getEmojiValue = function (emojiName) {
return `vrchat_${emojiName.replace(/ /g, '_').toLowerCase()}`;
};
$app.methods.getEmojiName = function (emojiValue) {
// uppercase first letter of each word
return emojiValue
.replace('vrchat_', '')
.replace(/_/g, ' ')
.replace(/\b\w/g, (l) => l.toUpperCase());
};
// #endregion
$app = new Vue($app);
window.$app = $app;
})();

View File

@@ -300,6 +300,7 @@ html
el-dropdown-item(v-else-if="userDialog.outgoingRequest" icon="el-icon-close" command="Cancel Friend Request") {{ $t('dialog.user.actions.cancel_friend_request') }}
el-dropdown-item(v-else icon="el-icon-plus" command="Send Friend Request") {{ $t('dialog.user.actions.send_friend_request') }}
el-dropdown-item(icon="el-icon-message" command="Invite To Group") {{ $t('dialog.user.actions.invite_to_group') }}
el-dropdown-item(icon="el-icon-thumb" command="Send Boop") {{ $t('dialog.user.actions.send_boop') }}
el-dropdown-item(icon="el-icon-s-custom" command="Show Avatar Author" divided) {{ $t('dialog.user.actions.show_avatar_author') }}
el-dropdown-item(icon="el-icon-s-custom" command="Show Fallback Avatar Details") {{ $t('dialog.user.actions.show_fallback_avatar') }}
el-dropdown-item(icon="el-icon-tickets" command="Previous Instances") {{ $t('dialog.user.actions.show_previous_instances') }}
@@ -1895,6 +1896,11 @@ html
el-radio-button(label="Off") {{ $t('dialog.shared_feed_filters.off') }}
el-radio-button(label="VIP") {{ $t('dialog.shared_feed_filters.favorite') }}
el-radio-button(label="Friends") {{ $t('dialog.shared_feed_filters.friends') }}
.toggle-item
span.toggle-name Boop
el-radio-group(v-model="sharedFeedFilters.noty.boop" size="mini" @change="saveSharedFeedFilters")
el-radio-button(label="Off") {{ $t('dialog.shared_feed_filters.off') }}
el-radio-button(label="On") {{ $t('dialog.shared_feed_filters.on') }}
.toggle-item
span.toggle-name Group Change
el-tooltip(placement="top" style="margin-left:5px" content="When you've left or been kicked from a group, group name changed, group owner changed, role added/removed")
@@ -2144,6 +2150,11 @@ html
el-radio-button(label="Off") {{ $t('dialog.shared_feed_filters.off') }}
el-radio-button(label="VIP") {{ $t('dialog.shared_feed_filters.favorite') }}
el-radio-button(label="Friends") {{ $t('dialog.shared_feed_filters.friends') }}
.toggle-item
span.toggle-name Boop
el-radio-group(v-model="sharedFeedFilters.wrist.boop" size="mini" @change="saveSharedFeedFilters")
el-radio-button(label="Off") {{ $t('dialog.shared_feed_filters.off') }}
el-radio-button(label="On") {{ $t('dialog.shared_feed_filters.on') }}
.toggle-item
span.toggle-name Group Change
el-tooltip(placement="top" style="margin-left:5px" content="When you've left or been kicked from a group, group name changed, group owner changed, role added/removed")
@@ -2452,7 +2463,7 @@ html
span(style="padding-bottom:10px") {{ $t('dialog.gallery_icons.description') }}
br
br
el-tabs(type="card")
el-tabs(type="card" ref="galleryTabs")
el-tab-pane(v-if="galleryDialogVisible" v-loading="galleryDialogGalleryLoading")
span(slot="label") {{ $t('dialog.gallery_icons.gallery') }}
span(style="color:#909399;font-size:12px;margin-left:5px") {{ galleryTable.length }}/64
@@ -3267,6 +3278,61 @@ html
el-button(v-if="groupPostEditDialog.postId" size="small" @click="editGroupPost") {{ $t('dialog.group_post_edit.edit_post') }}
el-button(v-else size="small" @click="createGroupPost") {{ $t('dialog.group_post_edit.create_post') }}
//- dialog: send boop
el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="sendBoopDialog" :visible.sync="sendBoopDialog.visible" :title="$t('dialog.boop_dialog.header')" width="450px")
div(v-if="sendBoopDialog.visible")
el-button(size="small" @click="showGalleryDialog(2)") {{ $t('dialog.boop_dialog.emoji_manager') }}
br
br
el-select(v-model="sendBoopDialog.userId" :placeholder="$t('dialog.new_instance.instance_creator_placeholder')" filterable style="width:100%")
el-option-group(v-if="friendsGroup0.length" :label="$t('side_panel.favorite')")
el-option.x-friend-item(v-for="friend in friendsGroup0" :key="friend.id" :label="friend.name" :value="friend.id" style="height:auto")
template(v-if="friend.ref")
.avatar(:class="userStatusClass(friend.ref)")
img(v-lazy="userImage(friend.ref)")
.detail
span.name(v-text="friend.ref.displayName" :style="{'color':friend.ref.$userColour}")
span(v-else v-text="friend.id")
el-option-group(v-if="friendsGroup1.length" :label="$t('side_panel.online')")
el-option.x-friend-item(v-for="friend in friendsGroup1" :key="friend.id" :label="friend.name" :value="friend.id" style="height:auto")
template(v-if="friend.ref")
.avatar(:class="userStatusClass(friend.ref)")
img(v-lazy="userImage(friend.ref)")
.detail
span.name(v-text="friend.ref.displayName" :style="{'color':friend.ref.$userColour}")
span(v-else v-text="friend.id")
el-option-group(v-if="friendsGroup2.length" :label="$t('side_panel.active')")
el-option.x-friend-item(v-for="friend in friendsGroup2" :key="friend.id" :label="friend.name" :value="friend.id" style="height:auto")
template(v-if="friend.ref")
.avatar
img(v-lazy="userImage(friend.ref)")
.detail
span.name(v-text="friend.ref.displayName" :style="{'color':friend.ref.$userColour}")
span(v-else v-text="friend.id")
el-option-group(v-if="friendsGroup3.length" :label="$t('side_panel.offline')")
el-option.x-friend-item(v-for="friend in friendsGroup3" :key="friend.id" :label="friend.name" :value="friend.id" style="height:auto")
template(v-if="friend.ref")
.avatar
img(v-lazy="userImage(friend.ref)")
.detail
span.name(v-text="friend.ref.displayName" :style="{'color':friend.ref.$userColour}")
span(v-else v-text="friend.id")
br
br
el-select(v-model="sendBoopDialog.fileId" clearable :placeholder="$t('dialog.boop_dialog.select_emoji')" size="small" style="width:100%" popper-class="max-height-el-select")
el-option-group(:label="$t('dialog.boop_dialog.my_emojis')")
el-option(v-if="image.versions && image.versions.length > 0" v-for="image in emojiTable" :key="image.id" :value="image.id" style="width:100%;height:100%")
.vrcplus-icon(v-if="image.versions[image.versions.length - 1].file.url" style="overflow:hidden;width:200px;height:200px;padding:10px")
template(v-if="image.frames")
.avatar(:style="generateEmojiStyle(image.versions[image.versions.length - 1].file.url, image.framesOverTime, image.frames, image.animationStyle)")
template(v-else)
img.avatar(v-lazy="image.versions[image.versions.length - 1].file.url" style="width:200px;height:200px")
el-option-group(:label="$t('dialog.boop_dialog.default_emojis')")
el-option(v-for="emojiName in photonEmojis" :key="emojiName" :value="getEmojiValue(emojiName)" style="width:100%;height:100%")
span(v-text="emojiName")
template(#footer)
el-button(size="small" @click="sendBoopDialog.visible = false") {{ $t('dialog.boop_dialog.cancel') }}
el-button(size="small" @click="sendBoop" :disabled="!sendBoopDialog.userId || !sendBoopDialog.fileId") {{ $t('dialog.boop_dialog.send') }}
//- el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="templateDialog" :visible.sync="templateDialog.visible" :title="$t('dialog.template_dialog.header')" width="450px")

View File

@@ -560,6 +560,7 @@
"request_invite": "Request Invite",
"request_invite_with_message": "Request Invite With Message",
"invite_to_group": "Invite To Group",
"send_boop": "Send Boop",
"manage_gallery_icon": "Manage Photos/Icons/Emojis",
"accept_friend_request": "Accept Friend Request",
"decline_friend_request": "Decline Friend Request",
@@ -1365,6 +1366,15 @@
"cancel": "Cancel",
"create_post": "Create Post",
"edit_post": "Edit Post"
},
"boop_dialog": {
"header": "Boop",
"emoji_manager": "Emoji Manager",
"select_emoji": "Select Emoji",
"my_emojis": "My Emojis",
"default_emojis": "Default Emojis",
"cancel": "Cancel",
"send": "Send"
}
},
"prompt": {

View File

@@ -4,7 +4,7 @@ mixin notificationsTab()
template(#tool)
div(style="margin:0 0 10px;display:flex;align-items:center")
el-select(v-model="notificationTable.filters[0].value" @change="saveTableFilters" multiple clearable collapse-tags style="flex:1" :placeholder="$t('view.notification.filter_placeholder')")
el-option(v-once v-for="type in ['requestInvite', 'invite', 'requestInviteResponse', 'inviteResponse', 'friendRequest', 'hiddenFriendRequest', 'message', 'groupChange', 'group.announcement', 'group.informative', 'group.invite', 'group.joinRequest', 'group.transfer', 'group.queueReady', 'moderation.warning.group', 'instance.closed']" :key="type" :label="type" :value="type")
el-option(v-once v-for="type in ['requestInvite', 'invite', 'requestInviteResponse', 'inviteResponse', 'friendRequest', 'hiddenFriendRequest', 'message', 'boop', 'groupChange', 'group.announcement', 'group.informative', 'group.invite', 'group.joinRequest', 'group.transfer', 'group.queueReady', 'moderation.warning.group', 'instance.closed']" :key="type" :label="type" :value="type")
el-input(v-model="notificationTable.filters[1].value" :placeholder="$t('view.notification.search_placeholder')" style="flex:none;width:150px;margin:0 10px")
el-tooltip(placement="bottom" :content="$t('view.notification.refresh_tooltip')" :disabled="hideTooltips")
el-button(type="default" :loading="API.isNotificationsLoading" @click="API.refreshNotifications()" icon="el-icon-refresh" circle style="flex:none")
@@ -79,8 +79,10 @@ mixin notificationsTab()
el-button(v-else-if="response.icon === 'cancel'" type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, response.type)")
el-button(v-else-if="response.icon === 'ban'" type="text" icon="el-icon-circle-close" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, response.type)")
el-button(v-else-if="response.icon === 'bell-slash'" type="text" icon="el-icon-bell" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, response.type)")
el-button(v-else-if="response.icon === 'reply' && scope.row.type === 'boop'" type="text" icon="el-icon-chat-line-square" size="mini" style="margin-left:5px" @click="showSendBoopDialog(scope.row.senderUserId)")
el-button(v-else-if="response.icon === 'reply'" type="text" icon="el-icon-chat-line-square" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, response.type)")
el-button(v-else type="text" icon="el-icon-collection-tag" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, response.type)")
template(v-if="scope.row.type !== 'requestInviteResponse' && scope.row.type !== 'inviteResponse' && scope.row.type !== 'message' && scope.row.type !== 'groupChange' && !scope.row.type.includes('group.') && !scope.row.type.includes('moderation.') && !scope.row.type.includes('instance.')")
template(v-if="scope.row.type !== 'requestInviteResponse' && scope.row.type !== 'inviteResponse' && scope.row.type !== 'message' && scope.row.type !== 'boop' && scope.row.type !== 'groupChange' && !scope.row.type.includes('group.') && !scope.row.type.includes('moderation.') && !scope.row.type.includes('instance.')")
el-tooltip(placement="top" content="Decline" :disabled="hideTooltips")
el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="hideNotification(scope.row)")
template(v-if="scope.row.type === 'group.queueReady'")

View File

@@ -821,6 +821,9 @@ class Database {
...row.details
}
};
if (entry.imageUrl && !entry.details.imageUrl) {
entry.details.imageUrl = entry.imageUrl;
}
var expired = 0;
if (row.$isExpired) {
expired = 1;

View File

@@ -599,6 +599,9 @@ Vue.component('marquee-text', MarqueeText);
case 'DisplayName':
text = `<strong>${noty.previousDisplayName}</strong> changed their name to ${noty.displayName}`;
break;
case 'boop':
text = noty.message;
break;
case 'groupChange':
text = `<strong>${noty.senderUsername}</strong> ${noty.message}`;
break;

View File

@@ -119,6 +119,11 @@ html
span.extra
span.time {{ feed.created_at | formatDate }}
| 🤝 #[span.name(v-text="feed.displayName")] {{ feed.previousTrustLevel }} #[i.el-icon-right] {{ feed.trustLevel }}
div(v-else-if="feed.type === 'boop'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
span.time {{ feed.created_at | formatDate }}
| 👉 #[span.name(v-text="feed.message")]
div(v-else-if="feed.type === 'groupChange'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
@@ -344,6 +349,11 @@ html
span.extra
span.time {{ feed.created_at | formatDate }}
| #[span.name(v-text="feed.displayName")] trust level is now {{ feed.trustLevel }}
div(v-else-if="feed.type === 'boop'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
span.time {{ feed.created_at | formatDate }}
| #[span.name(v-text="feed.message")]
div(v-else-if="feed.type === 'groupChange'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra