drugdose

package module
v0.0.0-...-d1cb7e0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 26, 2024 License: ISC Imports: 20 Imported by: 0

README

Welcome traveller!

I hope you find this interesting!

What's the purpose of this project?

It's an attempt at making a Go module and application for enabling a user to log their drug dosages and timings. It uses the logged information, along with fetched and stored locally information about a particular substance, to make assumptions about where things might be going with your experience.

The main purpose is harm reduction.

This project does NOT aim the endorsement of any psychoactive substance. If it prevents a person from consuming more, then it's done it's job.

Showing logs to others as a bragging point makes you look like a fool.

Current status

The project is in a very early phase, making the very first steps at being something remotely usable. If you feel like it, give it a try and please if you have any ideas, concerns, etc. write in the Issues.

Builds

If you don't want to build from source, you can checkout the "Releases" section. There should be a "snapshot" tag with archives attached to it.

Contributions

Pull requests are very welcome as well and there currently aren't any guidelines, just try sticking to the coding style and use gofmt! It would be nice if single lines stay <= 120 characters.

Module

Using this as a module now is a bad idea, because the API will most likely change a lot until the first stable tag.

If you're not sure what this means, then you don't need to worry about it.

Terminal tool changes

If after an update to the terminal tool something broke, it's best to clear all config files and database files/tables and start over. That's just what happens when a project is in rapid development without versioning.

If that didn't help with solving the issue, send a report on Github please!

Source Information

Currently the default source is https://psychonautwiki.org This means the info present there, that's available through their API, will be downloaded to your machine and stored in a local database. Later when you log new dosages or want information about a substance, it will use the local database, not the API, if it's already fetched the information.

You don't need to configure anything for the database or API by default.

Keep in mind, if you store data locally for too long, it might be obsolete. You shouldn't clear your local db too quickly as well, because this way you put more strain on the API servers for no reason and you might lose data, which you might need in case you don't have an Internet connection. It's also a lot slower to get data using the API compared to the local database.

gpd-names-configs

This directory and it's use might be a bit confusing. This is an attempt at making a simple explanation.

These files are all written manually, the program does not generate them.

When the program is first ran, it looks for a directory "gpd-names-configs" in the current working directory. If it finds it, it copies the whole directory over to the config directory of the operating system user, which depends on the OS where it's located. If a copy is started, it will print where it wants to copy to and afterwards the path can be found again by running gopsydose -get-paths.

When initializing the database properly, the contents of all the files in the now copied over "gpd-names-configs" directory are added to their own tables in the database. This is done once. The data is not modified, it's only read. In order to modify the data, the tables must be removed and recreated using the config files. The gopsydose API contains functions that do this. The CLI tool has a flag -overwrite-names.

These stored names are used so that different inputs from the users can be considered valid, even if they're not present in the source.

These names might change when something in the sources change. The config files can be manually modified to match those changes. The gopsydose source repository might also reflect those changes and the modifications can be fetched from there, but should be checked if they're not ahead or behind reality.

After modification, the flag -overwrite-names can be used to reflect the changes in the database.

Global alternative names

When adding a new log, the code first checks the global substance names, which is the "gpd-substance-names.toml" file. Remember, it doesn't check the file itself, but the data from the file added to the database. It compares the user input to the "AltNames" in the config file or more accurately, the "alternativeName" column in the database. If the user input matches one of the "altenative" names, it replaces the user input with the name set as "LocalName" in the config file or more accurately the "localName" column is the database. It then continues to the "source specific names".

Source specific names

After the code has checked the global names, it checks the "source-names-local-configs" directory a.k.a. the "source specific names". The directory contains other directories which correspond to a source. So the "psychonautwiki" directory has names specific to the PsychonautWiki source. The config files in this subdirectories have the same layout as the global configs. They are also stored in the database. If a match is found, it will replace the replaced "global name" and that's the final valid output. This is the name used when further processing or storage is done.

Terminal tool examples

Basic options

If you want to log a dose:

gopsydose -drug alcohol -route drink -dose 500 -units ml -perc 6

or

gopsydose -drug weed -route smoked -dose 60 -units mg -perc 5

or

gopsydose -drug mdma -route oral -dose 90 -units mg

Since Cannabis and Alcohol aren't usually consumed at once, there is a command to mark when the dosing has ended.

gopsydose -change-log -end-time now

This will set when you finished your dose for the last log. You can use an unix timestamp like in the example below instead of now.

To change the start time of dose use: gopsydose -change-log -start-time 1655443322

Changing the start time also changes the "id" of a dose. This means, if you're looking for the last dose and you've changed the start time to an earlier moment, it will get pushed back in the list.

You can set the times for a specific id by using the -for-id command like so: gopsydose -change-log -end-time now -for-id 1655443322

This works for both times.

If you're consuming something at once like LSD or Psilocybin mushrooms or anything else, there's no need for the -change-log -end-time command. Just continue without doing it.

To see the newest dose only: gopsydose -get-new-logs 1

To see all dosages: gopsydose -get-logs

To search the dosages: gopsydose -get-logs -search "whatever"

To see the progress of your last dosage: gopsydose -get-times

You can combine -get-logs or -get-times with: -for-id

to get information for a specific ID.

More options

If you want a log to be remembered and only set the dose for the next log:

gopsydose -remember -drug weed -route smoked -dose 60 -units mg -perc 5

Remembers the config and then for the next dose: gopsydose -dose 60 -perc 5

Not everything needs -perc.

Forgetting the last config: gopsydose -forget

If you're running Linux or another UNIX-like OS with GNU Watch, you can do:

watch -n 600 gopsydose -get-times

This will run the command every 10 minutes and show you the latest results.

Don't run it too fast, because it's bad for your storage device!

There is a limit set in a config file about how many dosages you can do, the default is 100, when the limit is reached it will not allow anymore, but you can clean the logs like so: gopsydose -clean-logs

To clean dosages with a search: gopsydose -clean-logs -search "whatever"

This will clean only dosages which contain the "whatever" string.

No need to delete all logs, you can delete X number of the oldest logs like so, for example to delete 3 of the oldest logs:

gopsydose -clean-old-logs 3

