Latest Tweets

Find Posts by Tag
Twitter

Entries in code (4)

Saturday
Feb112012

How To Download A Secret and Save To An Adobe Air ELS

One of the problems I had to solve with my recent management app was about how to store secret information. My app needed to have access to an API key, and a special username and password. All three of those things were items I would rather keep out of my user's hands so storing it in the Javascript that is part of the Adobe Air app was not a solution. But I found another way.

Let me explain the plaintext part of the problem. With any Adobe Air app, you can very easily look inside and see all source Javascript in plaintext. Since all the logic in my app is in the Javascript, thats a problem. So I decided to store that secret info on a secured page on a Squarespace site. If you type in the right login, you can see the page (assuming you know where to look).

The information stored on the page is in a JSON string, which is then encoded with Base64. So I set up my app to login, download the information, decode it, parse the JSON, and finally store the info locally using the Adobe encrypted local store. Almost all of that is done in that one ajax call.

Take a look at the source code below. It's actually written in CoffeeScript, since thats just how I roll. If you are unfamiliar with CoffeeScript, take the few minutes to learn it.

Even if you aren't using Adobe Air, this is a valuable little snippet. Change the last three lines to do something else with your newly found secret info. Not using Squarespace? Well, that will take a bit more work to get the URL and parameters just right.

The benefit of this is that I don't have to do much to manage who has rights to the app, I just have to manage who has rights to the page on Squarespace. Also, I stored a hash of the users password in the local store, so on the app's startup, I just compare the hash of the password the user enters to the stored hash to verify the user is valid and has access to the info.

I am sure there is a hole in here somewhere, but I think it is good enough for my purposes.

Tuesday
Dec112007

Adding a Simile Timeline to Community Server

In case you have visited this site recently, you may have noticed the cool timeline in the Blog Archives page. This is an amazing bit of Javascript that displays a any content you like on a long virtual sheet. Its kind of like those great "entire history of the world" posters that you grew up with. I found this after looking at Scott Hanselman's web site a few days ago. I figured that I had plenty of time, so I did the same thing. It wasn't very hard, but it wasn't exactly brain dead easy either.

To start off, I went up to the source page for the project at MIT. The examples on there are great. The documentation is a bit basic, but most of your needs are covered. After searching around a bit, I found a few other articles that were helpful, including a couple from LifeCycle Solutions and Ryan Kanno. Then it was just a matter of trying it out.

First, I added the javascript tag on my Blog Archives page:
<script type="text/javascript" src="http://simile.mit.edu/timeline/api/timeline-api.js"></script>

Further down I added a div where the timeline was actually going to go:
<div id="my-timeline" style="height: 450px; border: 1px solid #aaa; margin-right:20px;" />

Next I created a new .js file to hold my javascript. I think I will use this file for lots of other stuff, so I made a new namespace just for the timeline code. Here is the entire file:

   1:  var TECHNOVANGELIST = {};
2: TECHNOVANGELIST.TimeLine = function() {
3: var tl;
4: var resizeTimerID = null;
5:
6: return {
7:  
8: createTimeLine: function () {
9: var eventSource = new Timeline.DefaultEventSource();
10: var theme = Timeline.ClassicTheme.create();
11: theme.event.bubble.width=520;
12: theme.event.bubble.height=120;
13: var curdate = new Date()
14: var bandInfos = [
15: Timeline.createBandInfo({
16: eventSource: eventSource,
17: date: curdate.toGMTString(),
18: width: "80%",
19: intervalUnit: Timeline.DateTime.WEEK,
20: intervalPixels: 50
21: }),
22: Timeline.createBandInfo({
23: showEventText: false,
24: trackHeight: 0.5,
25: trackGap: 0.2,
26: eventSource: eventSource,
27: date: curdate.toGMTString(),
28: width: "20%",
29: intervalUnit: Timeline.DateTime.MONTH,
30: intervalPixels: 100
31: })
32: ];
33: bandInfos[1].syncWith = 0;
34: bandInfos[1].highlight = true;
35: bandInfos[1].eventPainter.setLayout(bandInfos[0].eventPainter.getLayout());
36: tl = Timeline.create(document.getElementById("my-timeline"), bandInfos);
37: Timeline.loadXML(http://technovangelist.com/BlogTimelineSource.ashx,
function(xml, url) { eventSource.loadXML(xml, url); });
38: },
39:
40: onResize: function() {
41: if (resizeTimerID == null) {
42: resizeTimerID = window.setTimeout(function() {
43: resizeTimerID = null;
44: tl.layout();
45: }, 500);
46: }
47: }
48: };
49: } ();

Back in the Blog Archives page, I added the pointer to that js file:

<CSControl:Script src="../Common/TVLIB.js" runat="server" />
<script type="text/javascript">
   window.onload=TECHNOVANGELIST.TimeLine.createTimeLine;
   window.onresize=TECHNOVANGELIST.TimeLine.onResize;
</script>

