BitNotifier 1.2.01 Is Out

•April 18, 2013 • Leave a Comment

Last week a change was made to the API I use to gather data from MtGox. This made BitNotifier think MtGox stopped responding, even though it was. It took a little bit to pinpoint the problem, but I sorted it out. You can download the newest version here: http://billdlabs.com/BitNotifier/1-20-01/setup.exe.

BitNotifier is now Open Source

•March 21, 2013 • Leave a Comment

BitNotifier will continue production as an open source application. You can download it from https://github.com/Billdlabs/BitNotifier. Enjoy!

BitNotifier 1.2 is out

•March 5, 2013 • 2 Comments

I’ve taken the feedback given so far and made some tweaks to the software.

Change log

  • Spacing on G19 should be fixed. I don’t have a G19, so some guess work is going into this.
  • Centered the text on the UI.
  • Added volume to the UI.
  • Moved website and donation functionality to the menu bar.
  • Added tabs to the UI.
  • Added a calculator to the UI.

calc

You can download the latest version here.

If you installed a previous version I’m afraid you’ll need to manually uninstall it before you can install this one. I’m looking into why that’s happening and hope to address it in a future release. My plans for the next version are going to be “behind the scenes” improvements which should lead to this project going open source . After that, I’ve got a list of features I’d like to implement including deeper MtGox support, personal wealth tracking, and maybe integration with some of the more popular software miners out there.

Do you want to see a feature that isn’t on my radar yet? Let me know.

Introducing BitNotifier

•March 1, 2013 • 1 Comment

I love bitcoins. I think they’re the greatest thing since e-mail. I’ve been kind of hung up on trading over at Mt Gox. However, I hated having to keep an eye on my web browser all of the time. So I decided to write an application that’d show me the last trade price in the system tray.

notification icon

Mission accomplished. Then I wanted some more information, like the high, low, average, current asking price and current buying price. So I made a window to show it to me.

main screen

Hey, neat. But what about when I’m playing video games and can’t see my desktop at all? Well, I have my Logitech G510 (should also work with G15, and G19) for that. If you don’t have one of these amazing keyboards, no worries – the other features still work!

LCD Display

And hey, that’s version 1! A desktop bitcoin market ticker with Logitech keyboard applet support. Here’s a ~2mb download to install it on your pc. I have some plans for future versions, but I was anxious to share what I’ve got so far. Please note this is a really early build. Let me know if you find any bugs.

Update: I added a feature. The icons, text, and led updates when MtGox does not respond for some reason.

Using DotNetOpenAuth to Authenticate Against StackOverflow in an MVC 4 Application

•January 9, 2013 • 3 Comments

Today I wanted to learn how to implement oAuth in as small a foot print as possible. I’ve written my own custom oAuth client before, but it was very bulky and not very reusable. Since DotNetOAuth ships with all .NET 4.0 MVC Web Applications (and I assume WebForms as well), I thought I’d look into writing against it. I wasn’t able to find much in the way of existing documentation or tutorials, so I’m posting my results for you. My examples are written for StackOverflow, but the same idea should apply to any OAuth provider.

We’re going to need three classes. They’ll be tightly coupled, so feel free to put them in a single file (as I did). Two of these classes are data contracts, and the third moves your data around. Let’s start with the data contracts.

They should simply mirror the json/xml result you expect to receive. StackOverflow throws a lot of data back on a simple user lookup request. Here’s what their JSON looks like:

{
  "items": [
    {
      "user_id": 1284102,
      "user_type": "registered",
      "creation_date": 1332351699,
      "display_name": "Billdr",
      "profile_image": "http://www.gravatar.com/avatar/30a850dc91432d86e940e4788a3f1c42?d=identicon&r=PG",
      "reputation": 536,
      "reputation_change_day": 0,
      "reputation_change_week": 10,
      "reputation_change_month": 10,
      "reputation_change_quarter": 10,
      "reputation_change_year": 10,
      "age": 30,
      "last_access_date": 1357764683,
      "last_modified_date": 1355755828,
      "is_employee": false,
      "link": "http://stackoverflow.com/users/1284102/billdr",
      "website_url": "http://www.billdlabs.com/",
      "location": "Minneapolis, MN",
      "account_id": 1342727,
      "badge_counts": {
        "gold": 0,
        "silver": 1,
        "bronze": 16
      },
      "accept_rate": 92
    }
  ],
  "quota_remaining": 9993,
  "quota_max": 10000,
  "has_more": false
}