You can do the command like so: gopsydose -clean-old-logs 1 -for-id 1655144869

to remove a specific ID, works with -clean-new-logs 1 as well.

After logging you can change the data of a log using -change-log it works for:

-start-time ; -end-time ; -drug ; -dose ; -units ; -route

So for example for changing the dose you would do:

gopsydose -change-log -dose 123

This will change the dose for the last log, to change for a specific log do:

gopsydose -change-log -dose 123 -for-id 1655144869

To see where your config files and database file are:

gopsydose -get-paths

Checkout the Configs Explained section for explanations on configuration options!

If you're paranoid, to clean the whole database: gopsydose -clean-db

Also don't forget, if you're using sqlite, which is the default, you can always do: gopsydose -get-paths

Then you can delete the database (db) file itself manually.

Even more options

Every single option is described quickly from the -help flag.

Security/Privacy

The issue is currently no files are encrypted and can't be until a proper implementation is done. See for more info: https://gitlab.com/cznic/sqlite/-/issues/105#note_1088508097

Also since by default drug information is fetched using the psychonautwiki API, it would be wise not to spam their servers too much. The information is stored locally on first fetch and reused later for everything. This way even if the Internet goes down, the logger can still be used properly and the API servers can relax.

For any more info, again: gopsydose -help

Proxy

In order to use a proxy, checkout the ProxyURL setting!

The address "socks5://127.0.0.1:9150" is for connection via Tor, using an opened Tor browser. When the browser is opened, it also starts a proxy server with that address, so it can be used to query data using Tor. Using the browser is the easier method, there's no need to run services/daemons manually and configuring them.

Alias

You can use the example.alias file to setup an easier to use environment for your system. The difference between this and the -remember command is that, shell aliases are system wide and from the operating system. The -remember command stores the "command" in the database, allowing even remote use of those settings, which is OS-independent. Aliases are a lot more flexible currently, so use them when you need them!

Installing Go

  • When using Windows, Go can be downloaded from the official website https://go.dev
  • When using Linux, a package manager can be used. Checkout the Go Linux Installation section!
Go Linux Installation

For OpenSUSE, Tumbleweed is preferable, since Leap would probably be falling behind a bit with the versions.

OpenSUSE: sudo zypper install go

Arch Linux: sudo pacman -S go

Fedora and Debian might be a bit out of date as well.

If for Debian the version is old, backports or sid need to be used. Do this at your own risk, since this is not the normal way of installing packages.

If for Fedora the version is old, a package from a newer release, Rawhide or a third party repository needs to be used. The same warning applies as for Debian.

Fedora: sudo dnf install golang

Debian: sudo apt install golang-go

Using asdf

If the distribution package manager isn't used, there's also an option with an external tool. Checkout https://asdf-vm.com

Installing this project

To install the gopsydose terminal tool, run in terminal:

go install github.com/psybits/gopsydose@latest

Afterwards a quick test can be done in the terminal: gopsydose -help

It should show a list of commands and their descriptions. If not, something went wrong in the installation.

There are a lot of commands and currently it isn't very clear, but a few examples and explanations are present in this document.

Viewing/Editing the database

To view or edit the sqlite database manually, SQLite Browser can be used.

For MariaDB/MySQL, DBeaver can be used.

To get the database directory, run in terminal: gopsydose -get-paths

Configs explained

gpd-settings.toml
MaxLogsPerUser

How many logs a user can make. You can log as a different user using the -user command.

UseSource

The name of the API set in gpd-sources.toml. The API needs to have an implementation, currently only psychonautwiki has one.

AutoFetch

Whether to fetch info from an API when logging. If set to false the source table needs to be manually filled using other tools.

AutoRemove

If set to true, will remove the oldest log when adding a new one, if the MaxLogsPerUser limit is reached, without telling the user.

DBDriver

Which database driver to use. Current options are "sqlite" or "mysql".

VerbosePrinting

If set to true, functions which print more verbose information, will print it.

Timezone

This by default is "Local", which means the code tries to figure out the local time zone using the operating system settings. If this fails, you can use the other possible strings from below.

You can change it to something like "Europe/Paris" to change the time zone to the one in that area.

All info about this string can be found here: https://pkg.go.dev/time#LoadLocation

ProxyURL

This by default is '' (empty), meaning no proxy is used. It can also be set to 'none' with the same effect. If the string is set to 'socks5://127.0.0.1:9150', all query connections are made using that proxy. This specific URL is the default for the Tor browser, so it can be used to query via the Tor network if the browser is open while querying.

Timeout

This setting depends on the way the API is used. Some developers might choose to ignore it, some might want to use it. When used, it's supposed to set the time to wait for a SQL query to finish or a connection to a server to be done. Some developers might use it as the total duration for all connections or queries, for example in the case of terminal programs.

When ignored, it would be good for the developers to set the value to something like "This Value Is Ignored".

If your connections have huge delays for some reason and this settings is not ignored, try setting the value higher.

The possible values are described here: https://pkg.go.dev/time#ParseDuration

CostCurrency

Sets a default currency to log when using the -cost flag. It can be bypassed using the -cost-cur flag per log.

DBSettings
DBSettings.mysql

Path - Credentials to access a MariaDB/MySQL database.

The database needs to be created in advance.

Example: user:password@tcp(127.0.0.1:3306)/database

Parameters - For MariaDB/MySQL, currently it doesn't change anything.

DBSettings.sqlite

Path - Disk location to access the SQLite database file.

The database file will be automatically created, no need to do it in advance.

Example: /home/user/.local/share/GPD/gpd.db

Parameters - For SQLite, these are the URI parameters added after the Path.

gpd-sources.toml
[NameOfTheApi]
API_ADDRESS = 'api.whereitis.com'

An implementation needs to be present in the code for the name of the API.

Documentation

Index

Constants

