You are hereForums / Coding / FreeBASIC / Snippets / Last.fm scrobbler class

Last.fm scrobbler class


1 post / 0 new
Last post
segin's picture
Offline
Joined: 08/08/2010
Last.fm scrobbler class

lastfm.bas:
[freebasic]/'
$Id $

This file is part of PsyMP3.

PsyMP3 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

PsyMP3 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PsyMP3; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'/

#Define PSYMP3_CORE_GLOBALS
#Include "psymp3.bi"

Constructor LastFM() Export
this.readConfig()
this.loadScrobbles()
printf(!"LastFM::LastFM(): username: %s, password: %s.\n", this.c_username, String(Len(this.c_password), "*"))
this.m_session = this.getSessionKey()
'Lastfm_sessionkey = this.m_session
printf(!"LastFM::LastFM(): Last.fm login successful!\n")
this.c_xmlpath = CurDir()
printf(!"LastFM::LastFM(): last.fm directory: %s\n", this.c_xmlpath)
End Constructor

Destructor LastFM() Export
printf(!"LastFM::~LastFM(): Dumping scrobbles\n")
this.dumpScrobbles()
End Destructor

Sub LastFM.readConfig Alias "readConfig" () Export
Dim fd As Integer = FreeFile()
Open "lastfm_config.txt" For Input As #fd
Line Input #fd, this.c_username
Line Input #fd, this.c_password
Close #fd
End Sub

Function LastFM.getSessionKey Alias "getSessionKey" () As String Export
Dim As Integer curtime = time_(NULL)
Dim As String authkey = MD5str(MD5str(this.c_password) & curtime)
Dim As ZString * 10000 response
Dim As String response_data, httpdata

If this.c_username = "" Or this.c_password = "" Then Return ""

printf(!"LastFM::getSessionKey(): Getting session key.\n")

httpdata = "GET /?hs=true&p=1.2.1&c=psy&v=" & PSYMP3_VERSION & "&u=" & this.c_username & "&t=" & curtime & "&a=" & authkey & " HTTP/1.1" & Chr(10) & "Host: post.audioscrobbler.com" & Chr(13) & Chr(10) & "User-Agent: PsyMP3/" & PSYMP3_VERSION & Chr(13) & Chr(10) & Chr (13) & Chr(10)
hStart()
Dim s As SOCKET, addr As Integer
s = hOpen()
addr = hResolve("post.audioscrobbler.com")
hConnect(s, addr, 80)
hSend(s, strptr(httpdata), len(httpdata))
hReceive(s, strptr(response), 10000)
hClose(s)

response_data = Mid(response, InStr(response, !"\r\n\r\n") + 4)

If left(response_data,3) = !"OK\n" Then
printf(!"LastFM::getSessionKey(): Session key retreived:%s\n", Mid(response_data, 4, 32))
Function = Mid(response_data, 4, 32)
response_data = Mid(response_data, InStr(response_data, !"\n") + 1)
response_data = Mid(response_data, InStr(response_data, !"\n") + 1)
Dim As String surl(2)
Dim As String buf, host, port, path
For curtime = 1 To 2
surl(curtime) = Left(response_data, InStr(response_data, !"\n") - 1)
host = Mid(response_data, InStr(response_data, !"http://") + 7)
buf = host
host = Left(host, InStr(host, !":") - 1)
port = Mid(buf, InStr(buf, !":") + 1)
buf = port
port = Left(port, InStr(buf, "/") - 1)
path = Mid(buf, InStr(buf, "/"))
path = Left(path, InStr(path, !"\n") - 1)
this.c_apihost(curtime) = host
this.c_apiport(curtime) = Val(port)
this.c_apipath(curtime) = path
response_data = Mid(response_data, InStr(response_data, !"\n") + 1)
Next curtime
Else
printf(!"LastFM::getSessionKey(): Failed to authenticate (bad password?)\n")
Function = ""
End If
End Function

Function LastFM.setNowPlaying Alias "setNowPlaying" () As SOCKET Export
Dim As Integer curtime = time_(NULL)
Dim As String authkey = MD5str(MD5str(this.c_password) & curtime)
Dim As ZString * 10000 response
Dim As String response_data, httpdata, postdata

Dim As Integer length = Int(FSOUND_Stream_GetLengthMs(stream)/1000)

If this.m_session = "" Then this.m_session = this.getSessionKey()

Dim As Boolean submitted = FALSE

While submitted = FALSE

httpdata = "POST " & this.c_apipath(1) & !" HTTP/1.1\n" & _
"Host: " & this.c_apihost(1) & !"\n" & _
"User-Agent: PsyMP3/" & PSYMP3_VERSION & !"\n"

