'
' Now this was fun to program! Created in only 2 and a half hours, fast! As it should be.
'
' Update your Twitter account using this nice tool. Remembers username and password.
'
' Peter van Eerten, March 2010 - GPL. Needs HUG 0.21 or higher.
'
' Version 1.0: initial release
' Version 1.1: show remaining characters instead of length of message - July 2010.
'---------------------------------------------------------------------------------------------------------------------------

CONST BASE64$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

SETENVIRON "LANG", "C"
INCLUDE "hug.bac"

'---------------------------------------------------------------------------------------------------------------------------
' Function to setup basic HTTP encoding - ripped from myself :-)

FUNCTION Base64_Encode$(STRING data$)

    LOCAL enc$
    LOCAL byte1, byte2, byte3, base1, base2, base3, base4

    WHILE LEN(data$) > 0 DO

        ' Find ASCII values
        IF LEN(data$) EQ 1 THEN
            byte1 = ASC(MID$(data$, 1, 1))
            byte2 = 0
            byte3 = 0
        ELIF LEN(data$) EQ 2 THEN
            byte1 = ASC(MID$(data$, 1, 1))
            byte2 = ASC(MID$(data$, 2, 1))
            byte3 = 0
        ELSE
            byte1 = ASC(MID$(data$, 1, 1))
            byte2 = ASC(MID$(data$, 2, 1))
            byte3 = ASC(MID$(data$, 3, 1))
        ENDIF

        ' Create Base64 values
        base1 = byte1 / 4
        base2 = (byte1 & 3) * 16 + (byte2 & 240) / 16
        base3 = (byte2 & 15) * 4 + (byte3 & 192) / 64
        base4 = byte3 & 63

        ' Find Base64 characters
        IF LEN(data$) EQ 1 THEN
            enc$ = CONCAT$(enc$, MID$(BASE64$, base1 + 1, 1))
            enc$ = CONCAT$(enc$, MID$(BASE64$, base2 + 1, 1))
            enc$ = CONCAT$(enc$, "==")
            data$ = ""
        ELIF LEN(data$) EQ 2 THEN
            enc$ = CONCAT$(enc$, MID$(BASE64$, base1 + 1, 1))
            enc$ = CONCAT$(enc$, MID$(BASE64$, base2 + 1, 1))
            enc$ = CONCAT$(enc$, MID$(BASE64$, base3 + 1, 1))
            enc$ = CONCAT$(enc$, "=")
            data$ = ""
        ELSE
            enc$ = CONCAT$(enc$, MID$(BASE64$, base1 + 1, 1))
            enc$ = CONCAT$(enc$, MID$(BASE64$, base2 + 1, 1))
            enc$ = CONCAT$(enc$, MID$(BASE64$, base3 + 1, 1))
            enc$ = CONCAT$(enc$, MID$(BASE64$, base4 + 1, 1))
            data$ = RIGHT$(data$, LEN(data$) - 3)
        ENDIF
    WEND

    RETURN enc$

END FUNCTION

'---------------------------------------------------------------------------------------------------------------------------
' Make sure not to exceed 140 characters

SUB Check_Length

    LOCAL content$

    content$ = GRAB$(update_txt)

    IF LEN(content$) > 140 THEN
        content$ = MID$(content$, 1, 140)
        TEXT(update_txt, "")
        TEXT(update_txt, content$)
    END IF

    TEXT(amount_label, CONCAT$("Remaining characters: ", STR$(140 - LEN(content$))))

END SUB

'---------------------------------------------------------------------------------------------------------------------------
' Should password field be visible

SUB Toggle_Visible

    SET(pass_entry, GET(vis_chk))

END SUB

'---------------------------------------------------------------------------------------------------------------------------
' Update the Twitter account