View Source
const ActionAddToDoseTable string = "adding to dose table completed"
View Source
const ActionAddToInfoTable string = "adding to info table completed"
View Source
const ActionChangeUserLog string = "changing user log completed"
View Source
const ActionFetchFromPsychonautWiki string = "fetching from psychonautwiki completed"
View Source
const ActionFetchFromSource string = "fetching from source completed"
View Source
const ActionForgetDosing string = "dosing forgetting completed"
View Source
const ActionRememberDosing string = "dosing remember completed"
View Source
const ActionRemoveLogs string = "removing logs from dose table completed"
View Source
const ActionRemoveSingleDrugInfo string = "removing single drug info completed"
View Source
const ActionSetUserSettings string = "user settings change completed"
View Source
const DefaultAutoFetch bool = true
View Source
const DefaultAutoRemove bool = false
View Source
const DefaultCostCurr string = ""
View Source
const DefaultDBDir string = "GPD"
View Source
const DefaultDBDriver string = SqliteDriver
View Source
const DefaultDBName string = "gpd.db"
View Source
const DefaultMySQLAccess string = "user:password@tcp(127.0.0.1:3306)/database"
View Source
const DefaultProxyURL string = ""
View Source
const DefaultSource string = "psychonautwiki"
View Source
const DefaultSourceAddress string = PsychonautwikiAddress
View Source
const DefaultTimeout string = "5s"
View Source
const DefaultTimezone string = "Local"
View Source
const DefaultUsername string = "defaultUser"
View Source
const DefaultVerbose bool = false
View Source
const ForgetInputConfigMagicNumber string = "0"

When this number is set as the reference ID for remembering a particular input, it means that it's now "forgotten" and there should be no attempts to "remember" any inputs. This is related to the RememberConfig() and ForgetConfig() functions.

View Source
const InfoDrugNameCol string = "drugName"
View Source
const InfoRouteCol string = "drugRoute"
View Source
const LogCostCol string = "cost"
View Source
const LogCostCurrencyCol string = "costCurrency"
View Source
const LogDoseCol string = "dose"
View Source
const LogDoseUnitsCol string = "doseUnits"
View Source
const LogDrugNameCol string = "drugName"
View Source
const LogDrugRouteCol string = "drugRoute"
View Source
const LogEndTimeCol string = "timeOfDoseEnd"
View Source
const LogStartTimeCol string = "timeOfDoseStart"
View Source
const MysqlDriver string = "mysql"
View Source
const NameTypeConvertUnits = "convUnits"
View Source
const NameTypeRoute = "route"
View Source
const NameTypeSubstance = "substance"

Constants used for matching names

View Source
const NameTypeUnits = "units"
View Source
const PsychonautwikiAddress string = "api.psychonautwiki.org"
View Source
const SqliteDriver string = "sqlite"

Variables

View Source
var ComboInputError error = errors.New("combo of input parameters not in database")
View Source
var ConvResultIsZeroError error = errors.New("conversion result is zero")
View Source
var DoseBelowThresholdError error = errors.New("the dosage is below the source threshold")
View Source
var EmptyListDrugNamesError error = errors.New("empty list of drug names from table")

EmptyListDrugNamesError is the error returned when no unique drug names could be retrieved from the database.

View Source
var InvalidColInput error = errors.New("an invalid column name has been given")
View Source
var LogDoesntExistError error = errors.New("log doesn't exist")
View Source
var LoggedRouteInfoError error = errors.New("dose route doesn't match anything in info table")
View Source
var LoggedUnitsInfoError error = errors.New("dose units don't match anything in info table")
View Source
var MaxLogsPerUserError error = errors.New("reached the maximum entries per user")
View Source
var NoDensitySubstanceError error = errors.New("got no density for substance")
View Source
var NoDrugInfoTable error = errors.New("no such drug in info table")
View Source
var NoDrugInfoTableError error = errors.New("no such drug in the info (source) table")
View Source
var NoLogsError error = errors.New("no logs returned for user")
View Source
var NoNamesReturnedError error = errors.New("no names returned")
View Source
var NoNametypeError error = errors.New("no nameType")
View Source
var NoROAForSubs error = errors.New("no route of administration for substance")

NoROAForSubs is the error returned when no routes of administration is returned for a substance could be retrieved from a source.

View Source
var NoUsersReturned error = errors.New("no usernames have been returned")

NoUsersReturned is the error returned when no unique usernames from the log table have been retrieved.

View Source
var NoValidSourceSel error = errors.New("no valid source selected")
View Source
var PsychonautwikiEmptyResp error = errors.New("Psychonautwiki returned nothing")
View Source
var RetConvertUnitEmptyError error = errors.New("returned convertUnit is empty")
View Source
var StructSliceEmpty error = errors.New("struct slice is empty, nothing added to DB")
View Source
var TimeoutValueEmptyError error = errors.New("timeout value is empty")
View Source
var TotalCostsEmptyError error = errors.New("there are no costs to return")
View Source
var WrongAmountNamesError error = errors.New("wrong amount of names")
View Source
var WrongAmountUnitInputsError error = errors.New("wrong amount of unitInputs")

Functions

func AddChannelHandler

func AddChannelHandler[CS ChannelStructs](wg *sync.WaitGroup,
	handler func(cs CS, a ...any) bool, a ...any) chan CS

AddChannelHandler starts receiving from a channel which it creates, using the structure given as the first argument of the handler function. The structure indicates from which goroutine it will receive data. For example the structure UserLogsError is related to the function GetLogs() since it is accepted as an argument by that function. This function will return the channel needed to be passed to GetLogs() if UserLogsError is used.

If the handler function returns false, the handler loop is stopped, the wait group counter is decremented by one and the channel won't accept any more data. It would be good to make sure that all sending goroutines like GetLogs() are stopped beforehand.

Using AddChannelHandler() isn't strictly required, creating channels manually and receiving data from them is acceptable as well. This function should be used when needed, for example when accepting multiple requests at once.

wg - the wait group has to be initialized manually and at the end of the main() function, wg.Wait() has to be executed, replacing wg with the name given when initializing the wait group

handler - the handler function has to be created manually using the type information defined in this function, it will be executed every time data is received from the running goroutines

func GetSourceData

func GetSourceData() map[string]SourceConfig

GetSourceData returns the map gotten from the source config file unmarshaled. The map returns for a given source name (key), an addres (value) for that source.

func InitSettingsDir

func InitSettingsDir() (error, string)

InitSettingsDir creates the directory for the configuration files using the system path for configs and sets the proper mode to the new directory. It first checks if it already exists, skips the creation if true.