You can try that out to see if it works so far, but your eventsource isn't working yet. I have that pointing to a BlogTimelineSource.ashx which we haven't created...yet. So here is the full file for BlogTimelineSource.ashx:

   1:  <%@ WebHandler Language="C#" Class="BlogTimeLineSource" Debug="true" %>
2: 
3: using System;
4: using System.Web;
5: using CommunityServer.Blogs.Controls;
6: using CommunityServer.Blogs.Components;
7: using System.Collections.Generic;
8: using CommunityServer.Components;
9: 
10: public class BlogTimeLineSource : IHttpHandler {
11:
12: public void ProcessRequest (HttpContext context) {
13: context.Response.ContentType = "text/xml";
14: context.Response.Write("<data>");
15: BlogThreadQuery query;
16: query = BlogThreadQuery.CreateNewAggregateQuery();
17:
18: query.PostConfig = BlogPostConfig.IsAggregated;
19: query.PageIndex = 0;
20: query.PageSize = 5000;
21: query.IncludeCategories = true;
22: query.BlogPostType = BlogPostType.Post | BlogPostType.Article;
23: 
24: ThreadSet ts = WeblogPosts.GetBlogThreads(query);
25: 
26: foreach (WeblogPost p in ts.Threads)
27: {
28: context.Response.Write("<event start=\"" + p.PostDate + "\" link=\""
+ p.ViewPostURL + "\" title=\"" + p.Subject + "\">");
29: context.Response.Write(context.Server.HtmlEncode(p.ForceExcerpt) + "</event>");
30: }
31: context.Response.Write("</data>");
32: }
33:
34: public bool IsReusable {
35: get {
36: return false;
37: }
38: }
39: 
40: }
At this point you run into a problem. The ashx file probably can't be found still. Its there, but Community Server doesn't know about it yet, so you may have to modify you SiteUrls_Override.config. I just added the following in an <override> block:
    <location name="BlogTimeLineSource" themeDir="common"  path="/" >
      <url name = "BlogTimelineSource"  path="/BlogTimelineSource.ashx" 
              pattern="/BlogTimelineSource.ashx" physicalPath="##themeDir##" 
              vanity="{2}" page="BlogTimelineSource.ashx"/>
    </location>

That's it!! The toughest parts for me were trying to figure out how to reference the javascript file on a CS site, figuring out Javascript namespaces, and then doing the query in the ashx file. Then I spent a while tweaking the look...changing the height (though its referenced as width) of the bands and changing the size of the popup. Let me know if this is useful for you...

Sunday
Jun172007

How To: Add a Caching Twitter Badge to CS2007

A few months ago I started playing around with Twitter. In case you are not familiar with it, Twitter is a blog of short messages. Think about all the times you want to say something, or tell people where you are, but you don't think the idea warrants an entire blog post. Those are the times you use Twitter. People can subscribe to your Twitter status and get those updates where ever they are. They can get them via IM, or SMS, or some 3rd party applications. Since I live in Europe, I subscribe to a few people's status via SMS (SMS in Europe is cheap, unlike the US where no one uses it since it is so expensive).

When I was thinking about what I wanted on this website, I thought some sort of Twitter status update would be cool. Not an entire history, just the latest item. If you go to Twitter's badge page, you can see a number of options for grabbing your current Twitter status. I tried the Javascript one first. It seemed to work, but I was annoyed at the fact that it polled Twitter on every refresh. Chances are I am not going to update my Twitter status that often, so I want to cache the information locally. But I don't want to cache the entire block of text, because the time since I made the update will change as time goes by. So I wanted to cache just the text and the time I made the update.

First I check the CSCache for my Twitter information:

string TwitterText = CSCache.Get("TwitterText") as string;
string TwitterTime = CSCache.Get("TwitterTime") as string;

If TwitterTime is null, then I grab the latest one status item from Twitter:

string TwitterPath = 
"http://www.twitter.com/statuses/usertimeline/2449901.xml?count=1";
WebClient webClient = new WebClient();
string TwitterXML = Encoding.ASCII.GetString(wClient.DownloadData(TwitterPath));

Once I have the TwitterXML string, I can parse it to get my text and time and I add that to the cache. From there I just converted the Javascript relative time code from Twitter's code to C#. Here is the full TwitterStatus.ascx:

<%@ Import namespace="System.Xml.XPath"%>
<%@ Import namespace="System.Net"%>
<%@ Import namespace="CommunityServer.Components"%>
<%@ Control Language="C#" ClassName="TwitterStatus" %>
<script runat=server language="C#">

