diff --git a/Gemfile b/Gemfile index d9f491e..ecf6f11 100644 --- a/Gemfile +++ b/Gemfile @@ -3,4 +3,8 @@ source 'https://rubygems.org' gem 'discordrb', git: 'https://github.com/shardlab/discordrb.git', branch: 'main' gem 'dotenv' -gem 'pg' \ No newline at end of file +gem 'pg' +gem 'prawn' +gem 'prawn-table' +gem 'gruff' +gem 'rmagick' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 5700f92..3b4efb9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -17,6 +17,7 @@ GEM remote: https://rubygems.org/ specs: base64 (0.3.0) + bigdecimal (3.3.1) domain_name (0.6.20240107) dotenv (3.2.0) event_emitter (0.2.6) @@ -31,18 +32,26 @@ GEM ffi (1.17.3-x86_64-darwin) ffi (1.17.3-x86_64-linux-gnu) ffi (1.17.3-x86_64-linux-musl) + gruff (0.29.0) + bigdecimal (>= 3.0) + histogram + rmagick (>= 5.5) + histogram (0.2.4.1) http-accept (1.7.0) http-cookie (1.1.0) domain_name (~> 0.5) logger (1.7.0) + matrix (0.4.3) mime-types (3.7.0) logger mime-types-data (~> 3.2025, >= 3.2025.0507) mime-types-data (3.2025.0924) mutex_m (0.3.0) netrc (0.11.0) + observer (0.1.2) opus-ruby (1.0.1) ffi + pdf-core (0.10.0) pg (1.6.3) pg (1.6.3-aarch64-linux) pg (1.6.3-aarch64-linux-musl) @@ -50,11 +59,23 @@ GEM pg (1.6.3-x86_64-darwin) pg (1.6.3-x86_64-linux) pg (1.6.3-x86_64-linux-musl) + pkg-config (1.6.5) + prawn (2.5.0) + matrix (~> 0.4) + pdf-core (~> 0.10.0) + ttfunk (~> 1.8) + prawn-table (0.2.2) + prawn (>= 1.3.0, < 3.0.0) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) + rmagick (6.1.5) + observer (~> 0.1) + pkg-config (~> 1.4) + ttfunk (1.8.0) + bigdecimal (~> 3.1) websocket (1.2.11) websocket-client-simple (0.9.0) base64 @@ -78,10 +99,15 @@ PLATFORMS DEPENDENCIES discordrb! dotenv + gruff pg + prawn + prawn-table + rmagick CHECKSUMS base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b + bigdecimal (3.3.1) sha256=eaa01e228be54c4f9f53bf3cc34fe3d5e845c31963e7fcc5bedb05a4e7d52218 discordrb (3.7.2) discordrb-webhooks (3.7.2) domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933 @@ -98,14 +124,19 @@ CHECKSUMS ffi (1.17.3-x86_64-darwin) sha256=1f211811eb5cfaa25998322cdd92ab104bfbd26d1c4c08471599c511f2c00bb5 ffi (1.17.3-x86_64-linux-gnu) sha256=3746b01f677aae7b16dc1acb7cb3cc17b3e35bdae7676a3f568153fb0e2c887f ffi (1.17.3-x86_64-linux-musl) sha256=086b221c3a68320b7564066f46fed23449a44f7a1935f1fe5a245bd89d9aea56 + gruff (0.29.0) sha256=ab808cbf507abda7ffacd4ba5805a43c47ad0ec6aa2a7b125cf8a165110047a0 + histogram (0.2.4.1) sha256=9a6e379172b88ea842ab71700a535dd037185a4e17abcce742c7444679ae2abc http-accept (1.7.0) sha256=c626860682bfbb3b46462f8c39cd470fd7b0584f61b3cc9df5b2e9eb9972a126 http-cookie (1.1.0) sha256=38a5e60d1527eebc396831b8c4b9455440509881219273a6c99943d29eadbb19 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 + matrix (0.4.3) sha256=a0d5ab7ddcc1973ff690ab361b67f359acbb16958d1dc072b8b956a286564c5b mime-types (3.7.0) sha256=dcebf61c246f08e15a4de34e386ebe8233791e868564a470c3fe77c00eed5e56 mime-types-data (3.2025.0924) sha256=f276bca15e59f35767cbcf2bc10e023e9200b30bd6a572c1daf7f4cc24994728 mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751 netrc (0.11.0) sha256=de1ce33da8c99ab1d97871726cba75151113f117146becbe45aa85cb3dabee3f + observer (0.1.2) sha256=d8a3107131ba661138d748e7be3dbafc0d82e732fffba9fccb3d7829880950ac opus-ruby (1.0.1) sha256=8dc808d6773a488469374cccf252808b2ad7bc7e00229a319832f0e09012ce53 + pdf-core (0.10.0) sha256=0a5d101e2063c01e3f941e1ee47cbb97f1adfc1395b58372f4f65f1300f3ce91 pg (1.6.3) sha256=1388d0563e13d2758c1089e35e973a3249e955c659592d10e5b77c468f628a99 pg (1.6.3-aarch64-linux) sha256=0698ad563e02383c27510b76bf7d4cd2de19cd1d16a5013f375dd473e4be72ea pg (1.6.3-aarch64-linux-musl) sha256=06a75f4ea04b05140146f2a10550b8e0d9f006a79cdaf8b5b130cde40e3ecc2c @@ -113,7 +144,12 @@ CHECKSUMS pg (1.6.3-x86_64-darwin) sha256=ee2e04a17c0627225054ffeb43e31a95be9d7e93abda2737ea3ce4a62f2729d6 pg (1.6.3-x86_64-linux) sha256=5d9e188c8f7a0295d162b7b88a768d8452a899977d44f3274d1946d67920ae8d pg (1.6.3-x86_64-linux-musl) sha256=9c9c90d98c72f78eb04c0f55e9618fe55d1512128e411035fe229ff427864009 + pkg-config (1.6.5) sha256=33f9f81c5322983d22b439b8b672f27777b406fea23bfec74ff14bbeb42ec733 + prawn (2.5.0) sha256=f4e20e3b4f30bf5b9ae37dad15eb421831594553aa930b2391b0fa0a99c43cb6 + prawn-table (0.2.2) sha256=336d46e39e003f77bf973337a958af6a68300b941c85cb22288872dc2b36addb rest-client (2.1.0) sha256=35a6400bdb14fae28596618e312776c158f7ebbb0ccad752ff4fa142bf2747e3 + rmagick (6.1.5) sha256=91c1734cc0effc3b1be18e6705ea0353d73cb492547002f542763fa08445acd1 + ttfunk (1.8.0) sha256=a7cbc7e489cc46e979dde04d34b5b9e4f5c8f1ee5fc6b1a7be39b829919d20ca websocket (1.2.11) sha256=b7e7a74e2410b5e85c25858b26b3322f29161e300935f70a0e0d3c35e0462737 websocket-client-simple (0.9.0) sha256=f9a37c5e4922b35a711e21e6d73ed1e25892efa47d183203ab2f5beb4e563109 diff --git a/src/commands/statement.rb b/src/commands/statement.rb new file mode 100644 index 0000000..7d5d26a --- /dev/null +++ b/src/commands/statement.rb @@ -0,0 +1,90 @@ +require 'prawn' +require 'prawn/table' +require 'gruff' + +module Commands + module Statement + extend self + + def register(bot, db) + bot.register_application_command(:statement, "Get a report of your incomes and expenses", server_id: ENV['TEST_SERVER_ID']) do |cmd| + cmd.string('period', 'Select time range', required: true, choices: { + 'Last Month' => '1 month', + 'Last Year' => '1 year' + }) + end + + bot.application_command(:statement) do |event| + event.defer + + user_id = event.user.id + period = event.options['period'] + + stats = db.fetch_report(user_id, period) + + # We generate a Graph + g = Gruff::Pie.new + g.title = "Report" + g.theme = Gruff::Themes::PASTEL + + # We add data + g.data('Income', stats[:income]) + g.data('Expenses', stats[:expenses].abs) + + # We save the graph temporarily + chart_path = "/tmp/chart_#{user_id}.png" + g.write(chart_path) + + # We generate a PDF + pdf_path = "/tmp/statement_#{user_id}.pdf" + + Prawn::Document.generate(pdf_path) do |pdf| + pdf.font "Helvetica" + + # This should be the Header + pdf.text "#{event.user.name}'s Financial Statement", size: 24, style: :bold, align: :center + pdf.move_down 10 + pdf.text "Period: #{period}", align: :center + pdf.move_down 20 + + # Summary table goes here + data = [ + ["Category", "$$$"], + ["Total Income", "#{stats[:income]} coins"], + ["Total Expenses", "#{stats[:expenses].abs} coins"], + ["Net Change", "#{stats[:net]} coins"] + ] + + pdf.table(data, position: :center) do + row(0).font_style = :bold + row(0).background_color = "CCCCCC" + self.header = true + self.width = 400 + end + + pdf.move_down 30 + + pdf.text "Visual Breakdown:", style: :bold + pdf.move_down 10 + # We embed the image chart + pdf.image chart_path, width: 400, position: :center + + # Footer + pdf.move_down 10 + pdf.text "Generated by Frugality using Gruff and Prawn", size: 10, align: :center, color: "888888" + end + + event.edit_response(content: "Report generated. Sending...") + + # Send the report to the user + File.open(pdf_path, 'r') do |f| + event.channel.send_file(f, caption: "Here's your report.") + end + + # Cleanup + File.delete(chart_path) if File.exist?(chart_path) + File.delete(pdf_path) if File.exist?(pdf_path) + end + end + end +end \ No newline at end of file diff --git a/src/database.rb b/src/database.rb index 8676614..b25a0d0 100644 --- a/src/database.rb +++ b/src/database.rb @@ -85,4 +85,25 @@ class Database @conn.exec_params(sql_update, [user_id, amount]) end end + + def fetch_report(user_id, interval_string) + sql = <<~SQL + SELECT + SUM(CASE WHEN amount > 0 THEN amount ELSE 0 END) as income, + SUM(CASE WHEN amount < 0 THEN amount ELSE 0 END) as expenses, + SUM(amount) AS net_change + FROM transactions + WHERE user_id = $1 + AND created_at >= NOW() - $2::INTERVAL + SQL + + result = @conn.exec_params(sql, [user_id, interval_string]) + row = result[0] + + { + income: row['income'].to_i, + expenses: row['expenses'].to_i, + net: row['net_change'].to_i + } + end end \ No newline at end of file