Thursday, December 15, 2011

Deciphering the Spotify Apps API

So, Spotify Apps has been launched and I presume a lot of developers are trying to figure out how to use the API that comes with it. The documentation is kind of a light read and doesn't really tell us how to use the API or what methods we can access through it. I wrote a function that recursively iterates through the sp object and prints its properties and functions, in order to see what's actually there. This is the result:

object core
string country
undefined user
undefined product
undefined isApplicationFocused
boolean developer
undefined isDebugMode
object library
number totalPlaylists
number totalAlbums
number totalArtists
number totalTracks
function removeEventListener() { [native code] }
function createPlaylistGroup() { [native code] }
function createPlaylist() { [native code] }
function getPlaylists() { [native code] }
function removePlaylist() { [native code] }
function getTracks() { [native code] }
function getAlbums() { [native code] }
function setFilter() { [native code] }
function addPlaylist() { [native code] }
function getArtists() { [native code] }
function addEventListener() { [native code] }
function movePlaylist() { [native code] }
string language
function getAbTestGroup() { [native code] }
function logClientEvent() { [native code] }
function getArguments() { [native code] }
function addEventListener() { [native code] }
function _set_body_size() { [native code] }
function activate() { [native code] }
function getAuthToken() { [native code] }
function getMetadata() { [native code] }
function internalCrash() { [native code] }
function removeEventListener() { [native code] }
function getPlaylist() { [native code] }
function getLastfmCredentials() { [native code] }
function browseUri() { [native code] }
function getLoginMode() { [native code] }
function suggestSearch() { [native code] }
function getAnonymousUserId() { [native code] }
function search() { [native code] }
function searchEx() { [native code] }
function parseHermesReply() { [native code] }
function setLastfmCredentials() { [native code] }
function getHermes() { [native code] }
function registerSchema() { [native code] }
function readFile() { [native code] }
function getStarredPlaylist() { [native code] }
function getLinkType() { [native code] }
function spotifyUriToHttpLink() { [native code] }
function spotifyHttpLinkToUri() { [native code] }
function getLinks() { [native code] }
function serializeToString() { [native code] }
function showClientMessage() { [native code] }
function showAuthDialog() { [native code] }
function getTemporaryPlaylist() { [native code] }
function createUserLink() { [native code] }
string uri
object desktop
undefined dropdown
function showContextMenu() { [native code] }
object social
undefined serviceStates
undefined relations
function removeEventListener() { [native code] }
function getUserByFacebookUid() { [native code] }
function showSharePopup() { [native code] }
function getRecentFacebookUsers() { [native code] }
function getUserByUsername() { [native code] }
function connectToFacebook() { [native code] }
function sendToInbox() { [native code] }
function getFavorites() { [native code] }
function getToplist() { [native code] }
function addEventListener() { [native code] }
function hideSharePopup() { [native code] }
object trackPlayer
function setContextCanSkipPrev() { [native code] }
function removeEventListener() { [native code] }
function setVolume() { [native code] }
function playTrackFromContext() { [native code] }
function canChangeRepeat() { [native code] }
function setShuffle() { [native code] }
function setContextCanShuffle() { [native code] }
function seek() { [native code] }
function skipToPreviousTrack() { [native code] }
function getShuffle() { [native code] }
function getPlayingContext() { [native code] }
function getPlaybackControlState() { [native code] }
function getNowPlayingTrack() { [native code] }
function getVolume() { [native code] }
function setContextCanSkipNext() { [native code] }
function skipToNextTrack() { [native code] }
function playTrackFromUri() { [native code] }
function setContextCanRepeat() { [native code] }
function getRepeat() { [native code] }
function canChangeShuffle() { [native code] }
function getIsPlaying() { [native code] }
function setRepeat() { [native code] }
function setIsPlaying() { [native code] }
function addEventListener() { [native code] }
object whatsnew
function reportAdStarted() { [native code] }
function getPartner() { [native code] }
function reportAdClicked() { [native code] }
function reportAdStopped() { [native code] }
function fetchAd() { [native code] }
object installer
function getApplicationState() { [native code] }
function removeEventListener() { [native code] }
function addEventListener() { [native code] }
function addApplicationFavorite() { [native code] }
function isApplicationFavorite() { [native code] }
function removeApplicationFavorite() { [native code] }

I haven't had the time to actually figure out what parameters to pass to each function, but I still find it kind of helpful. What I really want to know is what events I can bind listeners to, through addEventListener(), besides the "playerStateChanged" event, shown in the example.

Tuesday, October 5, 2010

Google Maps distanceFrom in MySQL

So, step two of the website I was working on here was to actually search my MySQL database for points within a given distance of a second point. I simply converted the Haversine formula to a MySQL query:

SELECT *
FROM Point
WHERE
6378137
*
(
2
*
ATAN2(
SQRT(
SIN((Point.Lat - 59.332526) * PI() / 180)
*
SIN((Point.Lat - 59.332526) * PI() / 180)
+
COS((59.332526 * PI() / 180))
*
COS((Point.Lat * PI() / 180))
*
SIN(((Point.Lng - 18.064091) * PI() / 180) / 2)
*
SIN(((Point.Lng - 18.064091) * PI() / 180) / 2)
),
SQRT(
1
-
(
SIN((Point.Lat - 59.332526) * PI() / 180)
*
SIN((Point.Lat - 59.332526) * PI() / 180)
+
COS((59.332526 * PI() / 180))
*
COS((Point.Lat * PI() / 180))
*
SIN(((Point.Lng - 18.064091) * PI() / 180) / 2)
*
SIN(((Point.Lng - 18.064091) * PI() / 180) / 2)
)
)
)
) <= 300 -- 300 meters