Returns the full path to the directory as a string.

func PrintTotalCosts

func PrintTotalCosts(costs []Cost, prefix bool)

PrintTotalCosts writes all costs for all currencies to console.

costs - the costs slice returned from GetTotalCosts()

prefix - if true the name of the function should be shown when writing to console

Types

type AllUsersError

type AllUsersError struct {
	AllUsers []string
	Username string
	Err      error
}

type AlternativeNames

type AlternativeNames struct {
	AltNames []string
}

type Config

type Config struct {
	MaxLogsPerUser  MaxLogsPerUserSize
	UseSource       string
	AutoFetch       bool
	AutoRemove      bool
	DBDriver        string
	VerbosePrinting bool
	DBSettings      map[string]DBSettings
	Timezone        string
	ProxyURL        string
	Timeout         string
	CostCurrency    string
}

func GetSettings

func GetSettings() Config

Get the Config structure data marshaled from the global config file. Returns the Config structure. Stops the program if an error is not nil.

func InitAllSettings

func InitAllSettings(sourcecfg string, dbDir string, dbName string, mysqlAccess string,
	recreateSettings bool, recreateSources bool, verbose bool, apiAddress string) Config

InitAllSettings initializes the Config struct with default values, uses the struct to create the global config file, unmarshales the newly created config and stores the struct in a new variable, initializes the source map using the set source in the Config struct and the given API address, uses the map to create the source config file and returns the Config struct. It then copies the directory containing the toml files for filling alternative names to the database.

Basically all you probably would want to do anyway, but in a single function.

Checkout InitConfigStruct(), InitDBSettings(), InitSettingsFile() GetSettings(), InitSourceMap(), InitSourceSettings() for more info.

sourcecfg - what source name to set by default for the Config struct

dbDir - the path for using the sqlite database file

dbName - the name of the sqlite db file

mysqlAccess - the path for accessing an MySQL/MariaDB database

recreateSettings - overwrite the settings file even if it exists

recreateSources - overwrite the source settings file even if it exists

verbose - if true print verbose information

apiAddress - the address to use when initializing the source map

func InitConfigStruct

func InitConfigStruct(sourcecfg string) Config

Initialise the Config struct using the default values.

sourcecfg - The name of the implemented source to use. The meaning of "source" is for example an API server for which code is present in this repository.

func (Config) AddToAllNamesTables

func (cfg Config) AddToAllNamesTables(db *sql.DB, ctx context.Context,
	overwrite bool) error

Calls AddToNamesTable() for all nameType.

overwrite - force overwrite of tables, it will not remove the old config files, that must be done manually, if they're not removed it will use their data for the database

func (Config) AddToDoseTable

func (cfg Config) AddToDoseTable(db *sql.DB, ctx context.Context, errChannel chan<- ErrorInfo,
	synct *SyncTimestamps, user string, drug string, route string,
	dose float32, units string, perc float32, cost float32, costCur string,
	printit bool)

AddToDoseTable adds a new logged dose to the local database.

This function is meant to be run concurrently.

db - open database connection

ctx - context to be passed to sql queries

errChannel - the gorouting channel which returns the errors

synct - pointer to SyncTimestamps struct used for synchronizing all AddToDoseTable() goroutines, it makes sure no conflicts happen when new doses are added

user - the username to log, if the same timestamps for the same username are chosen, the function will increment them all with 1 second to avoid conflicts

drug - the name of the drug to log, it has to be present in the local info (source) database

route - the name of the route to log, examples begin oral, smoked, etc. and it has to be present in the local info (source) database for the given drug

dose - the amount of drug to log

units - the units to be used for dose (amount)

perc - when not 0, will attempt to convert the amount and units to new amount and units according to the configurations present in the database, checkout ConvertUnits() in names.go for more information on how this works

cost - the cost in money for the log, it has to be calculated manually using the total amount paid

costCur - the currency the cost is in

printit - when true, prints what has been added to the database in the terminal

func (Config) AddToInfoTable

func (cfg Config) AddToInfoTable(db *sql.DB, ctx context.Context,
	errChannel chan<- ErrorInfo, subs []DrugInfo, username string)

AddToInfoTable uses subs[] to fill up the currently configured source table in the database. subs[] has to be filled prior to calling the function. This is usually achieved by fetching data from a source using it's API.

This function is meant to be run concurrently.

db - open database connection

ctx - context to be passed to sql queries

errChannel - the gorouting channel which returns the errors

subs - all substances of type DrugInfo to go through to add to source table

username - user requesting addition

func (Config) AddToNamesTable

func (cfg Config) AddToNamesTable(db *sql.DB, ctx context.Context,
	nameType string, sourceNames bool) error

Create the proper tables in the database, which will later be used to match alternative names to local names.

db - open database connection

ctx - context to be passed to sql queries

nameType - choose type for table to create, between exported constants: NameTypeSubstance, NameTypeRoute, NameTypeUnits or NameTypeConvertUnits

sourceNames - if true, will add data to the source specific config tables

func (Config) ChangeUserLog

func (cfg Config) ChangeUserLog(db *sql.DB, ctx context.Context, errChannel chan<- ErrorInfo,
	set string, id int64, username string, setValue string)

ChangeUserLog can be used to modify log data of a single log.

This function is meant to be run concurrently.

db - open database connection

ctx - context to be passed to sql queries

errChannel - the gorouting channel which returns the errors

set - what log data to change, if name is invalid, InvalidColInput error will be send through userLogsErrorChannel

id - if 0 will change the newest log, else it will change the log with the given id

username - the user who's log we're changing

setValue - the new value to set

func (Config) CheckTables

func (cfg Config) CheckTables(db *sql.DB, ctx context.Context, tableName string) bool

CheckTables returns true if a table exists in the database with the name tableName. It returns false in case of error or if the table isn't found. If tableName is empty it will search for all tables in the database.

db - open database connection

ctx - context to be passed to sql queries

tableName - name of table to check if it exists

func (Config) CleanDB

func (cfg Config) CleanDB(db *sql.DB, ctx context.Context) error

CleanDB deletes all tables in the database. Make sure you don't have any other tables related to other projects in the database! It's a good idea to create different databases for every project.

