TipBitco.in and GPG

Posted in Updates, Web Sites on February 18, 2014 by admin

Hey all,

Just wanted to drop a quick line. I’m still here, and still working. My new day job keeps me pretty busy. I have been working on another project under the Billd Labs umbrella, TipBitco.in. That site is a service to generate lanyards that display QR codes. The idea is you wear it to accept bitcoin tips while working in the service industry. I think it’s pretty neat.

I also wanted to make sure you have access to my GPG key. I’ve been sitting on it for a while without really making it known that I have/use it. This is my personal key. There is one associated with Billd Labs, but I haven’t quite figured out what I will do with that yet. I’ll probably write up a tutorial on how to use this in the not-too-distant future, but a quick search will probably give you plenty of useful posts on that topic.

 

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.17 (MingW32)

mQENBFFBPuMBCACemR8//Xy5RAyqSbSWGcOc5DwvD07gjLzHeaHbOm0LO20EVF45
bFyjXKmw98Mr4v2yaOgcfmOTKsJWmY/BYdmXN7fNvP15Y5sOk0zMADHS8DoZy5yj
NN7kSauPd+pneksEMB+Yx8dmW+hJK1d0VNHiNJgWFYya7//oolRO7dTYnQysY+UW
94mGJbNL/tQYTYfit182iXnGRPLwmPPnzpRXAh8RuhxP+b72vtNqpTPwb8iUneHq
lRBHR9oN4bjg5Im6v4NTadFjkk68No9cZ1Rmusc81shOfpme8nYLVolnyeg8k9ff
Vbx2Za1VOOVMyKe6FAT9ywhpqAcpYYxXJ/jfABEBAAG0LUJpbGwgU3RyYWl0IChm
b3Igb3RjKSA8YmlsbC5zdHJhaXRAZ21haWwuY29tPokBPgQTAQIAKAIbDwYLCQgH
AwIGFQgCCQoLBBYCAwECHgECF4AFAlFBQTYFCRLOF60ACgkQKfj5QcXOOGTSgQgA
h8oqJcvJFZrXzRojMYefawoveh3Wf59J5omkEa4lgBWO9JcHFBlkNxpsv36oWQmL
XF/3bYFolQIZINHY05IyHpvOAz8+diOwKWSFqgJtrZViY5cSZPRW79ZJlPo0cYum
hYe4O0EkJbJgT+6GVT/6VFVbUxCPf8LZzyRJ3kKg8VrgUmGC3tHE4aEcH8o1xMGq
CfomT3lr2ckf1tTGDn1ZBwBUEa+HVE1pfD+13qzQQG6MrLaGORV9ze7HJcvE8tL8
d3sC1lmT7UGnFfWlEUJoYLCg2t8ecwZzo51VhyBgWxmqGU9GgdHgiyjkD6vWsPjk
4I0CtZlPwUCTd1SQhLT1Qw==
=SdB4
-----END PGP PUBLIC KEY BLOCK-----

Bit Notifier 1.4 Is Out

Posted in BitNotifier, Windows Applications on May 27, 2013 by admin

There are a few changes with this version. The big one? I’ve added support for AUD, CAD, CHF, CNY, DKK, EUR, GBP, HKD, NZD, PLN, RUB, SGD, THB, NOK, CZK, JPY, and SEK. You can choose your desired currency in About->Settings. This was a user suggestion and I feel like a idiotic American for not including it in the first place.

The other change? I got in place updates to work. You shouldn’t have to uninstall/reinstall, starting with this version.

You can download the latest version here: Bit Notifier 1.4

You can view/download/tweak the source here: GitHub

BitNotifier 1.2.01 Is Out

Posted in BitNotifier, Windows Applications on April 18, 2013 by admin

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

Posted in BitNotifier, Windows Applications on March 21, 2013 by admin

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

Posted in BitNotifier, Windows Applications on March 5, 2013 by admin

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

Posted in BitNotifier, Windows Applications on March 1, 2013 by admin

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

Posted in Development, Web Sites on January 9, 2013 by admin

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

Posted in Development on November 6, 2012 by admin

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

Posted in Development on September 24, 2012 by admin

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!

Posted in Android Applications, News on March 27, 2012 by admin

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.