So how do we model all of that as C# classes? Well, you’ll notice the data we actually want is wrapped up in an array called “items.” So, we need to create a class to wrap the data we actually want. For completeness, I also included the quota information at the end.

    [DataContract]
    class StackOverflowJsonReturns
    {
        [DataMember(Name = "items")]
        public IEnumerable<StackOverflowClientData> Users { get; set; }

        [DataMember(Name = "quota_remaining")]
        public int QuotaRemaining { get; set; }

        [DataMember(Name = "quota_max")]
        public int QuotaMax { get; set; }

        [DataMember(Name = "has_more")]
        public bool HasMore { get; set; }
    }

You’ll see I wrapped the actual data in it’s own class, “StackOverflowClientData.” Here’s what that looks like.

    [DataContract]
    class StackOverflowClientData
    {
        [DataMember(Name = "user_id")]
        public int Id { get; set; }

        [DataMember(Name = "user_type")]
        public string SOUserType { get; set; }

        [DataMember(Name = "creation_date")]
        public int SOJoinDate { get; set; }

        [DataMember(Name = "display_name")]
        public string DisplayName { get; set; }

        [DataMember(Name = "profile_image")]
        public Uri ProfileImage { get; set; }

        [DataMember(Name = "reputation")]
        public int Reputation { get; set; }

        [DataMember(Name = "reputation_change_day")]
        public int ReputationChangeDay { get; set; }
        
        [DataMember(Name = "reptutation_change_week")]
        public int ReputationChangeWeek { get; set; }

        [DataMember(Name="reputation_change_month")]
        public int ReputationChangeMonth { get; set; }

        [DataMember(Name="reputation_change_quarter")]
        public int ReputationChangeQuarter { get; set; }

        [DataMember(Name = "reputation_change_year")]
        public int ReputationChangeYear { get; set; }

        [DataMember(Name = "age")]
        public int Age { get; set; }

        [DataMember(Name = "last_access_date")]
        public int LastAccessDate { get; set; }

        [DataMember(Name = "last_modified_date")]
        public int LastModifiedDate { get; set; }

        [DataMember(Name = "is_employee")]
        public bool SOEmployee { get; set; }

        [DataMember(Name = "link")]
        public Uri SOPage { get; set; }

        [DataMember(Name = "website_url")]
        public Uri PersonalPage { get; set; }

        [DataMember(Name = "location")]
        public string Location { get; set; }

        [DataMember(Name = "account_id")]
        public int SOAccountId { get; set; }

        [DataMember(Name = "badge_counts")]
        public IEnumerable<KeyValuePair<string,int>> BadgeCount { get; set; }

        [DataMember(Name="accept_rate")]
        public int AcceptRate { get; set; }
    }