db - open database connection

ctx - context to be passed to sql queries

func (Config) CleanInfoTable

func (cfg Config) CleanInfoTable(db *sql.DB, ctx context.Context) error

CleanInfoTable removes the currently configured info table. For example if source is set to "psychonautwiki" it will delete the table with the same name as the source, containing all data like dosages and timings. All user dosages aren't touched since they're not apart of the drug general information.

db - open database connection

ctx - context to be passed to sql queries

func (Config) CleanNamesTables

func (cfg Config) CleanNamesTables(db *sql.DB, ctx context.Context, replaceOnly bool) error

CleanNamesTables removes the main names tables and the currently configured ones as well. Names are "alternative names" like "weed" for "cannabis" and etc. Main names are global, they apply to all sources. Currently configured ones are source specific and are chosen based on the currently used source. This means, that any old names generated for another source aren't removed.

db - open database connection

ctx - context to be passed to sql queries

replaceOnly - if true, remove only replace tables (source specific), keep the global ones intact

func (Config) ConvertUnits

func (cfg Config) ConvertUnits(db *sql.DB, ctx context.Context,
	substance string, unitInputs ...float32) (error, float32, string)

ConvertUnits converts the given inputs for a given substance according to a predefined configuration in the database. Checkout AddToSubstanceNamesTable() for more info on how the configuration is done.

db - open database connection

ctx - context to be passed to sql queries

substance - the drug for which to convert units via the config

unitInputs - the inputs to use for the conversions, for example milliliters and percentage

func (Config) FetchFromSource

func (cfg Config) FetchFromSource(db *sql.DB, ctx context.Context,
	errChannel chan<- ErrorInfo, drugname string, username string,
	xtraNeeded ...any) ErrorInfo

FetchFromSource goes through all source names and picks the proper function for fetching drug information. The information is automatically added to the proper info table depending on the Config struct.

db - open database connection

ctx - context to be passed to sql queries

