rewrite module to typescript
This commit is contained in:
@@ -20,7 +20,7 @@
|
|||||||
"type": "node18",
|
"type": "node18",
|
||||||
"api": "nodejs-ipc",
|
"api": "nodejs-ipc",
|
||||||
"apiVersion": "0.0.0",
|
"apiVersion": "0.0.0",
|
||||||
"entrypoint": "../main.js"
|
"entrypoint": "../dist/main.js"
|
||||||
},
|
},
|
||||||
"manufacturer": "Toggl",
|
"manufacturer": "Toggl",
|
||||||
"products": ["Track"],
|
"products": ["Track"],
|
||||||
|
|||||||
5
eslint.config.mjs
Normal file
5
eslint.config.mjs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { generateEslintConfig } from '@companion-module/tools/eslint/config.mjs'
|
||||||
|
|
||||||
|
export default generateEslintConfig({
|
||||||
|
enableTypescript: true,
|
||||||
|
})
|
||||||
471
main.js
471
main.js
@@ -1,471 +0,0 @@
|
|||||||
// toggltrack module
|
|
||||||
// Peter Daniel
|
|
||||||
|
|
||||||
import { InstanceBase, Regex, runEntrypoint, InstanceStatus } from '@companion-module/base'
|
|
||||||
import UpdateActions from './actions.js'
|
|
||||||
import UpdatePresets from './presets.js'
|
|
||||||
import UpdateVariableDefinitions from './variables.js'
|
|
||||||
import UpgradeScripts from './upgrades.js'
|
|
||||||
|
|
||||||
import got from 'got'
|
|
||||||
|
|
||||||
class toggltrack extends InstanceBase {
|
|
||||||
constructor(internal) {
|
|
||||||
super(internal)
|
|
||||||
}
|
|
||||||
|
|
||||||
getConfigFields() {
|
|
||||||
// console.log('config fields')
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
type: 'textinput',
|
|
||||||
id: 'apiToken',
|
|
||||||
label: 'Personal API Token from your Toggl user profile (required)',
|
|
||||||
width: 12,
|
|
||||||
required: true,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'checkbox',
|
|
||||||
id: 'alwaysStart',
|
|
||||||
label: 'Always start a new timer even if there is one already running',
|
|
||||||
width: 12,
|
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
async destroy() {
|
|
||||||
console.log('destroy', this.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
async init(config) {
|
|
||||||
console.log('--- init toggltrack ---')
|
|
||||||
this.prefixUrl = 'https://api.track.toggl.com/api/v9/'
|
|
||||||
|
|
||||||
this.config = config
|
|
||||||
|
|
||||||
this.updateStatus(InstanceStatus.Ok)
|
|
||||||
|
|
||||||
this.gotOptions = {
|
|
||||||
responseType: 'json',
|
|
||||||
throwHttpErrors: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.gotOptions.prefixUrl = this.prefixUrl
|
|
||||||
|
|
||||||
this.workspace = null
|
|
||||||
this.workspaceName = null
|
|
||||||
this.projects = [{ id: '0', label: 'None' }]
|
|
||||||
|
|
||||||
this.updateVariableDefinitions()
|
|
||||||
this.updatePresets()
|
|
||||||
|
|
||||||
this.setVariableValues({
|
|
||||||
timerId: null,
|
|
||||||
timerDuration: null,
|
|
||||||
timerDescription: null,
|
|
||||||
lastTimerDuration: null,
|
|
||||||
workspace: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.gotOptions.headers = this.auth()
|
|
||||||
|
|
||||||
if (this.gotOptions.headers != null) {
|
|
||||||
this.getWorkspace().then(this.getCurrentTimer())
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateActions()
|
|
||||||
}
|
|
||||||
|
|
||||||
async configUpdated(config) {
|
|
||||||
console.log('config updated')
|
|
||||||
this.config = config
|
|
||||||
|
|
||||||
this.gotOptions.headers = this.auth()
|
|
||||||
|
|
||||||
if (this.gotOptions.headers != null) {
|
|
||||||
this.getWorkspace().then(this.getCurrentTimer())
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateActions()
|
|
||||||
this.updateVariables()
|
|
||||||
}
|
|
||||||
updateActions() {
|
|
||||||
UpdateActions(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFeedbacks() {
|
|
||||||
UpdateFeedbacks(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePresets() {
|
|
||||||
UpdatePresets(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
updateVariableDefinitions() {
|
|
||||||
UpdateVariableDefinitions(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
auth() {
|
|
||||||
if (this.config.apiToken !== null && this.config.apiToken.length > 0) {
|
|
||||||
let auth = Buffer.from(this.config.apiToken + ':' + 'api_token').toString('base64')
|
|
||||||
let headers = {}
|
|
||||||
headers['Content-Type'] = 'application/json'
|
|
||||||
headers['authorization'] = 'Basic ' + auth
|
|
||||||
return headers
|
|
||||||
} else {
|
|
||||||
this.log('warn', 'Please enter your toggl API token')
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
// console.log(this.gotOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCurrentTimer() {
|
|
||||||
console.log('function: getCurrentTimer')
|
|
||||||
|
|
||||||
if (this.gotOptions.headers == null) {
|
|
||||||
this.log('warn', 'Not authorized')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let cmd = 'me/time_entries/current'
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.sendGetCommand(cmd).then((result) => {
|
|
||||||
if (typeof result === 'object' && result !== null) {
|
|
||||||
if ('id' in result) {
|
|
||||||
this.setVariableValues({
|
|
||||||
timerId: result.id,
|
|
||||||
timerDescription: result.description,
|
|
||||||
timerDuration: result.duration,
|
|
||||||
})
|
|
||||||
this.log('info', 'Current timer id: ' + result.id)
|
|
||||||
resolve(result.id)
|
|
||||||
} else {
|
|
||||||
this.log('info', 'No current timer (no id in data)')
|
|
||||||
this.setVariableValues({
|
|
||||||
timerId: null,
|
|
||||||
timerDescription: null,
|
|
||||||
timerDuration: null,
|
|
||||||
})
|
|
||||||
resolve(null)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.log('info', 'No current timer (no object)')
|
|
||||||
this.setVariableValues({
|
|
||||||
timerId: null,
|
|
||||||
timerDescription: null,
|
|
||||||
timerDuration: null,
|
|
||||||
})
|
|
||||||
resolve(null)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async getWorkspace() {
|
|
||||||
let cmd = 'workspaces'
|
|
||||||
console.log('function: getWorkspace')
|
|
||||||
|
|
||||||
if (this.gotOptions.headers == null) {
|
|
||||||
this.log('warn', 'Not authorized')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset
|
|
||||||
this.workspace = null
|
|
||||||
this.setVariableValues({
|
|
||||||
workspace: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
// get workspace ID
|
|
||||||
this.sendGetCommand(cmd).then((result) => {
|
|
||||||
// console.log('result ' + JSON.stringify(result, null, 4))
|
|
||||||
if (typeof result === 'object' && result !== null) {
|
|
||||||
console.log('Found ' + result.length + ' workspace')
|
|
||||||
// only interested in first workspace
|
|
||||||
if ('id' in result[0]) {
|
|
||||||
this.workspace = result[0].id
|
|
||||||
this.workspaceName = result[0].name
|
|
||||||
this.log('info', 'Workspace: ' + this.workspace + ' - ' + this.workspaceName)
|
|
||||||
this.setVariableValues({
|
|
||||||
workspace: this.workspaceName,
|
|
||||||
})
|
|
||||||
this.getProjects()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('result ' + JSON.stringify(result, null, 4))
|
|
||||||
this.log('debug', 'No workspace')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
getProjects() {
|
|
||||||
console.log('function: getProjects')
|
|
||||||
|
|
||||||
if (this.workspace !== null) {
|
|
||||||
let cmd = 'workspaces/' + this.workspace + '/projects'
|
|
||||||
this.sendGetCommand(cmd).then((result) => {
|
|
||||||
// console.log('result ' + JSON.stringify(result, null, 4))
|
|
||||||
if (typeof result === 'object' && result !== null) {
|
|
||||||
// reset
|
|
||||||
this.projects = []
|
|
||||||
|
|
||||||
for (let p = 0; p < result.length; p++) {
|
|
||||||
if ('id' in result[p]) {
|
|
||||||
if (result[p].active === true) {
|
|
||||||
// don't add archived projects
|
|
||||||
this.projects.push({
|
|
||||||
id: result[p].id.toString(),
|
|
||||||
label: result[p].name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// this.log('debug', 'Project ' + result[p].id + ':' + result[p].name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.projects.sort((a, b) => {
|
|
||||||
let fa = a.label.toLowerCase()
|
|
||||||
let fb = b.label.toLowerCase()
|
|
||||||
|
|
||||||
if (fa < fb) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if (fa > fb) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
|
|
||||||
this.projects.unshift({ id: '0', label: 'None' })
|
|
||||||
console.log('Projects:')
|
|
||||||
console.log(this.projects)
|
|
||||||
this.updateActions()
|
|
||||||
} else {
|
|
||||||
console.log(result)
|
|
||||||
this.log('debug', 'No projects')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTimerDuration(id) {
|
|
||||||
// let cmd = 'time_entries/' + id
|
|
||||||
//
|
|
||||||
// return new Promise((resolve, reject) => {
|
|
||||||
// self.sendCommand('rest_get', cmd).then(
|
|
||||||
// (result) => {
|
|
||||||
// if (typeof result === 'object' && result.data !== null && result.data !== undefined) {
|
|
||||||
// if ('duration' in result.data) {
|
|
||||||
// self.setVariable('timerDuration', result.data.duration)
|
|
||||||
// resolve(result.data.duration)
|
|
||||||
// } else {
|
|
||||||
// self.log('debug', 'Error getting current timer duration (no id in data)')
|
|
||||||
// self.setVariable('timerDuration', null)
|
|
||||||
// resolve(null)
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// self.log('debug', 'Error getting current timer duration (no object)')
|
|
||||||
// self.setVariable('timerDuration', null)
|
|
||||||
// resolve(null)
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// (error) => {
|
|
||||||
// console.log('error ' + error)
|
|
||||||
// self.log('debug', 'Error getting current timer duration')
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
async startTimer(project, description) {
|
|
||||||
let body
|
|
||||||
let cmd
|
|
||||||
let timerId
|
|
||||||
const startTime = new Date()
|
|
||||||
this.getCurrentTimer().then((timerId) => {
|
|
||||||
console.log('timerId: ' + timerId)
|
|
||||||
if (timerId === null || this.config.alwaysStart === true) {
|
|
||||||
// no timer currently running or we want to restart it
|
|
||||||
cmd = 'workspaces/' + this.workspace + '/time_entries'
|
|
||||||
if (project == '0') {
|
|
||||||
body =
|
|
||||||
'{"wid":' +
|
|
||||||
this.workspace +
|
|
||||||
',"description":"' +
|
|
||||||
description +
|
|
||||||
'","created_with":"companion",' +
|
|
||||||
'"start":"' +
|
|
||||||
startTime.toISOString() +
|
|
||||||
'","duration":-1}'
|
|
||||||
} else {
|
|
||||||
body =
|
|
||||||
'{"wid":' +
|
|
||||||
this.workspace +
|
|
||||||
',"description":"' +
|
|
||||||
description +
|
|
||||||
'","created_with":"companion","project_id":' +
|
|
||||||
project +
|
|
||||||
',"start":"' +
|
|
||||||
startTime.toISOString() +
|
|
||||||
'","duration":-1}'
|
|
||||||
}
|
|
||||||
// console.log(body)
|
|
||||||
this.sendPostCommand(cmd, body).then((result) => {
|
|
||||||
if (typeof result === 'object' && result !== null) {
|
|
||||||
this.log('info', 'New timer started ' + result.id + ' ' + result.description)
|
|
||||||
this.setVariableValues({
|
|
||||||
timerId: result.id,
|
|
||||||
timerDescription: result.description,
|
|
||||||
timerDuration: result.duration,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.log('warn', 'Error starting timer')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.log('info', 'A timer is already running ' + timerId + ' not starting a new one!')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async stopTimer() {
|
|
||||||
console.log('function: stopTimer')
|
|
||||||
|
|
||||||
this.getCurrentTimer().then((timerId) => {
|
|
||||||
this.log('info', 'Trying to stop current timer id: ' + timerId)
|
|
||||||
// console.log(typeof timerId)
|
|
||||||
if (typeof timerId === 'number' && timerId > 0) {
|
|
||||||
let cmd = 'workspaces/' + this.workspace + '/time_entries/' + timerId + '/stop'
|
|
||||||
this.sendPatchCommand(cmd).then((result) => {
|
|
||||||
if (typeof result === 'object' && result !== null && result !== undefined) {
|
|
||||||
this.log('info', 'Stopped ' + result.id + ', duration ' + result.duration)
|
|
||||||
this.setVariableValues({
|
|
||||||
timerId: null,
|
|
||||||
timerDescription: null,
|
|
||||||
timerDuration: null,
|
|
||||||
lastTimerDuration: result.duration,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.log('warn', 'Error stopping timer')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.log('warn', 'No running timer to stop or running timer id unknown')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendGetCommand(GetURL) {
|
|
||||||
console.log('get: ' + GetURL)
|
|
||||||
let response
|
|
||||||
|
|
||||||
try {
|
|
||||||
response = await got.get(GetURL, this.gotOptions)
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
this.updateStatus(InstanceStatus.Ok)
|
|
||||||
return response.body
|
|
||||||
} else {
|
|
||||||
this.updateStatus(
|
|
||||||
InstanceStatus.UnknownError,
|
|
||||||
`Unexpected HTTP status code: ${response.statusCode} - ${response.body.error}`,
|
|
||||||
)
|
|
||||||
this.log('warn', `Unexpected HTTP status code: ${response.statusCode} - ${response.body.error}`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error.message)
|
|
||||||
this.processError(error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendPutCommand(PutURL) {
|
|
||||||
console.log('put: ' + PutURL)
|
|
||||||
let response
|
|
||||||
|
|
||||||
try {
|
|
||||||
response = await got.put(PutURL, this.gotOptions)
|
|
||||||
console.log('status: ' + response.statusCode)
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
console.log(response.body)
|
|
||||||
return response.body
|
|
||||||
} else {
|
|
||||||
this.updateStatus(
|
|
||||||
InstanceStatus.UnknownError,
|
|
||||||
`Unexpected HTTP status code: ${response.statusCode} - ${response.body.error}`,
|
|
||||||
)
|
|
||||||
this.log('warn', `Unexpected HTTP status code: ${response.statusCode} - ${response.body.error}`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error.message)
|
|
||||||
this.processError(error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendPatchCommand(PatchURL) {
|
|
||||||
console.log('patch: ' + PatchURL)
|
|
||||||
let response
|
|
||||||
|
|
||||||
try {
|
|
||||||
response = await got.patch(PatchURL, this.gotOptions)
|
|
||||||
// console.log('status: ' + response.statusCode)
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
// console.log(response.body)
|
|
||||||
return response.body
|
|
||||||
} else {
|
|
||||||
this.updateStatus(
|
|
||||||
InstanceStatus.UnknownError,
|
|
||||||
`Unexpected HTTP status code: ${response.statusCode} - ${response.body.error}`,
|
|
||||||
)
|
|
||||||
this.log('warn', `Unexpected HTTP status code: ${response.statusCode} - ${response.body.error}`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error.message)
|
|
||||||
this.processError(error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendPostCommand(cmd, body) {
|
|
||||||
console.log(body)
|
|
||||||
let response
|
|
||||||
let postdata = {}
|
|
||||||
postdata.prefixUrl = this.prefixUrl
|
|
||||||
;(postdata.responseType = 'json'), (postdata.throwHttpErrors = false)
|
|
||||||
postdata.headers = this.auth()
|
|
||||||
postdata.json = JSON.parse(body)
|
|
||||||
|
|
||||||
// console.log(postdata)
|
|
||||||
|
|
||||||
try {
|
|
||||||
response = await got.post(cmd, postdata)
|
|
||||||
// console.log(response.request.requestUrl)
|
|
||||||
// console.log(response.statusCode)
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return response.body
|
|
||||||
} else {
|
|
||||||
this.updateStatus(
|
|
||||||
InstanceStatus.UnknownError,
|
|
||||||
`Unexpected HTTP status code: ${response.statusCode} - ${response.body.error}`,
|
|
||||||
)
|
|
||||||
this.log('warn', `Unexpected HTTP status code: ${response.statusCode} - ${response.body.error}`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error.message)
|
|
||||||
this.processError(error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processError(error) {
|
|
||||||
console.log('gotError: ' + error.code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runEntrypoint(toggltrack, UpgradeScripts)
|
|
||||||
32
package.json
32
package.json
@@ -1,11 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "toggl-track",
|
"name": "toggl-track",
|
||||||
"version": "2.0.2",
|
"version": "2.1.0",
|
||||||
"main": "main.js",
|
"main": "dist/main.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "prettier -w .",
|
"format": "prettier -w .",
|
||||||
"package": "companion-module-build",
|
"package": "yarn run build && companion-module-build",
|
||||||
|
"build": "rimraf dist && yarn run build:main",
|
||||||
|
"build:main": "tsc -p tsconfig.build.json",
|
||||||
|
"dev": "tsc -p tsconfig.build.json --watch",
|
||||||
|
"lint:raw": "eslint",
|
||||||
|
"lint": "yarn run lint:raw .",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -15,11 +20,24 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@companion-module/base": "~1.11",
|
"@companion-module/base": "~1.11",
|
||||||
"got": "~13.0.0"
|
"toggl-track": "^0.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@companion-module/tools": "~2.1.0",
|
"@companion-module/tools": "~2.1.1",
|
||||||
"prettier": "^3.4.2"
|
"@types/node": "^22.10.2",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
|
"prettier": "^3.4.2",
|
||||||
|
"rimraf": "^5.0.10",
|
||||||
|
"typescript": "~5.5.4",
|
||||||
|
"typescript-eslint": "^8.18.1"
|
||||||
},
|
},
|
||||||
"prettier": "@companion-module/tools/.prettierrc.json"
|
"prettier": "@companion-module/tools/.prettierrc.json",
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{css,json,md,scss}": [
|
||||||
|
"prettier --write"
|
||||||
|
],
|
||||||
|
"*.{ts,tsx,js,jsx}": [
|
||||||
|
"yarn lint:raw --fix"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export default function (self) {
|
import { TogglTrack } from './main.js'
|
||||||
|
|
||||||
|
export default function (self: TogglTrack): void {
|
||||||
self.setActionDefinitions({
|
self.setActionDefinitions({
|
||||||
startNewTimer: {
|
startNewTimer: {
|
||||||
name: 'Start New Timer',
|
name: 'Start New Timer',
|
||||||
@@ -14,35 +16,35 @@ export default function (self) {
|
|||||||
label: 'Project',
|
label: 'Project',
|
||||||
id: 'project',
|
id: 'project',
|
||||||
default: '0',
|
default: '0',
|
||||||
choices: self.projects,
|
choices: self.projects!,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
callback: ({ options }) => {
|
callback: async ({ options }) => {
|
||||||
self.startTimer(options.project, options.description)
|
return self.startTimer(Number(options.project), options.description as string)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
getCurrentTimer: {
|
getCurrentTimer: {
|
||||||
name: 'Get Current Timer',
|
name: 'Get Current Timer',
|
||||||
options: [],
|
options: [],
|
||||||
callback: (action) => {
|
callback: async () => {
|
||||||
self.getCurrentTimer()
|
await self.getCurrentTimer()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
stopCurrentTimer: {
|
stopCurrentTimer: {
|
||||||
name: 'Stop Current Timer',
|
name: 'Stop Current Timer',
|
||||||
options: [],
|
options: [],
|
||||||
callback: (action) => {
|
callback: async () => {
|
||||||
self.stopTimer()
|
return self.stopTimer()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshProjects: {
|
refreshProjects: {
|
||||||
name: 'Refresh Project List',
|
name: 'Refresh Project List',
|
||||||
options: [],
|
options: [],
|
||||||
callback: (action) => {
|
callback: async () => {
|
||||||
self.getWorkspace()
|
await self.getWorkspace()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
36
src/config.ts
Normal file
36
src/config.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { type SomeCompanionConfigField } from '@companion-module/base'
|
||||||
|
|
||||||
|
export interface ModuleConfig {
|
||||||
|
apiToken?: string
|
||||||
|
workspaceName: string
|
||||||
|
alwaysStart?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GetConfigFields(): SomeCompanionConfigField[] {
|
||||||
|
// console.log('config fields')
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: 'textinput',
|
||||||
|
id: 'apiToken',
|
||||||
|
label: 'Personal API Token from your Toggl user profile (required)',
|
||||||
|
width: 12,
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'textinput',
|
||||||
|
id: 'workspaceName',
|
||||||
|
label: 'Workspace to default to (first found if unset)',
|
||||||
|
width: 12,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'checkbox',
|
||||||
|
id: 'alwaysStart',
|
||||||
|
label: 'Always start a new timer even if there is one already running',
|
||||||
|
width: 12,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
318
src/main.ts
Normal file
318
src/main.ts
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
// toggltrack module
|
||||||
|
// Peter Daniel
|
||||||
|
|
||||||
|
import { InstanceBase, runEntrypoint, InstanceStatus, SomeCompanionConfigField } from '@companion-module/base'
|
||||||
|
import { GetConfigFields, type ModuleConfig } from './config.js'
|
||||||
|
import UpdateActions from './actions.js'
|
||||||
|
import UpdatePresets from './presets.js'
|
||||||
|
import UpdateVariableDefinitions from './variables.js'
|
||||||
|
import UpgradeScripts from './upgrades.js'
|
||||||
|
import { Toggl, ITimeEntry, IWorkspaceProject } from 'toggl-track'
|
||||||
|
import { togglGetWorkspaces } from './toggl-extend.js'
|
||||||
|
|
||||||
|
export class TogglTrack extends InstanceBase<ModuleConfig> {
|
||||||
|
config!: ModuleConfig // Setup in init()
|
||||||
|
|
||||||
|
toggl?: Toggl
|
||||||
|
|
||||||
|
workspaceId?: number // current active workspace id
|
||||||
|
workspaceName: string = '' // name of workspace
|
||||||
|
projects?: { id: string; label: string }[]
|
||||||
|
|
||||||
|
constructor(internal: unknown) {
|
||||||
|
super(internal)
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfigFields(): SomeCompanionConfigField[] {
|
||||||
|
return GetConfigFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy(): Promise<void> {
|
||||||
|
console.log('destroy', this.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(config: ModuleConfig): Promise<void> {
|
||||||
|
console.log('--- init toggltrack ---')
|
||||||
|
|
||||||
|
this.config = config
|
||||||
|
|
||||||
|
this.updateStatus(InstanceStatus.Ok)
|
||||||
|
|
||||||
|
this.workspaceId = undefined
|
||||||
|
this.projects = [{ id: '0', label: 'None' }]
|
||||||
|
|
||||||
|
this.updateVariableDefinitions()
|
||||||
|
this.updatePresets()
|
||||||
|
|
||||||
|
this.setVariableValues({
|
||||||
|
timerId: undefined,
|
||||||
|
timerDuration: undefined,
|
||||||
|
timerDescription: undefined,
|
||||||
|
lastTimerDuration: undefined,
|
||||||
|
workspace: undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.initToggleConnection()
|
||||||
|
|
||||||
|
this.updateActions()
|
||||||
|
}
|
||||||
|
|
||||||
|
async configUpdated(config: ModuleConfig): Promise<void> {
|
||||||
|
console.log('config updated')
|
||||||
|
|
||||||
|
const apiTokenChanged: boolean = this.config.apiToken != config.apiToken
|
||||||
|
const workSpaceDefaultChanged: boolean = this.config.workspaceName != config.workspaceName
|
||||||
|
|
||||||
|
this.config = config
|
||||||
|
|
||||||
|
if (apiTokenChanged) {
|
||||||
|
this.log('debug', 'api token changed. init new toggle connection')
|
||||||
|
this.toggl = undefined
|
||||||
|
await this.initToggleConnection()
|
||||||
|
}
|
||||||
|
if (workSpaceDefaultChanged) {
|
||||||
|
this.log('debug', 'workspace default changed. reload workspaces')
|
||||||
|
await this.getWorkspace()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateActions()
|
||||||
|
this.updateVariables()
|
||||||
|
}
|
||||||
|
updateVariables(): void {
|
||||||
|
throw new Error('Method not implemented.')
|
||||||
|
}
|
||||||
|
updateActions(): void {
|
||||||
|
UpdateActions(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*updateFeedbacks() {
|
||||||
|
UpdateFeedbacks(this)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
updatePresets(): void {
|
||||||
|
UpdatePresets(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateVariableDefinitions(): void {
|
||||||
|
UpdateVariableDefinitions(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
async initToggleConnection(): Promise<void> {
|
||||||
|
if (this.config.apiToken!.length > 0) {
|
||||||
|
this.toggl = new Toggl({
|
||||||
|
auth: {
|
||||||
|
token: this.config.apiToken!,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const logged: string = await this.toggl.me.logged()
|
||||||
|
this.log('debug', logged)
|
||||||
|
if (logged === 'OK') {
|
||||||
|
await this.getWorkspace()
|
||||||
|
await this.getCurrentTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCurrentTimer(): Promise<number | null> {
|
||||||
|
console.log('function: getCurrentTimer')
|
||||||
|
|
||||||
|
if (!this.toggl) {
|
||||||
|
this.log('warn', 'Not authorized')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry: ITimeEntry = await this.toggl.timeEntry.current()
|
||||||
|
this.log('debug', 'response for timer id ' + JSON.stringify(entry))
|
||||||
|
|
||||||
|
if (entry) {
|
||||||
|
this.log('info', 'Current timer id: ' + entry.id)
|
||||||
|
this.setVariableValues({
|
||||||
|
timerId: entry.id,
|
||||||
|
timerDescription: entry.description,
|
||||||
|
timerDuration: entry.duration,
|
||||||
|
})
|
||||||
|
|
||||||
|
return entry.id
|
||||||
|
} else {
|
||||||
|
this.log('info', 'No current timer')
|
||||||
|
this.setVariableValues({
|
||||||
|
timerId: undefined,
|
||||||
|
timerDescription: undefined,
|
||||||
|
timerDuration: undefined,
|
||||||
|
})
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWorkspace(): Promise<void> {
|
||||||
|
console.log('function: getWorkspace')
|
||||||
|
if (!this.toggl) {
|
||||||
|
this.log('warn', 'Not authorized')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
this.workspaceId = undefined
|
||||||
|
this.setVariableValues({
|
||||||
|
workspace: undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
const workspaces = await togglGetWorkspaces(this.toggl)
|
||||||
|
this.log('info', 'Found ' + workspaces.length + ' workspace')
|
||||||
|
|
||||||
|
for (const ws of workspaces) {
|
||||||
|
if (this.config.workspaceName == '' || this.config.workspaceName == ws.name) {
|
||||||
|
// take the first or matching one and continue
|
||||||
|
this.workspaceId = ws.id
|
||||||
|
this.workspaceName = ws.name
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// workspaceName does not match => continue with next
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.workspaceId == undefined) {
|
||||||
|
// no workspace found
|
||||||
|
//console.log('result ' + JSON.stringify(workspaces, null, 4))
|
||||||
|
this.log('debug', 'No workspace found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log('info', 'Workspace: ' + this.workspaceId + ' - ' + this.workspaceName)
|
||||||
|
this.setVariableValues({
|
||||||
|
workspace: this.workspaceName,
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.getProjects()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getProjects(): Promise<void> {
|
||||||
|
console.log('function: getProjects ' + this.workspaceId)
|
||||||
|
|
||||||
|
if (!this.workspaceId) {
|
||||||
|
this.log('warn', 'workspaceId undefined')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const projects: IWorkspaceProject[] = await this.toggl!.projects.list(this.workspaceId)
|
||||||
|
|
||||||
|
//const projects: IWorkspaceProject[] = await togglGetProjects(this.toggl!, this.workspaceId!)
|
||||||
|
|
||||||
|
if (typeof projects === 'string' || projects.length == 0) {
|
||||||
|
this.projects = [{ id: '0', label: 'None' }]
|
||||||
|
console.log(projects)
|
||||||
|
this.log('debug', 'No projects')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.projects = projects
|
||||||
|
.filter((p) => p.active)
|
||||||
|
.map((p) => {
|
||||||
|
return {
|
||||||
|
id: p.id.toString(),
|
||||||
|
label: p.name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sort((a, b) => {
|
||||||
|
const fa = a.label.toLowerCase()
|
||||||
|
const fb = b.label.toLowerCase()
|
||||||
|
|
||||||
|
if (fa < fb) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if (fa > fb) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('Projects:')
|
||||||
|
console.log(this.projects)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTimerDuration(id) {
|
||||||
|
// let cmd = 'time_entries/' + id
|
||||||
|
//
|
||||||
|
// return new Promise((resolve, reject) => {
|
||||||
|
// self.sendCommand('rest_get', cmd).then(
|
||||||
|
// (result) => {
|
||||||
|
// if (typeof result === 'object' && result.data !== null && result.data !== undefined) {
|
||||||
|
// if ('duration' in result.data) {
|
||||||
|
// self.setVariable('timerDuration', result.data.duration)
|
||||||
|
// resolve(result.data.duration)
|
||||||
|
// } else {
|
||||||
|
// self.log('debug', 'Error getting current timer duration (no id in data)')
|
||||||
|
// self.setVariable('timerDuration', null)
|
||||||
|
// resolve(null)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// self.log('debug', 'Error getting current timer duration (no object)')
|
||||||
|
// self.setVariable('timerDuration', null)
|
||||||
|
// resolve(null)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// (error) => {
|
||||||
|
// console.log('error ' + error)
|
||||||
|
// self.log('debug', 'Error getting current timer duration')
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
async startTimer(project: number, description: string): Promise<void> {
|
||||||
|
if (!this.toggl || !this.workspaceId) {
|
||||||
|
this.log('error', 'toggle not initialized. Do not start time')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentId = await this.getCurrentTimer()
|
||||||
|
let newEntry: ITimeEntry
|
||||||
|
if (currentId === null || this.config.alwaysStart === true) {
|
||||||
|
// there is no running time entry or alwaysStart is true
|
||||||
|
newEntry = await this.toggl.timeEntry.create(this.workspaceId, {
|
||||||
|
description: description,
|
||||||
|
workspace_id: this.workspaceId,
|
||||||
|
created_with: 'companion',
|
||||||
|
start: new Date().toISOString(),
|
||||||
|
duration: -1,
|
||||||
|
project_id: project != 0 ? project : undefined,
|
||||||
|
})
|
||||||
|
this.log('info', 'New timer started ' + newEntry.id + ' ' + newEntry.description)
|
||||||
|
this.setVariableValues({
|
||||||
|
timerId: newEntry.id,
|
||||||
|
timerDescription: newEntry.description,
|
||||||
|
timerDuration: newEntry.duration,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.log('info', 'A timer is already running ' + currentId + ' not starting a new one!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopTimer(): Promise<void> {
|
||||||
|
this.log('debug', 'function: stopTimer')
|
||||||
|
|
||||||
|
if (!this.toggl || !this.workspaceId) {
|
||||||
|
this.log('error', 'toggle not initialized. Do not start time')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const currentId = await this.getCurrentTimer()
|
||||||
|
this.log('info', 'Trying to stop current timer id: ' + currentId)
|
||||||
|
// console.log(typeof timerId)
|
||||||
|
if (currentId !== null) {
|
||||||
|
const updated: ITimeEntry = await this.toggl.timeEntry.stop(currentId, this.workspaceId)
|
||||||
|
this.log('info', 'Stopped ' + updated.id + ', duration ' + updated.duration)
|
||||||
|
|
||||||
|
this.setVariableValues({
|
||||||
|
timerId: undefined,
|
||||||
|
timerDescription: undefined,
|
||||||
|
timerDuration: undefined,
|
||||||
|
lastTimerDuration: updated.duration,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.log('warn', 'No running timer to stop or running timer id unknown')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runEntrypoint(TogglTrack, UpgradeScripts)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { combineRgb } from '@companion-module/base'
|
import { combineRgb } from '@companion-module/base'
|
||||||
|
import { TogglTrack } from './main.js'
|
||||||
|
|
||||||
export default function (self) {
|
export default function (self: TogglTrack): void {
|
||||||
self.setPresetDefinitions({
|
self.setPresetDefinitions({
|
||||||
Start: {
|
Start: {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
11
src/toggl-extend.ts
Normal file
11
src/toggl-extend.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Toggl } from 'toggl-track'
|
||||||
|
|
||||||
|
export interface IWorkspace {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function togglGetWorkspaces(toggl: Toggl): Promise<IWorkspace[]> {
|
||||||
|
const resp: IWorkspace[] = await toggl.request<IWorkspace[]>('workspaces', {})
|
||||||
|
return resp
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
export default function (self) {
|
import { TogglTrack } from './main.js'
|
||||||
|
|
||||||
|
export default function (self: TogglTrack): void {
|
||||||
self.setVariableDefinitions([
|
self.setVariableDefinitions([
|
||||||
{
|
{
|
||||||
name: 'Workspace',
|
name: 'Workspace',
|
||||||
14
tsconfig.build.json
Normal file
14
tsconfig.build.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "@companion-module/tools/tsconfig/node18/recommended",
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["node_modules/**", "src/**/*spec.ts", "src/**/__tests__/*", "src/**/__mocks__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"paths": {
|
||||||
|
"*": ["./node_modules/*"]
|
||||||
|
},
|
||||||
|
"module": "Node16",
|
||||||
|
"moduleResolution": "Node16"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
tsconfig.json
Normal file
8
tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.build.json",
|
||||||
|
"include": ["src/**/*.ts"],
|
||||||
|
"exclude": ["node_modules/**"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["jest", "node"]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user