From a75477f73d8a8946bbbcf23577cb7a6829ff42ed Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Tue, 30 Aug 2016 16:04:35 +1000 Subject: [PATCH] Initial commit --- Mac Classic Twitter.img | Bin 0 -> 410624 bytes memo.go | 238 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 Mac Classic Twitter.img create mode 100644 memo.go diff --git a/Mac Classic Twitter.img b/Mac Classic Twitter.img new file mode 100644 index 0000000000000000000000000000000000000000..bddcc81d8c82e255094d7bade1902cb010dc5e7f GIT binary patch literal 410624 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 zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ z1W14cNPq-LfCN4R0^yRU-&lI>({FrnJ_0k8N9B34lrr(fr<50`G3NKio?lyM6*t#* vbTrjjHJ3MacE;L25H+0paZwKZ8P4gGeQG}`!*20C1^@U*0wnM!Ch&g&6cLp& literal 0 HcmV?d00001 diff --git a/memo.go b/memo.go new file mode 100644 index 0000000..284eb7e --- /dev/null +++ b/memo.go @@ -0,0 +1,238 @@ +package main + +/* +#cgo LDFLAGS: -L${SRCDIR} -latalk +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const charset_t kChMac = CH_MAC; +const charset_t kChUnix = CH_UNIX; +const size_t kSizeMax = SIZE_MAX; +const u_int8_t kATAddrAnyPort = ATADDR_ANYPORT; + +// C can't have Go pointers to Go pointers... + +struct sockaddr_at * make_sockaddr_at() { + return (struct sockaddr_at *)malloc(sizeof(struct sockaddr_at)); +} + +struct iovec * make_iovec() { + return (struct iovec *)malloc(sizeof(struct iovec)); +} + +// The ATP block contains a union which is painful to use in straight Go. +// Hence we need simple helpers. + +void set_rreqdata(struct atp_block *atpb, void *buf, int len) { + atpb->atp_rreqdata = buf; + atpb->atp_rreqdlen = len; +} + +int get_rreqdlen(struct atp_block *atpb) { + return atpb->atp_rreqdlen; +} + +void set_sresdata(struct atp_block *atpb, struct iovec * iov, int iovcnt) { + atpb->atp_sresiov = iov; + atpb->atp_sresiovcnt = iovcnt; +} +*/ +import "C" + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "os" + "os/signal" + "strings" + "unsafe" + + "github.com/ChimeraCoder/anaconda" +) + +var ( + api *anaconda.TwitterApi + + nbpName = flag.String("nbp_name", "GophersInYourAppletalk:Twitter", "Name to register for NBP") + standardResponse = flag.String("standard_response", "Gophers in your HyperCard!", "Standard response to ATP requests") + enableTweeting = flag.Bool("enable_tweeting", false, "Turns on the Twitter functionality; input will be tweeted") + twitterParamsFile = flag.String("twitter_params", "twitter_params.json", "Twitter parameters in JSON file") + twitterTokenFile = flag.String("twitter_token", "twitter_token.json", "Twitter user token in JSON file") +) + +type oauthToken struct { + Token string `json:"oauth_token"` + Secret string `json:"oauth_token_secret"` +} + +func doOAuth() (*anaconda.TwitterApi, error) { + u, c, err := anaconda.AuthorizationURL("") + if err != nil { + return nil, err + } + fmt.Printf("Go to %s in your Twitter browser session and then enter the PIN: ", u) + var p string + if _, err := fmt.Scanf("%s", &p); err != nil { + return nil, err + } + _, v, err := anaconda.GetCredentials(c, p) + if err != nil { + return nil, err + } + t := oauthToken{ + Token: v.Get("oauth_token"), + Secret: v.Get("oauth_token_secret"), + } + if err := saveToken(*twitterTokenFile, &t); err != nil { + return nil, err + } + return anaconda.NewTwitterApi(t.Token, t.Secret), nil +} + +func saveToken(path string, t *oauthToken) error { + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + return json.NewEncoder(f).Encode(t) +} + +func twitterAPI() (*anaconda.TwitterApi, error) { + var t oauthToken + f, err := os.Open(*twitterTokenFile) + if err != nil { + log.Printf("Cannot open Twitter token file: %v", err) + return doOAuth() + } + defer f.Close() + if err := json.NewDecoder(f).Decode(&t); err != nil { + log.Printf("Cannot decode Twitter token file: %v", err) + f.Close() + return doOAuth() + } + return anaconda.NewTwitterApi(t.Token, t.Secret), nil +} + +func main() { + flag.Parse() + if *enableTweeting { + log.Print("Loading Twitter consumer key & secret") + f, err := os.Open(*twitterParamsFile) + if err != nil { + log.Fatalf("Cannot load Twitter parameters: %v", err) + } + defer f.Close() + var p struct { + ConsumerKey string `json:"consumer_key"` + ConsumerSecret string `json:"consumer_secret"` + } + if err := json.NewDecoder(f).Decode(&p); err != nil { + log.Fatalf("Cannot load Twitter parameters: %v", err) + } + anaconda.SetConsumerKey(p.ConsumerKey) + anaconda.SetConsumerSecret(p.ConsumerSecret) + + log.Printf("Loading user OAuth token & secret") + a, err := twitterAPI() + if err != nil { + log.Fatalf("Cannot obtain OAuth token: %v", err) + } + api = a + } + + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + + // Make sure nbpName is a useful looking name, I think? + cn := C.CString(*nbpName) + defer C.free(unsafe.Pointer(cn)) + var convname *C.char + if C.convert_string_allocate(C.kChUnix, C.kChMac, unsafe.Pointer(cn), C.kSizeMax, &convname) == C.kSizeMax { + convname = cn + } + + var o, t, z *C.char + if s := C.nbp_name(convname, &o, &t, &z); s != 0 { + log.Fatalf("nbp_name: the name was wrong: %v", s) + } + + // Open ATP on an AppleTalk (DDP) port. + sat := C.make_sockaddr_at() + defer C.free(unsafe.Pointer(sat)) + + atAddr := (*C.struct_at_addr)(unsafe.Pointer(&sat.sat_addr)) + atp, err := C.atp_open(C.kATAddrAnyPort, atAddr) + if atp == nil || err != nil { + log.Fatalf("atp_open: %v", err) + } + defer C.atp_close(atp) + + // Register the NBP name. + addr := &atp.atph_saddr + n := fmt.Sprintf("%s:%s@%s", C.GoString(o), C.GoString(t), C.GoString(z)) + log.Printf("Registering NBP name %s", n) + if r := C.nbp_rgstr(addr, o, t, z); r < 0 { + log.Fatalf("Couldn't register %s\n: %d", n, r) + } + defer C.nbp_unrgstr(o, t, z, atAddr) + + // Serving loop. + go func() { + for { + if err := handleOne(sat, atp); err != nil { + log.Printf("Error handling request: %v", err) + } + } + }() + + <-interrupt + log.Println("SIGINT received, quitting...") + C.nbp_unrgstr(o, t, z, atAddr) +} + +func handleOne(sat *C.struct_sockaddr_at, atp *C.struct_atp_handle) error { + log.Println("Awaiting next request") + const bufSize = 4624 + buf := C.malloc(bufSize) + defer C.free(buf) + atpb := &C.struct_atp_block{ + atp_saddr: sat, + } + C.set_rreqdata(atpb, buf, bufSize) + if s := C.atp_rreq(atp, atpb); s < 0 { + return fmt.Errorf("atp_rreq: %v", s) + } + bt := C.GoBytes(buf, C.get_rreqdlen(atpb)) + log.Printf("Got request: %s", bt) + + if api != nil { + twt := strings.TrimPrefix(string(bt), "REQS") + if _, err := api.PostTweet(twt, nil); err != nil { + log.Printf("Error posting tweet: %v", err) + } + } + + cmsg := fmt.Sprintf("RESP%s", *standardResponse) + resp := unsafe.Pointer(C.CString(cmsg)) + defer C.free(resp) + iov := C.make_iovec() + defer C.free(unsafe.Pointer(iov)) + iov.iov_base = resp + iov.iov_len = C.size_t(len(cmsg) + 1) + C.set_sresdata(atpb, iov, 1) + if s := C.atp_sresp(atp, atpb); s < 0 { + return fmt.Errorf("atp_sresp: %v", s) + } + log.Printf("Responded with: %s", *standardResponse) + return nil +}