├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── README.md ├── cloud_formation_template.json ├── cmd ├── mac_app_builder.sh ├── sqscopy │ ├── appicon1024.png │ ├── main.go │ ├── sqscopy.ico │ └── versioninfo.json ├── sqscopysmallfile │ └── main.go ├── sqspaste │ ├── appicon1024.png │ ├── main.go │ ├── sqspaste.ico │ └── versioninfo.json ├── sqspastesmallfile │ └── main.go └── sqspurge │ ├── appicon1024.png │ ├── main.go │ ├── sqspurge.ico │ └── versioninfo.json ├── copypaste.go ├── create_stack.ps1 ├── create_stack.sh ├── customlog └── customlog.go ├── go.mod ├── go.sum ├── queue └── queue.go └── randstring └── randstring.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | dist.bat 17 | *.syso 18 | assets/ 19 | *DS_Store* 20 | .??*~ 21 | *.swp 22 | 23 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: sqs_clipboard 2 | 3 | before: 4 | hooks: 5 | - go get github.com/josephspurrier/goversioninfo/cmd/goversioninfo 6 | - go generate ./... 7 | 8 | builds: 9 | - main: . 10 | id: "sqscopy" 11 | binary: sqscopy 12 | dir: ./cmd/sqscopy 13 | goos: 14 | - linux 15 | - darwin 16 | goarch: 17 | - amd64 18 | - arm 19 | goarm: 20 | - 7 21 | 22 | - main: . 23 | id: "sqscopygui" 24 | binary: sqscopy 25 | dir: ./cmd/sqscopy 26 | ldflags: 27 | - -s -w -H windowsgui 28 | goos: 29 | - windows 30 | goarch: 31 | - amd64 32 | 33 | - main: . 34 | id: "sqspaste" 35 | binary: sqspaste 36 | dir: ./cmd/sqspaste 37 | goos: 38 | - linux 39 | - darwin 40 | goarch: 41 | - amd64 42 | - arm 43 | goarm: 44 | - 7 45 | 46 | - main: . 47 | id: "sqspastegui" 48 | binary: sqspaste 49 | dir: ./cmd/sqspaste 50 | ldflags: 51 | - -s -w -H windowsgui 52 | goos: 53 | - windows 54 | goarch: 55 | - amd64 56 | 57 | - main: . 58 | id: "sqscopysmallfile" 59 | binary: sqscopysmallfile 60 | dir: ./cmd/sqscopysmallfile 61 | goos: 62 | - linux 63 | - darwin 64 | goarch: 65 | - amd64 66 | - arm 67 | goarm: 68 | - 7 69 | 70 | - main: . 71 | id: "sqscopysmallfilegui" 72 | binary: sqscopysmallfile 73 | dir: ./cmd/sqscopysmallfile 74 | ldflags: 75 | - -s -w -H windowsgui 76 | goos: 77 | - windows 78 | goarch: 79 | - amd64 80 | 81 | - main: . 82 | id: "sqspastesmallfile" 83 | binary: sqspastesmallfile 84 | dir: ./cmd/sqspastesmallfile 85 | goos: 86 | - linux 87 | - darwin 88 | goarch: 89 | - amd64 90 | - arm 91 | goarm: 92 | - 7 93 | 94 | - main: . 95 | id: "sqspastesmallfilegui" 96 | binary: sqspastesmallfile 97 | dir: ./cmd/sqspastesmallfile 98 | ldflags: 99 | - -s -w -H windowsgui 100 | goos: 101 | - windows 102 | goarch: 103 | - amd64 104 | 105 | - main: . 106 | id: "sqspurge" 107 | binary: sqspurge 108 | dir: ./cmd/sqspurge 109 | goos: 110 | - linux 111 | - darwin 112 | goarch: 113 | - amd64 114 | - arm 115 | goarm: 116 | - 7 117 | 118 | - main: . 119 | id: "sqspurgegui" 120 | binary: sqspurge 121 | dir: ./cmd/sqspurge 122 | ldflags: 123 | - -s -w -H windowsgui 124 | goos: 125 | - windows 126 | goarch: 127 | - amd64 128 | 129 | archives: 130 | - name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ .Arm }}" 131 | format: tar.xz 132 | format_overrides: 133 | - goos: windows 134 | format: zip 135 | replacements: 136 | darwin: macOS 137 | wrap_in_directory: true 138 | 139 | checksum: 140 | name_template: "{{ .ProjectName }}_{{ .Version }}--sha256_checksums.txt" 141 | 142 | release: 143 | draft: true 144 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 John Taylor 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 | # sqs_clipboard 2 | Use [AWS SQS](https://aws.amazon.com/sqs/) as a clipboard to copy and paste across different systems and platforms. Clipboard contents are encrypted in transit and at rest. 3 | 4 | Binaries for Windows, MacOS, and Linux can be found on the [Releases Page](https://github.com/jftuga/sqs_clipboard/releases). 5 | ___ 6 | 7 | **Description** 8 | 9 | This set of programs can be used to *copy* and *paste* clipboard text by using an [AWS SQS FIFO Queue](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html) as an intermediary. To minimize the amount of data transferred, the contents are compressed with the `XZ` algorithm before sending to the SQS queue via `sqscopy` and then decompressed upon arrival via `sqspaste`. A maximum of `256 KB` of compressed *(and then encoded)* data can be sent to the queue. 10 | 11 | **NOTE:** There can be a small AWS cost when using this program. Each copy / paste operation uses 3 SQS requests, plus the data transferred associated with `sqspaste` and `sqspastesmallfile`. See [Amazon SQS pricing](https://aws.amazon.com/sqs/pricing/) for more details. 12 | 13 | **Programs** 14 | 15 | * `sqscopy` - send the system clipboard contents to a user-defined AWS SQS FIFO queue 16 | * `sqspaste` - get the queue contents and then place it onto the system clipboard 17 | * `sqspurge` - remove all entries from the queue 18 | * `sqscopysmallfile` - copy a small file to the queue with file name given on command line 19 | * * after XZ compression and base 91 encoding, the resulting file size must be less than 256 KB 20 | * `sqspastesmallfile` - retrieve a file from the queue 21 | * * **will overwrite** an existing file with the same name 22 | * * file name is stored in the queue along with the file 23 | * * no command line arguments needed 24 | 25 | **AWS Queue Creation** 26 | 27 | * Open the [Amazon SQS Console](https://console.aws.amazon.com/sqs/v2/home) 28 | * Make sure to create a `fifo` queue instead of a `standard` queue 29 | * * The name of your queue should end in `.fifo` 30 | * Set the `Receive message wait time` aka *long polling* to at least `12` seconds 31 | * **Note:** Do **not** check: `Content-based deduplication`, otherwise you will **not** be able to copy the exact same contents with a 5 minute interval. 32 | * Send / Receive Access: `Only the queue owner` 33 | * Encryption: Optional, but recommended 34 | * * Data key reuse period: `1 hour` 35 | * * A shorter time period provides better security, but results in more calls to AWS KMS, which might incur charges after Free Tier. 36 | 37 | **AWS Queue Creation - Automation** 38 | 39 | * Please review these scripts before running them and make any adjustments as needed: 40 | * * [Windows](create_stack.ps1) 41 | * * [Linux, MacOS](create_stack.sh) 42 | * Both of these scripts use [cloud_formation_template.json](cloud_formation_template.json) 43 | 44 | **AWS IAM Permissions** 45 | 46 | * Make sure to change the `Resource` value listed below. 47 | 48 | ```json 49 | { 50 | "Version": "2012-10-17", 51 | "Statement": [ 52 | { 53 | "Sid": "VisualEditor0", 54 | "Effect": "Allow", 55 | "Action": [ 56 | "sqs:DeleteMessage", 57 | "sqs:GetQueueUrl", 58 | "sqs:ChangeMessageVisibility", 59 | "sqs:PurgeQueue", 60 | "sqs:ReceiveMessage", 61 | "sqs:DeleteQueue", 62 | "sqs:SendMessage", 63 | "sqs:GetQueueAttributes", 64 | "sqs:ListQueueTags", 65 | "sqs:CreateQueue" 66 | ], 67 | "Resource": "arn:aws:sqs:region:account-id:QueueName.fifo" 68 | } 69 | ] 70 | } 71 | ``` 72 | 73 | **Setting Environment Variables** 74 | 75 | * The `SQS_CLIPBOARD_URL` environment variable should be set to URL of your SQS `FIFO` Queue 76 | * * This URL can be found on the AWS SQS Dashboard for the queue that you have created 77 | * [How to set environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html#envvars-set) 78 | 79 | **Setting Configuration File** 80 | * For Windows and MacOS, you might want to set the `SQS_CLIPBOARD_URL` value in a configuration file instead of using environment variable. 81 | * This will allow you to launch the programs from the *Windows Taskbar* or *MacOS Dock*. 82 | * To do this you will need to create this file: 83 | * * Windows: `%HOMEPATH%\.aws\sqs_clipboard` 84 | * * MacOS: `${HOME}/.aws/sqs_clipboard` 85 | 86 | File format: 87 | 88 | ```ini 89 | [default] 90 | SQS_CLIPBOARD_URL=https://sqs.region.amazonaws.com/account-id/queuename.fifo 91 | ``` 92 | 93 | ___ 94 | 95 | **Compilation** 96 | 97 | * Install [GoReleaser](https://goreleaser.com/) 98 | * Run: `goreleaser build --rm-dist --snapshot` 99 | * There should now be 5 resulting binaries found in the `dist` directory: 100 | * * sqscopy 101 | * * sqspaste 102 | * * sqspurge 103 | * * sqscopysmallfile 104 | * * sqspastesmallfile 105 | 106 | You can also create a *Mac App* by running [mac_app_builder.sh](cmd/mac_app_builder.sh) 107 | 108 | ___ 109 | 110 | **Windows Icons Used** 111 | 112 | * [Button Upload Icon](https://www.iconarchive.com/show/soft-scraps-icons-by-hopstarter/Button-Upload-icon.html) 113 | * [Button Download Icon](https://www.iconarchive.com/show/soft-scraps-icons-by-hopstarter/Button-Download-icon.html) 114 | * [Email Delete Icon](https://www.iconarchive.com/show/soft-scraps-icons-by-hopstarter/Email-Delete-icon.html) 115 | 116 | **Bundling Icons** 117 | 118 | * `Cross Platform` 119 | * [a cross platform Go library to place an icon and menu in the notification area](https://github.com/getlantern/systray) 120 | * `Windows` 121 | * [How do you set the application icon in golang?](https://stackoverflow.com/questions/25602600/how-do-you-set-the-application-icon-in-golang) 122 | * * [goversioninfo](https://github.com/josephspurrier/goversioninfo) 123 | * `Mac` 124 | * [Packaging a Go application for macOS](https://medium.com/@mattholt/packaging-a-go-application-for-macos-f7084b00f6b5) 125 | * * [Distribute your Go program (or any single binary) as a native macOS application](https://gist.github.com/mholt/11008646c95d787c30806d3f24b2c844) 126 | * * [Go library to create menubar apps- programs that live only in OSX's NSStatusBar](https://github.com/caseymrm/menuet) 127 | 128 | **AWS Resources** 129 | * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sqs-queues.html 130 | * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-cli-creating-stack.html 131 | * https://docs.aws.amazon.com/cli/latest/reference/cloudformation/describe-stacks.html 132 | 133 | -------------------------------------------------------------------------------- /cloud_formation_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion" : "2010-09-09", 3 | 4 | "Description" : "SQS Clipboard", 5 | 6 | "Parameters" : { 7 | "qName": { 8 | "Default": "", 9 | "Description": "Name must end in .fifo", 10 | "Type": "String" 11 | } 12 | }, 13 | 14 | "Resources" : { 15 | "MyQueue" : { 16 | "Type" : "AWS::SQS::Queue", 17 | "Properties" : { 18 | "ContentBasedDeduplication" : false, 19 | "DelaySeconds" : 0, 20 | "FifoQueue" : true, 21 | "KmsDataKeyReusePeriodSeconds" : 3600, 22 | "KmsMasterKeyId" : "alias/aws/sqs", 23 | "MessageRetentionPeriod" : 172800, 24 | "QueueName" : { "Ref": "qName" }, 25 | "ReceiveMessageWaitTimeSeconds" : 12, 26 | "VisibilityTimeout" : 30 27 | } 28 | }, 29 | }, 30 | "Outputs" : { 31 | "QueueURL" : { 32 | "Description" : "URL of new Amazon SQS Queue", 33 | "Value" : { "Ref" : "MyQueue" } 34 | }, 35 | "QueueARN" : { 36 | "Description" : "ARN of new Amazon SQS Queue", 37 | "Value" : { "Fn::GetAtt" : ["MyQueue", "Arn"]} 38 | }, 39 | "QueueName" : { 40 | "Description" : "Name new Amazon SQS Queue", 41 | "Value" : { "Fn::GetAtt" : ["MyQueue", "QueueName"]} 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cmd/mac_app_builder.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script can be used to create a "Mac App" complete with icons 4 | # Then, they can sit nicely on the Mac Desktop or on the Dock 5 | # 6 | # Cmd-line argument should be one of the following: 7 | # sqscopy, sqspaste, or sqspurge 8 | 9 | PGM=$1 10 | 11 | cd $1 12 | rm -rf ~/Desktop/${PGM}.app/ 13 | rm -rf ./assets/ 14 | rm -rf ${PGM}.app/ 15 | mkdir ./assets 16 | rm -f ${PGM} 17 | go build -ldflags="-s -w" 18 | sync ; sleep 3 ; sync 19 | cp ${PGM} ./assets/ 20 | sync ; sleep 3 ; sync 21 | # macapp:https://gist.github.com/jftuga/b3ec5a66472c0aec5676bfd7b90a1909 22 | macapp -bin ${PGM} -icon ./appicon1024.png -identifier github.jftuga.sqs_clipboard -name ${PGM} -assets ./assets/ 23 | sync ; sleep 3 ; sync 24 | mv ${PGM}.app/ ~/Desktop/ 25 | -------------------------------------------------------------------------------- /cmd/sqscopy/appicon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jftuga/sqs_clipboard/3c1e5e302e567708ac982a889d380141fc901cd2/cmd/sqscopy/appicon1024.png -------------------------------------------------------------------------------- /cmd/sqscopy/main.go: -------------------------------------------------------------------------------- 1 | //go:generate goversioninfo -icon=sqscopy.ico -platform-specific=true 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | 9 | "github.com/atotto/clipboard" 10 | "github.com/jftuga/copypaste" 11 | "github.com/jftuga/copypaste/customlog" 12 | "github.com/jftuga/copypaste/queue" 13 | ) 14 | 15 | func main() { 16 | argsVersion := flag.Bool("v", false, "display program version and then exit") 17 | flag.Parse() 18 | 19 | if *argsVersion { 20 | fmt.Println(copypaste.Version()) 21 | return 22 | } 23 | 24 | queueURL := queue.GetQueueURL() 25 | cp := copypaste.New(queueURL) 26 | data, err := clipboard.ReadAll() 27 | if err != nil { 28 | customlog.Log(err.Error()) 29 | } 30 | if len(data) <= 4 { 31 | customlog.Log("clipboard must have at least 5 bytes of data to send!") 32 | } 33 | cp.Copy(data) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/sqscopy/sqscopy.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jftuga/sqs_clipboard/3c1e5e302e567708ac982a889d380141fc901cd2/cmd/sqscopy/sqscopy.ico -------------------------------------------------------------------------------- /cmd/sqscopy/versioninfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "IconPath": "cmd\\sqscopy\\sqscopy.ico" 3 | } 4 | -------------------------------------------------------------------------------- /cmd/sqscopysmallfile/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "runtime" 10 | 11 | "github.com/gen2brain/dlgs" 12 | "github.com/jftuga/copypaste" 13 | "github.com/jftuga/copypaste/customlog" 14 | "github.com/jftuga/copypaste/queue" 15 | ) 16 | 17 | func fileExists(filename string) bool { 18 | info, err := os.Stat(filename) 19 | if os.IsNotExist(err) { 20 | return false 21 | } 22 | return !info.IsDir() 23 | } 24 | 25 | func main() { 26 | argsVersion := flag.Bool("v", false, "display program version and then exit") 27 | flag.Parse() 28 | 29 | if *argsVersion { 30 | fmt.Println(copypaste.Version()) 31 | return 32 | } 33 | 34 | fileName := "" 35 | if len(flag.Args()) == 1 { 36 | fileName = flag.Arg(0) 37 | if !fileExists(fileName) { 38 | customlog.Fatalf("File not found: %s", fileName) 39 | } 40 | } else { 41 | if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { 42 | var success bool 43 | var err error 44 | fileName, success, err = dlgs.File("SQS Copy Small File", "", false) 45 | if !success { 46 | //customlog.Fatalf("Unable to open file dialog") 47 | os.Exit(0) 48 | } 49 | if err != nil { 50 | customlog.Log(err.Error()) 51 | } 52 | } 53 | } 54 | 55 | data, err := ioutil.ReadFile(fileName) 56 | if err != nil { 57 | customlog.Log(err.Error()) 58 | } 59 | if len(data) <= 4 { 60 | customlog.Log("clipboard must have at least 5 bytes of data to send!") 61 | } 62 | 63 | queueURL := queue.GetQueueURL() 64 | cp := copypaste.New(queueURL) 65 | cp.CopySmallFile(filepath.Base(fileName), data) 66 | } 67 | -------------------------------------------------------------------------------- /cmd/sqspaste/appicon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jftuga/sqs_clipboard/3c1e5e302e567708ac982a889d380141fc901cd2/cmd/sqspaste/appicon1024.png -------------------------------------------------------------------------------- /cmd/sqspaste/main.go: -------------------------------------------------------------------------------- 1 | //go:generate goversioninfo -icon=sqspaste.ico -platform-specific=true 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | 9 | "github.com/atotto/clipboard" 10 | "github.com/jftuga/copypaste" 11 | "github.com/jftuga/copypaste/customlog" 12 | "github.com/jftuga/copypaste/queue" 13 | ) 14 | 15 | func main() { 16 | argsVersion := flag.Bool("v", false, "display program version and then exit") 17 | flag.Parse() 18 | 19 | if *argsVersion { 20 | fmt.Println(copypaste.Version()) 21 | return 22 | } 23 | 24 | queueURL := queue.GetQueueURL() 25 | cp := copypaste.New(queueURL) 26 | data := cp.Paste() 27 | if len(data) <= 4 { 28 | customlog.Log("clipboard must have at least 5 bytes of data to paste!") 29 | } 30 | err := clipboard.WriteAll(data) 31 | if err != nil { 32 | customlog.Log(err.Error()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cmd/sqspaste/sqspaste.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jftuga/sqs_clipboard/3c1e5e302e567708ac982a889d380141fc901cd2/cmd/sqspaste/sqspaste.ico -------------------------------------------------------------------------------- /cmd/sqspaste/versioninfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "IconPath": "cmd\\sqspaste\\sqspaste.ico" 3 | } 4 | -------------------------------------------------------------------------------- /cmd/sqspastesmallfile/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | 9 | "github.com/gen2brain/dlgs" 10 | "github.com/jftuga/copypaste" 11 | "github.com/jftuga/copypaste/customlog" 12 | "github.com/jftuga/copypaste/queue" 13 | ) 14 | 15 | func dirExists(dirname string) bool { 16 | info, err := os.Stat(dirname) 17 | if os.IsNotExist(err) { 18 | return false 19 | } 20 | return info.IsDir() 21 | } 22 | 23 | func main() { 24 | argsVersion := flag.Bool("v", false, "display program version and then exit") 25 | flag.Parse() 26 | 27 | if *argsVersion { 28 | fmt.Println(copypaste.Version()) 29 | return 30 | } 31 | 32 | queueURL := queue.GetQueueURL() 33 | cp := copypaste.New(queueURL) 34 | 35 | destPath := "." 36 | if len(flag.Args()) == 1 { 37 | destPath = flag.Arg(0) 38 | if !dirExists(destPath) { 39 | customlog.Fatalf("Directory not found: %s", destPath) 40 | } 41 | } else { 42 | if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { 43 | var success bool 44 | var err error 45 | destPath, success, err = dlgs.File("SQS Paste Small File", "", true) 46 | if !success { 47 | //customlog.Fatalf("Unable to open file dialog") 48 | os.Exit(0) 49 | } 50 | if err != nil { 51 | customlog.Log(err.Error()) 52 | } 53 | } 54 | } 55 | 56 | savedFileName := cp.PasteSmallFile(destPath) 57 | if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { 58 | dlgs.Info("SQS Paste Small File", fmt.Sprintf("Saved file: %s", savedFileName)) 59 | } else { 60 | fmt.Println("Saved file: ", savedFileName) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /cmd/sqspurge/appicon1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jftuga/sqs_clipboard/3c1e5e302e567708ac982a889d380141fc901cd2/cmd/sqspurge/appicon1024.png -------------------------------------------------------------------------------- /cmd/sqspurge/main.go: -------------------------------------------------------------------------------- 1 | //go:generate goversioninfo -icon=sqspurge.ico -platform-specific=true 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | 9 | "github.com/jftuga/copypaste" 10 | "github.com/jftuga/copypaste/queue" 11 | ) 12 | 13 | func main() { 14 | argsVersion := flag.Bool("v", false, "display program version and then exit") 15 | flag.Parse() 16 | 17 | if *argsVersion { 18 | fmt.Println(copypaste.Version()) 19 | return 20 | } 21 | 22 | queueURL := queue.GetQueueURL() 23 | cp := copypaste.New(queueURL) 24 | cp.Purge() 25 | } 26 | -------------------------------------------------------------------------------- /cmd/sqspurge/sqspurge.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jftuga/sqs_clipboard/3c1e5e302e567708ac982a889d380141fc901cd2/cmd/sqspurge/sqspurge.ico -------------------------------------------------------------------------------- /cmd/sqspurge/versioninfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "IconPath": "cmd\\sqspurge\\sqspurge.ico" 3 | } 4 | -------------------------------------------------------------------------------- /copypaste.go: -------------------------------------------------------------------------------- 1 | /* 2 | copypaste.go 3 | -John Taylor 4 | Nov-19-2020 5 | 6 | copy / paste text to / from SQS queue 7 | 8 | See also: 9 | https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/sqs-example-receive-message.html 10 | https://docs.aws.amazon.com/sdk-for-go/api/service/sqs/ 11 | 12 | */ 13 | 14 | package copypaste 15 | 16 | import ( 17 | "bytes" 18 | "fmt" 19 | "io" 20 | "io/ioutil" 21 | "path/filepath" 22 | 23 | "github.com/aws/aws-sdk-go/aws" 24 | "github.com/aws/aws-sdk-go/aws/session" 25 | "github.com/aws/aws-sdk-go/service/sqs" 26 | "github.com/jftuga/copypaste/customlog" 27 | "github.com/jftuga/copypaste/randstring" 28 | "github.com/mtraver/base91" 29 | "github.com/ulikunitz/xz" 30 | ) 31 | 32 | const pgmVersion = "1.1.1" 33 | const pgmName = "sqs_clipboard" 34 | const pgmUrl = "https://github.com/jftuga/sqs_clipboard" 35 | 36 | // CopyPaste contains the AWS SQS queue url 37 | type CopyPaste struct { 38 | QueueURL string 39 | sess session.Session 40 | svc sqs.SQS 41 | } 42 | 43 | // compress msg before sending it to SQS 44 | func compress(msg string) bytes.Buffer { 45 | var buf bytes.Buffer 46 | w, err := xz.NewWriter(&buf) 47 | if err != nil { 48 | customlog.Fatalf("xz.NewWriter error %s", err) 49 | } 50 | if _, err := io.WriteString(w, msg); err != nil { 51 | customlog.Fatalf("WriteString error %s", err) 52 | } 53 | if err := w.Close(); err != nil { 54 | customlog.Fatalf("w.Close error %s", err) 55 | } 56 | 57 | return buf 58 | } 59 | 60 | // compress msg before sending it to SQS 61 | func compressBinary(data []byte) bytes.Buffer { 62 | var buf bytes.Buffer 63 | w, err := xz.NewWriter(&buf) 64 | if err != nil { 65 | customlog.Fatalf("xz.NewWriter error %s", err) 66 | } 67 | if _, err := io.WriteString(w, bytes.NewBuffer(data).String()); err != nil { 68 | customlog.Fatalf("WriteString error %s", err) 69 | } 70 | if err := w.Close(); err != nil { 71 | customlog.Fatalf("w.Close error %s", err) 72 | } 73 | 74 | return buf 75 | } 76 | 77 | // decompress data sent from SQS 78 | func decompress(buf *bytes.Buffer) *bytes.Buffer { 79 | r, err := xz.NewReader(buf) 80 | if err != nil { 81 | customlog.Fatalf("NewReader error %s", err) 82 | } 83 | 84 | var uncompressed bytes.Buffer 85 | if _, err = io.Copy(&uncompressed, r); err != nil { 86 | customlog.Fatalf("io.Copy error %s", err) 87 | } 88 | return &uncompressed 89 | } 90 | 91 | // Version return the program version number 92 | func Version() string { 93 | return fmt.Sprintf("%s v%s\n%s\n", pgmName, pgmVersion, pgmUrl) 94 | } 95 | 96 | // New initializes a new CopyPaste object 97 | func New(queueURL string) *CopyPaste { 98 | sess := session.Must(session.NewSessionWithOptions(session.Options{ 99 | SharedConfigState: session.SharedConfigEnable, 100 | })) 101 | svc := sqs.New(sess) 102 | return &CopyPaste{ 103 | QueueURL: queueURL, 104 | sess: *sess, 105 | svc: *svc, 106 | } 107 | } 108 | 109 | // Copy a message to the SQS queue 110 | func (cp CopyPaste) Copy(msg string) { 111 | xz := compress(msg) 112 | encoding := "text" 113 | payload := &msg 114 | if float32(xz.Len()/len(msg)) < 0.85 { 115 | b91enc := base91.StdEncoding.EncodeToString(xz.Bytes()) 116 | //fmt.Println(len(msg), xz.Len(), len(b91enc)) 117 | 118 | maxSize := 256 * 1024 119 | if len(b91enc) > maxSize { 120 | customlog.Fatalf("Data of length %d is too big! Should be less than %d.", len(b91enc), maxSize) 121 | } 122 | 123 | // compare orig text size to b91enc size 124 | if len(b91enc) < len(msg) { 125 | encoding = "xzb91" 126 | payload = &b91enc 127 | } 128 | } 129 | 130 | _, err := cp.svc.SendMessage(&sqs.SendMessageInput{ 131 | MessageAttributes: map[string]*sqs.MessageAttributeValue{ 132 | "Encoding": &sqs.MessageAttributeValue{ 133 | DataType: aws.String("String"), 134 | StringValue: aws.String(encoding), 135 | }, 136 | }, 137 | MessageDeduplicationId: aws.String(randstring.String(32)), 138 | MessageGroupId: &cp.QueueURL, 139 | MessageBody: aws.String(*payload), 140 | QueueUrl: &cp.QueueURL, 141 | }) 142 | if err != nil { 143 | customlog.Log(err.Error()) 144 | return 145 | } 146 | } 147 | 148 | // Paste and remove a message from the SQS queue 149 | func (cp CopyPaste) Paste() string { 150 | var timeout int64 151 | timeout = 5 152 | 153 | msgResult, err := cp.svc.ReceiveMessage(&sqs.ReceiveMessageInput{ 154 | AttributeNames: []*string{ 155 | aws.String(sqs.MessageSystemAttributeNameSentTimestamp), 156 | }, 157 | MessageAttributeNames: []*string{ 158 | aws.String(sqs.QueueAttributeNameAll), 159 | }, 160 | QueueUrl: &cp.QueueURL, 161 | MaxNumberOfMessages: aws.Int64(1), 162 | VisibilityTimeout: &timeout, 163 | }) 164 | if err != nil { 165 | customlog.Log(err.Error()) 166 | return "" 167 | } 168 | 169 | if len(msgResult.Messages) == 0 { 170 | customlog.Fatalf("There are no messages in the SQS queue. This could be a transient error so try pasting again.") 171 | } 172 | 173 | if _, ok := (*msgResult.Messages[0]).MessageAttributes["Filename"]; ok { 174 | customlog.Fatalf("You are trying to paste a file. Use 'sqspastesmallfile' instead.") 175 | } 176 | 177 | cp.Delete(*msgResult.Messages[0].ReceiptHandle) 178 | 179 | encoding := (*msgResult.Messages[0]).MessageAttributes["Encoding"].StringValue 180 | if "xzb91" == *encoding { 181 | body := *msgResult.Messages[0].Body 182 | b91dec, err := base91.StdEncoding.DecodeString(body) 183 | if err != nil { 184 | customlog.Log(err.Error()) 185 | return "" 186 | } 187 | compressedData := bytes.NewBuffer(b91dec) 188 | un := decompress(compressedData) 189 | return un.String() 190 | } else if "text" == *encoding { 191 | return *msgResult.Messages[0].Body 192 | } 193 | 194 | customlog.Fatalf("Unknown encoding: %s", *encoding) 195 | return "" 196 | } 197 | 198 | // CopySmallFile send a file to the SQS queue 199 | // it must be smaller than 256 KB after XZ compression and b91 encoding 200 | func (cp CopyPaste) CopySmallFile(fileName string, data []byte) { 201 | xz := compressBinary(data) 202 | encoding := "xzb91" 203 | 204 | b91enc := base91.StdEncoding.EncodeToString(xz.Bytes()) 205 | //fmt.Println(len(data), xz.Len(), len(b91enc)) 206 | 207 | maxSize := 256 * 1024 208 | if len(b91enc) > maxSize { 209 | customlog.Fatalf("Data of length %d is too big! Should be less than %d.", len(b91enc), maxSize) 210 | } 211 | 212 | _, err := cp.svc.SendMessage(&sqs.SendMessageInput{ 213 | MessageAttributes: map[string]*sqs.MessageAttributeValue{ 214 | "Encoding": &sqs.MessageAttributeValue{ 215 | DataType: aws.String("String"), 216 | StringValue: aws.String(encoding), 217 | }, 218 | "Filename": &sqs.MessageAttributeValue{ 219 | DataType: aws.String("String"), 220 | StringValue: aws.String(fileName), 221 | }, 222 | }, 223 | MessageDeduplicationId: aws.String(randstring.String(32)), 224 | MessageGroupId: &cp.QueueURL, 225 | MessageBody: aws.String(b91enc), 226 | QueueUrl: &cp.QueueURL, 227 | }) 228 | if err != nil { 229 | customlog.Log(err.Error()) 230 | return 231 | } 232 | } 233 | 234 | // PasteSmallFile retrieves a file from SQS and saves it to the file system 235 | func (cp CopyPaste) PasteSmallFile(destPath string) string { 236 | var timeout int64 237 | timeout = 5 238 | 239 | msgResult, err := cp.svc.ReceiveMessage(&sqs.ReceiveMessageInput{ 240 | AttributeNames: []*string{ 241 | aws.String(sqs.MessageSystemAttributeNameSentTimestamp), 242 | }, 243 | MessageAttributeNames: []*string{ 244 | aws.String(sqs.QueueAttributeNameAll), 245 | }, 246 | QueueUrl: &cp.QueueURL, 247 | MaxNumberOfMessages: aws.Int64(1), 248 | VisibilityTimeout: &timeout, 249 | }) 250 | if err != nil { 251 | customlog.Log(err.Error()) 252 | return "" 253 | } 254 | 255 | if len(msgResult.Messages) == 0 { 256 | customlog.Fatalf("There are no messages in the SQS queue. This could be a transient error so try pasting again.") 257 | return "" 258 | } 259 | 260 | if _, ok := (*msgResult.Messages[0]).MessageAttributes["Filename"]; !ok { 261 | customlog.Fatalf("You are trying to paste a file. Use 'sqspaste' instead.") 262 | } 263 | 264 | cp.Delete(*msgResult.Messages[0].ReceiptHandle) 265 | 266 | fileName := (*msgResult.Messages[0]).MessageAttributes["Filename"].StringValue 267 | if len(destPath) > 0 { 268 | joined := filepath.Join(destPath, *fileName) 269 | fileName = &joined 270 | } 271 | encoding := (*msgResult.Messages[0]).MessageAttributes["Encoding"].StringValue 272 | if "xzb91" == *encoding { 273 | body := *msgResult.Messages[0].Body 274 | b91dec, err := base91.StdEncoding.DecodeString(body) 275 | if err != nil { 276 | customlog.Log(err.Error()) 277 | return "" 278 | } 279 | compressedData := bytes.NewBuffer(b91dec) 280 | un := decompress(compressedData) 281 | err = ioutil.WriteFile(*fileName, un.Bytes(), 0700) 282 | if err != nil { 283 | customlog.Log(err.Error()) 284 | return "" 285 | } 286 | } else { 287 | customlog.Fatalf("Unknown encoding: %s", *encoding) 288 | return "" 289 | } 290 | return *fileName 291 | } 292 | 293 | // Delete a message from the SQS queue 294 | func (cp CopyPaste) Delete(messageHandle string) { 295 | _, err := cp.svc.DeleteMessage(&sqs.DeleteMessageInput{ 296 | QueueUrl: &cp.QueueURL, 297 | ReceiptHandle: &messageHandle, 298 | }) 299 | if err != nil { 300 | customlog.Log(err.Error()) 301 | } 302 | } 303 | 304 | // Purge remove all messages from the SQS queue 305 | func (cp CopyPaste) Purge() { 306 | var input sqs.PurgeQueueInput 307 | input.QueueUrl = &cp.QueueURL 308 | _, err := cp.svc.PurgeQueue(&input) 309 | if err != nil { 310 | customlog.Log(err.Error()) 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /create_stack.ps1: -------------------------------------------------------------------------------- 1 | $qName=$args[0] 2 | if($qName -notmatch "\.fifo$") { 3 | "queue name must end in .fifo" 4 | exit 5 | } 6 | $now = get-date -format "yyyyMMddHHmmss" 7 | $sName="sqsClipboard$now" 8 | $templateName="cloud_formation_template.json" 9 | 10 | "" 11 | "[queue name] $qName" 12 | "[stack name] $sName" 13 | 14 | aws cloudformation create-stack --stack-name $sName --parameters ParameterKey=qName,ParameterValue=$qName --template-body "file://./$templateName" 15 | 16 | $i=0 17 | while($i -lt 15) { 18 | $qURL=(aws cloudformation describe-stacks --stack-name $sName --query "Stacks[0].Outputs[?OutputKey=='QueueURL'].OutputValue" --output text) -join "`n" 19 | if($qURL.length -gt 30) { 20 | "[queue url] $qURL" 21 | "" 22 | "Please update your environment by running:" 23 | "" 24 | "setx SQS_CLIPBOARD_URL $qURL" 25 | "" 26 | "See also:" 27 | "https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html#envvars-set" 28 | "" 29 | break 30 | } 31 | Start-Sleep 2 32 | $i += 1 33 | "waiting for stack creation to complete..." 34 | } -------------------------------------------------------------------------------- /create_stack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Give the SQS queue name that want to create for SQS Clipboard as the only command-line argument 4 | 5 | qName=$1 6 | now=$(date +"%Y%m%d%H%M%S") 7 | sName="sqsClipboard${now}" 8 | templateName="cloud_formation_template.json" 9 | if [[ $qName != *.fifo ]] ; then 10 | echo "queue name must end in: .fifo" 11 | exit 1 12 | fi 13 | 14 | echo "" 15 | echo "[queue name] $qName" 16 | echo "[stack name] $sName" 17 | echo "" 18 | 19 | aws cloudformation create-stack --stack-name ${sName} --parameters ParameterKey=qName,ParameterValue=${qName} --template-body "file://./${templateName}" 20 | 21 | i=0 22 | while [ $i -lt 15 ] ; do 23 | qURL=$(aws cloudformation describe-stacks --stack-name ${sName} --query "Stacks[0].Outputs[?OutputKey=='QueueURL'].OutputValue" --output text 2> /dev/null) 24 | if [ "${qURL}" != "None" ] ; then 25 | echo "[queue url] $qURL" 26 | echo "" 27 | echo "Please add this to your '.bash_profile' file:" 28 | echo 29 | echo "export SQS_CLIPBOARD_URL='${qURL}'" 30 | echo 31 | echo "See also:" 32 | echo "https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html#envvars-set" 33 | break 34 | else 35 | sleep 2 36 | let i=${i}+1 37 | echo "waiting for stack creation to complete..." 38 | fi 39 | done 40 | 41 | -------------------------------------------------------------------------------- /customlog/customlog.go: -------------------------------------------------------------------------------- 1 | package customlog 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/gen2brain/dlgs" 9 | "github.com/mattn/go-isatty" 10 | ) 11 | 12 | func debug(msg string, useGUI bool) { 13 | f, err := os.Create("/tmp/debug.log") 14 | if err != nil { 15 | fmt.Println(err) 16 | return 17 | } 18 | _, err = f.WriteString(fmt.Sprintf("%s\n%v\n%s\n",msg,useGUI,os.Args[0])) 19 | if err != nil { 20 | fmt.Println(err) 21 | f.Close() 22 | return 23 | } 24 | err = f.Close() 25 | if err != nil { 26 | fmt.Println(err) 27 | return 28 | } 29 | } 30 | 31 | // Log is similar to log.Fatal, but with a pause 32 | func Log(msg string) { 33 | useGUI := true 34 | if isatty.IsTerminal(os.Stdout.Fd()) { 35 | useGUI = false 36 | } else if isatty.IsCygwinTerminal(os.Stdout.Fd()) { 37 | useGUI = false 38 | } 39 | //debug(msg,useGUI) 40 | 41 | if useGUI { 42 | dlgs.Error("Error", msg) 43 | } else { 44 | log.Print(msg) 45 | } 46 | os.Exit(1) 47 | } 48 | 49 | // Fatalf is similar to log.Fatalf, but with a pause 50 | func Fatalf(format string, v ...interface{}) { 51 | useGUI := true 52 | if isatty.IsTerminal(os.Stdout.Fd()) { 53 | useGUI = false 54 | } else if isatty.IsCygwinTerminal(os.Stdout.Fd()) { 55 | useGUI = false 56 | } 57 | msg := fmt.Sprintf(format, v...) 58 | //debug(msg,useGUI) 59 | if useGUI { 60 | dlgs.Error("Error", msg) 61 | } else { 62 | log.Print(msg) 63 | } 64 | os.Exit(1) 65 | } 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jftuga/copypaste 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/atotto/clipboard v0.1.2 7 | github.com/aws/aws-sdk-go v1.35.31 8 | github.com/gen2brain/dlgs v0.0.0-20201118155338-03fe7f81ad25 9 | github.com/josephspurrier/goversioninfo v1.2.0 // indirect 10 | github.com/mattn/go-isatty v0.0.12 11 | github.com/mtraver/base91 v1.0.0 12 | github.com/ulikunitz/xz v0.5.8 13 | gopkg.in/ini.v1 v1.62.0 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw= 2 | github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= 3 | github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY= 4 | github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 5 | github.com/aws/aws-sdk-go v1.35.31 h1:6tlaYq4Q311qfhft/fIaND33XI27aW3zIdictcHxifE= 6 | github.com/aws/aws-sdk-go v1.35.31/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/gen2brain/dlgs v0.0.0-20201118155338-03fe7f81ad25 h1:x2nGRhm8HbvCAS4TN6TfsXu3iWTXsuNNfVCNPByyKMc= 9 | github.com/gen2brain/dlgs v0.0.0-20201118155338-03fe7f81ad25/go.mod h1:/eFcjDXaU2THSOOqLxOPETIbHETnamk8FA/hMjhg/gU= 10 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 11 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 12 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 13 | github.com/josephspurrier/goversioninfo v1.2.0 h1:tpLHXAxLHKHg/dCU2AAYx08A4m+v9/CWg6+WUvTF4uQ= 14 | github.com/josephspurrier/goversioninfo v1.2.0/go.mod h1:AGP2a+Y/OVJZ+s6XM4IwFUpkETwvn0orYurY8qpw1+0= 15 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 16 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 17 | github.com/mtraver/base91 v1.0.0 h1:vkIW96Xbw7QH1fbSV5VwXqv+xVuWWZji4O4ty8yYk28= 18 | github.com/mtraver/base91 v1.0.0/go.mod h1:Igwspit339nKvBhXGqrNOaNI8qGvh+Y4P76q5g4qH2Y= 19 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 20 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 21 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 22 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 23 | github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= 24 | github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 25 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 26 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 27 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 28 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 29 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 30 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 31 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 32 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= 33 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 34 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 35 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 36 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 37 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 38 | gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= 39 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 40 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 41 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 42 | -------------------------------------------------------------------------------- /queue/queue.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "runtime" 7 | 8 | "github.com/jftuga/copypaste/customlog" 9 | "gopkg.in/ini.v1" 10 | ) 11 | 12 | // GetQueueURL retrieves the URL from the OS environemnt 13 | // or from ini config file: $HOME/.aws/sqs_clipboard 14 | func GetQueueURL() string { 15 | queueURL := os.Getenv("SQS_CLIPBOARD_URL") 16 | if len(queueURL) > 8 { 17 | return queueURL 18 | } 19 | 20 | var configIni string 21 | if runtime.GOOS == "windows" { 22 | configIni = filepath.Join(os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH"), ".aws", "sqs_clipboard") 23 | } else { 24 | configIni = filepath.Join(os.Getenv("HOME"), ".aws", "sqs_clipboard") 25 | } 26 | cfg, err := ini.Load(configIni) 27 | if err != nil { 28 | customlog.Fatalf("Fail to read file: [%s]: %v", configIni, err) 29 | return "" 30 | } 31 | 32 | queueURL = cfg.Section("default").Key("SQS_CLIPBOARD_URL").String() 33 | if len(queueURL) < 8 { 34 | var location = "$HOME" 35 | if runtime.GOOS == "windows" { 36 | location = "%HOMEDRIVE%//%HOMEPATH%" 37 | } 38 | customlog.Fatalf("\n\nThe 'SQS_CLIPBOARD_URL' environment variable is not set correctly.\n\nOr %s/.aws/sqs_clipboard file is not properly configured.\n\nSee: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html#envvars-set\n", location) 39 | return "" 40 | } 41 | return queueURL 42 | } 43 | -------------------------------------------------------------------------------- /randstring/randstring.go: -------------------------------------------------------------------------------- 1 | // Adapted from: 2 | // https://www.calhoun.io/creating-random-strings-in-go/ 3 | 4 | package randstring 5 | 6 | import ( 7 | "math/rand" 8 | "time" 9 | ) 10 | 11 | const charset = "abcdefghijklmnopqrstuvwxyz" + 12 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 13 | 14 | var seededRand *rand.Rand = rand.New( 15 | rand.NewSource(time.Now().UnixNano())) 16 | 17 | func stringWithCharset(length int, charset string) string { 18 | b := make([]byte, length) 19 | for i := range b { 20 | b[i] = charset[seededRand.Intn(len(charset))] 21 | } 22 | return string(b) 23 | } 24 | 25 | // generate a random string of given length using only the characters given in charset 26 | func String(length int) string { 27 | return stringWithCharset(length, charset) 28 | } 29 | --------------------------------------------------------------------------------