protected int cacheTime;
public int CacheTime
{
get
{
return cacheTime;
}
set
{
cacheTime = value;
}
}
protected override void OnInit(EventArgs e)
{
string TwitterPath =
"http://www.twitter.com/statuses/user
timeline/2449901.xml?count=1";
string TwitterText = CSCache.Get(
"TwitterText") as string;
string TwitterTime = CSCache.Get(
"TwitterTime") as string;
string TwitterString =
"";

if (string.IsNullOrEmpty(TwitterTime))
{
string returnXML =
"";
WebClient wClient = new WebClient();
returnXML = Encoding.ASCII.GetString(wClient.DownloadData(TwitterPath));

XPathDocument doc = new XPathDocument(new System.IO.StringReader(returnXML));
XPathNavigator nav = doc.CreateNavigator();

//Get Twitter Time
XPathNodeIterator nodes = nav.Select(@
"/statuses/status/createdat");
nodes.MoveNext();
TwitterTime = nodes.Current.Value;
//Get Twitter Text
nodes = nav.Select(@
"/statuses/status/text");
nodes.MoveNext();
TwitterText = nodes.Current.Value;
string[] TimeValues = TwitterTime.Split(' ');
TwitterTime = TimeValues[1] +
" " + TimeValues[2]
+ " " + TimeValues[5] + " " + TimeValues[3] + " GMT";

CSCache.Insert(
"TwitterText",TwitterText, cacheTime);
CSCache.Insert(
"TwitterTime", TwitterTime, cacheTime);
}

DateTime twDate = Convert.ToDateTime(TwitterTime);

int secondsago = Convert.ToInt32(DateTime.Now.Subtract(twDate).TotalSeconds);
if (secondsago < 60)
TwitterString = "less than a minute ago";
else if (secondsago < 120)
TwitterString = "about a minute ago";
else if (secondsago < (45 * 60))
TwitterString = (secondsago / 60).ToString() + " minutes ago";
else if (secondsago < (90 * 60))
TwitterString = "about an hour ago";
else if (secondsago < (24 * 60 * 60))
TwitterString = "about " + (secondsago / 3600).ToString() + " hours ago";
else if (secondsago < (48 * 60 * 60))
TwitterString = "1 day ago";
else
TwitterString = (secondsago / 86400).ToString() +
" days ago";

TwitterTextLiteral.Text = TwitterText;
TwitterSinceLiteral.Text = TwitterString;

base.OnInit(e);
}

</script>

<div id=TwitterStatus>
According to <a href="http://twitter.com/Technovangelist" target=
blank>Twitter</a>
I am: <asp:Literal ID=TwitterTextLiteral runat=server></asp:Literal> as of
<asp:Literal ID=TwitterSinceLiteral runat=server></asp:Literal>
</div>

To use this I add the following at the top of my aspx page:

<%@ Register TagPrefix=MW TagName=TwitterStatus src="~/Themes/BW/Common/TwitterStatus.ascx" %>

And then add this where I want the TwitterStatus to go:

<MW:TwitterStatus runat=server CacheTime=300 />
This seems to be working for me for now. Let me know if you end up using it on your site. Or if you have any suggestions on improving this, I would love to hear that too.

Click to read more ...

Saturday
Jun162007

How To: Add Blog Posts to the CSCache with CS2007

After adding my new CS2007 theme, I wanted to take advantage of caching for my home page. There was some sample code in the default theme, but it wasn't exactly what I needed:

protected override void OnInit(EventArgs e)
{
List recentPosts = CSCache.Get("HomePageSearch-"
+ CurrentCSContext.User.RoleKey) as List;
if (recentPosts == null)
{
SearchQuery query = new SearchQuery();
query.StartDate = DateTime.Now.AddDays(-10);
query.EndDate = DateTime.Now.AddDays(1);
query.PageSize = 5;

recentPosts = CSSearch.Search(query).Posts;
CSCache.Insert("HomePageSearch-"
+ CurrentCSContext.User.RoleKey, recentPosts, CSCache.MinuteFactor * 5);
}
RecentPostList.DataSource = recentPosts;

base.OnInit(e);
}

This does a query for all additions to the site in the last 10 days, including images, comments, etc. But I wanted just blog entries. Looking around on the web, I found a discussion on this in the CS Forums. Using this as guide, I got exactly what I needed:

protected override void OnInit(EventArgs e)
{
int NumberOfBlogPosts = 3;
List recentPosts = CSCache.Get("HomePageBlog-"
+ CurrentCSContext.User.RoleKey) as List;

if (recentPosts == null)
{
List FilteredPosts = new List();
SearchQuery query = new SearchQuery();
query.PageSize = 20;
query.ApplicationTypes = new ApplicationType[] { ApplicationType.Weblog };
recentPosts = CSSearch.Search(query).Posts;

int PostNumber = 0;
foreach (IndexPost post in recentPosts)
if (PostNumber < NumberOfBlogPosts)
if (!post.Title.StartsWith("re:", true, null))
{
FilteredPosts.Add(post);
PostNumber++;
}


CSCache.Insert("HomePageBlog-"
+ CurrentCSContext.User.RoleKey, FilteredPosts, 300);
recentPosts = FilteredPosts;
}
RecentPostList.DataSource = recentPosts;
base.OnInit(e);
}

This finds 20 weblog posts, filters out everything that starts with "Re:", adds it to the cache, and uses it as a datasource for the blogpost list.

Click to read more ...