Merge branch 'GameLogSQLite'

This commit is contained in:
Natsumi
2021-09-26 22:32:34 +13:00
12 changed files with 2594 additions and 1752 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -173,6 +173,7 @@ html
span(v-else-if="scope.row.status === 'busy'") Do Not Disturb
span(v-else) Offline
i.x-user-status(:class="statusClass(scope.row.status)")
span
span(v-text="scope.row.statusDescription")
template(v-else-if="scope.row.type === 'Avatar'")
avatar-info(:imageurl="scope.row.currentAvatarImageUrl" :userid="scope.row.userId" :hintownerid="scope.row.ownerId" :hintavatarname="scope.row.avatarName")
@@ -183,10 +184,10 @@ html
template(#tool)
div(style="margin:0 0 10px;display:flex;align-items:center")
el-select(v-model="gameLogTable.filters[0].value" @change="saveTableFilters" multiple clearable collapse-tags style="flex:1" placeholder="Filter")
el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'PortalSpawn', 'Event', 'VideoPlay']" :key="type" :label="type" :value="type")
el-option(v-once v-for="type in ['Location', 'OnPlayerJoined', 'OnPlayerLeft', 'PortalSpawn', 'AvatarChange', 'Event', 'VideoPlay']" :key="type" :label="type" :value="type")
el-input(v-model="gameLogTable.filters[1].value" placeholder="Search" style="flex:none;width:150px;margin:0 10px")
el-tooltip(placement="bottom" content="Reset game log" :disabled="hideTooltips")
el-button(type="default" @click="resetGameLog()" icon="el-icon-refresh" circle style="flex:none")
el-tooltip(placement="bottom" content="Reload game log" :disabled="hideTooltips")
el-button(type="default" @click="refreshEntireGameLog" icon="el-icon-refresh" circle style="flex:none")
el-table-column(label="Date" prop="created_at" sortable="custom" width="90")
template(v-once #default="scope")
el-tooltip(placement="right")
@@ -194,17 +195,28 @@ html
span {{ scope.row.created_at | formatDate('YYYY-MM-DD HH24:MI:SS') }}
span {{ scope.row.created_at | formatDate('MM-DD HH24:MI') }}
el-table-column(label="Type" prop="type" width="120")
template(v-once #default="scope")
span.x-link(v-if="scope.row.location && scope.row.type !== 'Location'" v-text="scope.row.type" @click="showWorldDialog(scope.row.location)")
span(v-else v-text="scope.row.type")
el-table-column(label="User" prop="displayName" width="160")
template(v-once #default="scope")
span.x-link(v-text="scope.row.displayName" @click="lookupUser(scope.row)")
el-table-column(label="Detail" prop="data")
template(v-once #default="scope")
location(v-if="scope.row.type === 'Location'" :location="scope.row.data[0]" :hint="scope.row.data[1]")
location(v-if="scope.row.type === 'Location'" :location="scope.row.location" :hint="scope.row.worldName")
location(v-else-if="scope.row.type === 'PortalSpawn'" :location="scope.row.instanceId" :hint="scope.row.worldName")
template(v-else-if="scope.row.type === 'AvatarChange'")
span.x-link(@click="showUserDialog(scope.row.authorId)" v-text="scope.row.name")
template(v-if="scope.row.description && scope.row.name !== scope.row.description")
| - {{ scope.row.description }}
template(v-else-if="scope.row.type === 'Event'")
span(v-text="scope.row.data")
template(v-else-if="scope.row.type === 'VideoPlay'")
span.x-link(v-text="scope.row.data" @click="openExternalLink(scope.row.data)")
template(v-if="scope.row.displayName")
span.x-link(@click="lookupUser(scope.row.displayName)") ({{ scope.row.displayName }})
template(v-else-if="scope.row.type === 'Notification'")
span.x-link(v-else v-text="scope.row.data" @click="lookupUser(scope.row.data)")
span(v-if="scope.row.videoId") {{ scope.row.videoId }}:
span.x-link(v-if="scope.row.videoName" @click="openExternalLink(scope.row.videoUrl)" v-text="scope.row.videoName")
span.x-link(v-else @click="openExternalLink(scope.row.videoUrl)" v-text="scope.row.videoUrl")
template(v-else-if="scope.row.type === 'Notification' || scope.row.type === 'OnPlayerJoined' || scope.row.type === 'OnPlayerLeft'")
span.x-link(v-else v-text="scope.row.data")
//- search
.x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'search'")
@@ -458,15 +470,26 @@ html
span(v-else-if='scope.row.details && scope.row.details.inviteMessage' v-text="scope.row.details.inviteMessage")
span(v-else-if='scope.row.details && scope.row.details.requestMessage' v-text="scope.row.details.requestMessage")
span(v-else-if='scope.row.details && scope.row.details.responseMessage' v-text="scope.row.details.responseMessage")
el-table-column(label="Action" width="80" align="right")
el-table-column(label="Action" width="100" align="right")
template(v-once #default="scope")
template(v-if="scope.row.senderUserId !== API.currentUser.id")
el-button(v-if="scope.row.type === 'friendRequest'" type="text" icon="el-icon-check" size="mini" @click="acceptNotification(scope.row)")
el-button(v-else-if="scope.row.type === 'invite'" type="text" icon="el-icon-chat-line-square" size="mini" @click="showSendInviteResponseDialog(scope.row)")
template(v-if="scope.row.senderUserId !== API.currentUser.id && !scope.row.$isExpired")
template(v-if="scope.row.type === 'friendRequest'")
el-tooltip(placement="top" content="Accept" :disabled="hideTooltips")
el-button(type="text" icon="el-icon-check" size="mini" @click="acceptNotification(scope.row)")
template(v-else-if="scope.row.type === 'invite'")
el-tooltip(placement="top" content="Decline with message" :disabled="hideTooltips")
el-button(type="text" icon="el-icon-chat-line-square" size="mini" @click="showSendInviteResponseDialog(scope.row)")
template(v-else-if="scope.row.type === 'requestInvite'")
el-button(v-if="lastLocation.location && isGameRunning && !checkCanInvite(lastLocation.location)" type="text" icon="el-icon-check" size="mini" @click="acceptRequestInvite(scope.row)")
el-button(type="text" icon="el-icon-chat-line-square" size="mini" style="margin-left:5px" @click="showSendInviteRequestResponseDialog(scope.row)")
el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="hideNotification(scope.row)")
template(v-if="lastLocation.location && isGameRunning && !checkCanInvite(lastLocation.location)")
el-tooltip(placement="top" content="Invite" :disabled="hideTooltips")
el-button(type="text" icon="el-icon-check" size="mini" @click="acceptRequestInvite(scope.row)")
el-tooltip(placement="top" content="Decline with message" :disabled="hideTooltips")
el-button(type="text" icon="el-icon-chat-line-square" size="mini" style="margin-left:5px" @click="showSendInviteRequestResponseDialog(scope.row)")
template(v-if="scope.row.type !== 'requestInviteResponse' && scope.row.type !== 'inviteResponse' && scope.row.type !== 'message'")
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)")
el-tooltip(placement="top" content="Delete log" :disabled="hideTooltips")
el-button(type="text" icon="el-icon-delete" size="mini" style="margin-left:5px" @click="deleteNotificationLog(scope.row)")
//- profile
.x-container(v-show="$refs.menu && $refs.menu.activeIndex === 'profile'")
@@ -696,10 +719,10 @@ html
el-radio-button(label="dark") Dark
div.options-container-item
span.name VRCPlus Profile Icons
el-switch(v-model="displayVRCPlusIconsAsAvatar")
el-switch(v-model="displayVRCPlusIconsAsAvatar" @change="saveOpenVROption")
div.options-container-item
span.name Disable Tooltips
el-switch(v-model="hideTooltips")
el-switch(v-model="hideTooltips" @change="saveOpenVROption")
div.options-container
span.header Side Panel
br
@@ -766,10 +789,16 @@ html
span * Only works when VRChat is running.
div.options-container-item
span.name Enable
el-switch(v-model="discordActive")
el-switch(v-model="discordActive" @change="saveDiscordOption")
div.options-container-item
span.name Instance details
el-switch(v-model="discordInstance" :disabled="!discordActive")
span.name Instance type/player count
el-switch(v-model="discordInstance" @change="saveDiscordOption" :disabled="!discordActive")
div.options-container-item
span.name Join button (public only)
el-switch(v-model="discordJoinButton" @change="saveDiscordOption" :disabled="!discordActive")
div.options-container-item
span.name Hide world details in private
el-switch(v-model="discordHideInvite" @change="saveDiscordOption" :disabled="!discordActive")
div.options-container
span.header SteamVR Overlay
div.options-container-item
@@ -782,61 +811,64 @@ html
br
div.options-container-item
span.name Enable
el-switch(v-model="openVR")
el-switch(v-model="openVR" @change="saveOpenVROption")
div.options-container-item
span.name Force Run (Opens SteamVR)
el-switch(v-model="openVRAlways" :disabled="!openVR")
el-switch(v-model="openVRAlways" @change="saveOpenVROption" :disabled="!openVR")
div.options-container-item
span.name Hide Private Worlds
el-switch(v-model="hidePrivateFromFeed")
el-switch(v-model="hidePrivateFromFeed" @change="saveOpenVROption")
br
span.sub-header Wrist Feed
div.options-container-item
span.name Wrist Feed Overlay
el-switch(v-model="overlayWrist" :disabled="!openVR")
el-switch(v-model="overlayWrist" @change="saveOpenVROption" :disabled="!openVR")
div.options-container-item
span.name(style="min-width:137px") Overlay Button
el-switch(v-model="overlaybutton" inactive-text="Grip" active-text="Menu" :disabled="!openVR || !overlayWrist")
el-switch(v-model="overlaybutton" @change="saveOpenVROption" inactive-text="Grip" active-text="Menu" :disabled="!openVR || !overlayWrist")
div.options-container-item
span.name Background Color
el-switch(v-model="vrBackgroundEnabled" :disabled="!openVR || !overlayWrist")
el-switch(v-model="vrBackgroundEnabled" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
div.options-container-item
span.name Minimal Feed Icons
el-switch(v-model="minimalFeed" :disabled="!openVR || !overlayWrist")
el-switch(v-model="minimalFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
div.options-container-item
span.name Hide VR Devices
el-switch(v-model="hideDevicesFromFeed" :disabled="!openVR || !overlayWrist")
el-switch(v-model="hideDevicesFromFeed" @change="saveOpenVROption" :disabled="!openVR || !overlayWrist")
div.options-container-item
el-button(size="small" icon="el-icon-notebook-2" @click="showWristFeedFiltersDialog()" :disabled="!openVR || !overlayWrist") Wrist Feed Filters
el-button(size="small" icon="el-icon-notebook-2" @click="showWristFeedFiltersDialog" :disabled="!openVR || !overlayWrist") Wrist Feed Filters
br
span.sub-header Notifications
div.options-container-item
span.name Overlay Notifications
el-switch(v-model="overlayNotifications" :disabled="!openVR")
el-switch(v-model="overlayNotifications" @change="saveOpenVROption" :disabled="!openVR")
div.options-container-item
el-button(size="small" icon="el-icon-rank" @click="showNotificationPositionDialog()" :disabled="!overlayNotifications || !openVR") Notification Position
el-button(size="small" icon="el-icon-rank" @click="showNotificationPositionDialog" :disabled="!overlayNotifications || !openVR") Notification Position
div.options-container-item
span.name XSOverlay Notifications
el-switch(v-model="xsNotifications")
el-switch(v-model="xsNotifications" @change="saveOpenVROption")
div.options-container-item
el-button(size="small" icon="el-icon-time" @click="promptNotificationTimeout()" :disabled="(!overlayNotifications || !openVR) && !xsNotifications") Notification Timeout
span.name User images (slower)
el-switch(v-model="imageNotifications" @change="saveOpenVROption")
div.options-container-item
el-button(size="small" icon="el-icon-time" @click="promptNotificationTimeout" :disabled="(!overlayNotifications || !openVR) && !xsNotifications") Notification Timeout
div.options-container-item
span.name Desktop Notifications, When to display:
br
el-radio-group(v-model="desktopToast" size="mini")
el-radio-group(v-model="desktopToast" @change="saveOpenVROption" size="mini")
el-radio-button(label="Never")
el-radio-button(label="Inside VR")
el-radio-button(label="Game Closed")
el-radio-button(label="Game Running")
el-radio-button(label="Always")
div.options-container-item
el-button(size="small" icon="el-icon-chat-square" @click="showNotyFeedFiltersDialog()") Notification Filters
el-button(size="small" icon="el-icon-chat-square" @click="showNotyFeedFiltersDialog") Notification Filters
br
span.sub-header Text-To-Speech Options
div.options-container-item
span.name Notification TTS, When to play:
br
el-radio-group(v-model="notificationTTS" size="mini")
el-radio-group(v-model="notificationTTS" @change="saveNotificationTTS" size="mini")
el-radio-button(label="Never")
el-radio-button(label="Inside VR")
el-radio-button(label="Game Closed")
@@ -857,31 +889,30 @@ html
div.options-container-item
span.name Download on invite:
br
el-radio-group(v-model="worldAutoCacheInvite" size="mini")
el-radio-group(v-model="worldAutoCacheInvite" @change="saveOpenVROption" size="mini")
el-radio-button(label="Never")
el-radio-button(label="Game Closed")
el-radio-button(label="Game Running")
el-radio-button(label="Always")
div.options-container-item
el-switch(v-model="worldAutoCacheInviteFilter" inactive-text="VIP" active-text="Everyone" :disabled="worldAutoCacheInvite == 'Never'")
el-switch(v-model="worldAutoCacheInviteFilter" @change="saveOpenVROption" inactive-text="VIP" active-text="Everyone" :disabled="worldAutoCacheInvite === 'Never'")
div.options-container-item
span.name Download on GPS:
br
el-radio-group(v-model="worldAutoCacheGPS" size="mini")
el-radio-group(v-model="worldAutoCacheGPS" @change="saveOpenVROption" size="mini")
el-radio-button(label="Never")
el-radio-button(label="Game Closed")
el-radio-button(label="Game Running")
el-radio-button(label="Always")
div.options-container-item
el-switch(v-model="worldAutoCacheGPSFilter" inactive-text="VIP" active-text="Everyone" :disabled="worldAutoCacheGPS == 'Never'")
el-switch(v-model="worldAutoCacheGPSFilter" @change="saveOpenVROption" inactive-text="VIP" active-text="Everyone" :disabled="worldAutoCacheGPS === 'Never'")
div.options-container-item
el-button-group
el-button(size="small" icon="el-icon-download" @click="showDownloadDialog()") Download History
el-button(size="small" icon="el-icon-download" @click="showDownloadDialog") Download History
br
span.sub-header Automatically Manage Cache When Closing VRChat
div.options-container-item
span.name(style="min-width:300px") Auto delete old versions from cache
el-switch(v-model="autoSweepVRChatCache")
el-switch(v-model="autoSweepVRChatCache" @change="saveOpenVROption")
div.options-container
span.header Application
div.options-container-item
@@ -900,6 +931,13 @@ html
el-button-group
el-button(size="small" icon="el-icon-s-operation" @click="showLaunchOptions()") Launch Options
el-button(size="small" icon="el-icon-s-operation" @click="showVRChatConfig()") VRChat config.json
div.options-container
span.header YouTube API
div.options-container-item
span.name Enabled
el-switch(v-model="youTubeApi" @change="changeYouTubeApi")
div.options-container-item
el-button(size="small" icon="el-icon-caret-right" @click="showYouTubeApiDialog") YouTube API Key
div.options-container(style="margin-top:45px;border-top:1px solid #eee;padding-top:30px")
span.header Legal Notice
div.options-container-item
@@ -1053,6 +1091,7 @@ html
el-button(:type="(userDialog.incomingRequest || userDialog.outgoingRequest) ? 'success' : (userDialog.isBlock || userDialog.isMute || userDialog.isHideAvatar) ? 'danger' : 'default'" icon="el-icon-more" circle style="margin-left:5px")
el-dropdown-menu(#default="dropdown")
el-dropdown-item(icon="el-icon-refresh" command="Refresh") Refresh
el-dropdown-item(icon="el-icon-s-order" command="Copy User") Copy User URL
template(v-if="userDialog.ref.id === API.currentUser.id")
el-dropdown-item(icon="el-icon-picture-outline" command="Manage Gallery" divided) Manage Gallery/Icons
el-dropdown-item(icon="el-icon-s-custom" command="Show Avatar Author") Show Avatar Author
@@ -1075,7 +1114,7 @@ html
el-dropdown-item(v-else icon="el-icon-plus" command="Send Friend Request") Send Friend Request
el-dropdown-item(icon="el-icon-s-custom" command="Show Avatar Author" divided) Show Avatar Author
el-dropdown-item(icon="el-icon-s-custom" command="Show Fallback Avatar Details") Show Fallback Avatar Details
el-dropdown-item(v-if="userDialog.currentAvatarImageUrl !== 'https://assets.vrchat.com/system/defaultAvatar.png'" icon="el-icon-picture-outline" command="Previous Images") Show Avatar Previous Images
el-dropdown-item(v-if="userDialog.ref.currentAvatarImageUrl !== 'https://assets.vrchat.com/system/defaultAvatar.png'" icon="el-icon-picture-outline" command="Previous Images") Show Avatar Previous Images
el-dropdown-item(v-if="userDialog.isBlock" icon="el-icon-circle-check" command="Unblock" divided style="color:#F56C6C") Unblock
el-dropdown-item(v-else icon="el-icon-circle-close" command="Block" divided :disabled="userDialog.ref.$isModerator") Block
el-dropdown-item(v-if="userDialog.isMute" icon="el-icon-microphone" command="Unmute" style="color:#F56C6C") Unmute
@@ -1665,6 +1704,16 @@ html
el-button(size="small" @click="VRChatConfigDialog.visible = false") Cancel
el-button(type="primary" size="small" :disabled="VRChatConfigDialog.loading" @click="saveVRChatConfigFile") Save
//- dialog: YouTube Api Dialog
el-dialog.x-dialog(ref="youTubeApiDialog" :visible.sync="youTubeApiDialog.visible" title="YouTube API" width="400px")
div(style='font-size:12px;')
| Enter your YouTube API Key (optional) #[br]
el-input(type="textarea" v-model="youTubeApiKey" placeholder="YouTube API Key" maxlength="39" show-word-limit style="dispaly:block;margin-top:10px")
template(#footer)
div(style="display:flex")
el-button(size="small" @click="openExternalLink('https://rapidapi.com/blog/how-to-get-youtube-api-key/')") Guide
el-button(type="primary" size="small" @click="testYouTubeApiKey" style="margin-left:auto") Save
//- dialog: Cache Download
el-dialog.x-dialog(ref="downloadDialog" :visible.sync="downloadDialog.visible" title="Download History" width="770px")
div(v-if="downloadInProgress && downloadCurrent.ref")
@@ -1769,7 +1818,7 @@ html
el-button(type="primary" size="small" style="margin-left:auto" @click="notificationPositionDialog.visible = false") OK
//- dialog: Noty feed filters
el-dialog.x-dialog(ref="notyFeedFiltersDialog" :visible.sync="notyFeedFiltersDialog.visible" title="Notification Filters" width="480px")
el-dialog.x-dialog(ref="notyFeedFiltersDialog" :visible.sync="notyFeedFiltersDialog.visible" title="Notification Filters" width="500px")
.toggle-list
.toggle-item
span.toggle-name OnPlayerJoining
@@ -1868,21 +1917,30 @@ html
el-radio-button(label="Friends")
.toggle-item
span.toggle-name Portal Spawn
el-tooltip(placement="top" style="margin-left:5px" content="Requires '--enable-sdk-log-levels' steam launch option")
i.el-icon-warning
el-radio-group(v-model="sharedFeedFilters.noty.PortalSpawn" size="mini")
el-radio-button(label="Off")
el-radio-button(label="VIP")
el-radio-button(label="Friends")
el-radio-button(label="Everyone")
.toggle-item
span.toggle-name Events
el-radio-group(v-model="sharedFeedFilters.noty.Event" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
//- .toggle-item
//- span.toggle-name Avatar Change
//- el-radio-group(v-model="sharedFeedFilters.noty.AvatarChange" size="mini")
//- el-radio-button(label="Off")
//- el-radio-button(label="VIP")
//- el-radio-button(label="Friends")
//- el-radio-button(label="Everyone")
.toggle-item
span.toggle-name Video Play
el-radio-group(v-model="sharedFeedFilters.noty.VideoPlay" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
.toggle-item
span.toggle-name Events
el-radio-group(v-model="sharedFeedFilters.noty.Event" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
.toggle-item
span.toggle-name Blocked Player Joins
el-radio-group(v-model="sharedFeedFilters.noty.BlockedOnPlayerJoined" size="mini")
@@ -1916,7 +1974,7 @@ html
el-button(type="primary" size="small" style="margin-left:10px" @click="saveSharedFeedFilters") Save
//- dialog: wrist feed filters
el-dialog.x-dialog(ref="wristFeedFiltersDialog" :visible.sync="wristFeedFiltersDialog.visible" title="Wrist Feed Filters" width="480px")
el-dialog.x-dialog(ref="wristFeedFiltersDialog" :visible.sync="wristFeedFiltersDialog.visible" title="Wrist Feed Filters" width="500px")
.toggle-list
.toggle-item
span.toggle-name Self Location
@@ -2020,19 +2078,28 @@ html
el-radio-button(label="Friends")
.toggle-item
span.toggle-name Portal Spawn
el-tooltip(placement="top" style="margin-left:5px" content="Requires '--enable-sdk-log-levels' steam launch option")
i.el-icon-warning
el-radio-group(v-model="sharedFeedFilters.wrist.PortalSpawn" size="mini")
el-radio-button(label="Off")
el-radio-button(label="VIP")
el-radio-button(label="Friends")
el-radio-button(label="Everyone")
//- .toggle-item
//- span.toggle-name Avatar Change
//- el-radio-group(v-model="sharedFeedFilters.wrist.AvatarChange" size="mini")
//- el-radio-button(label="Off")
//- el-radio-button(label="VIP")
//- el-radio-button(label="Friends")
//- el-radio-button(label="Everyone")
.toggle-item
span.toggle-name Events
el-radio-group(v-model="sharedFeedFilters.wrist.Event" size="mini")
span.toggle-name Video Play
el-radio-group(v-model="sharedFeedFilters.wrist.VideoPlay" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
.toggle-item
span.toggle-name Video Play
el-radio-group(v-model="sharedFeedFilters.noty.VideoPlay" size="mini")
span.toggle-name Events
el-radio-group(v-model="sharedFeedFilters.wrist.Event" size="mini")
el-radio-button(label="Off")
el-radio-button(label="On")
.toggle-item

View File

@@ -1,7 +1,7 @@
import sqliteService from '../service/sqlite.js';
class Database {
async init(userId) {
async initUserTables(userId) {
Database.userId = userId.replaceAll('-', '').replaceAll('_', '');
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS ${Database.userId}_feed_gps (id INTEGER PRIMARY KEY, created_at TEXT, user_id TEXT, display_name TEXT, location TEXT, world_name TEXT, previous_location TEXT, time INTEGER)`
@@ -21,11 +21,32 @@ class Database {
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS ${Database.userId}_friend_log_history (id INTEGER PRIMARY KEY, created_at TEXT, type TEXT, user_id TEXT, display_name TEXT, previous_display_name TEXT, trust_level TEXT, previous_trust_level TEXT)`
);
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS ${Database.userId}_notifications (id TEXT PRIMARY KEY, created_at TEXT, type TEXT, sender_user_id TEXT, sender_username TEXT, receiver_user_id TEXT, message TEXT, world_id TEXT, world_name TEXT, image_url TEXT, invite_message TEXT, request_message TEXT, response_message TEXT, expired INTEGER)`
);
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS memos (user_id TEXT PRIMARY KEY, edited_at TEXT, memo TEXT)`
);
}
async initTables() {
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS gamelog_location (id INTEGER PRIMARY KEY, created_at TEXT, location TEXT, world_id TEXT, world_name TEXT, time INTEGER, UNIQUE(created_at, location))`
);
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS gamelog_join_leave (id INTEGER PRIMARY KEY, created_at TEXT, type TEXT, display_name TEXT, location TEXT, user_id TEXT, time INTEGER, UNIQUE(created_at, type, display_name))`
);
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS gamelog_portal_spawn (id INTEGER PRIMARY KEY, created_at TEXT, display_name TEXT, location TEXT, user_id TEXT, instance_id TEXT, world_name TEXT, UNIQUE(created_at, display_name))`
);
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS gamelog_video_play (id INTEGER PRIMARY KEY, created_at TEXT, video_url TEXT, video_name TEXT, video_id TEXT, location TEXT, display_name TEXT, user_id TEXT, UNIQUE(created_at, video_url))`
);
await sqliteService.executeNonQuery(
`CREATE TABLE IF NOT EXISTS gamelog_event (id INTEGER PRIMARY KEY, created_at TEXT, data TEXT, UNIQUE(created_at, data))`
);
}
async getFeedDatabase() {
var feedDatabase = [];
var date = new Date();
@@ -349,6 +370,259 @@ class Database {
}
);
}
async getGamelogDatabase() {
var gamelogDatabase = [];
var date = new Date();
date.setDate(date.getDate() - 7); // 7 day limit
var dateOffset = date.toJSON();
await sqliteService.execute((dbRow) => {
var row = {
rowId: dbRow[0],
created_at: dbRow[1],
type: 'Location',
location: dbRow[2],
worldId: dbRow[3],
worldName: dbRow[4],
time: dbRow[5]
};
gamelogDatabase.unshift(row);
}, `SELECT * FROM gamelog_location WHERE created_at >= date('${dateOffset}')`);
await sqliteService.execute((dbRow) => {
var row = {
rowId: dbRow[0],
created_at: dbRow[1],
type: dbRow[2],
displayName: dbRow[3],
location: dbRow[4],
userId: dbRow[5],
time: dbRow[6]
};
gamelogDatabase.unshift(row);
}, `SELECT * FROM gamelog_join_leave WHERE created_at >= date('${dateOffset}')`);
await sqliteService.execute((dbRow) => {
var row = {
rowId: dbRow[0],
created_at: dbRow[1],
type: 'PortalSpawn',
displayName: dbRow[2],
location: dbRow[3],
userId: dbRow[4],
instanceId: dbRow[5],
worldName: dbRow[6]
};
gamelogDatabase.unshift(row);
}, `SELECT * FROM gamelog_portal_spawn WHERE created_at >= date('${dateOffset}')`);
await sqliteService.execute((dbRow) => {
var row = {
rowId: dbRow[0],
created_at: dbRow[1],
type: 'VideoPlay',
videoUrl: dbRow[2],
videoName: dbRow[3],
videoId: dbRow[4],
location: dbRow[5],
displayName: dbRow[6],
userId: dbRow[7]
};
gamelogDatabase.unshift(row);
}, `SELECT * FROM gamelog_video_play WHERE created_at >= date('${dateOffset}')`);
await sqliteService.execute((dbRow) => {
var row = {
rowId: dbRow[0],
created_at: dbRow[1],
type: 'Event',
data: dbRow[2]
};
gamelogDatabase.unshift(row);
}, `SELECT * FROM gamelog_event WHERE created_at >= date('${dateOffset}')`);
var compareByCreatedAt = function (a, b) {
var A = a.created_at;
var B = b.created_at;
if (A < B) {
return -1;
}
if (A > B) {
return 1;
}
return 0;
};
gamelogDatabase.sort(compareByCreatedAt);
return gamelogDatabase;
}
addGamelogLocationToDatabase(entry) {
sqliteService.executeNonQuery(
`INSERT OR IGNORE INTO gamelog_location (created_at, location, world_id, world_name, time) VALUES (@created_at, @location, @world_id, @world_name, @time)`,
{
'@created_at': entry.created_at,
'@location': entry.location,
'@world_id': entry.worldId,
'@world_name': entry.worldName,
'@time': entry.time
}
);
}
updateGamelogLocationTimeToDatabase(entry) {
sqliteService.executeNonQuery(
`UPDATE gamelog_location SET time = @time WHERE created_at = @created_at`,
{
'@created_at': entry.created_at,
'@time': entry.time
}
);
}
addGamelogJoinLeaveToDatabase(entry) {
sqliteService.executeNonQuery(
`INSERT OR IGNORE INTO gamelog_join_leave (created_at, type, display_name, location, user_id, time) VALUES (@created_at, @type, @display_name, @location, @user_id, @time)`,
{
'@created_at': entry.created_at,
'@type': entry.type,
'@display_name': entry.displayName,
'@location': entry.location,
'@user_id': entry.userId,
'@time': entry.time
}
);
}
addGamelogPortalSpawnToDatabase(entry) {
sqliteService.executeNonQuery(
`INSERT OR IGNORE INTO gamelog_portal_spawn (created_at, display_name, location, user_id, instance_id, world_name) VALUES (@created_at, @display_name, @location, @user_id, @instance_id, @world_name)`,
{
'@created_at': entry.created_at,
'@display_name': entry.displayName,
'@location': entry.location,
'@user_id': entry.userId,
'@instance_id': entry.instanceId,
'@world_name': entry.worldName
}
);
}
addGamelogVideoPlayToDatabase(entry) {
sqliteService.executeNonQuery(
`INSERT OR IGNORE INTO gamelog_video_play (created_at, video_url, video_name, video_id, location, display_name, user_id) VALUES (@created_at, @video_url, @video_name, @video_id, @location, @display_name, @user_id)`,
{
'@created_at': entry.created_at,
'@video_url': entry.videoUrl,
'@video_name': entry.videoName,
'@video_id': entry.videoId,
'@location': entry.location,
'@display_name': entry.displayName,
'@user_id': entry.userId
}
);
}
addGamelogEventToDatabase(entry) {
sqliteService.executeNonQuery(
`INSERT OR IGNORE INTO gamelog_event (created_at, data) VALUES (@created_at, @data)`,
{
'@created_at': entry.created_at,
'@data': entry.data
}
);
}
async getNotifications() {
var notifications = [];
await sqliteService.execute((dbRow) => {
var row = {
id: dbRow[0],
created_at: dbRow[1],
type: dbRow[2],
senderUserId: dbRow[3],
senderUsername: dbRow[4],
receiverUserId: dbRow[5],
message: dbRow[6],
details: {
worldId: dbRow[7],
worldName: dbRow[8],
imageUrl: dbRow[9],
inviteMessage: dbRow[10],
requestMessage: dbRow[11],
responseMessage: dbRow[12]
}
};
row.$isExpired = false;
if (dbRow[13] === 1) {
row.$isExpired = true;
}
notifications.unshift(row);
}, `SELECT * FROM ${Database.userId}_notifications LIMIT 5000`);
return notifications;
}
addNotificationToDatabase(row) {
var entry = {
id: '',
created_at: '',
type: '',
senderUserId: '',
senderUsername: '',
receiverUserId: '',
message: '',
...row,
details: {
worldId: '',
worldName: '',
imageUrl: '',
inviteMessage: '',
requestMessage: '',
responseMessage: '',
...row.details
}
};
var expired = 0;
if (row.$isExpired) {
expired = 1;
}
sqliteService.executeNonQuery(
`INSERT OR IGNORE INTO ${Database.userId}_notifications (id, created_at, type, sender_user_id, sender_username, receiver_user_id, message, world_id, world_name, image_url, invite_message, request_message, response_message, expired) VALUES (@id, @created_at, @type, @sender_user_id, @sender_username, @receiver_user_id, @message, @world_id, @world_name, @image_url, @invite_message, @request_message, @response_message, @expired)`,
{
'@id': entry.id,
'@created_at': entry.created_at,
'@type': entry.type,
'@sender_user_id': entry.senderUserId,
'@sender_username': entry.senderUsername,
'@receiver_user_id': entry.receiverUserId,
'@message': entry.message,
'@world_id': entry.details.worldId,
'@world_name': entry.details.worldName,
'@image_url': entry.details.imageUrl,
'@invite_message': entry.details.inviteMessage,
'@request_message': entry.details.requestMessage,
'@response_message': entry.details.responseMessage,
'@expired': expired
}
);
}
deleteNotification(rowId) {
sqliteService.executeNonQuery(
`DELETE FROM ${Database.userId}_notifications WHERE id = @row_id`,
{
'@row_id': rowId
}
);
}
updateNotificationExpired(entry) {
var expired = 0;
if (entry.$isExpired) {
expired = 1;
}
sqliteService.executeNonQuery(
`UPDATE ${Database.userId}_notifications SET expired = @expired WHERE id = @id`,
{
'@id': entry.id,
'@expired': expired
}
);
}
}
var self = new Database();

View File

@@ -1,93 +1,82 @@
// requires binding of LogWatcher
// <string, object>
var contextMap = new Map();
function parseRawGameLog(dt, type, args) {
var gameLog = {
dt,
type
};
switch (type) {
case 'location':
gameLog.location = args[0];
gameLog.worldName = args[1];
break;
case 'player-joined':
gameLog.userDisplayName = args[0];
gameLog.userType = args[1];
break;
case 'player-left':
gameLog.userDisplayName = args[0];
break;
case 'notification':
gameLog.json = args[0];
break;
case 'portal-spawn':
gameLog.userDisplayName = args[0];
break;
case 'event':
gameLog.event = args[0];
break;
case 'video-play':
gameLog.videoURL = args[0];
gameLog.displayName = args[1];
break;
default:
break;
}
return gameLog;
}
class GameLogService {
async poll() {
var rawGameLogs = await LogWatcher.Get();
var gameLogs = [];
var now = Date.now();
parseRawGameLog(dt, type, args) {
var gameLog = {
dt,
type
};
for (var [fileName, dt, type, ...args] of rawGameLogs) {
var context = contextMap.get(fileName);
if (typeof context === 'undefined') {
context = {
updatedAt: null,
switch (type) {
case 'location':
gameLog.location = args[0];
gameLog.worldName = args[1];
break;
// location
location: null
};
contextMap.set(fileName, context);
}
case 'location-destination':
gameLog.location = args[0];
break;
var gameLog = parseRawGameLog(dt, type, args);
case 'player-joined':
gameLog.userDisplayName = args[0];
gameLog.userType = args[1];
break;
switch (gameLog.type) {
case 'location':
context.location = gameLog.location;
break;
case 'player-left':
gameLog.userDisplayName = args[0];
break;
default:
break;
}
case 'notification':
gameLog.json = args[0];
break;
context.updatedAt = now;
case 'portal-spawn':
gameLog.userDisplayName = args[0];
break;
gameLogs.push(gameLog);
case 'event':
gameLog.event = args[0];
break;
case 'video-play':
gameLog.videoUrl = args[0];
gameLog.displayName = args[1];
break;
case 'vrcx':
gameLog.data = args[0];
break;
default:
break;
}
return gameLog;
}
async getAll() {
var gameLogs = [];
var done = false;
while (!done) {
var rawGameLogs = await LogWatcher.Get();
// eslint-disable-next-line no-unused-vars
for (var [fileName, dt, type, ...args] of rawGameLogs) {
var gameLog = this.parseRawGameLog(dt, type, args);
gameLogs.push(gameLog);
}
if (rawGameLogs.length === 0) {
done = true;
}
}
return gameLogs;
}
async setDateTill(dateTill) {
await LogWatcher.SetDateTill(dateTill);
}
async reset() {
await LogWatcher.Reset();
contextMap.clear();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -36,17 +36,23 @@ html
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
| #[span.name(v-text="feed.displayName")] #[i.x-user-status(:class="statusClass(feed.status)")] {{feed.statusDescription}}
| #[span.name(v-text="feed.displayName")]
template(v-if="feed.statusDescription === feed.previousStatusDescription")
i.x-user-status(:class="statusClass(feed.previousStatus)")
i.el-icon-right
i.x-user-status(:class="statusClass(feed.status)")
template(v-else)
| #[i.x-user-status(:class="statusClass(feed.status)")] {{feed.statusDescription}}
div(v-else-if="feed.type === 'OnPlayerJoined'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
| ▶️ #[span.name(v-text="feed.data")]
| ▶️ #[span.name(v-text="feed.displayName")]
div(v-else-if="feed.type === 'OnPlayerLeft'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
| ◀️ #[span.name(v-text="feed.data")]
| ◀️ #[span.name(v-text="feed.displayName")]
div(v-else-if="feed.type === 'OnPlayerJoining'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
@@ -57,7 +63,16 @@ html
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
location(:location="feed.data[0]" :hint="feed.data[1]")
location(:location="feed.location" :hint="feed.worldName")
div(v-else-if="feed.type === 'VideoPlay'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
| 🎵 #[span.name(v-text="feed.displayName")]
template(v-if="feed.videoName")
| #[span(v-text="feed.videoName")]
template(v-else)
| #[span(v-text="feed.videoUrl")]
div(v-else-if="feed.type === 'invite'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
@@ -107,7 +122,16 @@ html
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
| ✨ #[span.name(v-text="feed.data")]
| ✨ #[span.name(v-text="feed.displayName")]
template(v-if="feed.worldName")
| #[location(:location="feed.instanceId" :hint="feed.worldName")]
div(v-else-if="feed.type === 'AvatarChange'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
| 🧍 #[span.name(v-text="feed.displayName")] {{ feed.name }}
template(v-if="feed.description && feed.description !== feed.name")
| - {{ feed.description }}
div(v-else-if="feed.type === 'Event'" class="x-friend-item")
.detail
span.extra
@@ -159,17 +183,23 @@ html
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
| #[span.name(v-text="feed.displayName")] is #[i.x-user-status(:class="statusClass(feed.status)")] {{feed.statusDescription}}
| #[span.name(v-text="feed.displayName")]
template(v-if="feed.statusDescription === feed.previousStatusDescription")
i.x-user-status(:class="statusClass(feed.previousStatus)")
i.el-icon-right
i.x-user-status(:class="statusClass(feed.status)")
template(v-else)
| #[i.x-user-status(:class="statusClass(feed.status)")] {{feed.statusDescription}}
div(v-else-if="feed.type === 'OnPlayerJoined'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
| #[span.name(v-text="feed.data")] has joined
| #[span.name(v-text="feed.displayName")] has joined
div(v-else-if="feed.type === 'OnPlayerLeft'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
| #[span.name(v-text="feed.data")] has left
| #[span.name(v-text="feed.displayName")] has left
div(v-else-if="feed.type === 'OnPlayerJoining'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
@@ -179,7 +209,16 @@ html
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
location(:location="feed.data[0]" :hint="feed.data[1]")
location(:location="feed.location" :hint="feed.worldName")
div(v-else-if="feed.type === 'VideoPlay'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
| #[span.name(v-text="feed.displayName")] changed video to
template(v-if="feed.videoName")
| #[span(v-text="feed.videoName")]
template(v-else)
| #[span(v-text="feed.videoUrl")]
div(v-else-if="feed.type === 'invite'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
@@ -229,7 +268,16 @@ html
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
| #[span.name(v-text="feed.data")] has spawned a portal
| #[span.name(v-text="feed.displayName")] has spawned a portal
template(v-if="feed.worldName")
| to #[location(:location="feed.instanceId" :hint="feed.worldName")]
div(v-else-if="feed.type === 'AvatarChange'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }")
.detail
span.extra
span.time {{ feed.created_at | formatDate('HH:MI') }}
| #[span.name(v-text="feed.displayName")] changed into avatar {{ feed.name }}
template(v-if="feed.description && feed.description !== feed.name")
| - {{ feed.description }}
div(v-else-if="feed.type === 'Event'" class="x-friend-item")
.detail
span.extra
@@ -301,21 +349,25 @@ html
br
span {{ device[2] }}%
.x-containerbottom
template(v-if="nowPlaying.playing")
span(style="float:right;padding-left:10px") {{ nowPlaying.remainingText }}
marquee-text {{ nowPlaying.name }}
div.np-progress-bar(:style="{ width: nowPlaying.percentage + '%' }")
template(v-if="config && config.minimalFeed")
template(v-if="downloadProgress === 100")
template(v-if="config.downloadProgress === 100")
span(style="display:inline-block;margin-right:5px") #[i.el-icon-loading]
template(v-else-if="downloadProgress > 0")
span(style="display:inline-block;margin-right:5px") {{ downloadProgress }}%
template(v-if="lastLocation.date != 0")
template(v-else-if="config.downloadProgress > 0")
span(style="display:inline-block;margin-right:5px") {{ config.downloadProgress }}%
template(v-if="lastLocation.date !== 0")
span(style="float:right") {{ lastLocationTimer }}
span(style="display:inline-block") {{ lastLocation.playerList.length }}
span(style="display:inline-block;font-weight:bold") {{ lastLocation.friendList.length !== 0 ? ` (${lastLocation.friendList.length})` : ''}}
template(v-else)
template(v-if="downloadProgress === 100")
template(v-if="config.downloadProgress === 100")
span(style="display:inline-block;margin-right:5px") Downloading: #[i.el-icon-loading]
template(v-else-if="downloadProgress > 0")
span(style="display:inline-block;margin-right:5px") Downloading: {{ downloadProgress }}%
template(v-if="lastLocation.date != 0")
template(v-else-if="config.downloadProgress > 0")
span(style="display:inline-block;margin-right:5px") Downloading: {{ config.downloadProgress }}%
template(v-if="lastLocation.date !== 0")
span(style="float:right") Timer: {{ lastLocationTimer }}
span(style="display:inline-block") Players: {{ lastLocation.playerList.length }}
span(style="display:inline-block;font-weight:bold") {{ lastLocation.friendList.length !== 0 ? ` (${lastLocation.friendList.length})` : ''}}

View File

@@ -22,9 +22,6 @@
.noty_body {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.noty_layout {
@@ -34,6 +31,7 @@
.noty_theme__relax.noty_bar,
.noty_theme__sunset.noty_bar {
height: 42px;
position: relative;
margin: 4px 0;
overflow: hidden;
@@ -42,9 +40,7 @@
.noty_theme__relax.noty_bar .noty_body,
.noty_theme__sunset.noty_bar .noty_body {
padding: 5px 10px 10px;
font-size: 15px;
text-align: center;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
}
@@ -143,6 +139,19 @@
opacity: 0.6;
}
.noty-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 8px 8px 0 11px;
}
.noty-img {
height: 42px;
float: left;
border-radius: 4px;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
@@ -202,16 +211,22 @@ button {
}
.x-containerbottom span {
padding: 0px;
display: block;
overflow: hidden;
}
.np-progress-bar {
width: 0%;
height: 2px;
background-color: white;
}
.x-friend-item {
box-sizing: border-box;
display: flex;
align-items: center;
font-size: 18px;
height: 27.1px;
}
.x-friend-item .time {