errChannel - the gorouting channel which returns the errors (set to nil if function doesn't need to be concurrent)

drugname - the name of the substance to fetch information for

username - the user requesting the fetch

xtraNeeded - these are values dependent on the configured source, the order in which they're given also matters, the order in which every source is described, is the one in which the values should be given

for psychonautwiki: the initialised structure for the graphql client, best done using InitGraphqlClient(), but can be done manually if needed

func (Config) FetchPsyWiki

func (cfg Config) FetchPsyWiki(db *sql.DB, ctx context.Context,
	errChannel chan<- ErrorInfo, drugname string, client graphql.Client,
	username string)

FetchPsyWiki gets information from Psychonautwiki about a given substance and stores it in the local info table. The table is determined by the source chosen in the Config struct. The name of the table is the same as the name of the source, in this case "psychonautwiki".

This function is meant to be run concurrently.

db - open database connection

ctx - context to be passed to sql queries

errChannel - the gorouting channel which returns the errors

drugname - the substance to get information about

client - the initialised structure for the graphql client, best done using InitGraphqlClient(), but can be done manually if needed

username - the user that requested the fetch request

func (Config) ForgetDosing

func (cfg Config) ForgetDosing(db *sql.DB, ctx context.Context,
	errChannel chan<- ErrorInfo, username string) ErrorInfo

ForgetDosing is meant to reset the setting for remembering dosings. ForgetInputConfigMagicNumber is a constant containing the value meant to be ignored. Checkout RememberDosing()!

db - open database connection

ctx - context to be passed to sql queries

errChannel - the gorouting channel which returns the errors (set to nil if function doesn't need to be concurrent)

username - the user for which to forget the dosing

func (Config) GetAllAltNames

func (cfg Config) GetAllAltNames(db *sql.DB, ctx context.Context,
	namesErrChan chan<- DrugNamesError, inputName string,
	nameType string, sourceNames bool, username string) DrugNamesError

Returns all alternative names for a given local name. For example if the input is "cannabis" it should return something like "weed", "marijuana", etc. The alt names themselves can't be used to find the other alt names, it requires the "main" name in the local info table.

db - open database connection

ctx - context to passed to sql query function

namesErrChan - the goroutine channel used to return the alternative names for a given "global" name and the error (set to nil if function doesn't need to be concurrent)

inputName - local name to get alt names for

nameType - choose type to get alt names for, between exported constants: NameTypeSubstance, NameTypeRoute, NameTypeUnits or NameTypeConvertUnits

sourceNames - use source specific names instead of global ones

username - the user requesting the alt names

func (Config) GetDBSize

func (cfg Config) GetDBSize() int64

GetDBSize returns the total size of the database in bytes (int64).

func (Config) GetLocalInfo

func (cfg Config) GetLocalInfo(db *sql.DB, ctx context.Context,
	drugInfoErrChan chan<- DrugInfoError, drug string, username string)

GetLocalInfo returns a slice containing all information about a drug.

This function is meant to be run concurrently.

db - open database connection

ctx - context to be passed to sql queries

drugInfoErrChan - the goroutine channel used to return the slice containing information about all routes for a drug and the error

drug - drug to get information about

username - the user requesting the local info

func (Config) GetLoggedNames

func (cfg Config) GetLoggedNames(db *sql.DB, ctx context.Context,
	drugNamesErrorChannel chan<- DrugNamesError, info bool,
	username string, getExact string)

GetLoggedNames returns a slice containing all unique names of drugs present in the local info table or log table.

This function is meant to be run concurrently.

db - open database connection

ctx - context to be passed to sql queries

drugNamesErrorChannel - the goroutine channel used to return the list of drug names and the error

info - if true will get names from local info table, if false from log table

getExact - choose which column to get unique names for, if name is invalid, InvalidColInput error will be send through userLogsErrorChannel

func (Config) GetLogs

func (cfg Config) GetLogs(db *sql.DB, ctx context.Context,
	userLogsErrorChannel chan<- UserLogsError, num int, id int64,
	user string, desc bool, search string, getExact string)

GetLogs returns all logs for a given username in the drug log table. It uses a single channel with the type UserLogsError, containing a slice of UserLogs structs and a variable with an error type. When using this function, the error must be checked before reading the logs. Every log is a separate element of the UserLogs slice.

This function is meant to be run concurrently.

db - an open database connection

ctx - context that will be passed to the sql query function

userLogsErrorChannel - the goroutine channel used to return the logs and the error

num - amount of logs to return (limit), if 0 returns all logs (without limit)

id - if not 0, will return the exact log matching that id for the given user

user - the user which created the logs, will returns only the logs for that username

desc - if true (descending) go from high values to low values, this should return the newest logs first, false (ascending) is the opposite direction

search - return logs which contain this string

getExact - if not empty, choose which column to search for and changes the search behavior to exact matching, if name is invalid, InvalidColInput error will be send through userLogsErrorChannel

func (Config) GetLogsCount

func (cfg Config) GetLogsCount(db *sql.DB, ctx context.Context, user string,
	logCountErrChan chan<- LogCountError)

GetLogsCount returns total amount of logs in the dose log table for username set in user parameter.

This function is meant to be run concurrently.

db - open database connection

ctx - context to be passed to sql queries

user - user to get log count for

logCountErrChan - the goroutine channel used to return the log count and the error

func (Config) GetTimes

func (cfg Config) GetTimes(db *sql.DB, ctx context.Context,
	timeTillErrChan chan<- TimeTillError, username string, getid int64)

GetTimes returns the times till reaching a specific point of the experience. The points are defined in the TimeTill struct. PrintTimeTill() can be used to output the information gathered in this function to the terminal.

This function is meant to be run concurrently.

db - open database connection

ctx - context to be passed to sql queries

timeTillErrChan - the goroutine channel which returns the TimeTill struct and an error

username - the user for which to get the information

getid - if 0 gives information about the last log, a specific ID can be passed to get the times for that log

func (Config) GetTotalCosts

func (cfg Config) GetTotalCosts(db *sql.DB, ctx context.Context,
	costsErrChan chan<- CostsError, username string)

GetTotalCosts returns a slice containing all costs about all drugs in all currencies.

This function is meant to be run concurrently.

db - open database connection

ctx - context to be passed to sql queries

costsErrChan - the goroutine channel used to return the slice containing all costs

username - the user for which to return the costs

func (Config) GetUserSettings

func (cfg Config) GetUserSettings(db *sql.DB, ctx context.Context,
	userSetErrChannel chan<- UserSettingError, set string, username string) UserSettingError

GetUserSettings return the value of a setting for a given user from the database.

db - open database connection

ctx - context to be passed to sql queries

userSetErrChannel - a gorouting channel which is of type UserSettingError, holding an error variable and a string of the value for a given setting, make sure to always check the error first (set to nil if function doesn't need to be concurrent)

set - the name of the setting to get the value of

username - the user for which to get the setting

func (Config) GetUsers

func (cfg Config) GetUsers(db *sql.DB, ctx context.Context,
	allUsersErrChan chan<- AllUsersError, username string)

GetUsers returns all unique usernames currently present in the dose log table.

This function is meant to be run concurrently.

db - open database connection

ctx - context to be passed to sql queries

allUsersErrChan - the goroutine channel used to return all unique usernames and the error

username - user to get unique usernames for

func (Config) InitAllDB

func (cfg Config) InitAllDB(ctx context.Context)

InitAllDB initializes the DB file structure if needed and all tables. It will stop the program if it encounters an error.

ctx - context to be passed to sql queries

func (Config) InitAllDBTables

func (cfg Config) InitAllDBTables(db *sql.DB, ctx context.Context) error

InitAllDBTables creates all tables needed to run the program properly. This function should be sufficient on it's own for most use cases. Even if the function is called every time the program is started, it should not be an issue, since all called functions first check if the tables they're trying to create already exist.

db - open database connection

ctx - context to be passed to sql queries

func (Config) InitDBFileStructure

func (cfg Config) InitDBFileStructure()

InitDBFileStructure creates the basic file structure for the database. This should be run only once! This is currently only useful for sqlite. If Config.DBDriver is not set to "sqlite" it will exit the program.

func (Config) InitDBSettings

func (initcfg Config) InitDBSettings(dbdir string, dbname string, mysqlaccess string) (error, Config)

InitDBSettings returns a modified Config structure, which contains a properly formatted DBSettings map. Before using the modified struct, checkout if the returned error is not nil!

dbdir - if this is set to the DefaultDBDir constant, it will try to use the system user directory as a path, if not the full path must be specified

dbname - the name of sqlite db file

mysqlaccess - the path for connecting to MySQL/MariaDB, example user:password@tcp(127.0.0.1:3306)/database

func (Config) InitGraphqlClient

func (cfg Config) InitGraphqlClient() (error, graphql.Client)

Used to initialise the GraphQL struct, using the source address from the drugdose Config struct.

returns the GraphQL struct used with github.com/hasura/go-graphql-client

func (Config) InitInfoTable

func (cfg Config) InitInfoTable(db *sql.DB, ctx context.Context) error

InitInfoTable creates the table for the currently configured source if it doesn't exist. It will use the source's name to set the table's name. It's called "Info", because it stores general information like dosages and timings for every route of administration.

db - open database connection

ctx - context to be passed to sql queries

func (Config) InitLogsTable

func (cfg Config) InitLogsTable(db *sql.DB, ctx context.Context) error

InitLogsTable creates the table for all user drug logs if it doesn't exist.

db - open database connection

ctx - context to be passed to sql queries

func (Config) InitNamesAltTables

func (cfg Config) InitNamesAltTables(db *sql.DB, ctx context.Context, replace bool) error

InitNamesAltTables creates all alternative names tables if they don't exist. Alternative names are names like "weed" instead of "cannabis" and etc. There are global tables which are used for any source. There are also source specific names which "replace" the global names.

db - open database connection

ctx - context to be passed to sql queries

replace - if true, creates the tables for the currently configured source only, meaning for alt names specific to the source

func (Config) InitNamesFiles

func (cfg Config) InitNamesFiles() error

InitNamesFiles copies to the OS config directory, the directory containing the toml files for configuring alternative names. If it doesn't exists in the config directory, the code checks if it's present in the current working directory. If it is, it's copied over to the OS config directory and used later to fill in the database.

func (Config) InitSettingsFile

func (initconf Config) InitSettingsFile(recreate bool, verbose bool)

InitSettingsFile creates and fills the main global config file which is used for the Config struct. It sets the proper mode and stops the program on error. The data for writing to the file is taken from the passed Config structure.

recreate - if true overwrites the currently existing config file with the currently passed Config struct data

verbose - whether to print verbose information

func (Config) InitSourceMap

func (cfg Config) InitSourceMap(apiAddress string) map[string]SourceConfig

InitSourceMap returns a map which for the given key (configured source) returns the address as it's value. The address could be an IP address, an URL and etc.

apiAddress - the address to map to the source name from the Config struct

func (Config) InitSourceSettings

func (cfg Config) InitSourceSettings(newcfg map[string]SourceConfig, recreate bool) error

InitSourceSettings creates the config file for the sources. This file contains the api name mapped to the api address. InitSourceMap() can be used to create the map, this function marshals it and writes it to the actual config file.

newcfg - the source api name to api address map

recreate - overwrite the current file if it exists with a new map

func (Config) InitUserSetTable

func (cfg Config) InitUserSetTable(db *sql.DB, ctx context.Context) error

InitUserSetTable creates the table for all user settings if it doesn't exist.

db - open database connection

ctx - context to be passed to sql queries

func (Config) InitUserSettings

func (cfg Config) InitUserSettings(db *sql.DB, ctx context.Context, username string) error

InitUserSettings creates the row with default settings for a user. These settings are kept in the database and are not global like the config files. All users have their own settings.

db - open database connection

ctx - context to be passed to sql queries

username - the user to create default settings for

func (Config) MatchAndReplace

func (cfg Config) MatchAndReplace(db *sql.DB, ctx context.Context,
	inputName string, nameType string) string

Returns the local name, using both the global config and the source specific config. Checkout MatchName()

db - open database connection

ctx - context to passed to sql query function

inputName - the alternative name

nameType - choose type to replace, between exported constants: NameTypeSubstance, NameTypeRoute, NameTypeUnits or NameTypeConvertUnits

func (Config) MatchAndReplaceAll

func (cfg Config) MatchAndReplaceAll(db *sql.DB, ctx context.Context,
	inputName string) string

Tries matching a single string to all alternative names tables. If it finds a match it will return the alt name for that single string. It matches all alt drugs, route and units, so a single input can be checked for all of them. Checkout MatchAndReplace()

db - open database connection

ctx - context to passed to sql query function

inputName - single string to match all alt names for

func (Config) MatchName

func (cfg Config) MatchName(db *sql.DB, ctx context.Context,
	inputName string, nameType string, sourceNames bool) string

MatchName replaces an input name with a configured output name present in the database. For example if there's a need to translate "weed" to "cannabis". Checkout AddToSubstanceNamesTable() for information on how the configuration is done.

db - open database connection

ctx - context to passed to sql query function

inputName - the alternative name

nameType - choose type to replace, between exported constants: NameTypeSubstance, NameTypeRoute, NameTypeUnits or NameTypeConvertUnits

sourceNames - if true, it will use the config for the source, meaning the names specific for the source

Returns the local name for a given alternative name.

func (Config) OpenDBConnection

func (cfg Config) OpenDBConnection(ctx context.Context) *sql.DB

Open a database connection using the Config struct.

After calling this function, don't forget to run: defer db.Close()

db being the name of the returned *sql.DB variable

ctx - context to be passed to PingDB(), first passing through WithTimeout()

func (Config) PingDB

func (cfg Config) PingDB(db *sql.DB, ctx context.Context)

Ping verifies a connection to the database is still alive, establishing a connection if necessary.

db - open database connection

ctx - context to be passed to PingContext()

func (Config) PrintLocalInfo

func (cfg Config) PrintLocalInfo(drugInfo []DrugInfo, prefix bool)

PrintLocalInfo prints the information gotten from the source, present in the local database.

drugInfo - slice returned from GetLocalInfo()

prefix - whether to add the function name to console output

func (Config) PrintLogs

func (cfg Config) PrintLogs(userLogs []UserLog, prefix bool)

PrintLogs writes all logs present in userLogs to console.

userLogs - the logs slice returned from GetLogs()

prefix - if true the name of the function should be shown when writing to console

func (Config) PrintTimeTill

func (cfg Config) PrintTimeTill(timeTillErr TimeTillError, prefix bool) error

PrintTimeTill prints the information gotten using GetTimes() to the terminal.

timeTillErr - the struct returned from GetTimes()

prefix - if true, adds the function name to every print

func (Config) RecallDosing

func (cfg Config) RecallDosing(db *sql.DB, ctx context.Context,
	userLogsErrorChannel chan<- UserLogsError, username string) UserLogsError

RecallDosing gives the data for the last configured dosing using the ID. Checkout RememberDosing() for more info!

db - open database connection

ctx - context to be passed to sql queries

userLogsErrorChannel - the goroutine channel used to return the logs and the error (set to nil if function doesn't need to be concurrent)

username - the user to recall the logs for

func (Config) RememberDosing

func (cfg Config) RememberDosing(db *sql.DB, ctx context.Context,
	errChannel chan<- ErrorInfo, username string, forID int64)

RememberDosing stores an ID of a log to reuse later via RecallDosing(). This allows input of the dose only then drug, route, units will be reused according to the ID set to recall.

This function is meant to be run concurrently.

db - open database connection

ctx - context to be passed to sql queries

errChannel - the gorouting channel which returns the errors

username - the user to use for remembering a dosing

forID - the ID to use for remembering a dosing

func (Config) RemoveLogs

func (cfg Config) RemoveLogs(db *sql.DB, ctx context.Context,
	errChannel chan<- ErrorInfo, username string, amount int, reverse bool,
	remID int64, search string, getExact string) ErrorInfo

RemoveLogs removes logs from the dose log table.

db - open database connection

ctx - context to be passed to sql queries

errChannel - the gorouting channel which returns the errors (set to nil if function doesn't need to be concurrent)

username - the user's logs that will be removed, no other user's logs will be touched

amount - how many logs to remove, if 0 it removes all

reverse - from which direction to start removing logs, if true go from high values to low values, this should remove the newest logs first, false is the opposite direction

remID - if not 0, remove a specific log using it's start timestamp (ID)

search - remove logs only matching this string

func (Config) RemoveSingleDrugInfo

func (cfg Config) RemoveSingleDrugInfo(db *sql.DB, ctx context.Context,
	errChannel chan<- ErrorInfo, drug string, username string) ErrorInfo

RemoveSingleDrugInfo removes all entries of a single drug from the local info DB, instead of deleting the whole DB/table. For example if there's a need to clear all information about dosage and timing for a specific drug if it's old or incorrect.

db - open database connection

ctx - context to be passed to sql queries

errChannel - go routine channel which returns any errors (set to nil if function doesn't need to be concurrent)

drug - name of drug to be removed from source table

username - the user which requested the drug removal

func (Config) SetUserSettings

func (cfg Config) SetUserSettings(db *sql.DB, ctx context.Context,
	errChannel chan<- ErrorInfo, set string, username string, setValue string) ErrorInfo

SetUserSettings changes the user settings in the database.

db - open database connection

ctx - context to be passed to sql queries

errChannel - the gorouting channel which returns the errors (set to nil if function doesn't need to be concurrent)

set - the name of the setting to change, available names are: remember-id

username - the user the setting is changed for

setValue - the value the setting is changed to

func (Config) UseConfigTimeout

func (cfg Config) UseConfigTimeout() (context.Context, context.CancelFunc, error)

Uses the value of Timeout from the settings file to create a WithTimeout context. If no errors are found, it then returns the context to be used where it's needed.

type Cost

type Cost struct {
	Substance    string
	TotalCost    float32
	CostCurrency string
}

type CostsError

type CostsError struct {
	Costs []Cost
	Err   error
}

type DBSettings

type DBSettings struct {
	Path       string
	Parameters string
}

type DrugInfo

type DrugInfo struct {
	DrugName      string
	DrugRoute     string
	Threshold     float32
	LowDoseMin    float32
	LowDoseMax    float32
	MediumDoseMin float32
	MediumDoseMax float32
	HighDoseMin   float32
	HighDoseMax   float32
	DoseUnits     string
	OnsetMin      float32
	OnsetMax      float32
	OnsetUnits    string
	ComeUpMin     float32
	ComeUpMax     float32
	ComeUpUnits   string
	PeakMin       float32
	PeakMax       float32
	PeakUnits     string
	OffsetMin     float32
	OffsetMax     float32
	OffsetUnits   string
	TotalDurMin   float32
	TotalDurMax   float32
	TotalDurUnits string
	TimeOfFetch   int64
}

type DrugInfoError

type DrugInfoError struct {
	DrugI    []DrugInfo
	Username string
	Err      error
}

type DrugNamesError

type DrugNamesError struct {
	DrugNames []string
	Username  string
	Err       error
}

type ErrorInfo

type ErrorInfo struct {
	Err      error
	Action   string
	Username string
}

type LogCountError

type LogCountError struct {
	LogCount uint32
	Username string
	Err      error
}

type MaxLogsPerUserSize

type MaxLogsPerUserSize int32
const DefaultMaxLogsPerUser MaxLogsPerUserSize = 100

type PsychonautwikiSubstance

type PsychonautwikiSubstance []struct {
	Name string

	Roas []struct {
		Name string

		Dose struct {
			Units     string
			Threshold float64
			Light     struct {
				Min float64
				Max float64
			}
			Common struct {
				Min float64
				Max float64
			}
			Strong struct {
				Min float64
				Max float64
			}
		}

		Duration struct {
			Onset struct {
				Min   float64
				Max   float64
				Units string
			}

			Comeup struct {
				Min   float64
				Max   float64
				Units string
			}

			Peak struct {
				Min   float64
				Max   float64
				Units string
			}

			Offset struct {
				Min   float64
				Max   float64
				Units string
			}

			Total struct {
				Min   float64
				Max   float64
				Units string
			}
		}
	}
}

type SourceConfig

type SourceConfig struct {
	API_ADDRESS string
}

type SubstanceName

type SubstanceName struct {
	LocalName map[string]AlternativeNames
}

func GetNamesConfig

func GetNamesConfig(nameType string, source string) (error, *SubstanceName)

Read the config file for matching names and return the proper struct.

nameType - choose between getting alt names for: substance, route, units or convUnits (conversion units)

source - if not empty, will read the source specific config

type SyncTimestamps

type SyncTimestamps struct {
	LastTimestamp int64
	LastUser      string
	Lock          sync.Mutex
}

type TimeTill

type TimeTill struct {
	// In seconds
	//
	// Information for the different points is from Psychonautwiki.
	//
	// The onset phase can be defined as the period until the very first
	// changes in perception (i.e. "first alerts") are able to be detected.
	TimeTillOnset int64
	// The "come up" phase can be defined as the period between the first
	// noticeable changes in perception and the point of highest subjective
	// intensity.
	TimeTillComeup int64
	// The peak phase can be defined as period of time in which the
	// intensity of the substance's effects are at its height.
	TimeTillPeak int64
	// The offset phase can be defined as the amount of time in between the
	// conclusion of the peak and shifting into a sober state.
	TimeTillOffset int64
	// The total duration of a substance can be defined as the amount of
	// time it takes for the effects of a substance to completely wear off
	// into sobriety, starting from the moment the substance is first
	// administered.
	TimeTillTotal int64
	// Percentage of completion
	TotalCompleteMin float32
	TotalCompleteMax float32
	TotalCompleteAvg float32
	// In unix time
	StartDose int64
	EnDose    int64
}

type TimeTillError

type TimeTillError struct {
	TimeT    *TimeTill
	Username string
	Err      error
	// contains filtered or unexported fields
}

type UserLog

type UserLog struct {
	StartTime    int64
	Username     string
	EndTime      int64
	DrugName     string
	Dose         float32
	DoseUnits    string
	DrugRoute    string
	Cost         float32
	CostCurrency string
}

type UserLogsError

type UserLogsError struct {
	UserLogs []UserLog
	Username string
	Err      error
}

type UserSettingError

type UserSettingError struct {
	UserSetting string
	Username    string
	Err         error
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL