Created
August 27, 2015 03:59
-
-
Save quiznilo1/6f21db0f71705b2d40fc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* SLMagic - A very dangerous app. Stay back! | |
SLMagic (slmagic) is a small app designed to ease the burden of | |
corporation management for the CEOs of corporations that are a | |
part of Eve Online's player-ran GSF. If you have no idea what | |
I'm talking about, this program would be useless to you. | |
slmagic is designed to be ran as a cron task, perhaps once a | |
week. It's designed for any basic linux system, no guarantees | |
that it will work on Windows, but it's possible. | |
Install Go (as a user): | |
http://golang.org/doc/install | |
As a final test: | |
% go version | |
If this fails, seek help to install Go. Go is a great little | |
programming language. Next, please ensure that your system has | |
sqlite3. See your package manager for details. As root: | |
(root) # emerge -avt sqlite3 | |
And you'll need some Go packages (back as user): | |
% go get github.com/mattn/go-sqlite3 | |
% go get github.com/spf13/viper | |
$ go get github.com/spf13/cobra | |
Now grab the raw copy of this source code from the github GIST and | |
compile it (back as user): | |
% go build slmagic.go | |
Assuming everything built okay, move it to its final resting spot | |
% mkdir ~/.slmagic | |
% mv slmagic ~/.slmagic/ | |
% cd ~/.slmagic | |
Because slmagic is best operated as a cron task, with no | |
interaction from the user, ensure that vixie-cron, fcron, or some | |
variant is installed. There is no reason slmagic won't run with | |
At on windows systems, see the At docs. | |
Notes: | |
Trello app key: 6fb1834f9b6ee0b21fe8a70b6f3455c7 | |
The Eve APIv2 endpoint CharNotifications is described here | |
https://neweden-dev.com/Char/Notifications and conforms to 'Short | |
Cache Style'. You should know these rules before you set up your | |
cron jobs. | |
o A request will only return a maximum of 200 most-recent events | |
o A request will only return events from the previous 7 days | |
o Subsequent requests will not repeat events reported earlier | |
o To 'reset timer' and get all (max 200, 7 days) events, you | |
must wait at least 6 hours between API calls. From my own | |
experience though, I kept getting the same test data over and | |
over without having to wait any 6 hours. YMMV IANAL IKR LOL. | |
At least set your conf file to perms 0600 so no one else who has | |
access to your computer can read your API keys and smtp server creds. | |
% chmod 0600 slmagic.toml | |
The other endpoint, the corporate one /Corp/WalletJournal conforms to | |
the Short Cache Style also, though it took a long time for me to get | |
a test bounty tax to show up. | |
The post should look thusly: | |
Aug 25: | |
Join: 5 | |
Leave 2 | |
Tax rate: 10% | |
Income 4.4b | |
News: One recruiter fired, one hired | |
Some of this code ganked from | |
https://github.com/kilgarth/member_tracking Kilgarth's awesome | |
corp tracking software. The bitch hasn't licensed it, so I'll | |
just assume WTFPL. Fucking pubbie. | |
TODO: | |
o Clean up the file comments | |
o Test that #s are correct | |
Copyright (c) 2015, Andail Chanter <xVMx> [condi] | |
All rights reserved. | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions | |
are met: | |
1. Redistributions of source code must retain the above copyright | |
notice, this list of conditions and the following disclaimer. | |
2. Redistributions in binary form must reproduce the above | |
copyright notice, this list of conditions and the following | |
disclaimer in the documentation and/or other materials provided | |
with the distribution. | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR | |
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF | |
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED | |
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | |
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
POSSIBILITY OF SUCH DAMAGE. SO THERE! | |
*/ | |
package main | |
import ( | |
"database/sql" | |
"encoding/json" | |
"encoding/xml" | |
"fmt" | |
"io/ioutil" | |
"log" | |
"net/http" | |
"net/smtp" | |
"net/url" | |
"os" | |
"strconv" | |
"strings" | |
"time" | |
_ "github.com/mattn/go-sqlite3" | |
"github.com/spf13/cobra" | |
"github.com/spf13/viper" | |
) | |
func main() { | |
var err error | |
viper.SetConfigName("slmagic") | |
viper.SetConfigType("toml") | |
pwd, err = os.Getwd() | |
checkErr("os.Getwd():", err) | |
viper.AddConfigPath(pwd) | |
if err := viper.ReadInConfig(); err != nil { | |
// Config file not found! create it and terminate application | |
createConfFile() | |
appGood = false | |
} | |
SlmagicCmd.AddCommand(fetchCmd, postCmd) | |
// Plug and pray | |
if appGood { | |
SlmagicCmd.Execute() | |
} | |
} | |
// Cobra Setup | |
var SlmagicCmd = &cobra.Command{ | |
Use: "slmagic", | |
Short: fmt.Sprintf("SLMagic %s is a very dangerous app. Stay back!", | |
appVer), | |
Long: `SLMagic eases the burden of the reporting requirements of CEOs of | |
corporations belonging to the Goonswarm Federation, in the Eve Online | |
game.`, | |
Run: func(cmd *cobra.Command, args []string) { | |
// Do stuff here | |
}, | |
} | |
var fetchCmd = &cobra.Command{ | |
Use: "fetch", | |
Short: "Fetches data from Eve's API", | |
Long: `Fetches corporation data from Eve Online's CREST interface, and writes | |
this data to the app's database.`, | |
Run: func(cmd *cobra.Command, args []string) { | |
initDB() | |
// FIRST: Fetch from CEO API, Users joining and quitting corp | |
// Get conf file data | |
ids := &Records{} | |
ceoKey := viper.Get("eve.ceoKey") | |
ceovCode := viper.Get("eve.ceovCode") | |
req, err := http.NewRequest( | |
"GET", | |
fmt.Sprintf("https://api.eveonline.com/Char/Notifications.xml.aspx?keyid=%s&vcode=%s", | |
ceoKey, | |
ceovCode), nil) | |
client := &http.Client{} | |
res, err := client.Do(req) | |
defer res.Body.Close() | |
if err != nil { | |
log.Printf("Error getting Header API data: %s", err) | |
return | |
} | |
if res.StatusCode != 200 { | |
log.Printf("Header API returned non 200 response: %d", res.StatusCode) | |
return | |
} | |
// validate result from CCP | |
d := xml.NewDecoder(res.Body) | |
decoded := &NotificationHeaders{} | |
err = d.Decode(&decoded) | |
if err != nil { | |
log.Printf("Error decoding header API data: %s", err) | |
} | |
// Emit xml for testing purposes. Note you can do this once every 21 mins | |
for _, v := range decoded.Notifications { | |
if v.TypeID == 21 || v.TypeID == 128 { | |
sentDate, err := time.Parse("2006-01-02 15:04:05", v.SentDate) | |
if err != nil { | |
log.Printf("Error parsing time: %s", err) | |
} | |
ids.Events = append( | |
ids.Events, | |
InsertData{ | |
CharID: v.SenderID, | |
SenderName: v.SenderName, | |
NotificationID: v.NotificationID, | |
NotificationTypeID: v.TypeID, | |
Date: sentDate}) | |
} | |
} | |
// Store resulting notifications | |
storeEvents(ids) | |
// SECOND: Fetch Ratting Bounties from Corp API | |
wids := &Wrecords{} | |
corpKey := viper.Get("eve.corpKey") | |
corpvCode := viper.Get("eve.corpvCode") | |
wreq, err := http.NewRequest( | |
"GET", | |
fmt.Sprintf("https://api.eveonline.com/Corp/WalletJournal.xml.aspx?keyid=%s&vcode=%s", | |
corpKey, | |
corpvCode), nil) | |
wclient := &http.Client{} | |
wres, err := wclient.Do(wreq) | |
defer wres.Body.Close() | |
if err != nil { | |
log.Printf("Error getting Events API data: %s", err) | |
return | |
} | |
if wres.StatusCode != 200 { | |
log.Printf("Events API returned non 200 response: %d", wres.StatusCode) | |
return | |
} | |
// validate result from ccp | |
wd := xml.NewDecoder(wres.Body) | |
wdecoded := &EntriesHeaders{} | |
err = wd.Decode(&wdecoded) | |
if err != nil { | |
log.Fatalf("Error decoding header API data: %s", err) | |
} | |
// Emit xml for testing purposes | |
for _, v := range wdecoded.Entries { | |
if v.RefTypeID == 33 || v.RefTypeID == 34 || v.RefTypeID == 85 { | |
date, err := time.Parse("2006-01-02 15:04:05", v.Date) | |
if err != nil { | |
log.Fatalf("Error parsing date/time: %s", err) | |
} | |
wids.Jentries = append( | |
wids.Jentries, | |
EData{ | |
RefID: v.RefID, | |
RefTypeID: v.RefTypeID, | |
Date: date, | |
Amount: v.Amount, | |
}) | |
} | |
} | |
storeEntries(wids) | |
}, | |
} | |
var postCmd = &cobra.Command{ | |
Use: "post", | |
Short: "Posts data to Trello", | |
Long: "Posts all compiled data to your comment card on Trello", | |
Run: func(cmd *cobra.Command, args []string) { | |
var corpTax string | |
dateStamp := time.Now().Format("Jan 2") | |
now := time.Now() | |
_, month, _ := now.Date() | |
// Test email system: | |
rm := &SendMail{ | |
smtp: viper.Get("application.smtp").(string), | |
user: viper.Get("application.smtpuser").(string), | |
pw: viper.Get("application.smtppw").(string), | |
toAddress: viper.Get("application.email").(string), | |
subject: "Subject placeholder", | |
message: "message placeholder", | |
} | |
hasMail := true | |
if rm.smtp == "" || rm.user == "" || rm.pw == "" || rm.toAddress == "" { | |
hasMail = false | |
} | |
db, err := sql.Open("sqlite3", "slmagic.db") | |
CheckPostError(rm, "sql.Open", hasMail, err) | |
// Grab current tax rate | |
corpKey := viper.Get("eve.corpKey") | |
corpvCode := viper.Get("eve.corpvCode") | |
req, err := http.NewRequest( | |
"GET", | |
fmt.Sprintf("https://api.eveonline.com/Corp/CorporationSheet.xml.aspx?keyid=%s&vcode=%s", | |
corpKey, | |
corpvCode), nil) | |
client := &http.Client{} | |
res, err := client.Do(req) | |
defer res.Body.Close() | |
CheckPostError(rm, "res.Body.Close()", hasMail, err) | |
if res.StatusCode != 200 { | |
log.Printf("API returned non 200 response: %d\n", res.StatusCode) | |
return | |
} | |
d := xml.NewDecoder(res.Body) | |
decoded := CorpSheet{} | |
err = d.Decode(&decoded) | |
corpTax = decoded.TaxRate | |
CheckPostError(rm, "decoded.TaxRate", hasMail, err) | |
// Generate stats | |
// Joins and quits | |
joins := 0 | |
quits := 0 | |
rowsN, err := db.Query("select notificationTypeID, eventDate from member_tracking") | |
CheckPostError(rm, "db.Query notificationID", hasMail, err) | |
defer rowsN.Close() | |
for rowsN.Next() { | |
var Date time.Time | |
var ID int | |
rowsN.Scan(&ID, &Date) | |
_, Rmonth, _ := Date.Date() | |
if Rmonth == month { | |
if ID == 128 { | |
joins++ | |
} else { | |
quits++ | |
} | |
} | |
} | |
rowsN.Close() | |
// Generate income report | |
rowsB, err := db.Query("select date, amount FROM journal_tracking") | |
CheckPostError(rm, "db.Query journal_tracking", hasMail, err) | |
defer rowsB.Close() | |
v := 0 | |
for rowsB.Next() { | |
var Date time.Time | |
var Amount string | |
rowsB.Scan(&Date, &Amount) | |
_, Rmonth, _ := Date.Date() | |
if Rmonth == month { | |
value, err := strconv.Atoi(strings.Replace(Amount, ".", "", 1)) | |
CheckPostError(rm, "strconv.Atoi", hasMail, err) | |
v += value | |
} | |
} | |
rowsB.Close() | |
// Read in post.txt | |
tFile := viper.Get("application.textFile").(string) | |
dat, err := ioutil.ReadFile(tFile) | |
CheckPostError(rm, "ioutil.ReadFile", hasMail, err) | |
// Generate to-post file hallelujah | |
ePost := fmt.Sprintf("https://api.trello.com/1/actions/%v/text?key=%v&token=%v&value=%s", | |
viper.Get("trello.commentId"), | |
viper.Get("trello.appkey"), | |
viper.Get("trello.token"), | |
strings.Replace( | |
url.QueryEscape( | |
fmt.Sprintf("%s:\\n\\nJoin: %d\\nLeave: %d\\nTax Rate: %s%%\\nIncome: %s\\nNews: %s", | |
dateStamp, | |
joins, | |
quits, | |
corpTax, | |
fmt.Sprintf("%v.%02v", tt(v/100, ""), v-((v/100)*100)), | |
string(dat))), "%5Cn", "%0A", -1)) | |
// Post to trello card | |
reqA, err := http.NewRequest("PUT", ePost, nil) | |
clientA := &http.Client{} | |
resA, errResp := clientA.Do(reqA) | |
defer resA.Body.Close() | |
readback, errrb := ioutil.ReadAll(resA.Body) | |
if err != nil || errResp != nil || errrb != nil || resA.StatusCode != 200 { | |
if hasMail { | |
rm.subject = "slmagic: Posting failure" | |
rm.message = fmt.Sprintf( | |
"At %v, slmagic failed to update Trello card:\n\rStatusCode: %v\r\nTrello Responded:%s", | |
time.Now().Format(time.RFC822), | |
resA.StatusCode, | |
readback) | |
if err := NewSendMail(rm); err != nil { | |
log.Fatalln(err) | |
} | |
return | |
} else { | |
log.Fatalln(err) | |
} | |
//log.Fatalln("opps") | |
} | |
if err != nil && hasMail { | |
rm.subject = "slmagic: Posting failure" | |
rm.message = time.Now().Format(time.RFC822) | |
if err := NewSendMail(rm); err != nil { | |
log.Fatalln(err) | |
} else { | |
log.Fatalln(err) | |
} | |
} | |
// Email notification to whoever needs emailing | |
if hasMail { | |
rm.subject = "slmagic: Trello card updated" | |
// Assemble approximation of message | |
rm.message = fmt.Sprintf( | |
"%s:\r\n\r\nJoin: %d\r\nLeave: %d\r\nTax Rate:%s%%\r\nIncome: %s\r\nNews: %s", | |
dateStamp, | |
joins, | |
quits, | |
corpTax, | |
fmt.Sprintf("%v.%02v", tt(v/100, ""), v-((v/100)*100)), | |
string(dat)) | |
if err := NewSendMail(rm); err != nil { | |
log.Fatalln("sendMail:", err) | |
} | |
} | |
}, | |
} | |
// Globals | |
var appVer string = "v0.2" | |
var pwd string | |
var appGood bool = true | |
var appKey string = "6fb1834f9b6ee0b21fe8a70b6f3455c7" | |
// Utility functions | |
func NewSendMail(mo *SendMail) error { | |
// Setup headers | |
header := make(map[string]string) | |
header["From"] = "slmagic <[email protected]>" | |
header["To"] = "shitlord <" + mo.toAddress + ">" | |
header["Subject"] = mo.subject | |
header["MIME-Version"] = "1.0" | |
header["Content-Type"] = "text/plain; charset=\"utf-8\"" | |
msg := "" | |
for k, v := range header { | |
msg += fmt.Sprintf("%s: %s\r\n", k, v) | |
} | |
msg += "\r\n" + string(mo.message) | |
auth := smtp.PlainAuth("", mo.user, mo.pw, mo.smtp[:strings.Index(mo.smtp, ":")]) | |
to := []string{mo.toAddress} | |
if err := smtp.SendMail(mo.smtp, auth, mo.user, to, []byte(msg)); err != nil { | |
return err | |
} | |
return nil | |
} | |
func sendMail(ssmtp, smtpuser, smtppw, uemail, subject string, res []byte) error { | |
// Setup headers | |
header := make(map[string]string) | |
header["From"] = "slmagic <[email protected]>" | |
header["To"] = "shitlord <" + uemail + ">" | |
//header["Subject"] = encodeRFC2047(subject) | |
header["Subject"] = "Test 4" | |
header["MINE-Version"] = "1.0" | |
header["Content-Type"] = "text/plain; charset=\"utf-8\"" | |
msg := "" | |
for k, v := range header { | |
msg += fmt.Sprintf("%s: %s\r\n", k, v) | |
} | |
//msg += "\r\n" + base64.StdEncoding.EncodeToString([]byte(res)) | |
msg += "\r\n" + string(res) | |
auth := smtp.PlainAuth("", smtpuser, smtppw, ssmtp[:strings.Index(ssmtp, ":")]) | |
to := []string{uemail} | |
fmt.Printf("PlainAuth(\"\", \"%s\", \"%s\", \"%s\")\n", smtpuser, smtppw, ssmtp) | |
if err := smtp.SendMail(ssmtp, auth, smtpuser, to, []byte(msg)); err != nil { | |
return err | |
} | |
return nil | |
} | |
func CheckPostError(rm *SendMail, problem string, hasMail bool, err error) { | |
if err == nil { | |
return | |
} | |
if !hasMail { | |
log.Fatalln(problem, err) | |
} | |
rm.subject = "slmagic: Posting failure" | |
rm.message = fmt.Sprintf( | |
"At %v, slmagic failed to update Trello card:\r\n\r\n%s: %s", | |
time.Now().Format(time.RFC822), | |
problem, | |
err) | |
if err := NewSendMail(rm); err != nil { | |
log.Fatalf("CheckPostError: %v\n%v\n", problem, err) | |
} else { | |
log.Fatalln(err) | |
} | |
} | |
func checkErr(where string, err error) { | |
if err != nil { | |
log.Fatalln(where, err) | |
} | |
} | |
func checkConfVal(what string, val string) { | |
if val == "" { | |
log.Fatalln("Check your conf file. Empty val", what) | |
} | |
} | |
func createConfFile() { | |
// TODO: refactor the fuck out of this megamethod | |
// TODO: make each textblock and prompt into a structure | |
appName := "slmagic" | |
getTokenURL := fmt.Sprintf("https://trello.com/1/authorize?key=%s&name=%s&expiration=never&response_type=token&scope=read,write,account", | |
appKey, | |
appName) | |
fmt.Println("slmagic will write a new conf file for you now. If you somehow manage") | |
fmt.Println("to fuck this up, stop the app, delete your conf file \"slmagic.toml\",") | |
fmt.Println("and start over.\n") | |
fmt.Printf("Log in to your trello account and then go to this URL and grab your token.\n%v\n", | |
getTokenURL) | |
fmt.Print("Your token: ") | |
var token string | |
if _, err := fmt.Scan(&token); err != nil { | |
log.Fatalln(err) | |
} | |
var card string | |
fmt.Println("\nThis next part is a bit complicated. Trello boards have Trello lists,") | |
fmt.Println("which have Trello cards. If you go to your corporation reporting card,") | |
fmt.Println("and click on it, you'll see the 'back of the card'. I need the card ID") | |
fmt.Println("from the URL as you're viewing the back of the corporation reporting") | |
fmt.Println("card. The URL is in the form:\n") | |
fmt.Println("https://trello.com/c/<card ID>/#-<card title>\n") | |
fmt.Println("From that URL, I need the super-secret card ID.") | |
fmt.Print("8 digit card ID: ") | |
if _, err := fmt.Scan(&card); err != nil { | |
log.Fatalln(err) | |
} | |
var commentIdNum int | |
userId := getUserID(token) | |
fmt.Println("\nHere is a list of comments on that card that you're assigned to.") | |
comments := getTrelloComments(token, card, userId) | |
for i, v := range comments { | |
fmt.Printf("%d: %s\nLast Edited: %s\n=========\n%s\n\n", | |
i+1, v.Id, v.Data.DateLastEdited, v.Data.Text) | |
} | |
fmt.Println("Please enter the number of the comment you wish to edit monthly.") | |
fmt.Print("Card #: ") | |
if _, err := fmt.Scan(&commentIdNum); err != nil { | |
log.Fatalln(err) | |
} | |
if commentIdNum < 1 || commentIdNum > len(comments) { | |
fmt.Println("Oops, you answered", commentIdNum) | |
log.Fatalln("Try again...") | |
} | |
commentId := comments[commentIdNum-1].Id | |
var CEOCorpKey string | |
fmt.Println("\nSlmagic requires your corp's CEO's API credentials. This is because CCP") | |
fmt.Println("put the endpoint for people joining and quitting the corp in the CEO's") | |
fmt.Println("API. Specifically, we must use join and part notifications that the CEO") | |
fmt.Println("receives.") | |
fmt.Print("CEO's key: ") | |
if _, err := fmt.Scan(&CEOCorpKey); err != nil { | |
log.Fatalln(err) | |
} | |
var eveceovCode string | |
fmt.Print("CEO's vCode: ") | |
if _, err := fmt.Scan(&eveceovCode); err != nil { | |
log.Fatalln(err) | |
} | |
var corpKey string | |
fmt.Println("\nSlmagic needs your corp's API credentials for ratting income.") | |
fmt.Print("corp's key: ") | |
if _, err := fmt.Scan(&corpKey); err != nil { | |
log.Fatalln(err) | |
} | |
var corpvCode string | |
fmt.Print("corp's vCode: ") | |
if _, err := fmt.Scan(&corpvCode); err != nil { | |
log.Fatalln(err) | |
} | |
var corpId string | |
fmt.Println("Find your corp ID at: http://evemaps.dotlan.net/corp/<corp_name>") | |
fmt.Print("Corp ID: ") | |
if _, err := fmt.Scan(&corpId); err != nil { | |
log.Fatalln(err) | |
} | |
var email string | |
fmt.Println("\nType in your email address now") | |
fmt.Print("email: ") | |
if _, err := fmt.Scan(&email); err != nil { | |
log.Fatalln(err) | |
} | |
var smtp string | |
fmt.Println("\nThe email deal is new to the app and is experimental. Feel free to just") | |
fmt.Println("leave these fields blank, you can always return later and edit the conf") | |
fmt.Println("file") | |
fmt.Print("smtp server address (i.e. smtp.mail.com:587): ") | |
if _, err := fmt.Scan(&smtp); err != nil { | |
log.Fatalln(err) | |
} | |
var smtpuser string | |
fmt.Print("smtp user: ") | |
if _, err := fmt.Scan(&smtpuser); err != nil { | |
log.Fatalln(err) | |
} | |
var smtppw string | |
fmt.Print("smtp password: ") | |
if _, err := fmt.Scan(&smtppw); err != nil { | |
log.Fatalln(err) | |
} | |
replacer := strings.NewReplacer( | |
"change me token", token, | |
"change me id", userId, | |
"change me commentId", commentId, | |
"change me CEOCorpKey", CEOCorpKey, | |
"change me eveceovCode", eveceovCode, | |
"change me corpKey", corpKey, | |
"change me corpvCode", corpvCode, | |
"change me email", email, | |
"change me smtp", smtp, | |
"change me smtpuser", smtpuser, | |
"change me smtppw", smtppw, | |
"change me corpId", corpId) | |
var perms os.FileMode = 0600 | |
err := ioutil.WriteFile("slmagic.toml", []byte(replacer.Replace(sample)), perms) | |
checkErr("ioutil.WriteFile:", err) | |
fmt.Println("\n\nIMPORTANT: Okay, the conf file is written, and now, for all the tacos,") | |
fmt.Println("first, create a post.txt file in the directory with slmagic and put your") | |
fmt.Println("corp new in that file. Set up your cron jobs, and make sure you first") | |
fmt.Println("'cd /home/user/<slmagic working directory>' to change your working directory") | |
fmt.Println("so the app will work right.") | |
} | |
func getUserID(token string) string { | |
UserIdURL := fmt.Sprintf("https://trello.com/1/members/me?key=%s&token=%s", | |
appKey, token) | |
var user TrelloUser | |
res, err := http.Get(UserIdURL) | |
body, err := ioutil.ReadAll(res.Body) | |
checkErr("ioutil.ReadAll():", err) | |
json.Unmarshal(body, &user) | |
return user.Id | |
} | |
func getTrelloComments(token string, card string, userId string) TrelloComments { | |
// TODO: test this function with someone who actually has a m | |
CommentsURL := fmt.Sprintf("https://trello.com/1/cards/%s/actions?key=%s&token=%s", | |
card, appKey, token) | |
var comments TrelloComments | |
var MyComments TrelloComments | |
res, err := http.Get(CommentsURL) | |
body, err := ioutil.ReadAll(res.Body) | |
checkErr("getTrelloComments():", err) | |
json.Unmarshal(body, &comments) | |
for _, k := range comments { | |
if k.IdMemberCreator == userId { | |
MyComments = append(MyComments, k) | |
} | |
} | |
return MyComments | |
} | |
func formatTime(t time.Time) string { | |
return t.Format("2006-01-02 15:04:05") | |
} | |
func initDB() { | |
// Creates the DB if it doesn't exist | |
db, err := sql.Open("sqlite3", "./slmagic.db") | |
if err != nil { | |
log.Fatalf("Error creating database: %s\n", err) | |
} | |
defer db.Close() | |
sql := `CREATE TABLE IF NOT EXISTS member_tracking ( | |
notificationID integer not null primary key, | |
charID integer not null, | |
charName text not null, | |
notificationTypeID integer not null, | |
eventDate timestamp not null)` | |
// Setup the table if it doesn't already exist | |
_, err = db.Exec(sql) | |
if err != nil { | |
log.Fatalf("Error creating table: %s\n", err) | |
} | |
sql2 := `CREATE TABLE IF NOT EXISTS journal_tracking ( | |
refID integer not null primary key, | |
refTypeID integer not null, | |
date timestamp not null, | |
amount text not null)` | |
// Setup the table if it doesn't already exist | |
_, err = db.Exec(sql2) | |
if err != nil { | |
log.Fatalf("Error creating table: %s\n", err) | |
} | |
} | |
func storeEvents(data *Records) { | |
// Open the connection to the db file | |
db, err := sql.Open("sqlite3", "./slmagic.db") | |
if err != nil { | |
log.Fatalf("Error opening database: %s\n", err) | |
} | |
defer db.Close() | |
// Loop through data and store it | |
for _, v := range data.Events { | |
stmt, err := db.Prepare( | |
"INSERT OR REPLACE INTO member_tracking (notificationID, charID, charName, notificationTypeID, eventDate) VALUES (?,?,?,?,?);") | |
if err != nil { | |
log.Fatalf("Error preparing SQL SELECT: %s\n", err) | |
} | |
_, err = stmt.Exec(v.NotificationID, v.CharID, v.SenderName, v.NotificationTypeID, v.Date) | |
if err != nil { | |
log.Fatalf("Error inserting data: %s\n", err) | |
} | |
} | |
} | |
func storeEntries(data *Wrecords) { | |
// Open the connection to the db file | |
db, err := sql.Open("sqlite3", "./slmagic.db") | |
if err != nil { | |
log.Fatalf("Error opening database: %s\n", err) | |
} | |
defer db.Close() | |
// Loop through the data and store it | |
for _, v := range data.Jentries { | |
stmt, err := db.Prepare( | |
"INSERT OR REPLACE INTO journal_tracking (refID, refTypeID, date, amount) VALUES (?,?,?,?);") | |
if err != nil { | |
log.Fatalf("Error preparing SQL SELECT: %s\n", err) | |
} | |
_, err = stmt.Exec(v.RefID, v.RefTypeID, v.Date, v.Amount) | |
if err != nil { | |
log.Fatalf("Error inserting data: %s", err) | |
} | |
} | |
} | |
func tt(x int, s string) string { | |
if x < 1000 { | |
return fmt.Sprintf("%v%v", x, s) | |
} | |
return tt(x/1000, fmt.Sprintf(",%03v", x%1000)+s) | |
} | |
// App data structures | |
type GSFCorps struct { | |
corpname, corpId string | |
} | |
type SendMail struct { | |
smtp, user, pw, toAddress, subject, message string | |
result []byte | |
} | |
// XML data structures | |
type NotificationHeaders struct { | |
Notifications []Notifications `xml:"result>rowset>row"` | |
} | |
type Notifications struct { | |
NotificationID int64 `xml:"notificationID,attr"` | |
TypeID int `xml:"typeID,attr"` | |
SenderID int64 `xml:"senderID,attr"` | |
SenderName string `xml:"senderName,attr"` | |
SentDate string `xml:"sentDate,attr"` | |
Read int64 `xml:"read,attr"` | |
} | |
type EntriesHeaders struct { | |
Entries []Entries `xml:"result>rowset>row"` | |
} | |
type Entries struct { | |
Date string `xml:"date,attr"` | |
RefID int64 `xml:"refID,attr"` | |
RefTypeID int `xml:"refTypeID,attr"` | |
Amount string `xml:"amount,attr"` | |
} | |
type CorpSheet struct { | |
TaxRate string `xml:"result>taxRate"` | |
} | |
// Database data structures | |
type Records struct { | |
Events []InsertData | |
} | |
type InsertData struct { | |
CharID int64 | |
Date time.Time | |
NotificationID int64 | |
NotificationTypeID int | |
SenderName string | |
} | |
type Wrecords struct { | |
Jentries []EData | |
} | |
type EData struct { | |
RefID int64 | |
RefTypeID int | |
Date time.Time | |
Amount string | |
} | |
// Trello data structures | |
type TrelloUser struct { | |
Username string `json:"username"` | |
Email string `json:"email"` | |
Id string `json:"id"` | |
} | |
type TrelloComments []TrelloComment | |
type TrelloComment struct { | |
IdMemberCreator string `json:"idMemberCreator"` | |
// TODO: test if I can make this field anonymous | |
Data TrelloCommentData `json:"data"` | |
Id string `json:"id"` | |
} | |
type TrelloCommentData struct { | |
// TODO: Change this field to a true time type | |
DateLastEdited string `json:"dateLastEdited"` | |
Text string `json:"Text"` | |
} | |
// Sample conf file | |
var sample string = `#toml configuration file | |
[application] | |
textFile = "post.txt" | |
email = "change me email" | |
smtp = "change me smtp" | |
smtpuser = "change me smtpuser" | |
smtppw = "change me smtppw" | |
[trello] | |
trelloId = "change me id" | |
appkey = "6fb1834f9b6ee0b21fe8a70b6f3455c7" | |
token = "change me token" | |
commentId = "change me commentId" | |
[eve] | |
ceoKey = "change me CEOCorpKey" | |
ceovCode = "change me eveceovCode" | |
corpKey = "change me corpKey" | |
corpvCode = "change me corpvCode" | |
corpId = "change me corpId" | |
` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment