├── LICENSE ├── README.md ├── config.example.sh └── ticktask /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 UnkwUsr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ticktask 2 | 3 | Ticktask is a simple cli for [ticktick](https://ticktick.com) to create tasks. 4 | 5 | ## Features 6 | 7 | * Uses [official](https://developer.ticktick.com/docs#/openapi) open api (which 8 | is still under development, but creating tasks is already work) 9 | * Support date (\*today and \*tomorrow) 10 | * Support tags (starts with #) 11 | * Support task body (see [config](#Config) section) 12 | 13 | ## Installation 14 | 15 | ### Manual 16 | 17 | Just save the 18 | [./ticktask](https://raw.githubusercontent.com/UnkwUsr/ticktask/master/ticktask) 19 | file somewhere on your computer (for example, in `/usr/local/bin/`). Don't 20 | forget to set execute permission on file: `chmod +x /path/to/ticktask` 21 | 22 | ## Setup 23 | 24 | You need to obtain api key for ticktick, so go to 25 | [https://developer.ticktick.com/manage](https://developer.ticktick.com/manage) 26 | and create your "app". P.S. you will be asked for *ridirect_url* setting. Set 27 | it to anything you want, for example `http://127.0.0.1`. 28 | 29 | Next, you need to write your `cliend_id` and `client_secret` in config. Create 30 | `~/.config/ticktask/config.sh` file with the following content: 31 | 32 | ```sh 33 | CLIENT_ID="your_client_id" 34 | CLIENT_SECRET="your_client_secret" 35 | ``` 36 | 37 | Then run `ticktask` and follow its instructions. 38 | 39 | Congratulations! Your token saved in `~/.local/share/ticktask/token`. Now you 40 | can jump to usage section. 41 | 42 | ## Usage 43 | 44 | Just run `ticktask` with your task title as argument. Example: 45 | 46 | ```sh 47 | ticktask my new task created with ticktask 48 | ``` 49 | 50 | You can use date rules `*today` and `*tomorrow` which will be applied to task 51 | and removed from title text. The same for tags which starts with `#`. 52 | 53 | Note: if for some reasons ticktask can't send request to ticktick, your task 54 | text will be saved in `~/.local/share/ticktask/error_tasks/` folder, so you 55 | will never lose anything. P.S. in such situation ticktask exit with code 2, so 56 | you can use it in your scripts (for example send notify with `notify-send`) 57 | 58 | ### Use with hotkey 59 | 60 | [Personally](https://github.com/UnkwUsr/dotfiles/blob/d296e8629f9945efe67e699c0475d3202c53a8d5/config/i3/config#L241-L243), 61 | I use ticktask with [dmenu](https://tools.suckless.org/dmenu/). Command I use 62 | in hotkey: 63 | 64 | ```bash 65 | echo -n | dmenu -p "ticktask:" | xargs -0 ticktask \ 66 | && notify-send ticktask -t 1000 sent \ 67 | || notify-send ticktask -u critical -t 2000 "not sent" 68 | ``` 69 | 70 | It opens dmenu prompt, then forwards result to tictask, and then sends notify 71 | with status "sent" or "not sent". 72 | 73 | ## Config 74 | 75 | Config file located at `~/.config/ticktask/config.sh`. This is simply bash 76 | script, so it's very hackable and extensible. 77 | 78 | For examples see [./config.example.sh](./config.example.sh) 79 | 80 | ### Task body (not necessary) 81 | 82 | To be able to enter a task description, you can define function 83 | `cmd_get_description()` which should output the description as a result. For 84 | example, you can run any gui program with prompt, which will then print it to 85 | stdout. 86 | 87 | ## Related projects 88 | 89 | * [ticked](https://github.com/UnkwUsr/ticked) - allows you to edit your 90 | [ticktick.com](https://ticktick.com) tasks in your favorite editor (like 91 | vim/neovim). 92 | -------------------------------------------------------------------------------- /config.example.sh: -------------------------------------------------------------------------------- 1 | CLIENT_ID='your_client_id' 2 | CLIENT_SECRET='your_client_secret' 3 | 4 | # cmd_get_description() is optional, just remove it if you don't need. 5 | # Used to provide task description 6 | # 7 | # examples: 8 | 9 | # with dmenu 10 | cmd_get_description() { 11 | echo -n | dmenu -p "ticktask description:" 12 | } 13 | 14 | # with gxmessage 15 | cmd_get_description() { 16 | gxmessage "Type description" --entry 17 | } 18 | 19 | # read from terminal input (end on newline) 20 | cmd_get_description() { 21 | read -ep "Type description: " line 22 | echo -n "$line" 23 | } 24 | 25 | # read from terminal input (allow multiline, end with ctrl+d) 26 | cmd_get_description() { 27 | echo Type description: >&2 28 | cat < /dev/stdin 29 | } 30 | 31 | # open st terminal with vim 32 | cmd_get_description() { 33 | filename=/tmp/ticktask_description_$(date +"%s") 34 | st -e vim "$filename" 35 | cat "$filename" 36 | rm "$filename" 37 | } 38 | 39 | # you can ever customize behavior depending on executable name 40 | cmd_get_description() { 41 | if [ "$(basename "$0")" = "ticktask_with_desc" ]; then 42 | echo "something that prints description" 43 | fi 44 | } 45 | -------------------------------------------------------------------------------- /ticktask: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ACCESS_TOKEN_FILE="$HOME/.local/share/ticktask/token" 4 | CONFIG_FILE="$HOME/.config/ticktask/config.sh" 5 | FOLDER_ERROR_TASKS="$HOME/.local/share/ticktask/error_tasks/" 6 | 7 | if [ -z "$1" ]; then 8 | echo "Usage: $0 your task title" 9 | 10 | exit 1 11 | fi 12 | 13 | # escape \ and " symbols 14 | task_title=$(echo "$@" | sed 's/\\/\\\\/g; s/"/\\"/g') 15 | 16 | if [ ! -f $CONFIG_FILE ]; then 17 | echo "Please create config file: $CONFIG_FILE" 18 | 19 | exit 1 20 | fi 21 | 22 | source $CONFIG_FILE 23 | 24 | if [ -f $ACCESS_TOKEN_FILE ]; then 25 | access_token=$(<$ACCESS_TOKEN_FILE) 26 | else 27 | # authorization 28 | echo "No access_token cached. Receiving new one" 29 | 30 | REDIRECT_URL="http://127.0.0.1" 31 | URL_LEN=$(echo "$REDIRECT_URL" | wc -c) 32 | 33 | # docs says "comma separated", but comma not work. So we use space there 34 | SCOPE="tasks:write%20tasks:read" 35 | 36 | auth_url="https://ticktick.com/oauth/authorize?scope=$SCOPE&client_id=$CLIENT_ID&state=state&redirect_uri=$REDIRECT_URL&response_type=code" 37 | 38 | echo "Opening browser" 39 | user_auth_url=$(curl -ILsS -w "%{url_effective}\n" "$auth_url" | tail -n1) 40 | xdg-open $user_auth_url 2> /dev/null 41 | 42 | read -ep "Paste url you've been redirected: " url_with_code 43 | code=$(echo -n $url_with_code | tail -c +$(($URL_LEN + 7)) | head -c 6) 44 | echo "Code: $code" 45 | 46 | payload_get_acces_token="grant_type=authorization_code&code=$code&redirect_uri=$REDIRECT_URL" 47 | resp_get_access_token=$(curl -s --header "Content-Type: application/x-www-form-urlencoded" \ 48 | -u $CLIENT_ID:$CLIENT_SECRET \ 49 | --request POST \ 50 | --data "$payload_get_acces_token" \ 51 | https://ticktick.com/oauth/token) 52 | 53 | if [[ $resp_get_access_token =~ (access_token\":\")([^\"]*) ]]; then 54 | access_token=${BASH_REMATCH[2]} 55 | echo "access_token received. You can find it in $ACCESS_TOKEN_FILE" 56 | 57 | mkdir -p $(dirname $ACCESS_TOKEN_FILE) 58 | echo -n "$access_token" > $ACCESS_TOKEN_FILE 59 | else 60 | echo "Bad response for getting access_token: $resp_get_access_token" 61 | 62 | exit 2 63 | fi 64 | fi 65 | 66 | # getting task description 67 | if [ $(declare -F cmd_get_description) ]; then 68 | # escape \ and " and newline symbols 69 | desc=$(cmd_get_description | sed 's/\\/\\\\/g; s/"/\\"/g' \ 70 | | awk '{printf "%s\\n", $0}') 71 | field_content=', "content": "'$desc'"' 72 | fi 73 | 74 | # parse date 75 | if [[ $task_title =~ (^| )\*(today|tomorrow)( |$).* ]]; then 76 | title_date=${BASH_REMATCH[2]} 77 | # date must be 1 day ago than real 78 | title_date=$(date --date="$title_date 1 day ago" -Iseconds) 79 | field_duedate=', "dueDate": "'$title_date'"' 80 | 81 | # remove date entries from title text 82 | task_title="$(echo "$task_title" | sed -E 's/(^| )\*today( |$)/ /g; s/(^| )\*tomorrow( |$)/ /g; s/(^ | $)//g')" 83 | fi 84 | # parse tags 85 | if [[ $task_title =~ (^| )#([a-zA-Z0-9_]+)( |$) ]]; then 86 | tags=$(echo "$task_title" | grep -Eo "(^| )#\w+" | tr -d "\n") 87 | 88 | # HACK. desc is not actual description (for real description use field 89 | # 'content'). This field is not even displayed (at least in web version), 90 | # but ticktick parses tags from this field 91 | field_desc=', "desc": "'$tags'"' 92 | 93 | # remove tags from title text 94 | task_title="$(echo "$task_title" | sed -E 's/(^| )(#\w+( |$))+/ /g; s/(^ | $)//g')" 95 | fi 96 | 97 | json_task='{ "title": "'$task_title'"'$field_content$field_duedate$field_desc' }' 98 | 99 | # finally send request to create task 100 | resp_create_task=$(curl -s \ 101 | --fail-with-body \ 102 | --header "Content-Type: application/json" \ 103 | --header "Authorization: Bearer $access_token" \ 104 | --request POST \ 105 | --data "$json_task" \ 106 | https://api.ticktick.com/open/v1/task) 107 | if (( $? != 0 )); then 108 | echo "Error on creating task. Server response:" 109 | echo "$resp_create_task" 110 | 111 | mkdir -p $FOLDER_ERROR_TASKS 112 | error_task_file=$(date +%s) 113 | echo "$@" > $FOLDER_ERROR_TASKS/$error_task_file 114 | echo "Task saved to $FOLDER_ERROR_TASKS" 115 | 116 | exit 2 117 | fi 118 | --------------------------------------------------------------------------------