about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt5
-rw-r--r--database.cpp217
-rw-r--r--database.h62
-rw-r--r--lunatic.cpp105
m---------vendor/hkutil0
6 files changed, 74 insertions, 318 deletions
diff --git a/.gitmodules b/.gitmodules index b7a969a..79e1759 100644 --- a/.gitmodules +++ b/.gitmodules
@@ -1,3 +1,6 @@
1[submodule "vendor/libtwittercpp"] 1[submodule "vendor/libtwittercpp"]
2 path = vendor/libtwittercpp 2 path = vendor/libtwittercpp
3 url = git@github.com:hatkirby/libtwittercpp 3 url = git@github.com:hatkirby/libtwittercpp
4[submodule "vendor/hkutil"]
5 path = vendor/hkutil
6 url = git@github.com:hatkirby/hkutil
diff --git a/CMakeLists.txt b/CMakeLists.txt index 00c0ad9..d9a289c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -11,11 +11,12 @@ pkg_check_modules(sqlite3 sqlite3>=3.8.3 REQUIRED)
11include_directories( 11include_directories(
12 vendor/libtwittercpp/src 12 vendor/libtwittercpp/src
13 ${yaml-cpp_INCLUDE_DIRS} 13 ${yaml-cpp_INCLUDE_DIRS}
14 ${GraphicsMagick_INCLUDE_DIRS}) 14 ${GraphicsMagick_INCLUDE_DIRS}
15 vendor/hkutil)
15 16
16link_directories(${GraphicsMagick_LIBRARY_DIRS}) 17link_directories(${GraphicsMagick_LIBRARY_DIRS})
17 18
18add_executable(lunatic lunatic.cpp database.cpp) 19add_executable(lunatic lunatic.cpp)
19set_property(TARGET lunatic PROPERTY CXX_STANDARD 11) 20set_property(TARGET lunatic PROPERTY CXX_STANDARD 11)
20set_property(TARGET lunatic PROPERTY CXX_STANDARD_REQUIRED ON) 21set_property(TARGET lunatic PROPERTY CXX_STANDARD_REQUIRED ON)
21target_link_libraries(lunatic ${yaml-cpp_LIBRARIES} ${GraphicsMagick_LIBRARIES} ${sqlite3_LIBRARIES} twitter++) \ No newline at end of file 22target_link_libraries(lunatic ${yaml-cpp_LIBRARIES} ${GraphicsMagick_LIBRARIES} ${sqlite3_LIBRARIES} twitter++) \ No newline at end of file
diff --git a/database.cpp b/database.cpp deleted file mode 100644 index 19ba0e0..0000000 --- a/database.cpp +++ /dev/null
@@ -1,217 +0,0 @@
1#include "database.h"
2#include <sqlite3.h>
3#include <stdexcept>
4#include <sstream>
5#include <iomanip>
6
7database::database(std::string path)
8{
9 if (sqlite3_open_v2(
10 path.c_str(),
11 &ppdb_,
12 SQLITE_OPEN_READONLY,
13 NULL) != SQLITE_OK)
14 {
15 // We still have to free the resources allocated. In the event that
16 // allocation failed, ppdb will be null and sqlite3_close_v2 will just
17 // ignore it.
18 std::string errmsg(sqlite3_errmsg(ppdb_));
19 sqlite3_close_v2(ppdb_);
20
21 throw std::logic_error(errmsg);
22 }
23}
24
25database::database(database&& other) : database()
26{
27 swap(*this, other);
28}
29
30database& database::operator=(database&& other)
31{
32 swap(*this, other);
33
34 return *this;
35}
36
37void swap(database& first, database& second)
38{
39 std::swap(first.ppdb_, second.ppdb_);
40}
41
42database::~database()
43{
44 sqlite3_close_v2(ppdb_);
45}
46
47achievement database::getRandomAchievement() const
48{
49 std::string queryString = "SELECT achievements.achievement_id, achievements.game_id, achievements.title, games.color FROM achievements INNER JOIN games ON games.game_id = achievements.game_id ORDER BY RANDOM() LIMIT 1";
50
51 sqlite3_stmt* ppstmt;
52 if (sqlite3_prepare_v2(
53 ppdb_,
54 queryString.c_str(),
55 queryString.length(),
56 &ppstmt,
57 NULL) != SQLITE_OK)
58 {
59 std::string errorMsg = sqlite3_errmsg(ppdb_);
60 sqlite3_finalize(ppstmt);
61
62 throw std::logic_error(errorMsg);
63 }
64
65 if (sqlite3_step(ppstmt) != SQLITE_ROW)
66 {
67 std::string errorMsg = sqlite3_errmsg(ppdb_);
68 sqlite3_finalize(ppstmt);
69
70 throw std::logic_error(errorMsg);
71 }
72
73 achievement result;
74
75 result.achievementId = sqlite3_column_int(ppstmt, 0);
76 result.gameId = sqlite3_column_int(ppstmt, 1);
77 result.title = reinterpret_cast<const char*>(sqlite3_column_text(ppstmt, 2));
78 result.color = reinterpret_cast<const char*>(sqlite3_column_text(ppstmt, 3));
79
80 sqlite3_finalize(ppstmt);
81
82 return result;
83}
84
85bool database::doesGameHaveImages(int gameId) const
86{
87 std::string queryString = "SELECT COUNT(*) FROM images WHERE game_id = ? ORDER BY RANDOM() LIMIT 1";
88
89 sqlite3_stmt* ppstmt;
90 if (sqlite3_prepare_v2(
91 ppdb_,
92 queryString.c_str(),
93 queryString.length(),
94 &ppstmt,
95 NULL) != SQLITE_OK)
96 {
97 std::string errorMsg = sqlite3_errmsg(ppdb_);
98 sqlite3_finalize(ppstmt);
99
100 throw std::logic_error(errorMsg);
101 }
102
103 if (sqlite3_bind_int(ppstmt, 1, gameId) != SQLITE_OK)
104 {
105 std::string errorMsg = sqlite3_errmsg(ppdb_);
106 sqlite3_finalize(ppstmt);
107
108 throw std::logic_error(errorMsg);
109 }
110
111 if (sqlite3_step(ppstmt) != SQLITE_ROW)
112 {
113 std::string errorMsg = sqlite3_errmsg(ppdb_);
114 sqlite3_finalize(ppstmt);
115
116 throw std::logic_error(errorMsg);
117 }
118
119 int result = sqlite3_column_int(ppstmt, 0);
120
121 sqlite3_finalize(ppstmt);
122
123 return (result > 0);
124}
125
126std::string database::getRandomImageForGame(int gameId) const
127{
128 std::string queryString = "SELECT filename FROM images WHERE game_id = ? ORDER BY RANDOM() LIMIT 1";
129
130 sqlite3_stmt* ppstmt;
131 if (sqlite3_prepare_v2(
132 ppdb_,
133 queryString.c_str(),
134 queryString.length(),
135 &ppstmt,
136 NULL) != SQLITE_OK)
137 {
138 std::string errorMsg = sqlite3_errmsg(ppdb_);
139 sqlite3_finalize(ppstmt);
140
141 throw std::logic_error(errorMsg);
142 }
143
144 if (sqlite3_bind_int(ppstmt, 1, gameId) != SQLITE_OK)
145 {
146 std::string errorMsg = sqlite3_errmsg(ppdb_);
147 sqlite3_finalize(ppstmt);
148
149 throw std::logic_error(errorMsg);
150 }
151
152 if (sqlite3_step(ppstmt) != SQLITE_ROW)
153 {
154 std::string errorMsg = sqlite3_errmsg(ppdb_);
155 sqlite3_finalize(ppstmt);
156
157 throw std::logic_error(errorMsg);
158 }
159
160 std::string result = reinterpret_cast<const char*>(sqlite3_column_text(ppstmt, 0));
161
162 sqlite3_finalize(ppstmt);
163
164 return result;
165}
166
167did database::getRandomDidForAchievement(int achievementId) const
168{
169 std::string queryString = "SELECT profile_id, DATETIME(achieved_at) FROM dids WHERE achievement_id = ? ORDER BY RANDOM() LIMIT 1";
170
171 sqlite3_stmt* ppstmt;
172 if (sqlite3_prepare_v2(
173 ppdb_,
174 queryString.c_str(),
175 queryString.length(),
176 &ppstmt,
177 NULL) != SQLITE_OK)
178 {
179 std::string errorMsg = sqlite3_errmsg(ppdb_);
180 sqlite3_finalize(ppstmt);
181
182 throw std::logic_error(errorMsg);
183 }
184
185 if (sqlite3_bind_int(ppstmt, 1, achievementId) != SQLITE_OK)
186 {
187 std::string errorMsg = sqlite3_errmsg(ppdb_);
188 sqlite3_finalize(ppstmt);
189
190 throw std::logic_error(errorMsg);
191 }
192
193 if (sqlite3_step(ppstmt) != SQLITE_ROW)
194 {
195 std::string errorMsg = sqlite3_errmsg(ppdb_);
196 sqlite3_finalize(ppstmt);
197
198 throw std::logic_error(errorMsg);
199 }
200
201 did result;
202 result.profileId = sqlite3_column_int(ppstmt, 0);
203
204 std::tm achievedAt = {};
205
206 std::stringstream dateParser;
207 dateParser << reinterpret_cast<const char*>(sqlite3_column_text(ppstmt, 1));
208 dateParser >> std::get_time(&achievedAt, "%Y-%m-%d %H:%M:%S");
209
210 std::stringstream dateFormatter;
211 dateFormatter << std::put_time(&achievedAt, "%m/%d/%Y");
212 result.date = dateFormatter.str();
213
214 sqlite3_finalize(ppstmt);
215
216 return result;
217}
diff --git a/database.h b/database.h deleted file mode 100644 index 50f5b55..0000000 --- a/database.h +++ /dev/null
@@ -1,62 +0,0 @@
1#ifndef DATABASE_H_75C3CE0F
2#define DATABASE_H_75C3CE0F
3
4#include <string>
5
6struct sqlite3;
7
8struct achievement {
9 int achievementId;
10 int gameId;
11 std::string title;
12 std::string color;
13};
14
15struct did {
16 int profileId;
17 std::string date;
18};
19
20class database {
21public:
22
23 // Constructor
24
25 explicit database(std::string path);
26
27 // Disable copying
28
29 database(const database& other) = delete;
30 database& operator=(const database& other) = delete;
31
32 // Move constructor and move assignment
33
34 database(database&& other);
35 database& operator=(database&& other);
36
37 // Swap
38
39 friend void swap(database& first, database& second);
40
41 // Destructor
42
43 ~database();
44
45 // Accessors
46
47 achievement getRandomAchievement() const;
48
49 bool doesGameHaveImages(int gameId) const;
50
51 std::string getRandomImageForGame(int gameId) const;
52
53 did getRandomDidForAchievement(int achievementId) const;
54
55private:
56
57 database() = default;
58
59 sqlite3* ppdb_ = nullptr;
60};
61
62#endif /* end of include guard: DATABASE_H_75C3CE0F */
diff --git a/lunatic.cpp b/lunatic.cpp index daebc73..59241fa 100644 --- a/lunatic.cpp +++ b/lunatic.cpp
@@ -10,30 +10,9 @@
10#include <string> 10#include <string>
11#include <set> 11#include <set>
12#include <sstream> 12#include <sstream>
13#include "database.h" 13#include <hkutil/string.h>
14 14#include <hkutil/database.h>
15template <class Container> 15#include <iomanip>
16Container split(std::string input, std::string delimiter)
17{
18 Container result;
19
20 while (!input.empty())
21 {
22 int divider = input.find(delimiter);
23 if (divider == std::string::npos)
24 {
25 result.push_back(input);
26
27 input = "";
28 } else {
29 result.push_back(input.substr(0, divider));
30
31 input = input.substr(divider+delimiter.length());
32 }
33 }
34
35 return result;
36}
37 16
38int main(int argc, char** argv) 17int main(int argc, char** argv)
39{ 18{
@@ -56,7 +35,9 @@ int main(int argc, char** argv)
56 35
57 twitter::client client(auth); 36 twitter::client client(auth);
58 37
59 database db(config["database"].as<std::string>()); 38 hatkirby::database database(
39 config["database"].as<std::string>(),
40 hatkirby::dbmode::read);
60 41
61 std::random_device randomDevice; 42 std::random_device randomDevice;
62 std::mt19937 rng(randomDevice()); 43 std::mt19937 rng(randomDevice());
@@ -87,15 +68,29 @@ int main(int argc, char** argv)
87 { 68 {
88 std::cout << "Generating tweet" << std::endl; 69 std::cout << "Generating tweet" << std::endl;
89 70
90 achievement ach = db.getRandomAchievement(); 71 hatkirby::row achRow =
91 72 database.queryFirst(
92 if (blacklist.count(ach.title)) 73 "SELECT achievements.achievement_id, \
74 achievements.game_id, \
75 achievements.title, \
76 games.color \
77 FROM achievements \
78 INNER JOIN games ON games.game_id = achievements.game_id \
79 ORDER BY RANDOM() \
80 LIMIT 1");
81
82 int achId = mpark::get<int>(achRow[0]);
83 int gameId = mpark::get<int>(achRow[1]);
84 std::string achTitle = mpark::get<std::string>(achRow[2]);
85 std::string achColor = mpark::get<std::string>(achRow[3]);
86
87 if (blacklist.count(achTitle))
93 { 88 {
94 continue; 89 continue;
95 } 90 }
96 91
97 Magick::Image moonColor; 92 Magick::Image moonColor;
98 moonColor.read("res/" + ach.color + ".png"); 93 moonColor.read("res/" + achColor + ".png");
99 94
100 // Start with the Odyssey text overlay 95 // Start with the Odyssey text overlay
101 Magick::Image overlay; 96 Magick::Image overlay;
@@ -115,9 +110,11 @@ int main(int argc, char** argv)
115 overlay.fillColor("white"); 110 overlay.fillColor("white");
116 overlay.font("@" + config["title_font"].as<std::string>()); 111 overlay.font("@" + config["title_font"].as<std::string>());
117 112
118 std::list<std::string> words = split<std::list<std::string>>( 113 std::list<std::string> words =
119 ach.title, 114 hatkirby::split<std::list<std::string>>(
120 " "); 115 achTitle,
116 " ");
117
121 std::ostringstream wrappingStream; 118 std::ostringstream wrappingStream;
122 std::string curline; 119 std::string curline;
123 int lines = 1; 120 int lines = 1;
@@ -163,14 +160,32 @@ int main(int argc, char** argv)
163 Magick::GravityType::NorthGravity); 160 Magick::GravityType::NorthGravity);
164 161
165 // Add the achievement date 162 // Add the achievement date
166 did theDid = db.getRandomDidForAchievement(ach.achievementId); 163 hatkirby::row didRow =
164 database.queryFirst(
165 "SELECT DATETIME(achieved_at) \
166 FROM dids \
167 WHERE achievement_id = ? \
168 ORDER BY RANDOM() \
169 LIMIT 1",
170 { achId });
171
172 std::tm achievedAt = {};
173
174 std::stringstream dateParser;
175 dateParser << mpark::get<std::string>(didRow[0]);
176 dateParser >> std::get_time(&achievedAt, "%Y-%m-%d %H:%M:%S");
177
178 std::stringstream dateFormatter;
179 dateFormatter << std::put_time(&achievedAt, "%m/%d/%Y");
180
181 std::string didDate = dateFormatter.str();
167 182
168 overlay.fontTypeMetrics(wrapped, &metric); 183 overlay.fontTypeMetrics(wrapped, &metric);
169 184
170 overlay.fontPointsize(20); 185 overlay.fontPointsize(20);
171 overlay.font("@" + config["date_font"].as<std::string>()); 186 overlay.font("@" + config["date_font"].as<std::string>());
172 overlay.annotate( 187 overlay.annotate(
173 theDid.date, 188 didDate,
174 Magick::Geometry(1600, 228, 0, 710 + metric.textHeight() * lines - 22), 189 Magick::Geometry(1600, 228, 0, 710 + metric.textHeight() * lines - 22),
175 Magick::GravityType::NorthGravity); 190 Magick::GravityType::NorthGravity);
176 191
@@ -182,9 +197,25 @@ int main(int argc, char** argv)
182 // Read the game image, using a default if the game has no images 197 // Read the game image, using a default if the game has no images
183 Magick::Image image; 198 Magick::Image image;
184 199
185 if (db.doesGameHaveImages(ach.gameId)) 200 hatkirby::row checkRow =
201 database.queryFirst(
202 "SELECT COUNT(*) \
203 FROM images \
204 WHERE game_id = ?",
205 { gameId });
206
207 if (mpark::get<int>(checkRow[0]) > 0)
186 { 208 {
187 std::string imageName = db.getRandomImageForGame(ach.gameId); 209 hatkirby::row imageRow =
210 database.queryFirst(
211 "SELECT filename \
212 FROM images \
213 WHERE game_id = ? \
214 ORDER BY RANDOM() \
215 LIMIT 1",
216 { gameId });
217
218 std::string imageName = mpark::get<std::string>(imageRow[0]);
188 std::string imagePath = config["images"].as<std::string>() 219 std::string imagePath = config["images"].as<std::string>()
189 + "/" + imageName; 220 + "/" + imageName;
190 221
@@ -229,7 +260,7 @@ int main(int argc, char** argv)
229 } 260 }
230 261
231 std::string header = "YOU GOT A MOON!"; 262 std::string header = "YOU GOT A MOON!";
232 std::string action = header + "\n" + ach.title; 263 std::string action = header + "\n" + achTitle;
233 action.resize(140); 264 action.resize(140);
234 265
235 std::cout << action << std::endl; 266 std::cout << action << std::endl;
diff --git a/vendor/hkutil b/vendor/hkutil new file mode 160000
Subproject eb30ce13012108fe38709cdf0732aa8b2ec2d52