This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

Rewrite in Java.

  • Loading branch information...
1 parent b6cd2c9 commit 7cd9ea3394429ac53b403272028aeac3dc65ac22 @codahale committed Oct 27, 2011
View
@@ -1,2 +1,3 @@
-.last_checked
-.credentials
+data
+target
+*.properties
View
@@ -1,9 +1,4 @@
-test:
- ./sfearthquakes.rb -v
-
deploy:
- ssh codahale.com "cd ~/sfearthquakes && git pull"
-
-token:
- ./token_dance.rb
-
+ mvn verify
+ scp target/sfearthquakes-*.jar codahale.com:~/sfearthquakes/sfearthquakes.jar
+ scp sfearthquakes.properties codahale.com:~/sfearthquakes/sfearthquakes.properties
View
88 pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>com.codahale</groupId>
+ <artifactId>sfearthquakes</artifactId>
+ <version>1.0-SNAPSHOT</version>
+
+ <repositories>
+ <repository>
+ <id>twitter4j.org</id>
+ <url>http://twitter4j.org/maven2</url>
+ </repository>
+ </repositories>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jsoup</groupId>
+ <artifactId>jsoup</artifactId>
+ <version>1.6.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>10.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jackson</groupId>
+ <artifactId>jackson-mapper-asl</artifactId>
+ <version>1.9.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.twitter4j</groupId>
+ <artifactId>twitter4j-core</artifactId>
+ <version>2.2.5</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ <encoding>UTF-8</encoding>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.5</version>
+ <configuration>
+ <encoding>UTF-8</encoding>
+ <outputDirectory/>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>1.4</version>
+ <configuration>
+ <createDependencyReducedPom>true</createDependencyReducedPom>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <mainClass>com.codahale.sfearthquakes.SfEarthquakes</mainClass>
+ </transformer>
+ </transformers>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
View
@@ -1,154 +0,0 @@
-#!/usr/bin/env ruby
-require 'rubygems'
-require 'hpricot'
-require 'twitter_oauth'
-require 'grackle'
-require 'yaml'
-require 'bitly'
-
-LAST_CHECKED_FILE = File.join(File.dirname(__FILE__), ".last_checked")
-CREDENTIALS_FILE = File.join(File.dirname(__FILE__), ".credentials")
-
-class Earthquake < Struct.new(:url, :location, :magnitude, :latitude, :longitude, :timestamp)
- EXPRESSIONS = [
- "Wat.", # > 0.0 Richter
- "Huh.", # > 1.0 Richter
- "Meh.", # > 2.0 Richter
- "Yawn.", # > 3.0 Richter
- "Hey.", # > 4.0 Richter
- "Whoah.", # > 5.0 Richter
- "DUDE.", # > 6.0 Richter
- "HOLY CRAP.", # > 7.0 Richter
- "MY GOD.", # > 8.0 Richter
- "GOODBYE." # > 9.0 Richter
- ]
-
- def expression
- EXPRESSIONS[magnitude.floor]
- end
-
- def message(bitly)
- "#{expression} A #{magnitude} quake just happened #{location}: #{bitly.shorten(url).short_url}".gsub(/[\s]+/, " ")
- end
-
- def noticable?
- magnitude >= 3.0
- end
-end
-
-class Scraper
- class ParsingError < Exception; end
- USGS_URL = "http://earthquake.usgs.gov/earthquakes/recenteqscanv/FaultMaps/San_Francisco_eqs.html"
-
- attr_reader :page, :quakes
-
- def initialize
- @page = Net::HTTP.get(URI.parse(USGS_URL))
- @doc = Hpricot(@page)
- @quakes = parse(@doc)
- end
-
-private
-
- def parse(doc)
- quakes = []
-
- magnitudes = doc.search("td.magnitude").map { |td| td.inner_html.to_f }
- dates = doc.search("td.event_date").map { |td| td.inner_html }
- times = doc.search("td.event_time").map { |td| td.inner_html }
- latitudes = doc.search("td.latitude").map { |td| td.inner_html }
- longitudes = doc.search("td.longitude").map { |td| td.inner_html }
- links = doc.search("td.location a").map { |a| "http://earthquake.usgs.gov" + a["href"] }
- locations = doc.search("td.location a").map { |a| a.inner_html }
-
- if [magnitudes.size, dates.size, times.size, latitudes.size,
- longitudes.size, links.size, locations.size].uniq.size == 1 && magnitudes.size > 0
-
- data = magnitudes.zip(dates).zip(times).zip(latitudes).zip(longitudes).zip(links).zip(locations).map { |t| t.flatten }
- for (magnitude, date, time, latitude, longitude, link, location) in data
- timestamp = Time.parse("#{date} #{time} -800")
-
- quakes << Earthquake.new(link, location, magnitude, latitude, longitude, timestamp)
- end
- else
- raise ParsingError.new("Error parsing document:\n#{doc.inner_html}")
- end
- quakes
- end
-
-end
-
-if __FILE__ == $0
- begin
- verbose = ARGV.include?("-v")
- publishing = ARGV.include?("--publish")
-
- credentials = if File.exist?(CREDENTIALS_FILE)
- YAML.load_file(CREDENTIALS_FILE)
- else
- raise "Unable to find credentials: #{CREDENTIALS_FILE}"
- end
-
- scraper = Scraper.new
- if verbose
- puts "Found quakes:"
- p scraper.quakes
- end
-
- last_checked_at = if File.exist?(LAST_CHECKED_FILE)
- Time.parse(File.read(LAST_CHECKED_FILE))
- else
- Time.now - (60 * 60) # 1 hour ago
- end
-
- if verbose
- puts "Last checked at #{last_checked_at}"
- end
-
- new_quakes = scraper.quakes.select { |q| q.timestamp > last_checked_at && q.noticable? }.sort_by { |q| q.timestamp }
-
- if verbose
- puts "#{new_quakes.size} new quakes to tweet about"
- end
-
- hidden_quakes = scraper.quakes.select { |q| q.timestamp > last_checked_at && !q.noticable? }
- if !hidden_quakes.empty?
- puts "Hidden quakes:"
- for quake in hidden_quakes
- puts "* #{quake.magnitude} @ #{quake.url}"
- end
- end
-
- client = Grackle::Client.new(
- :auth => credentials[:twitter].merge(:type => :oauth),
- :headers => {'User-Agent' => "SFEarthqakes/1.0 Grackle/#{Grackle::VERSION}"}
- )
-
- Bitly.use_api_version_3
- bitly = Bitly.new(credentials[:bitly][:username], credentials[:bitly][:api_key])
-
- for quake in new_quakes
- message = quake.message(bitly)
-
- if verbose
- if !publishing
- puts "(Not Really) Tweeting: #{message}"
- else
- puts "Tweeting: #{message}"
- end
- end
-
- if publishing
- client.statuses.update!(:status => message)
- end
- end
-
-
- # update the last time we ran this
- File.open(LAST_CHECKED_FILE, "w") do |f|
- f << (new_quakes.last ? new_quakes.last.timestamp.to_s : Time.now.to_s)
- end
- rescue Timeout::Error
- # I don't care about timeouts
- end
-end
@@ -0,0 +1,52 @@
+package com.codahale.sfearthquakes;
+
+import com.google.common.collect.ImmutableList;
+
+import static com.google.common.primitives.Ints.min;
+import static java.lang.Math.floor;
+import static java.lang.Math.round;
+import static java.lang.String.format;
+
+public class Announcement {
+ private static final ImmutableList<String> PREFIXES = ImmutableList.of(
+ "Meh.", // > 0.0 Richter
+ "Meh.", // > 1.0 Richter
+ "Meh.", // > 2.0 Richter
+ "Huh.", // > 3.0 Richter
+ "Hey.", // > 4.0 Richter
+ "Whoah.", // > 5.0 Richter
+ "DUDE.", // > 6.0 Richter
+ "HOLY CRAP.", // > 7.0 Richter
+ "MY GOD.", // > 8.0 Richter
+ "GOODBYE." // > 9.0 Richter
+ );
+
+ private static String prefix(double magnitude) {
+ return PREFIXES.get(min(10, (int) round(floor(magnitude))));
+ }
+
+ private final Earthquake earthquake;
+
+ public Announcement(Earthquake earthquake) {
+ this.earthquake = earthquake;
+ }
+
+ public boolean isTweetable() {
+ return earthquake.getMagnitude() >= 3.0;
+ }
+
+ @Override
+ public String toString() {
+ return format("%s A %1.1f quake just happened %s: %s",
+ prefix(earthquake.getMagnitude()), earthquake.getMagnitude(),
+ earthquake.getLocation(), earthquake.getURI());
+ }
+
+ public double getLongitude() {
+ return earthquake.getLongitude();
+ }
+
+ public double getLatitude() {
+ return earthquake.getLatitude();
+ }
+}
@@ -0,0 +1,26 @@
+package com.codahale.sfearthquakes;
+
+import twitter4j.*;
+import twitter4j.auth.AccessToken;
+
+public class Announcer {
+ private final Twitter twitter;
+
+ public Announcer(String consumerKey,
+ String consumerSecret,
+ String accessToken,
+ String accessSecret) {
+ this.twitter = TwitterFactory.getSingleton();
+ twitter.setOAuthConsumer(consumerKey, consumerSecret);
+ twitter.setOAuthAccessToken(new AccessToken(accessToken, accessSecret));
+ }
+
+ public void tweet(Announcement announcement) throws TwitterException {
+ final StatusUpdate update = new StatusUpdate(announcement.toString());
+ final GeoLocation location = new GeoLocation(announcement.getLatitude(),
+ announcement.getLongitude());
+ update.setLocation(location);
+ update.setDisplayCoordinates(true);
+ twitter.updateStatus(update);
+ }
+}
@@ -0,0 +1,35 @@
+package com.codahale.sfearthquakes;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class Configuration {
+ private final Properties properties;
+
+ public Configuration(File configFile) throws IOException {
+ this.properties = new Properties();
+ final FileInputStream input = new FileInputStream(configFile);
+ properties.load(input);
+ input.close();
+ }
+
+ public String getTwitterConsumerKey() {
+ return checkNotNull(properties.getProperty("twitter.consumerKey"));
+ }
+
+ public String getTwitterConsumerSecret() {
+ return checkNotNull(properties.getProperty("twitter.consumerSecret"));
+ }
+
+ public String getTwitterAccessToken() {
+ return checkNotNull(properties.getProperty("twitter.accessToken"));
+ }
+
+ public String getTwitterAccessSecret() {
+ return checkNotNull(properties.getProperty("twitter.accessSecret"));
+ }
+}
Oops, something went wrong.

0 comments on commit 7cd9ea3

Please sign in to comment.