about summary refs log tree commit diff stats
path: root/cadence.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'cadence.cpp')
-rw-r--r--cadence.cpp220
1 files changed, 220 insertions, 0 deletions
diff --git a/cadence.cpp b/cadence.cpp new file mode 100644 index 0000000..c91ca4a --- /dev/null +++ b/cadence.cpp
@@ -0,0 +1,220 @@
1#include <stdexcept>
2#include <vector>
3#include <string>
4#include <yaml-cpp/yaml.h>
5#include <sqlite3.h>
6#include <iostream>
7#include <fstream>
8#include <twitter.h>
9#include <chrono>
10#include <thread>
11#include <random>
12#include <list>
13#include "util.h"
14
15const std::vector<std::string> moods = {"party", "chill", "crazy", "happy", "sad", "instrumental", "vocal"};
16
17int main(int argc, char** argv)
18{
19 if (argc != 2)
20 {
21 std::cout << "usage: cadence [configfile]" << std::endl;
22 return -1;
23 }
24
25 std::string configfile(argv[1]);
26 YAML::Node config = YAML::LoadFile(configfile);
27
28 // Set up the Twitter client.
29 twitter::auth auth;
30 auth.setConsumerKey(config["consumer_key"].as<std::string>());
31 auth.setConsumerSecret(config["consumer_secret"].as<std::string>());
32 auth.setAccessKey(config["access_key"].as<std::string>());
33 auth.setAccessSecret(config["access_secret"].as<std::string>());
34
35 twitter::client client(auth);
36
37 // Read in the forms file.
38 std::map<std::string, std::vector<std::string>> groups;
39 std::ifstream datafile(config["forms"].as<std::string>());
40 if (!datafile.is_open())
41 {
42 std::cout << "Could not find forms file." << std::endl;
43 return 1;
44 }
45
46 bool newgroup = true;
47 std::string line;
48 std::string curgroup;
49 while (getline(datafile, line))
50 {
51 if (line.back() == '\r')
52 {
53 line.pop_back();
54 }
55
56 if (newgroup)
57 {
58 curgroup = line;
59 newgroup = false;
60 } else {
61 if (line.empty())
62 {
63 newgroup = true;
64 } else {
65 groups[curgroup].push_back(line);
66 }
67 }
68 }
69
70 // Initialize the random number generator.
71 std::random_device random_device;
72 std::mt19937 random_engine{random_device()};
73
74 // Connect to the AcousticBrainz data file.
75 sqlite3* ppdb = nullptr;
76
77 if (sqlite3_open_v2(config["acoustic_datafile"].as<std::string>().c_str(), &ppdb, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK)
78 {
79 // We still have to free the resources allocated. In the event that
80 // allocation failed, ppdb will be null and sqlite3_close_v2 will just
81 // ignore it.
82 sqlite3_close_v2(ppdb);
83
84 std::cout << "Could not open acoustic datafile." << std::endl;
85
86 return 1;
87 }
88
89 for (;;)
90 {
91 std::cout << "Generating tweet..." << std::endl;
92
93 std::string mood = moods[std::uniform_int_distribution<int>(0, moods.size()-1)(random_engine)];
94
95 std::string songTitle;
96 std::string songArtist;
97
98 sqlite3_stmt* ppstmt;
99 std::string query = "SELECT title, artist FROM songs WHERE category = ? ORDER BY RANDOM() LIMIT 1";
100
101 if (sqlite3_prepare_v2(ppdb, query.c_str(), query.length(), &ppstmt, NULL) != SQLITE_OK)
102 {
103 std::cout << "Error reading from acoustic datafile:" << std::endl;
104 std::cout << sqlite3_errmsg(ppdb) << std::endl;
105
106 return 1;
107 }
108
109 sqlite3_bind_text(ppstmt, 1, mood.c_str(), mood.length(), SQLITE_TRANSIENT);
110
111 if (sqlite3_step(ppstmt) == SQLITE_ROW)
112 {
113 songTitle = reinterpret_cast<const char*>(sqlite3_column_blob(ppstmt, 0));
114 songArtist = reinterpret_cast<const char*>(sqlite3_column_blob(ppstmt, 1));
115 } else {
116 std::cout << "Error reading from acoustic datafile:" << std::endl;
117 std::cout << sqlite3_errmsg(ppdb) << std::endl;
118
119 return 1;
120 }
121
122 sqlite3_finalize(ppstmt);
123
124 std::string action = "{" + cadence::uppercase(mood) + "}";
125 int tknloc;
126 while ((tknloc = action.find("{")) != std::string::npos)
127 {
128 std::string token = action.substr(tknloc+1, action.find("}")-tknloc-1);
129 std::string modifier;
130 int modloc;
131 if ((modloc = token.find(":")) != std::string::npos)
132 {
133 modifier = token.substr(modloc+1);
134 token = token.substr(0, modloc);
135 }
136
137 std::string canontkn;
138 std::transform(std::begin(token), std::end(token), std::back_inserter(canontkn), [] (char ch) {
139 return std::toupper(ch);
140 });
141
142 std::string result;
143 if (canontkn == "\\N")
144 {
145 result = "\n";
146 } else if (canontkn == "ARTIST")
147 {
148 result = songArtist;
149 } else if (canontkn == "SONG")
150 {
151 result = "\"" + songTitle + "\"";
152 } else if (groups.count(canontkn)) {
153 auto& group = groups.at(canontkn);
154 std::uniform_int_distribution<int> dist(0, group.size() - 1);
155
156 result = group[dist(random_engine)];
157 } else {
158 std::cout << "Badly formatted forms file:" << std::endl;
159 std::cout << "No such form as " + canontkn << std::endl;
160
161 return 1;
162 }
163
164 if (modifier == "indefinite")
165 {
166 if ((result.length() > 1) && (isupper(result[0])) && (isupper(result[1])))
167 {
168 result = "an " + result;
169 } else if ((result[0] == 'a') || (result[0] == 'e') || (result[0] == 'i') || (result[0] == 'o') || (result[0] == 'u'))
170 {
171 result = "an " + result;
172 } else {
173 result = "a " + result;
174 }
175 }
176
177 std::string finalresult;
178 if (islower(token[0]))
179 {
180 std::transform(std::begin(result), std::end(result), std::back_inserter(finalresult), [] (char ch) {
181 return std::tolower(ch);
182 });
183 } else if (isupper(token[0]) && !isupper(token[1]))
184 {
185 auto words = cadence::split<std::list<std::string>>(result, " ");
186 for (auto& word : words)
187 {
188 word[0] = std::toupper(word[0]);
189 }
190
191 finalresult = cadence::implode(std::begin(words), std::end(words), " ");
192 } else {
193 finalresult = result;
194 }
195
196 action.replace(tknloc, action.find("}")-tknloc+1, finalresult);
197 }
198
199 if (action.size() <= 140)
200 {
201 try
202 {
203 client.updateStatus(action);
204
205 std::cout << action << std::endl;
206 } catch (const twitter::twitter_error& e)
207 {
208 std::cout << "Twitter error: " << e.what() << std::endl;
209 }
210
211 std::cout << "Waiting..." << std::endl;
212
213 std::this_thread::sleep_for(std::chrono::hours(1));
214
215 std::cout << std::endl;
216 } else {
217 std::cout << "Tweet was too long, regenerating." << std::endl;
218 }
219 }
220}