This class made me want to hire someone to do data entry. 99% of the reason you’re reading this code is because I don’t want you to suffer like I suffered, creating that class. Now that the data management is out of the way, here’s the code that does the work.

    public class SEoAuthClient : OAuth2Client
    {
        #region Constants and Fields
        private const string AuthorizationEndpoint = "https://stackexchange.com/oauth";
        private const string TokenEndpoint = "https://stackexchange.com/oauth/access_token";
        private readonly string appId;
        private readonly string appSecret;
        private readonly string key;
        #endregion

        #region Constructors and Destructors

        /// The public constructor for a new instance of theSEoAuthClient class.
        public SEoAuthClient(string appId, string appSecret, string key)
            : this("Stack Overflow", appId, appSecret, key)
        {
        }

        /// Initializes a new instance of the SEoAuthClient class.
        protected SEoAuthClient(string providerName, string appId, string appSecret, string key)
            : base(providerName)
        {
            if (!string.IsNullOrEmpty(appId))
            {
                this.appId = appId;
            }
            else
            {
                throw new Exception("Missing required data in appId when calling SEoAuth.");
            }
            if (!string.IsNullOrEmpty(appSecret))
            {
                this.appSecret = appSecret;
            }
            else
            {
                throw new Exception("Missing required data in appSecret when calling SEoAuth.");
            }
            if (!string.IsNullOrEmpty(key))
            {
                this.key = key;
            }
            else
            {
                throw new Exception("Missing required data in key when calling SEoAuth.");
            }
        }
        #endregion

        /// Gets the id for this client as it is registered with SE.
        protected string AppId
        {
            get { return this.appId; }
        }

        #region Methods
        /// Builds the URL the user will be directed to, with queries.
        protected override Uri GetServiceLoginUrl(Uri returnUrl)
        {
            var builder = new UriBuilder(AuthorizationEndpoint);
            builder.Query = "client_id=" + this.appId + "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.AbsoluteUri);
            return builder.Uri;
        }

        /// Sends a request with the access token to fetch the user's data. 
        /// This is actually the last method to be called in the flow.
        protected override IDictionary<string, string> GetUserData(string accessToken)
        {
            StackOverflowClientData graph;
            //SE returns the user data as a JSON formatted string, compressed with gzip. For my sanity's sake we're going after it with a WebClient instead of a WebRequest.
            using (var client = new WebClient())
            { 
                //SE requests this be set, even if it 'fails back' to gzip anyway. We're nice folks.
                client.Headers[HttpRequestHeader.AcceptEncoding] = "gzip";   
                client.Headers[HttpRequestHeader.ContentType] = "application/json; charset=utf-8;";
                var data =
                    client.DownloadData("https://api.stackexchange.com/2.1/me?site=stackoverflow&key=" +
                                        HttpUtility.UrlEncode(key) + "&access_token=" +
                                        HttpUtility.UrlEncode(accessToken));

                string jsonString = string.Empty;

                //this block decompresses the result and turns it into a string. 
                using (var gzipStream = new GZipStream(new MemoryStream(data), CompressionMode.Decompress))
                {
                    const int size = 4096;
                    var buffer = new byte[size];
                    using (var memory = new MemoryStream())
                    {
                        int count = 0;
                        do
                        {
                            count = gzipStream.Read(buffer, 0, size);
                            if (count > 0)
                            {
                                memory.Write(buffer, 0, count);
                            }
                        } while (count > 0);
                        //failing to return the position to 0 generates an obnoxious error.
                        memory.Position = 0;
                        //DotNetOpenAuth uses DataContractJsonSerializer, so that's what we're doing
                        var des = new DataContractJsonSerializer(typeof(StackOverflowJsonReturns));
                        var allData = (StackOverflowJsonReturns) des.ReadObject(memory);
                        graph = allData.Users.First();
                    }
                }
            }
            //Now we take all of our carefully formatted data and toss it into a string, string dictionary.
            var userData = new Dictionary<string, string>();
            userData.Add("id", graph.Id.ToString());
            userData.Add("username", graph.DisplayName);
            userData.Add("name", graph.DisplayName);
            userData.Add("link", graph.SOPage.ToString());
            userData.Add("user_type", graph.SOUserType);
            userData.Add("creation_date", graph.SOJoinDate.ToString());
            userData.Add("profile_image", graph.ProfileImage.ToString());
            userData.Add("reputation", graph.Reputation.ToString());
            userData.Add("reputation_change_day", graph.ReputationChangeDay.ToString());
            userData.Add("reputation_change_week", graph.ReputationChangeWeek.ToString());
            userData.Add("reputation_change_month", graph.ReputationChangeMonth.ToString());
            userData.Add("reputation_change_quarter", graph.ReputationChangeQuarter.ToString());
            userData.Add("reputation_change_year", graph.ReputationChangeYear.ToString());
            userData.Add("age", graph.Age.ToString());
            userData.Add("last_access_date", graph.LastAccessDate.ToString());
            userData.Add("last_modified_date", graph.LastModifiedDate.ToString());
            userData.Add("website_url", graph.PersonalPage.ToString());
            userData.Add("location", graph.Location);
            userData.Add("account_id", graph.SOAccountId.ToString());
            foreach (KeyValuePair<string, int> kvp in graph.BadgeCount)
            {
                userData.Add(kvp.Key, kvp.Value.ToString());
            }
            userData.Add("accept_rate", graph.AcceptRate.ToString());

            return userData;
        }

        /// Trades the authorization code in for an access token.
        protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
        {
            var entity = "client_id=" + this.appId + "&client_secret=" + this.appSecret + "&code=" + authorizationCode +
            "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.AbsoluteUri);

            var tokenRequest = WebRequest.Create(TokenEndpoint);
            tokenRequest.ContentType = "application/x-www-form-urlencoded";
            tokenRequest.ContentLength = entity.Length;
            tokenRequest.Method = "POST";

            using (Stream requestStream = tokenRequest.GetRequestStream())
            {
                var writer = new StreamWriter(requestStream);
                writer.Write(entity);
                writer.Flush();
            }

            HttpWebResponse tokenResponse = (HttpWebResponse) tokenRequest.GetResponse();
            if (tokenResponse.StatusCode == HttpStatusCode.OK)
            {
                using (Stream responseStream = tokenResponse.GetResponseStream())
                {
                    //SE gives us the response as a string. Not an argument appended to the callback but a string in the body of a page.
                    // It looks like this: access_token=fdagfdsf4&expires=8600
                    var response = tokenResponse.GetResponseStream();
                    StreamReader reader = new StreamReader(responseStream);
                    var responseString = reader.ReadToEnd();
                    var tokenSection = responseString.Split('&')[0];
                    return tokenSection.Split('=')[1];
                }
            }
            return null;
        }
        #endregion
    }