SUB Upload_Content

    LOCAL text$, http$, len$, data$, response$

    text$ = CONCAT$("status=", GRAB$(update_txt))

    len$ = STR$(LEN(text$))

    ' Create the HTTP request
    http$ = CONCAT$("POST /1/statuses/update.xml HTTP/1.1\r\nAuthorization: Basic ", Base64_Encode$(CONCAT$(GRAB$(name_entry), \
        ":", GRAB$(pass_entry))), "\r\nUser-Agent: BaCon Twitter Client\r\nHost: api.twitter.com\r\nAccept: */*\r\nContent-Length: ", \
        len$, "\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\n", text$)

    OPEN "api.twitter.com:80" FOR NETWORK AS twitter

    SEND http$ TO twitter

    REPEAT
        RECEIVE data$ FROM twitter
        response$ = CONCAT$(response$, data$)
        SYNC
    UNTIL ISFALSE(WAIT(twitter, 500)) OR LEN(data$) IS 0

    CLOSE NETWORK twitter

    IF INSTR(response$, "Status: 200 OK") THEN
        TEXT(update_txt, "")
        SHOW(infodlg)
    ELSE
        SHOW(errdlg)
    END IF

END SUB

'---------------------------------------------------------------------------------------------------------------------------
' Closes info dialog

SUB Close_Dialog(NUMBER widget)

    HIDE(widget)
    FOCUS(update_txt)

END SUB

'---------------------------------------------------------------------------------------------------------------------------
' Get account info

SUB Get_Data

    LOCAL config TYPE FILE*
    LOCAL text$

    IF FILEEXISTS(CONCAT$(GETENVIRON$("HOME"), "/.twitter.txt")) THEN

        OPEN CONCAT$(GETENVIRON$("HOME"), "/.twitter.txt") FOR READING AS config
            READLN text$ FROM config
            TEXT(name_entry, text$)
            READLN text$ FROM config
            TEXT(pass_entry, text$)
        CLOSE FILE config

    END IF

END SUB

'---------------------------------------------------------------------------------------------------------------------------
' Save account info

SUB Program_Quit

    LOCAL config TYPE FILE*

    OPEN CONCAT$(GETENVIRON$("HOME"), "/.twitter.txt") FOR WRITING AS config
        WRITELN GRAB$(name_entry) TO config
        WRITELN GRAB$(pass_entry) TO config
    CLOSE FILE config

    QUIT

END SUB

'---------------------------------------------------------------------------------------------------------------------------
' Main program

' Create main window
win = WINDOW("Twitter Updater", 400, 300)

' Account field
frame1 = FRAME(390, 110)
ATTACH(win, frame1, 5, 5)
TEXT(frame1, " Account ")
name_label = MARK("Username: ", 70, 30)
ATTACH(win, name_label, 15, 30)
name_entry = ENTRY("", 290, 30)
ATTACH(win, name_entry, 95, 30)
pass_label = MARK("Password: ", 70, 30)
ATTACH(win, pass_label, 15, 70)
pass_entry = PASSWORD(200, 30)
ATTACH(win, pass_entry, 95, 70)
vis_chk = CHECK("Visible", 70, 30)
ATTACH(win, vis_chk, 305, 70)

' See if configfile exists
Get_Data

' Update field
frame2 = FRAME(390, 120)
ATTACH(win, frame2, 5, 125)
TEXT(frame2, " Update ")
update_txt = EDIT(370, 80)
ATTACH(win, update_txt, 15, 150)
amount_label = MARK("Remaining characters: 140", 180, 30)
ATTACH(win, amount_label, 110, 255)

' Buttons
send_btn = STOCK("gtk-save", 85, 35)
ATTACH(win, send_btn, 5, 255)
quit_btn = STOCK("gtk-quit", 85, 35)
ATTACH(win, quit_btn, 310, 255)

' Dialogs
errdlg = MSGDIALOG("The account could not be updated!", 250, 120, 3, 2)
infodlg = MSGDIALOG("Twitter updated!", 270, 120, 0, 2)

' Callbacks
CALLBACK(quit_btn, Program_Quit)
CALLBACK(update_txt, Check_Length)
CALLBACK(vis_chk, Toggle_Visible)
CALLBACK(send_btn, Upload_Content)
CALLBACK(errdlg, Close_Dialog)
CALLBACK(infodlg, Close_Dialog)

IF LEN(GRAB$(name_entry)) > 0 THEN FOCUS(update_txt)
ELSE FOCUS(name_entry)

' Endless GTK loop
DISPLAY