postdata = "s=" & this.m_session & "&" & _
"a=" & percent_encodeW(mp3artistW) & "&" & _
"t=" & percent_encodeW(mp3nameW) & "&" & _
"b=" & percent_encodeW(mp3albumW) & "&" & _
"l=" & length & "&" & _
"n=&m="

httpdata &= !"Content-Length: " & Len(postdata) & !"\n" & _
!"Content-Type: application/x-www-form-urlencoded\n\n" & _
postdata

Dim s As SOCKET = this.submitData(httpdata, 1)
hReceive(s, strptr(response), 10000)
response_data = Mid(response, InStr(response, !"\r\n\r\n") + 4)
Dim status As String = Left(response_data, InStr(response_data, !"\n") - 1)

Select Case status
Case "OK"
printf !"LastFM::setNowPlaying(): Server response OK. Track sucessfully sent as nowplaying!\n"
this.submitSavedScrobbles()
submitted = TRUE
Case "BADSESSION"
this.m_session = this.getSessionKey()
Case Else
printf !"LastFM::setNowPlaying(): Sending track as nowplaying failed\n"
submitted = TRUE
End Select
Wend
End Function

Function LastFM.scrobbleTrack Alias "scrobbleTrack" () As Integer Export
Dim As Integer curtime = time_(NULL)
Dim As Integer length = Int( FSOUND_Stream_GetLengthMs(stream) /1000)

Dim As String artist, Title, album
Artist = percent_encodeW(mp3artistW)
Title = percent_encodeW(mp3nameW)
Album = percent_encodeW(mp3albumW)

Dim As Integer qual

' If this fails, just forget it and store the scrobble.
If this.m_session = "" Then this.m_session = this.getSessionKey()

' IIf keeps giving me "Invalid data types" so go with a more verbose workaround
If (songlength / 4000) > 240 Then
qual = 240
Else
qual = songlength / 4000
EndIf

If songlength < 30000 Then Return 0

If (curtime - songstart) < qual Then Return 0

curtime = this.submitScrobble(Artist, Title, Album, Length, curtime)
If curtime = TRUE Then
this.submitSavedScrobbles()
EndIf
End Function

Function LastFM.submitData Alias "submitData" (sData As String, host As Integer) As SOCKET
Dim rhost As String
Dim rport As Integer
If this.c_apihost(host) <> "" Then rhost = this.c_apihost(host) Else rhost = "post.audioscrobbler.com"
If this.c_apiport(host) <> 0 Then rport = this.c_apiport(host) Else rport = 80
Dim s As SOCKET, addr As Integer
s = hOpen()
printf !"LastFM::submitData(): host %s port %d\n", rhost, rport
addr = hResolve(rhost)
hConnect(s, addr, rport)
hSend(s, strptr(sData), len(sData))
Return(s)
End Function

Function LastFM.submitScrobble Alias "submitScrobble" (artist As String, Title As String, album As String, length As Integer, curtime As UInteger) As Integer Export
Dim As Boolean submitted = FALSE
Dim As ZString * 10000 response
Dim As String response_data, httpdata, postdata

While submitted = FALSE

httpdata = "POST " & this.c_apipath(2) & !" HTTP/1.1\n" & _
"Host: " &this.c_apihost(2) & !"\n" & _
"User-Agent: PsyMP3/" & PSYMP3_VERSION & !"\n"

postdata = "s=" & this.m_session & "&" & _
"a[0]=" & Artist & "&" & _
"t[0]=" & Title & "&" & _
"i[0]=" & curtime & "&" & _
"o[0]=P&" & _
"r[0]=&" & _
"l[0]=" & length & "&" & _
"b[0]=" & Album & "&" & _
"n[0]=&m[0]="

httpdata &= _
!"Content-Length: " & Len(postdata) & !"\n" & _
!"Content-Type: application/x-www-form-urlencoded\n\n" & _
postdata

Dim s As SOCKET = this.submitData(httpdata, 2)
hReceive(s, strptr(response), 10000)
response_data = Mid(response, InStr(response, !"\r\n\r\n") + 4)

Dim status As String = Left(response_data, InStr(response_data, !"\n") - 1)

Select Case status
Case "OK"
printf !"LastFM::submitScrobble(): Server response OK. Track sucessfully scrobbled!\n"
submitted = TRUE
Case "BADSESSION"
this.m_session = this.getSessionKey()
Case Else
printf !"LastFM::submitScrobble(): Scrobbling track failed!\n"
this.saveScrobble(artist, Title, album , length, curtime)
submitted = TRUE
End Select
Wend
End Function

Sub LastFM.submitSavedScrobbles Alias "submitSavedScrobbles" ()
Dim s As Scrobble Ptr
If This.m_scrobbles.Empty() Then Return
While(This.m_scrobbles.Empty() = 0)
s = This.m_scrobbles.Front()
this.submitScrobble(s->m_artist, s->m_title, s->m_album, s->m_length, s->m_curtime)
This.m_scrobbles.Pop()
Wend
End Sub