One last thing! In your /App_Start/AuthConfig.cs file, you will need to add this line somewhere in the RegisterAuth() method: OAuthWebSecurity.RegisterClient(new SeoAuthClient("YourAppId", "YourAppSecret", "YourKey"), "Stack Overflow", null);

If anything needs clarification, please contact me or leave a comment below.

How to execute assembly written for x86 on Windows 7 x64

•November 6, 2012 • Leave a Comment

I’m playing around in Assembly. All the tutorials out there seem to be for x86 machines. I’m on an Intel 64-bit processor running Windows 7. Nothing would run even though x64 is backwards compatible with x86. I spent all day trying to figure this out. To run assembly code written for x86 you must put it in the /Program Files (x86)/ folder. Hopefully you will see this and save yourself some grey hair/premature balding.

Google Chrome Extension: Crawling a page for specific tags and selectors

•September 24, 2012 • Leave a Comment

I’m about to talk about code I wrote on the job for someone else. To protect their rights to the code, this post will go through the general information needed to scrape a page with generic samples. At the end of the post you’ll have a working Chrome Extension. This was my first Chrome Extension, and I taught myself how to do it in a few hours. It may not adhere to best practices in all instances. If you’re making something to sell, I recommend you do further research.

Problem: I needed to take all CSS selectors from a page I had written and store it into a file to hand off to designers. There are several pages, and each page had hundreds of tags, classes, and IDs so doing it by hand would take an obnoxious amount of time.

Solution: For this project there are five standard files of the Chrome extension that we need to build. First let’s take a look at manifest.json, which handles permissions.

manifest.json

{
    "name": "TagRipper",
    "version": "1.0",
    "manifest_version": 2,
    "description": "Looks over a page for valid CSS selectors.",
    "browser_action": {
        "default_icon": "icon.png",
        "default_popup": "popup.html"
    },
    "content_scripts":[{
        "matches": ["http://*/","https://*/*"],
        "js": ["content.js"]
     }],
    "permissions": ["tabs"]
}

