diff options
-rw-r--r-- | CMakeLists.txt | 11 | ||||
-rw-r--r-- | Gemfile | 5 | ||||
-rw-r--r-- | Gemfile.lock | 19 | ||||
-rw-r--r-- | database.cpp | 122 | ||||
-rw-r--r-- | database.h | 53 | ||||
-rw-r--r-- | lunatic.cpp | 50 | ||||
-rw-r--r-- | moons/README.md | 1 | ||||
-rw-r--r-- | moons/blue.png | bin | 0 -> 37618 bytes | |||
-rw-r--r-- | moons/brown.png | bin | 0 -> 38150 bytes | |||
-rw-r--r-- | moons/cyan.png | bin | 0 -> 33139 bytes | |||
-rw-r--r-- | moons/green.png | bin | 0 -> 36760 bytes | |||
-rw-r--r-- | moons/orange.png | bin | 0 -> 36465 bytes | |||
-rw-r--r-- | moons/pink.png | bin | 0 -> 30838 bytes | |||
-rw-r--r-- | moons/purple.png | bin | 0 -> 32331 bytes | |||
-rw-r--r-- | moons/red.png | bin | 0 -> 37165 bytes | |||
-rw-r--r-- | moons/star.png | bin | 0 -> 27829 bytes | |||
-rw-r--r-- | moons/white.png | bin | 0 -> 29878 bytes | |||
-rw-r--r-- | moons/yellow.png | bin | 0 -> 32939 bytes | |||
-rw-r--r-- | schema.sql | 32 | ||||
-rw-r--r-- | scrape.rb | 181 |
20 files changed, 442 insertions, 32 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 55a671b..00c0ad9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
@@ -5,12 +5,17 @@ add_subdirectory(vendor/libtwittercpp) | |||
5 | 5 | ||
6 | find_package(PkgConfig) | 6 | find_package(PkgConfig) |
7 | pkg_check_modules(yaml-cpp yaml-cpp REQUIRED) | 7 | pkg_check_modules(yaml-cpp yaml-cpp REQUIRED) |
8 | pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED) | ||
9 | pkg_check_modules(sqlite3 sqlite3>=3.8.3 REQUIRED) | ||
8 | 10 | ||
9 | include_directories( | 11 | include_directories( |
10 | vendor/libtwittercpp/src | 12 | vendor/libtwittercpp/src |
11 | ${yaml-cpp_INCLUDE_DIRS}) | 13 | ${yaml-cpp_INCLUDE_DIRS} |
14 | ${GraphicsMagick_INCLUDE_DIRS}) | ||
12 | 15 | ||
13 | add_executable(lunatic lunatic.cpp) | 16 | link_directories(${GraphicsMagick_LIBRARY_DIRS}) |
17 | |||
18 | add_executable(lunatic lunatic.cpp database.cpp) | ||
14 | set_property(TARGET lunatic PROPERTY CXX_STANDARD 11) | 19 | set_property(TARGET lunatic PROPERTY CXX_STANDARD 11) |
15 | set_property(TARGET lunatic PROPERTY CXX_STANDARD_REQUIRED ON) | 20 | set_property(TARGET lunatic PROPERTY CXX_STANDARD_REQUIRED ON) |
16 | target_link_libraries(lunatic ${yaml-cpp_LIBRARIES} twitter++) \ No newline at end of file | 21 | target_link_libraries(lunatic ${yaml-cpp_LIBRARIES} ${GraphicsMagick_LIBRARIES} ${sqlite3_LIBRARIES} twitter++) \ No newline at end of file |
diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..57a02a2 --- /dev/null +++ b/Gemfile | |||
@@ -0,0 +1,5 @@ | |||
1 | source 'https://rubygems.org' | ||
2 | |||
3 | gem 'sqlite3' | ||
4 | gem 'sequel' | ||
5 | gem 'nokogiri' | ||
diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..58ec121 --- /dev/null +++ b/Gemfile.lock | |||
@@ -0,0 +1,19 @@ | |||
1 | GEM | ||
2 | remote: https://rubygems.org/ | ||
3 | specs: | ||
4 | mini_portile2 (2.3.0) | ||
5 | nokogiri (1.8.2) | ||
6 | mini_portile2 (~> 2.3.0) | ||
7 | sequel (5.5.0) | ||
8 | sqlite3 (1.3.13) | ||
9 | |||
10 | PLATFORMS | ||
11 | ruby | ||
12 | |||
13 | DEPENDENCIES | ||
14 | nokogiri | ||
15 | sequel | ||
16 | sqlite3 | ||
17 | |||
18 | BUNDLED WITH | ||
19 | 1.16.1 | ||
diff --git a/database.cpp b/database.cpp new file mode 100644 index 0000000..f8b5016 --- /dev/null +++ b/database.cpp | |||
@@ -0,0 +1,122 @@ | |||
1 | #include "database.h" | ||
2 | #include <sqlite3.h> | ||
3 | #include <stdexcept> | ||
4 | |||
5 | database::database(std::string path) | ||
6 | { | ||
7 | if (sqlite3_open_v2( | ||
8 | path.c_str(), | ||
9 | &ppdb_, | ||
10 | SQLITE_OPEN_READONLY, | ||
11 | NULL) != SQLITE_OK) | ||
12 | { | ||
13 | // We still have to free the resources allocated. In the event that | ||
14 | // allocation failed, ppdb will be null and sqlite3_close_v2 will just | ||
15 | // ignore it. | ||
16 | std::string errmsg(sqlite3_errmsg(ppdb_)); | ||
17 | sqlite3_close_v2(ppdb_); | ||
18 | |||
19 | throw std::logic_error(errmsg); | ||
20 | } | ||
21 | } | ||
22 | |||
23 | database::database(database&& other) : database() | ||
24 | { | ||
25 | swap(*this, other); | ||
26 | } | ||
27 | |||
28 | database& database::operator=(database&& other) | ||
29 | { | ||
30 | swap(*this, other); | ||
31 | |||
32 | return *this; | ||
33 | } | ||
34 | |||
35 | void swap(database& first, database& second) | ||
36 | { | ||
37 | std::swap(first.ppdb_, second.ppdb_); | ||
38 | } | ||
39 | |||
40 | database::~database() | ||
41 | { | ||
42 | sqlite3_close_v2(ppdb_); | ||
43 | } | ||
44 | |||
45 | achievement database::getRandomAchievement() const | ||
46 | { | ||
47 | std::string queryString = "SELECT achievements.achievement_id, achievements.game_id, achievements.title, games.moon_image FROM achievements INNER JOIN games ON games.game_id = achievements.game_id ORDER BY RANDOM() LIMIT 1"; | ||
48 | |||
49 | sqlite3_stmt* ppstmt; | ||
50 | if (sqlite3_prepare_v2( | ||
51 | ppdb_, | ||
52 | queryString.c_str(), | ||
53 | queryString.length(), | ||
54 | &ppstmt, | ||
55 | NULL) != SQLITE_OK) | ||
56 | { | ||
57 | std::string errorMsg = sqlite3_errmsg(ppdb_); | ||
58 | sqlite3_finalize(ppstmt); | ||
59 | |||
60 | throw std::logic_error(errorMsg); | ||
61 | } | ||
62 | |||
63 | if (sqlite3_step(ppstmt) != SQLITE_ROW) | ||
64 | { | ||
65 | std::string errorMsg = sqlite3_errmsg(ppdb_); | ||
66 | sqlite3_finalize(ppstmt); | ||
67 | |||
68 | throw std::logic_error(errorMsg); | ||
69 | } | ||
70 | |||
71 | achievement result; | ||
72 | |||
73 | result.achievementId = sqlite3_column_int(ppstmt, 0); | ||
74 | result.gameId = sqlite3_column_int(ppstmt, 1); | ||
75 | result.title = reinterpret_cast<const char*>(sqlite3_column_text(ppstmt, 2)); | ||
76 | result.moonImage = reinterpret_cast<const char*>(sqlite3_column_text(ppstmt, 3)); | ||
77 | |||
78 | sqlite3_finalize(ppstmt); | ||
79 | |||
80 | return result; | ||
81 | } | ||
82 | |||
83 | std::string database::getRandomImageForGame(int gameId) const | ||
84 | { | ||
85 | std::string queryString = "SELECT filename FROM images WHERE game_id = ? ORDER BY RANDOM() LIMIT 1"; | ||
86 | |||
87 | sqlite3_stmt* ppstmt; | ||
88 | if (sqlite3_prepare_v2( | ||
89 | ppdb_, | ||
90 | queryString.c_str(), | ||
91 | queryString.length(), | ||
92 | &ppstmt, | ||
93 | NULL) != SQLITE_OK) | ||
94 | { | ||
95 | std::string errorMsg = sqlite3_errmsg(ppdb_); | ||
96 | sqlite3_finalize(ppstmt); | ||
97 | |||
98 | throw std::logic_error(errorMsg); | ||
99 | } | ||
100 | |||
101 | if (sqlite3_bind_int(ppstmt, 1, gameId) != SQLITE_OK) | ||
102 | { | ||
103 | std::string errorMsg = sqlite3_errmsg(ppdb_); | ||
104 | sqlite3_finalize(ppstmt); | ||
105 | |||
106 | throw std::logic_error(errorMsg); | ||
107 | } | ||
108 | |||
109 | if (sqlite3_step(ppstmt) != SQLITE_ROW) | ||
110 | { | ||
111 | std::string errorMsg = sqlite3_errmsg(ppdb_); | ||
112 | sqlite3_finalize(ppstmt); | ||
113 | |||
114 | throw std::logic_error(errorMsg); | ||
115 | } | ||
116 | |||
117 | std::string result = reinterpret_cast<const char*>(sqlite3_column_text(ppstmt, 0)); | ||
118 | |||
119 | sqlite3_finalize(ppstmt); | ||
120 | |||
121 | return result; | ||
122 | } | ||
diff --git a/database.h b/database.h new file mode 100644 index 0000000..560eeda --- /dev/null +++ b/database.h | |||
@@ -0,0 +1,53 @@ | |||
1 | #ifndef DATABASE_H_75C3CE0F | ||
2 | #define DATABASE_H_75C3CE0F | ||
3 | |||
4 | #include <string> | ||
5 | |||
6 | struct sqlite3; | ||
7 | |||
8 | struct achievement { | ||
9 | int achievementId; | ||
10 | int gameId; | ||
11 | std::string title; | ||
12 | std::string moonImage; | ||
13 | }; | ||
14 | |||
15 | class database { | ||
16 | public: | ||
17 | |||
18 | // Constructor | ||
19 | |||
20 | explicit database(std::string path); | ||
21 | |||
22 | // Disable copying | ||
23 | |||
24 | database(const database& other) = delete; | ||
25 | database& operator=(const database& other) = delete; | ||
26 | |||
27 | // Move constructor and move assignment | ||
28 | |||
29 | database(database&& other); | ||
30 | database& operator=(database&& other); | ||
31 | |||
32 | // Swap | ||
33 | |||
34 | friend void swap(database& first, database& second); | ||
35 | |||
36 | // Destructor | ||
37 | |||
38 | ~database(); | ||
39 | |||
40 | // Accessors | ||
41 | |||
42 | achievement getRandomAchievement() const; | ||
43 | |||
44 | std::string getRandomImageForGame(int gameId) const; | ||
45 | |||
46 | private: | ||
47 | |||
48 | database() = default; | ||
49 | |||
50 | sqlite3* ppdb_ = nullptr; | ||
51 | }; | ||
52 | |||
53 | #endif /* end of include guard: DATABASE_H_75C3CE0F */ | ||
diff --git a/lunatic.cpp b/lunatic.cpp index 09dcc41..ca3140a 100644 --- a/lunatic.cpp +++ b/lunatic.cpp | |||
@@ -5,6 +5,8 @@ | |||
5 | #include <twitter.h> | 5 | #include <twitter.h> |
6 | #include <chrono> | 6 | #include <chrono> |
7 | #include <thread> | 7 | #include <thread> |
8 | #include <Magick++.h> | ||
9 | #include "database.h" | ||
8 | 10 | ||
9 | int main(int argc, char** argv) | 11 | int main(int argc, char** argv) |
10 | { | 12 | { |
@@ -14,6 +16,8 @@ int main(int argc, char** argv) | |||
14 | return -1; | 16 | return -1; |
15 | } | 17 | } |
16 | 18 | ||
19 | Magick::InitializeMagick(nullptr); | ||
20 | |||
17 | std::string configfile(argv[1]); | 21 | std::string configfile(argv[1]); |
18 | YAML::Node config = YAML::LoadFile(configfile); | 22 | YAML::Node config = YAML::LoadFile(configfile); |
19 | 23 | ||
@@ -23,7 +27,9 @@ int main(int argc, char** argv) | |||
23 | auth.setAccessKey(config["access_key"].as<std::string>()); | 27 | auth.setAccessKey(config["access_key"].as<std::string>()); |
24 | auth.setAccessSecret(config["access_secret"].as<std::string>()); | 28 | auth.setAccessSecret(config["access_secret"].as<std::string>()); |
25 | 29 | ||
26 | twitter::client client(auth); | 30 | //twitter::client client(auth); |
31 | |||
32 | database db(config["database"].as<std::string>()); | ||
27 | 33 | ||
28 | std::random_device randomDevice; | 34 | std::random_device randomDevice; |
29 | std::mt19937 rng(randomDevice()); | 35 | std::mt19937 rng(randomDevice()); |
@@ -32,6 +38,40 @@ int main(int argc, char** argv) | |||
32 | { | 38 | { |
33 | std::cout << "Generating tweet" << std::endl; | 39 | std::cout << "Generating tweet" << std::endl; |
34 | 40 | ||
41 | achievement ach = db.getRandomAchievement(); | ||
42 | std::string imageName = db.getRandomImageForGame(ach.gameId); | ||
43 | std::string imagePath = config["images"].as<std::string>() | ||
44 | + "/" + imageName; | ||
45 | |||
46 | |||
47 | try | ||
48 | { | ||
49 | Magick::Image image; | ||
50 | image.read(imagePath); | ||
51 | image.transform("1600x900"); | ||
52 | image.scale("160x90"); | ||
53 | image.scale("1600x900"); | ||
54 | image.magick("png"); | ||
55 | image.write("output.png"); | ||
56 | } catch (const Magick::WarningCoder& ex) | ||
57 | { | ||
58 | // Ok | ||
59 | } | ||
60 | |||
61 | |||
62 | |||
63 | |||
64 | |||
65 | |||
66 | |||
67 | |||
68 | |||
69 | |||
70 | |||
71 | |||
72 | |||
73 | /* | ||
74 | |||
35 | // Reload achievements list every time in case it has been updated | 75 | // Reload achievements list every time in case it has been updated |
36 | std::vector<std::string> achievements; | 76 | std::vector<std::string> achievements; |
37 | std::ifstream datafile(config["achievements"].as<std::string>()); | 77 | std::ifstream datafile(config["achievements"].as<std::string>()); |
@@ -53,7 +93,7 @@ int main(int argc, char** argv) | |||
53 | } | 93 | } |
54 | 94 | ||
55 | std::uniform_int_distribution<int> dist(0, achievements.size() - 1); | 95 | std::uniform_int_distribution<int> dist(0, achievements.size() - 1); |
56 | std::string achievement = achievements[dist(rng)]; | 96 | std::string achievement = achievements[dist(rng)];*/ |
57 | 97 | ||
58 | std::string header; | 98 | std::string header; |
59 | if (std::bernoulli_distribution(1.0 / 50.0)(rng)) | 99 | if (std::bernoulli_distribution(1.0 / 50.0)(rng)) |
@@ -63,16 +103,16 @@ int main(int argc, char** argv) | |||
63 | header = "YOU GOT A MOON!"; | 103 | header = "YOU GOT A MOON!"; |
64 | } | 104 | } |
65 | 105 | ||
66 | std::string action = header + "\n" + achievement; | 106 | std::string action = header + "\n" + ach.title; |
67 | action.resize(140); | 107 | action.resize(140); |
68 | 108 | ||
69 | try | 109 | /*try |
70 | { | 110 | { |
71 | client.updateStatus(action); | 111 | client.updateStatus(action); |
72 | } catch (const twitter::twitter_error& e) | 112 | } catch (const twitter::twitter_error& e) |
73 | { | 113 | { |
74 | std::cout << "Twitter error: " << e.what() << std::endl; | 114 | std::cout << "Twitter error: " << e.what() << std::endl; |
75 | } | 115 | }*/ |
76 | 116 | ||
77 | std::cout << action << std::endl; | 117 | std::cout << action << std::endl; |
78 | std::cout << "Waiting" << std::endl; | 118 | std::cout << "Waiting" << std::endl; |
diff --git a/moons/README.md b/moons/README.md new file mode 100644 index 0000000..876f0db --- /dev/null +++ b/moons/README.md | |||
@@ -0,0 +1 @@ | |||
The images in this directory are originally from the game Super Mario Odyssey, which is copyrighted by Nintendo. \ No newline at end of file | |||
diff --git a/moons/blue.png b/moons/blue.png new file mode 100644 index 0000000..5e4ca02 --- /dev/null +++ b/moons/blue.png | |||
Binary files differ | |||
diff --git a/moons/brown.png b/moons/brown.png new file mode 100644 index 0000000..6a31b16 --- /dev/null +++ b/moons/brown.png | |||
Binary files differ | |||
diff --git a/moons/cyan.png b/moons/cyan.png new file mode 100644 index 0000000..8984126 --- /dev/null +++ b/moons/cyan.png | |||
Binary files differ | |||
diff --git a/moons/green.png b/moons/green.png new file mode 100644 index 0000000..5e7d311 --- /dev/null +++ b/moons/green.png | |||
Binary files differ | |||
diff --git a/moons/orange.png b/moons/orange.png new file mode 100644 index 0000000..06ca595 --- /dev/null +++ b/moons/orange.png | |||
Binary files differ | |||
diff --git a/moons/pink.png b/moons/pink.png new file mode 100644 index 0000000..3b1521b --- /dev/null +++ b/moons/pink.png | |||
Binary files differ | |||
diff --git a/moons/purple.png b/moons/purple.png new file mode 100644 index 0000000..9eeacf9 --- /dev/null +++ b/moons/purple.png | |||
Binary files differ | |||
diff --git a/moons/red.png b/moons/red.png new file mode 100644 index 0000000..2c0ef77 --- /dev/null +++ b/moons/red.png | |||
Binary files differ | |||
diff --git a/moons/star.png b/moons/star.png new file mode 100644 index 0000000..29263d5 --- /dev/null +++ b/moons/star.png | |||
Binary files differ | |||
diff --git a/moons/white.png b/moons/white.png new file mode 100644 index 0000000..fae666b --- /dev/null +++ b/moons/white.png | |||
Binary files differ | |||
diff --git a/moons/yellow.png b/moons/yellow.png new file mode 100644 index 0000000..8c012df --- /dev/null +++ b/moons/yellow.png | |||
Binary files differ | |||
diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..61fdc45 --- /dev/null +++ b/schema.sql | |||
@@ -0,0 +1,32 @@ | |||
1 | CREATE TABLE `profiles` ( | ||
2 | `profile_id` INTEGER PRIMARY KEY, | ||
3 | `profile_path` VARCHAR(255) NOT NULL | ||
4 | ); | ||
5 | |||
6 | CREATE UNIQUE INDEX `profile_by_path` ON `profiles`(`profile_path`); | ||
7 | |||
8 | CREATE TABLE `games` ( | ||
9 | `game_id` INTEGER PRIMARY KEY, | ||
10 | `steam_appid` INTEGER NOT NULL, | ||
11 | `moon_image` VARCHAR(255) NOT NULL | ||
12 | ); | ||
13 | |||
14 | CREATE UNIQUE INDEX `game_by_appid` ON `games`(`steam_appid`); | ||
15 | |||
16 | CREATE TABLE `achievements` ( | ||
17 | `achievement_id` INTEGER PRIMARY KEY, | ||
18 | `game_id` INTEGER NOT NULL, | ||
19 | `title` VARCHAR(255) NOT NULL | ||
20 | ); | ||
21 | |||
22 | CREATE TABLE `dids` ( | ||
23 | `profile_id` INTEGER NOT NULL, | ||
24 | `achievement_id` INTEGER NOT NULL, | ||
25 | `achieved_at` DATETIME NOT NULL | ||
26 | ); | ||
27 | |||
28 | CREATE TABLE `images` ( | ||
29 | `image_id` INTEGER PRIMARY KEY, | ||
30 | `game_id` INTEGER NOT NULL, | ||
31 | `filename` VARCHAR(255) NOT NULL | ||
32 | ); | ||
diff --git a/scrape.rb b/scrape.rb index a28f4c5..6f3a8e4 100644 --- a/scrape.rb +++ b/scrape.rb | |||
@@ -1,42 +1,175 @@ | |||
1 | require 'json' | 1 | require 'json' |
2 | require 'nokogiri' | ||
3 | require 'open-uri' | 2 | require 'open-uri' |
4 | require 'yaml' | 3 | require 'yaml' |
5 | 4 | ||
6 | config = YAML.load(open(ARGV[0])) | 5 | require 'rubygems' |
7 | usernames = config["usernames"] | 6 | require 'bundler/setup' |
7 | Bundler.require :default | ||
8 | 8 | ||
9 | achieves = usernames.map do |username| | 9 | @config = YAML.load(open(ARGV[0])) |
10 | page = Nokogiri::HTML(open("https://steamcommunity.com/#{username}/games/?tab=all")) | 10 | db_existed = File.exists?(@config["database"]) |
11 | db = Sequel.connect("sqlite://" + @config["database"]) | ||
12 | |||
13 | if ARGV[1] == "init" | ||
14 | if db_existed | ||
15 | raise "Datafile already exists" | ||
16 | end | ||
17 | |||
18 | schema = File.read("schema.sql") | ||
19 | |||
20 | db.run schema | ||
21 | |||
22 | puts "Initialized datafile" | ||
23 | |||
24 | exit | ||
25 | end | ||
26 | |||
27 | class Profile < Sequel::Model | ||
28 | many_to_many :achievements, join_table: :dids | ||
29 | end | ||
30 | |||
31 | class Game < Sequel::Model | ||
32 | one_to_many :achievements | ||
33 | one_to_many :images | ||
34 | end | ||
35 | |||
36 | class Achievement < Sequel::Model | ||
37 | many_to_one :game | ||
38 | many_to_many :profiles, join_table: :dids | ||
39 | end | ||
40 | |||
41 | class Image < Sequel::Model | ||
42 | many_to_one :game | ||
43 | end | ||
44 | |||
45 | class Did < Sequel::Model | ||
46 | many_to_one :profile | ||
47 | many_to_one :achievement | ||
48 | end | ||
49 | |||
50 | @moonimgs = Dir.entries(@config["moon_images"]).select do |img| | ||
51 | img.end_with? ".png" | ||
52 | end | ||
53 | |||
54 | def scrape_profile(profile, full) | ||
55 | if full | ||
56 | url = "https://steamcommunity.com/#{profile.profile_path}/games/?tab=all" | ||
57 | else | ||
58 | url = "https://steamcommunity.com/#{profile.profile_path}/games/" | ||
59 | end | ||
60 | |||
61 | page = Nokogiri::HTML(open(url)) | ||
11 | script = page.css(".responsive_page_template_content script").text[18..-1] | 62 | script = page.css(".responsive_page_template_content script").text[18..-1] |
12 | data = JSON.parse(script[0..script.index(";\r\n\t\t")-1]) | 63 | data = JSON.parse(script[0..script.index(";\r\n\t\t")-1]) |
13 | ids = data.map { |d| d["appid"] } | 64 | ids = data.map { |d| d["appid"] } |
14 | 65 | ||
15 | index = 0 | 66 | index = 0 |
16 | ids.map do |id| | 67 | ids.each do |id| |
17 | index += 1 | 68 | index += 1 |
18 | puts "#{username} - #{index}/#{ids.count}" | 69 | puts "#{profile.profile_path} - #{index}/#{ids.count}" |
19 | 70 | ||
20 | achsp = Nokogiri::HTML(open("https://steamcommunity.com/#{username}/stats/#{id}/")) | 71 | achsp = Nokogiri::HTML( |
21 | achsp.css(".achieveTxt .achieveUnlockTime + h3").map { |d| d.text } | 72 | open("https://steamcommunity.com/#{profile.profile_path}/stats/#{id}/")) |
22 | end | ||
23 | end.flatten | ||
24 | 73 | ||
25 | if File.exists?(config["achievements"]) | 74 | achsp.css(".achieveTxt").each do |node| |
26 | already = File.read(config["achievements"]).split("\n") | 75 | unless node.css(".achieveUnlockTime").empty? |
27 | all_achieves = achieves + already | 76 | if Game.where(steam_appid: id).count > 0 |
28 | else | 77 | game = Game.where(steam_appid: id).first |
29 | all_achieves = achieves | 78 | else |
30 | end | 79 | moon_index = Random.rand(@moonimgs.size) |
80 | |||
81 | game = Game.new(steam_appid: id, moon_image: @moonimgs[moon_index]) | ||
82 | game.save | ||
83 | |||
84 | storepage = Nokogiri::HTML( | ||
85 | open("http://store.steampowered.com/app/#{id}")) | ||
86 | |||
87 | img_id = 0 | ||
88 | storepage.css(".highlight_screenshot_link").each do |node| | ||
89 | begin | ||
90 | imagepage = open(node["href"]).read | ||
91 | |||
92 | img_id += 1 | ||
93 | img_filename = "#{id}-#{img_id}.jpg" | ||
94 | img_filepath = File.join(@config["images"], img_filename) | ||
95 | |||
96 | img_file = File.open(img_filepath, "w") | ||
97 | img_file.write(imagepage) | ||
98 | img_file.close | ||
99 | |||
100 | image = Image.new(game: game, filename: img_filename) | ||
101 | image.save | ||
102 | rescue OpenURI::HTTPError | ||
103 | puts "Error downloading an image" | ||
104 | end | ||
105 | |||
106 | sleep 2 | ||
107 | end | ||
108 | end | ||
109 | |||
110 | title = node.at_css("h3").text | ||
31 | 111 | ||
32 | all_achieves.sort! | 112 | if game.achievements_dataset.where(title: title).count > 0 |
33 | all_achieves.uniq! | 113 | achievement = game.achievements_dataset.where(title: title).first |
114 | else | ||
115 | achievement = Achievement.new(game: game, title: title) | ||
116 | achievement.save | ||
117 | end | ||
34 | 118 | ||
35 | if config.key? "blacklist" | 119 | unless Did.where(profile: profile, achievement: achievement).count > 0 |
36 | blacklist = File.read(config["blacklist"]).split("\n") | 120 | begin |
37 | all_achieves.reject! { |l| blacklist.include? l } | 121 | unlock = DateTime.strptime( |
122 | node.css(".achieveUnlockTime").text.lstrip[9..-1], | ||
123 | "%b %d, %Y @ %l:%M%P") | ||
124 | rescue ArgumentError | ||
125 | unlock = DateTime.strptime( | ||
126 | node.css(".achieveUnlockTime").text.lstrip[9..-1], | ||
127 | "%b %d @ %l:%M%P") | ||
128 | end | ||
129 | |||
130 | join = Did.new( | ||
131 | profile: profile, | ||
132 | achievement: achievement, | ||
133 | achieved_at: unlock) | ||
134 | join.save | ||
135 | end | ||
136 | end | ||
137 | end | ||
138 | end | ||
38 | end | 139 | end |
39 | 140 | ||
40 | File.open(config["achievements"], "w") do |f| | 141 | if ARGV[1] == "add" |
41 | f << all_achieves.join("\n") | 142 | userpath = ARGV[2] |
143 | |||
144 | if Profile.where(profile_path: userpath).count > 0 | ||
145 | raise "Profile " + userpath + " already exists" | ||
146 | end | ||
147 | |||
148 | profile = Profile.new(profile_path: userpath) | ||
149 | profile.save | ||
150 | |||
151 | scrape_profile profile, true | ||
152 | elsif ARGV[1] == "update" | ||
153 | if ARGV.size == 3 | ||
154 | scrape_profile Profile.where(profile_path: ARGV[2]).first, false | ||
155 | else | ||
156 | Profile.all.each do |profile| | ||
157 | scrape_profile profile, false | ||
158 | end | ||
159 | end | ||
160 | elsif ARGV[1] == "full" | ||
161 | if ARGV.size == 3 | ||
162 | scrape_profile Profile.where(profile_path: ARGV[2]).first, true | ||
163 | else | ||
164 | Profile.all.each do |profile| | ||
165 | scrape_profile profile, true | ||
166 | end | ||
167 | end | ||
168 | elsif ARGV[1] == "recolor" | ||
169 | Game.all.each do |game| | ||
170 | moon_index = Random.rand(@moonimgs.size) | ||
171 | |||
172 | game.moon_image = @moonimgs[moon_index] | ||
173 | game.save | ||
174 | end | ||
42 | end | 175 | end |