Function LastFM.saveScrobble Alias "saveScrobble" (artist As String, Title As String, album As String, length As Integer, curtime As UInteger) As Integer Export
Dim s As Scrobble
printf(!"LastFM::saveScrobble(): storing scrobble in memory.\n")
s.setData(artist, title, album, length, curtime)
This.m_scrobbles.Push(s)
End Function

Sub LastFM.dumpScrobbles2 Alias "dumpScrobbles2" () Export
Dim fd As FILE Ptr
Dim i As Integer, ret As Integer
Dim s As Scrobble Ptr
fd = fopen(this.c_xmlpath & "/lastfm.xml","wb")

If fd = 0 Then
printf(!"LastFM::dumpScrobbles2(): Error opening lastfm.xml.\n")
Return
EndIf

ret = fprintf(fd, !"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n")
If fd = 0 Then
printf(!"LastFM::dumpScrobbles2(): Error writing lastfm.xml.\n")
Return
EndIf
While(This.m_scrobbles.Empty() = 0)
s = This.m_scrobbles.Front()
ret = fprintf(fd, !"\t\n", s->m_artist, s->m_title, s->m_album, s->m_curtime, s->m_length)
If fd = 0 Then
printf(!"LastFM::dumpScrobbles2(): Error writing lastfm.xml.\n")
Return
EndIf
This.m_scrobbles.Pop()
Wend
ret = fprintf(fd, "")
If fd = 0 Then
printf(!"LastFM::dumpScrobbles2(): Error writing lastfm.xml.\n")
Return
EndIf
End Sub

Sub LastFM.dumpScrobbles Alias "dumpScrobbles" () Export
Dim As xmlTextWriterPtr writer
Dim As Integer ret, i
Dim As Scrobble Ptr s

printf(!"LastFM::dumpScrobbles(): Dumping scrobbles to " & this.c_xmlpath & !"/lastfm.xml\n")
writer = xmlNewTextWriterFilename(this.c_xmlpath & "/lastfm.xml", 0)

If writer = 0 Then
printf !"LastFM::dumpScrobbles(): xmlNewTextWriterFilename() failed!\n"
Return
EndIf

xmlTextWriterStartDocument(writer, NULL, "UTF-8", NULL)
xmlTextWriterStartElement(writer, "lastfm")
xmlTextWriterStartElement(writer, "encoding")
xmlTextWriterWriteAttribute(writer, "character", "UTF-8")
xmlTextWriterWriteAttribute(writer, "data", "RFC 1378")
xmlTextWriterEndElement(writer)
While(This.m_scrobbles.Empty() = 0)
s = This.m_scrobbles.Front()
xmlTextWriterStartElement(writer, "entry")
xmlTextWriterWriteAttribute(writer, "artist", s->m_artist)
xmlTextWriterWriteAttribute(writer, "title", s->m_title)
xmlTextWriterWriteAttribute(writer, "album", s->m_album)
xmlTextWriterWriteAttribute(writer, "time", Str(s->m_curtime))
xmlTextWriterWriteAttribute(writer, "length", Str(s->m_length))
xmlTextWriterEndElement(writer)
This.m_scrobbles.Pop()
Wend
xmlTextWriterEndElement(writer)
xmlTextWriterEndDocument(writer)
xmlFreeTextWriter(writer)

printf(!"LastFM::dumpScrobbles(): Done.\n")
End Sub

Sub LastFM.loadScrobbles Alias "loadScrobbles" () Export
Dim reader As xmlTextReaderPtr
Dim ret As Integer, getNext As Integer
Dim As Integer entries
dim constname as zstring ptr, value as zstring Ptr
Dim As String Artist, Title, Album
Dim As Integer curtime, length

reader = xmlReaderForFile("lastfm.xml", NULL, 0)

If reader = NULL Then Return
ret = xmlTextReaderRead(reader)
ret = 1
Do While(ret = 1)
constname = xmlTextReaderConstName(reader)
value = xmlTextReaderConstValue(reader)
Select Case *constname
Case "lastfm"
Select Case xmlTextReaderNodeType(reader)
Case 1
printf !"LastFM::loadScrobbles(): Start of database\n"
Case 15
printf !"LastFM::loadScrobbles(): End of database\n"
End Select
Case "entry"
Artist = *xmlTextReaderGetAttribute(reader, "artist")
Title = *xmlTextReaderGetAttribute(reader, "title")
Album = *xmlTextReaderGetAttribute(reader, "album")
curtime = Val(*xmlTextReaderGetAttribute(reader, "time"))
length = Val(*xmlTextReaderGetAttribute(reader, "length"))
this.saveScrobble(artist, Title, album, curtime, length)
End Select
ret = xmlTextReaderRead(reader)
Loop