Let’s take this line-by-line. The first thing you’ll notice if you’re new to JSON is the entire thing is enclosed in brackets. It may look weird, but that is how it’s supposed to be. For futher reading you can check some examples of JSON compared to XML here. Some of the tags are pretty self-explanatory, such as name (the extension’s name), version (the extension’s version), and description (a description of what the extension does).

At the time of this writing “manifest_version” should always be set to 2. Note that’s an integer 2, not a string “2.” You can find the current manifest version (and some details about what the manifest offers) here.

The “browser_action” adds elements to the browser itself, next to the configuration wrench. “Default_icon” is the image that is displayed, and “default_popup” is the “balloon” that will come out of the icon when clicked. We’ll talk about these actual files in a moment. The icon.png I used was the one from Chrome’s “Hello World” tutorial. You can view that tutorial here if you haven’t been there already.

The “content_scripts” tells Chrome to run something in the background. The “matches” tag tells Chrome when to do it. In this example I set wildcard strings so that the script runs on all http and https connections. If you only want your extension to work for certain sites (for reskinning extensions or to provide additional functionality to a site) you would specify it there. Remeber, “*” indicates a wildcard. If you wanted to make something like the Reddit Enhancement Suite the line would look like this: “matches”: ["http://www.reddit.com/*","http://reddit.com/*"]. The “js” tells Chrome we want to run javascript. We could put a script in there directly, but for readability’s sake I’ve specified a file.

The last line here is the “permissions” line. This application needs to see what the user is looking at in their browser, so we need the “tabs” permission. Here is a list of permissions and a general description of what they give you access to.

popup.html

<!doctype html>
<html>
    <head>
        <title>CSSScraper</title>
            <style>
                body
                {
                    min-width:357px;
                    overflow-x:hidden;
                }
            </style>
            <!-- JavaScript and HTML must be in separate files for security. -->
            <script src="popup.js"></script>
    </head>
    <body>
        <p id="results"></p>
    </body>
</html>

This file is very straight forward, but there are three quick points of interest: 1) I did not put the javascript here. Chrome doesn’t like it if you try. Put it in an external javascript document and move on. 2) I created an empty paragraph section in my document. This is going to be targeted by popup.js later. 3) The css styling here is what pops out when the icon is pressed. It can do anything an html/css webpage can, so go nuts with it.

The last two files are Javascript, so I will explain them with inline comments.