In the above example I use the LatLng position of central Stockholm (59.332526, 18.064091) as the base point and select all points within a 300-meter radius from it. Of course this can be simplified, but you get the Point.

Wednesday, September 15, 2010

Google Maps API V3 - distanceFrom method

I recently started upgrading a website, which uses Google Maps, from V2 to V3. On one page I needed to calculate the distance between two GLatLngs. This was easily done in V2 with the GLatLng.distanceFrom method. Although this method seems to have disappeared when V3 was released, so I created it:

google.maps.LatLng.prototype.distanceFrom = function(p2) {
var R = 6378137; // earth's mean radius in meters (this was a parameter in V2)
var rad = function(x) {return x*Math.PI/180;}
var dLat = rad(p2.lat() - this.lat());
var dLong = rad(p2.lng() - this.lng());

var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(rad(this.lat())) * Math.cos(rad(p2.lat())) * Math.sin(dLong/2) * Math.sin(dLong/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c;

return d.toFixed(3);
}


Using the Haversine formula (found here) I created a custom method within the LatLng class. I altered it a little in order to get the distance in meters instead of kilometres. Seems to work as intended!

Monday, May 31, 2010

EMAILProtect: Secure e-mail addresses in Wordpress

I wrote a plugin for Wordpress which secures e-mail addresses from being found by spam spiders (and other crawlers).

The plugin rewrites e.g. "something@something.com" to a JavaScript. The script displays a clickable link for the user, but the original string is hidden from "non-JavaScript crawlers".

Instead of updating the actual script here - and on other sites - will I only be using the Wordpress SVN.

I will, however, be answering questions and taking suggestions through the comments.

Wordpress plugin site:
http://wordpress.org/extend/plugins/emailprotect/

Fun fact #1: In less than one week on Wordpress, EMAILProtect was downloaded more than 1000 times.

Wednesday, May 5, 2010

Display hidden files in Finder

You may have been in the situation where you want to display hidden files in Finder. I've found that necessary when I want to edit system files e.g. ".htaccess" .

There's a bunch of applications and script to do this but what I've noticed is that everyone of these are unnecessarily advanced.

What I did is that I developed a _simple_ AppleScript application which you execute to display hidden files and when/if you want to hide them you just execute it again.

set currentStatus to (do shell script "defaults read com.apple.Finder AppleShowAllFiles")

if currentStatus is "YES" then
do shell script "defaults write com.apple.Finder AppleShowAllFiles NO"
else
do shell script "defaults write com.apple.Finder AppleShowAllFiles YES"
end if

do shell script "KillAll Finder"


Download file
Mirror site #1
Mirror site #2

Wednesday, November 11, 2009

Rewrite URL to HTML with PHP

When presenting an article or other information it's convenient for readers to be able to click on links you are referring to. With regular expressions the composers are able to write clickable links without know any HTML.

In the example below we are getting the plain text in the variable $str.

Step one, which protocols are allowed? By adding more protocols to the array you're accepting them to be rewritten.
// Protocols
$protocols = array("http://", "https://", "ftp://");
// Imploding the array to fit the regex
$protocols = str_replace("/", "\/", implode("|", $protocols));

Step two, adding http:// to URLs without a specified protocol, like www.example.com
$str = preg_replace("/(?<=^|\(|\s)(www\.[A-Za-z0-9-_]+\.[A-Za-z\.]{2,6}(?:[\/\?].*)?)(?=[\.,!\?]*(?:\s|\(|\)|$))/U", "http://$1", $str);
Step three, replace all accepted URLs with HTML

$str = preg_replace("/(?<=^|\(|\s)({$protocols})((?:[A-Za-z0-9-_]+\.)?[A-Za-z0-9_-]+\.[A-Za-z\.]{2,6}(?:[\/\?].*)?)(?=[\.,!\?]*(?:\s|\(|\)|$))/U", "<a href=\"$1$2\">$2</a>", $str);
This code will work with the following ways to write URLs:
www.example.com
http://www.example.com
http://sub.example.com
http://example.com

www.example.com/example.html
www.example.com/exempe/
www.example.com?id=1
www.example.com/?id=1
www.example.com?id=1#23
www.example.com/example/#23

(http://example.com)
(example: www.example.com)
www.example.com/example)
www.example.com,
www.example.com?
www.example.com!
http://example.com. - The dot won't be a part of the link
http://example.com/. - The dot won't be a part of the link
www.example.com?id=id=123.43. - The last dot won't be a part of the link

But not with:
example.com
sub.example.com

Tuesday, November 10, 2009

Automatic XBMC library update

Since I have my computer set up to automatically download TV shows I wanted my XBMC library to be automatically updated at all time. The "Update on startup" option was insufficient in my case since I rarely restart my computer. The media center is running on Windows XP which meant Python was not an option (since it can't fully interact with XBMC under Windows). So I created a small VB application that uses the XBMC HTTP API to update the library:
Call UpdateXBMCLibrary()

Sub UpdateXBMCLibrary()
WScript.Timeout = 120
Dim objRequest
Dim URL

Set objRequest = CreateObject("Msxml2.ServerXMLHTTP")

URL = "http://localhost:8080/xbmcCmds/xbmcHttp?command=ExecBuiltIn&parameter=XBMC.updatelibrary(video)"

objRequest.open "GET", URL , false, "username", "password"
objRequest.Send

Set objRequest = Nothing
End Sub

I set up the web interface so that it runs on port 8080 with my desired username and password, used the Msxml2.ServerXMLHTTP object to make the HTTP request and voilĂ ! It works.

I added a scheduled task in order to make it update frequently. I guess you could modify the application so that it recognizes changes amongst the files but one update an hour works fine for me.