End Sub

'' End Last.fm code
[/freebasic]

lastfm.bi:
[freebasic]
/'
$Id $

This file is part of PsyMP3.

PsyMP3 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

PsyMP3 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PsyMP3; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'/

#Ifndef __LASTFM_BI__
#Define __LASTFM_BI__

Namespace ext
Type ScrobbleList As fbext_Queue( ((Scrobble)) )
End Namespace

Type LastFM Alias "LastFM"
Private:
m_scrobbles As ext.ScrobbleList
m_session As String
c_username As String
c_password As String
c_apihost(2) As String
c_apiport(2) As Short
c_apipath(2) As String
c_xmlpath As String
Declare Sub readConfig Alias "readConfig" ()
Declare Function getSessionKey Alias "getSessionKey" () As String
Declare Function submitData Alias "submitData" (sData As String, host As Integer) As SOCKET
Declare Function submitScrobble Alias "submitScrobble" (artist As String, Title As String, album As String, length As Integer, curtime As UInteger) As Integer
Declare Function saveScrobble Alias "saveScrobble" (artist As String, Title As String, album As String, length As Integer, curtime As UInteger) As Integer
Declare Sub dumpScrobbles Alias "dumpScrobbles" ()
Declare Sub dumpScrobbles2 Alias "dumpScrobbles2" ()
Declare Sub loadScrobbles Alias "loadScrobbles" ()
Declare Sub submitSavedScrobbles Alias "submitSavedScrobbles" ()
Public:
Declare Constructor()
Declare Destructor()
Declare Function setNowPlaying Alias "setNowPlaying" () As SOCKET
Declare Function scrobbleTrack Alias "scrobbleTrack" () As Integer
End Type

#EndIf /' __LASTFM_BI__ '/
[/freebasic]

scrobble.bas:
[freebasic]

/'
$Id $

This file is part of PsyMP3.

PsyMP3 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

PsyMP3 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PsyMP3; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'/

#Include "psymp3.bi"

Constructor Scrobble()
printf(!"Scrobble::Scrobble(): Creating scrobble object!\n")
This.setData("", "", "", 0, 0)
End Constructor

Constructor Scrobble(ByRef cpy As Const Scrobble)
printf(!"Scrobble::Scrobble(Scrobble&): Copying preexisting scrobble.\n")
This.setData(cpy.m_artist, cpy.m_title, cpy.m_album, cpy.m_length, cpy.m_curtime)
End Constructor

Constructor Scrobble(artist As Const String, title As Const String, album As Const String, length As Const UInteger, curtime As Const UInteger)
This.setData(artist, title, album, length, curtime)
End Constructor

Destructor Scrobble()
printf(!"Scrobble::~Scrobble(): Deleting scrobble object!\n")
This.setData("", "", "", 0, 0)
End Destructor

Sub Scrobble.setData Alias "setData" (artist As Const String, title As Const String, album As Const String, length As Const UInteger, curtime As Const UInteger)
This.m_artist = artist
This.m_title = title
This.m_album = album
This.m_length = length
This.m_curtime = curtime
End Sub

Operator = (ByRef lhs As Scrobble, ByRef rhs As Scrobble) As Integer Export
If lhs.m_curtime = rhs.m_curtime Then
Return TRUE
Else
Return FALSE
EndIf
End Operator
[/freebasic]

scrobble.bi:
[freebasic]
/'
$Id $

This file is part of PsyMP3.

PsyMP3 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

PsyMP3 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with PsyMP3; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'/

#Ifndef __SCROBBLE_BI__
#Define __SCROBBLE_BI__

'#Include Once "psymp3.bi"

Type Scrobble Alias "Scrobble"
Public: 'This is probably a bad idea, but who cares?
m_artist As String
m_title As String
m_album As String
m_length As Integer
m_curtime As Integer
Declare Constructor()
Declare Constructor(ByRef cpy As Const Scrobble)
Declare Constructor(artist As Const String, title As Const String, album As Const String, length As Const UInteger, curtime As Const UInteger)
Declare Destructor()
Declare Sub setData Alias "setData" (artist As Const String, title As Const String, album As Const String, length As Const UInteger, curtime As Const UInteger)
End Type

Declare Operator = (ByRef lhs As Scrobble, ByRef rhs As Scrobble) As Integer
Declare Operator <> (ByRef lhs As Scrobble, ByRef rhs As Scrobble) As Integer

fbext_Instanciate( fbExt_Queue, ((Scrobble)) )

#EndIf /' __SCROBBLE_BI__ '/
[/freebasic]

It's incomplete but make something of it.