chrome.extension.onMessage.addListener(function(request, sender, sendResponse)
{
    //request has two properties that we're working about, Greeting and Farewell. Both variables are strings. The greeting can be anything, as long as the receiving file and sending file agree on what it is.
    if(request.greeting=="gimmieyodatas")
    {
        //declare an array of all known html elements. I got this list from a random website. It may not be complete, but it covers everything I used.
	var elements = new Array("a","abbr","acronym","address","applet","area","article","aside","audio","b","base","basefont","bdi","bdo","big","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","command","datalist","dd","del","details","dfn","dir","div","dl","dt","em","embed","fieldset","figcaption","figure","font","footer","form","frame","frameset","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","i","iframe","img","input","ins","keygen","kbd","label","legend","li","link","map","mark","menu","meta","meter","nav","noframes","noscript","object","ol","optgroup","option","output","p","param","pre","progress","q","rp","rt","ruby","s","samp","section","select","small","source","span","strike","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","tt","u","ul","var","video","wbr");
	//declare an array for found elements
	var foundElements = new Array();
	//declare an array for found ids
	var foundIds = new Array();
	//and we're going to output everthing in a giantic string.
	var output ="/*Page: " + document.URL + "*/<br />";
	//this counter is used to hold positions in the element array.
	var elementCounter = 0;
	//this counter is used to hold positions in the foundIds array
	var idsCounter = 0;
	//this counter is used to hold positions in the classCounter array.
	var classCounter = 0;
 
	//scrape the page for all elements
	for (var i = 0; i < elements.length; i++)
        {
	    var current = document.getElementsByTagName(elements[i]);
	    if(current.length>0)
	    {
		output += elements[i] + "{}<br />";
		elementCounter++;
		//now that we have an array of a tag, we want to check it for IDs and classes.
		for (var y = 0; y<current.length; y++)
		{
		    //check to see if the element has an id
		    if(current[y].id)
		    {
		        //these should be unique, but you never know... here's a bool we'll use to keep track of collisions.
			var hit = false;
			for (var x = 0; x<foundIds.length; x++)
			{
			    if(foundIds[x]==current[y].id)
			    {
			        hit=true;
			    }
			}
 
			//if there was no hit...
			if(!hit)
			{
			    foundIds[idsCounter]=current[y].id;
			    idsCounter++;
			    output+="#" + current[y].id + "{} <br />";
			}
                    }
                    //now we pull the classes
		    var classes = current[y].classList;
		    if(classes.length>0)
		    {
		        for (var x = 0; x<classes.Length; x++)
		        {
		            //now we verify we haven't already listed this class. we'll use a bool to keep track of colisions.
			    var hit = false;
			    for (var z = 0; z<foundClasses.length; z++)
			    {
			        if(foundClasses[z]==classes[x])
			        {
			            hit=true;
			        }
			    }
 
			    //if there was not a hit
			    if(!hit)
			    {
			        foundClasses[classCounter]=classes[x];
			        classCounter++;
			        output+="." + classes[x] +"{} <br />";
			    }
		        }
		    }
	        }
            }
        }
        //finally we send the output back to whatever made the call - which we assume is popup.js.
        sendResponse({farewell: output});
    }
    else{
        sendResponse({}); //the request wasn't sent with the correct string. We send nothing back..
    }
});

Finally we have the popup.js, which is more simple.

//this first line tells chrome to get the user's current tab, which it stores to the variable "tab."
chrome.tabs.getSelected(null,function(tab){
        //This tells chrome to send a message to the background file (content.js in this extension's case) for the current tab.
	chrome.tabs.sendMessage(tab.id, 
		{
			greeting:"gimmieyodatas"
		},
                //This tells chrome what to do with the response. In this case, we're binding it to the paragraph in our popup.html with the id "results."
		function(response)
		{
			document.getElementById("results").innerHTML=response.farewell;
		}
	);
});

So to recap: our manifest file tells Chrome what our extension does, what resources(files) it uses, it’s name, and other information that may/may not be surfaced to the user. The “content.js” file works in the background, and will run constantly on any page where we have a match. The icon and popup.html files are what we present to the user. The popup.js file binds everything else together.

So, now you’ve got a bunch of files. Now what? Well, toss them in a folder on your desktop (or wheverever), click the wrench icon in chrome, then click ‘Settings.’ From there click ‘Extensions’ and then click the checkbox for “developer mode.” Click the button that says “Load Unpacked Extension” and then select the folder you created. Make sure the extension is enabled, and you should see your icon loaded in the top-right corner of chrome. Click the icon, and view the results of your labor. You may need to restart chrome for the plugin to work.

Can this be optimized? Is there a better way to do this? Did I help you out? Let me know.

dMinion 2.1 is out!

•March 27, 2012 • Leave a Comment

Last night I released dMinion 2.1. The new version has an improved layout, the ability to save/load characters, and a tracker for rounds and turns.

You can get the new version here.

We are also awaiting approval on the Amazon App Store so the app can be loaded on your (and my) Kindle fire.

We are live.

•September 13, 2011 • Leave a Comment

After numerous distractions, side projects, and dilly-dalling it is finally go time. My Android developer application has been approved. I’ve got a few sites in my portfolio, and I’m just about done with my first for-public-consumption application.

I’m hoping to have an application launched by the end of the week. Once that is done I’ll turn my attention to designing a layout for this site.