diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3988d36..7637324 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,53 +1,58 @@
-### 1.2.0 - 2022-09-24 - Always Listening
-
-- Added a toggle-able `Always Listening` mode
- - Each command must be prefixed with an activation phrase (defaulted to `okay ghost`)
-- Added fix for `maxPower` command (didn't work in normal DIM, only in beta)
-- Added fix for search already being populated when performing a command.
-
-### 1.1.3 - 2022-09-20 - Perk fix
-
-- Fixed perk matching
-
-### 1.1.2 - 2022-09-19 - Options page links
-
-- Added an `onInstalled` event for the extension. It'll direct users to the options page.
-- Link to the options page from the `?`
-
-### 1.1.1 - 2022-09-16 - Better performance
-
-- Use a `waitForElement/waitForSearchToUpdate` function instead of arbitrarily sleeping in code
- - Results in at least a 300% improvement in action duration
-
-### 1.1.0 - 2022-09-13 - The One with Custom Commands
-
-- Quite a few changes in this one
-- Reworked how the extension waits for the search to update visible (not dimmed) items
- - Really reworked how all UI interactions are performed
-- Added the ability to customize the command words for particular actions
- - Visit the options page (click the extension icon) to set these
-- Added a `store` command for storing items in the vault
-- Added an `equip` command for directly equipping an item on your current character (when possible)
- - This works like how `transfer` previously did, and `transfer` has been updated to only transfer, not equip
-- (from above) `Transfer` now only transfers instead of equipping
-- Added mic icon on page with a link to the website
- - When listening, there will be text next to the icon that updates as the user speaks their command
-- Added link to the [Voice DIM website](https://www.voicedim.com) via the `?` icon.
-- Fixed `Start Farming mode` command if a user has at least 10 loadouts saved
-
-### 1.0.1 - 2022-08-23 - Corrected Shortcut
-
-- Defaulted the shortcut correctly (set to `Ctrl+Shift+0`)
-
-### 1.0.0 - 2022-08-19 - Initial Release
-
-- See [Reddit post](https://www.reddit.com/r/DestinyTheGame/comments/wseigx/interact_with_dim_using_your_voice/) about the available commands with more info
- - Commands are available to:
- - Transfer a weapon by name
- - Transfer a weapon with particular perks
- - Transfer a weapon by attribute (energy type, slot, ammo type, etc)
- - Collect from the postmaster
- - Start/Stop farming mode
- - Equip loadouts by name
- - Equip max power
-- Use global shortcut to activate listening
+### 1.2.1 - 2022-10-03 - Only in Inventory
+
+- Added fix for loading DIM on another tab. Previously required reloading the page on the inventory tab. Now Voice DIM will load no matter what page is started on
+- Added fix for a user having the item popup sidebar being collapsed.
+
+### 1.2.0 - 2022-09-24 - Always Listening
+
+- Added a toggle-able `Always Listening` mode
+ - Each command must be prefixed with an activation phrase (defaulted to `okay ghost`)
+- Added fix for `maxPower` command (didn't work in normal DIM, only in beta)
+- Added fix for search already being populated when performing a command.
+
+### 1.1.3 - 2022-09-20 - Perk fix
+
+- Fixed perk matching
+
+### 1.1.2 - 2022-09-19 - Options page links
+
+- Added an `onInstalled` event for the extension. It'll direct users to the options page.
+- Link to the options page from the `?`
+
+### 1.1.1 - 2022-09-16 - Better performance
+
+- Use a `waitForElement/waitForSearchToUpdate` function instead of arbitrarily sleeping in code
+ - Results in at least a 300% improvement in action duration
+
+### 1.1.0 - 2022-09-13 - The One with Custom Commands
+
+- Quite a few changes in this one
+- Reworked how the extension waits for the search to update visible (not dimmed) items
+ - Really reworked how all UI interactions are performed
+- Added the ability to customize the command words for particular actions
+ - Visit the options page (click the extension icon) to set these
+- Added a `store` command for storing items in the vault
+- Added an `equip` command for directly equipping an item on your current character (when possible)
+ - This works like how `transfer` previously did, and `transfer` has been updated to only transfer, not equip
+- (from above) `Transfer` now only transfers instead of equipping
+- Added mic icon on page with a link to the website
+ - When listening, there will be text next to the icon that updates as the user speaks their command
+- Added link to the [Voice DIM website](https://www.voicedim.com) via the `?` icon.
+- Fixed `Start Farming mode` command if a user has at least 10 loadouts saved
+
+### 1.0.1 - 2022-08-23 - Corrected Shortcut
+
+- Defaulted the shortcut correctly (set to `Ctrl+Shift+0`)
+
+### 1.0.0 - 2022-08-19 - Initial Release
+
+- See [Reddit post](https://www.reddit.com/r/DestinyTheGame/comments/wseigx/interact_with_dim_using_your_voice/) about the available commands with more info
+ - Commands are available to:
+ - Transfer a weapon by name
+ - Transfer a weapon with particular perks
+ - Transfer a weapon by attribute (energy type, slot, ammo type, etc)
+ - Collect from the postmaster
+ - Start/Stop farming mode
+ - Equip loadouts by name
+ - Equip max power
+- Use global shortcut to activate listening
diff --git a/package.json b/package.json
index b9b279a..4aaa6a8 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,14 @@
{
"name": "voice-dim",
- "version": "1.2.0",
+ "version": "1.2.1",
"description": "Perform common DIM actions by using speech recognition.",
"main": "dist/chrome/js/voice-dim.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"zip": "npm run build && python scripts/build.py",
"build": "rimraf dist/ && webpack --env mode=production --config webpack.config.js",
- "watch": "webpack --env mode=development --config webpack.config.js --progress --watch"
+ "watch": "webpack --env mode=development --config webpack.config.js --progress --watch",
+ "update-version": "python scripts/update_version.py"
},
"repository": {
"type": "git",
@@ -45,4 +46,4 @@
"webpack-cli": "^4.10.0",
"webpack-visualizer-plugin2": "^1.0.0"
}
-}
+}
\ No newline at end of file
diff --git a/public/manifest.chrome.json b/public/manifest.chrome.json
index 45f0d48..059316b 100644
--- a/public/manifest.chrome.json
+++ b/public/manifest.chrome.json
@@ -1,14 +1,14 @@
{
"name": "Voice DIM",
"description": "Control DIM with your voice.",
- "version": "1.2.0",
+ "version": "1.2.1",
"manifest_version": 3,
"background": {
"service_worker": "js/background.js"
},
"content_scripts": [
{
- "matches": ["https://*.destinyitemmanager.com/*inventory"],
+ "matches": ["https://*.destinyitemmanager.com/*"],
"js": ["js/voiceDim.js"],
"css": ["css/voiceDim.css"]
}
diff --git a/public/manifest.firefox.json b/public/manifest.firefox.json
index 18bfbed..79fe09c 100644
--- a/public/manifest.firefox.json
+++ b/public/manifest.firefox.json
@@ -1,7 +1,7 @@
{
"name": "Voice DIM",
"description": "Control DIM with your voice.",
- "version": "1.2.0",
+ "version": "1.2.1",
"manifest_version": 2,
"background": {
"scripts": ["js/background.js"]
diff --git a/scripts/update_version.py b/scripts/update_version.py
index f21b13b..fc18959 100644
--- a/scripts/update_version.py
+++ b/scripts/update_version.py
@@ -1,3 +1,4 @@
+from datetime import datetime
import getopt
import json
import os
@@ -37,31 +38,54 @@ def get_next_version(current_version: str, part_to_update: str):
return f"{major_version}.{minor_version}.{bugfix_version}"
-def write_new_version(file_path, new_version):
+def write_new_version(file_path: str, new_version: str, dry_run: bool = False):
with open(file_path, 'r') as f:
data = json.load(f)
data['version'] = f"{new_version}"
+ if dry_run:
+ return
+
with open(file_path, 'w') as f:
- json.dump(data, f, indent=4)
+ json.dump(data, f, indent=2)
+
+
+def write_changelog_update(file_path: str, new_version: str, dry_run: bool = False):
+ with open(file_path, 'r') as f:
+ data = f.read()
+
+ if data.find(new_version) < 0:
+ now = datetime.now()
+ date = now.strftime('%Y-%m-%d')
+
+ data = f"### {new_version} - {date} -
\n\n- \n\n{data}"
+ print(data)
+
+ if dry_run:
+ return
+
+ with open(file_path, 'w') as f:
+ f.write(data)
def main():
argument_list = sys.argv[1:]
- options = 'p:'
- long_options = "part="
+ options = 'dp:'
+ long_options = ["dry-run", "part="]
# Parsing argument
arguments, values = getopt.getopt(argument_list, options, long_options)
part = 'bugfix'
+ dry_run = False
# checking each argument
for current_arg, current_value in arguments:
-
if current_arg in ("-p", "--part"):
if current_value in ('major', 'minor', 'bugfix'):
part = current_value
else:
print("No part provided. Updating the bugfix version by default")
+ if current_arg in ('-d', '--dry-run'):
+ dry_run = True
git_root = get_git_root(os.getcwd())
files = [git_root + '/public/manifest.chrome.json', git_root +
@@ -86,7 +110,9 @@ def main():
print(next_version)
for file in files:
- write_new_version(file, next_version)
+ write_new_version(file, next_version, dry_run)
+
+ write_changelog_update(git_root+"/CHANGELOG.md", next_version, dry_run)
if __name__ == "__main__":
diff --git a/src/ts/background.ts b/src/ts/background.ts
index 1845280..db5c4a6 100644
--- a/src/ts/background.ts
+++ b/src/ts/background.ts
@@ -2,18 +2,36 @@ import { infoLog } from './common';
chrome.commands.onCommand.addListener((command: any) => {
infoLog('voice dim', `Command "${command}" triggered`);
+ sendDimTabMessage({ dimShortcutPressed: true });
+});
- chrome.tabs.query({}, (tabs: any[]) => {
- const dimTabs = tabs.filter((tab: { url: string }) => tab.url?.match(/destinyitemmanager\.com.*inventory/));
-
- if (dimTabs && dimTabs[0]?.id)
- chrome.tabs.sendMessage(dimTabs[0].id, { dimShortcutPressed: true }, (response: any) => {
- infoLog('voice dim', { response });
- });
- });
+chrome.tabs.onUpdated.addListener(async function (tabId, changeInfo, tab) {
+ const dimTabId = await getDimTabId();
+ if (dimTabId && tabId === dimTabId) {
+ if (changeInfo.url && !changeInfo.url.includes('inventory')) {
+ sendDimTabMessage('not on inventory page');
+ } else if (changeInfo.url && changeInfo.url.includes('inventory')) {
+ sendDimTabMessage('on inventory page');
+ }
+ }
});
-chrome.runtime.onMessage.addListener((data: any) => {
+async function getDimTabId(): Promise {
+ const dimTabs = await chrome.tabs.query({ url: 'https://*.destinyitemmanager.com/*' });
+ return dimTabs && dimTabs.length >= 1 ? dimTabs[0]?.id : null;
+}
+async function sendDimTabMessage(message: any) {
+ const dimTabId = await getDimTabId();
+ if (dimTabId) {
+ infoLog('message', 'sending', message);
+
+ chrome.tabs.sendMessage(dimTabId, message, (response: any) => {
+ infoLog('voice dim', { response });
+ });
+ }
+}
+
+chrome.runtime.onMessage.addListener((data: any, sender: chrome.runtime.MessageSender) => {
infoLog('voice dim', { data });
if (data === 'showOptions') {
openOptionsPage();
diff --git a/src/ts/common.ts b/src/ts/common.ts
index 767fd46..fe22474 100644
--- a/src/ts/common.ts
+++ b/src/ts/common.ts
@@ -1,12 +1,11 @@
-export interface Action {
- func: () => void;
- timeout: number;
-}
-
export function infoLog(tag: string, message: unknown, ...args: unknown[]) {
console.log(`[${tag}]`, message, ...args);
}
+export function debugLog(tag: string, message: unknown, ...args: unknown[]) {
+ console.debug(`[${tag}]`, message, ...args);
+}
+
export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
export const debounce = ) => ReturnType>(func: F, waitFor: number = 300) => {
@@ -54,7 +53,7 @@ export async function waitForElementToDisplay(
checkFrequencyInMs: number = 50,
timeoutInMs: number = 2000
): Promise {
- return new Promise((resolve) => {
+ return new Promise((resolve, reject) => {
var startTimeInMs = Date.now();
(function loopSearch() {
if (document.querySelector(selector) != null) {
@@ -62,8 +61,8 @@ export async function waitForElementToDisplay(
} else {
setTimeout(function () {
if (timeoutInMs && Date.now() - startTimeInMs > timeoutInMs) {
- infoLog('voice dim', "couldn't find", selector);
- return;
+ debugLog('voice dim', "couldn't find", selector);
+ return reject();
}
loopSearch();
}, checkFrequencyInMs);
@@ -93,13 +92,13 @@ export const DEFAULT_ALWAYS_LISTENING: AlwaysListening = {
activationPhrase: 'okay ghost',
};
-export function store(key: string, value: any) {
+export function store(key: string, value: T) {
chrome.storage.local.set({ [key]: value }, () => {
infoLog('voice dim', 'Stored', key, value);
});
}
-export function retrieve(key: string, defaultValue: any): Promise {
+export function retrieve(key: string, defaultValue: T): Promise {
return new Promise((resolve, reject) => {
chrome.storage.local.get([key], function (result) {
if (chrome.runtime.lastError) {
diff --git a/src/ts/options.ts b/src/ts/options.ts
index b29060d..06a5936 100644
--- a/src/ts/options.ts
+++ b/src/ts/options.ts
@@ -13,7 +13,7 @@ function onCommandChange() {
Object.keys(DEFAULT_COMMANDS).forEach((command) => {
commands[command] = getTextValueById(command);
});
- store('commands', commands);
+ store>('commands', commands);
updateSaveText(true, 'Saved!');
setTimeout(() => updateSaveText(false), 3000);
@@ -46,7 +46,10 @@ function onActivationPhraseChange() {
updateSaveText(true, 'Saved!');
setTimeout(() => updateSaveText(false), 3000);
- store('alwaysListening', { active: listeningToggle.checked, activationPhrase: activationPhrase.value });
+ store('alwaysListening', {
+ active: listeningToggle.checked,
+ activationPhrase: activationPhrase.value,
+ });
sendListenOptionsMessage();
}
@@ -56,7 +59,10 @@ function onAlwaysListeningChange(listeningOptions: AlwaysListening) {
updateSaveText(true, 'Saved!');
setTimeout(() => updateSaveText(false), 3000);
- store('alwaysListening', { active: listeningOptions.active, activationPhrase: listeningOptions.activationPhrase });
+ store('alwaysListening', {
+ active: listeningOptions.active,
+ activationPhrase: listeningOptions.activationPhrase,
+ });
sendListenOptionsMessage();
}
diff --git a/src/ts/voiceDim.ts b/src/ts/voiceDim.ts
index 9d159fa..e253529 100644
--- a/src/ts/voiceDim.ts
+++ b/src/ts/voiceDim.ts
@@ -1,6 +1,7 @@
import Fuse from 'fuse.js';
import {
AlwaysListening,
+ debugLog,
DEFAULT_ALWAYS_LISTENING,
DEFAULT_COMMANDS,
getVisibleItems,
@@ -197,7 +198,6 @@ async function parseSpeech(this: any, transcript: string) {
infoLog('voice dim', "Couldn't determine correct action");
return;
}
- infoLog('voice dim', 'Action:', closestAction.match);
query = getNewQuery(query, closestMatch.match);
await potentialActions[closestAction.match].call(this, query, closestAction.match);
@@ -242,7 +242,7 @@ function getCurrentCharacterClass(): string {
async function handleItemMovement(query: string, action: string): Promise {
infoLog('voice dim', 'in handleItemMovement', { query, action });
const itemToMove = await getItemToMove(query);
- infoLog('voice dim', { itemToMove });
+ debugLog('voice dim', { itemToMove });
if (!itemToMove) {
await clearSearchBar();
return;
@@ -286,7 +286,11 @@ async function getItemToMove(query: string): Promise {
async function transferItem(item: Element) {
item.dispatchEvent(uiEvents.singleClick);
const currentClass = getCurrentCharacterClass();
- const storeDiv = await waitForElementToDisplay(`[title^="Store"] [data-icon*="${currentClass}"]`);
+ const expandCollapseButton = await waitForElementToDisplay('div[title^="Expand or collapse"]');
+ if (!document.querySelector('div[class^="ItemMoveLocations"]')) {
+ expandCollapseButton?.dispatchEvent(uiEvents.singleClick);
+ }
+ const storeDiv = await waitForElementToDisplay(`[title^="Store"] [data-icon*="${currentClass}"]`, 500);
storeDiv?.dispatchEvent(uiEvents.singleClick);
}
@@ -432,18 +436,18 @@ function getClosestMatch(availableItems: string[], query: string): FuseMatch | n
};
const fuse = new Fuse(availableItems, options);
const result = fuse.search(query);
- infoLog('voice dim', { result, query });
+ debugLog('voice dim', { result, query });
if (isAcceptableResult(result)) {
return { toReplace: query, match: result[0].item };
}
- infoLog('voice dim', "Couldn't find a match. Trying to find match by splitting the current query.");
+ debugLog('voice dim', "Couldn't find a match. Trying to find match by splitting the current query.");
const splitQuery = query.split(' ');
for (const split of splitQuery) {
const splitResult = fuse.search(split);
- infoLog('voice dim', { splitResult, split });
+ debugLog('voice dim', { splitResult, split });
return isAcceptableResult(splitResult)
? { toReplace: split, match: splitResult[0].item }
: { toReplace: '', match: '' };
@@ -478,7 +482,6 @@ async function clearSearchBar() {
const clearButton = document.querySelector('.filter-bar-button[title^=Clear]');
const initialCount = getVisibleItems().length;
let waitForUpdate = false;
- console.log(clearButton);
clearButton?.dispatchEvent(uiEvents.singleClick);
if (searchBar && searchBar?.value !== '') {
searchBar.value = '';
@@ -498,8 +501,9 @@ function handleShortcutPress() {
function initializeShortcutListening() {
annyang.addCallback('result', (userSaid: string[]) => {
- infoLog('shortcut', userSaid);
+ debugLog('shortcut', userSaid);
const transcript = userSaid[0].trim().toLowerCase();
+ infoLog('voice dim', 'Heard', transcript);
updateUiTranscript(transcript, true);
parseSpeech(transcript);
annyang.abort();
@@ -510,7 +514,7 @@ function initializeShortcutListening() {
function initializeAlwaysListening() {
annyang.start({ autoRestart: listeningOptions.active, continuous: listeningOptions.active });
annyang.addCallback('result', (userSaid?: string[] | undefined) => {
- infoLog('voice dim', { userSaid });
+ debugLog('voice dim', { userSaid });
if (userSaid) {
let actionPerformed = false;
for (let said of userSaid) {
@@ -524,7 +528,7 @@ function initializeAlwaysListening() {
// include a space intentionally
if (said.includes(`${phrase} `)) {
const transcript = said.split(`${phrase} `)[1];
- infoLog('voice dim', { transcript });
+ infoLog('voice dim', 'Heard', transcript);
updateUiTranscript(transcript, true);
parseSpeech(transcript);
actionPerformed = true;
@@ -539,19 +543,26 @@ function initializeAlwaysListening() {
}
chrome.runtime.onMessage.addListener(async function (request, sender, sendResponse) {
- infoLog('voice dim', { request });
+ infoLog('voice dim', 'Message received', { request });
if (request.dimShortcutPressed && !listeningOptions.active) {
- sendResponse({ ack: 'Acknowledged.' });
handleShortcutPress();
- return;
}
if (request === 'shortcut updated') {
await getCustomCommands();
- sendResponse({ ack: 'Acknowledged.' });
}
if (request === 'listening options updated') {
await getAlwaysListeningOptions();
}
+ if (request === 'not on inventory page') {
+ infoLog('voice dim', 'no longer on inventory page');
+ stopVoiceDim();
+ }
+ if (request === 'on inventory page') {
+ infoLog('voice dim', 'on inventory page');
+ init();
+ }
+ sendResponse({ ack: 'Acknowledged.' });
+ return true;
});
async function getCustomCommands() {
@@ -560,7 +571,7 @@ async function getCustomCommands() {
infoLog('voice dim', { commands, mappedCommands });
}
-function reverseMapCustomCommands(commands: any) {
+function reverseMapCustomCommands(commands: Record) {
const newCommands: Record = {};
for (const propName in commands) {
const arr: Array = commands[propName];
@@ -574,12 +585,22 @@ function reverseMapCustomCommands(commands: any) {
async function getAlwaysListeningOptions() {
listeningOptions = await retrieve('alwaysListening', DEFAULT_ALWAYS_LISTENING);
infoLog('voice dim', { listeningOptions });
+ startListening();
+ // annyang.debug(true);
+}
+
+function startListening() {
annyang.abort();
- infoLog('voice dim', 'initializing annyang');
annyang.removeCallback();
- if (listeningOptions.active) initializeAlwaysListening();
- else initializeShortcutListening();
- // annyang.debug(true);
+ if (listeningOptions.active) {
+ initializeAlwaysListening();
+ } else {
+ initializeShortcutListening();
+ }
+}
+
+function stopListening() {
+ annyang.abort();
}
function createMicDiv() {
@@ -628,11 +649,22 @@ async function getPerks() {
}
function init() {
- getPerks();
- getCustomCommands();
- getAlwaysListeningOptions();
- createMicDiv();
- createHelpDiv();
+ if (window.location.href.includes('inventory')) {
+ getPerks();
+ getCustomCommands();
+ getAlwaysListeningOptions();
+ createMicDiv();
+ createHelpDiv();
+ }
}
window.addEventListener('load', init);
+
+function stopVoiceDim() {
+ const voiceDimDiv = document.getElementById('voiceDim');
+ if (voiceDimDiv) voiceDimDiv.remove();
+ const voiceDimHelp = document.getElementById('voiceDimHelp');
+ if (voiceDimHelp) voiceDimHelp.remove();
+
+ stopListening();
+}