mirror of
https://github.com/bellingcat/reddit-post-scraping-tool.git
synced 2026-06-10 12:38:30 +03:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
750967c322 | ||
|
|
cfef86cbe3 | ||
|
|
2a2696403d | ||
|
|
b0a8d75d8c | ||
|
|
b31c38f5cc | ||
|
|
b5b7df868e | ||
|
|
566f558720 | ||
|
|
c3e5ce6441 | ||
|
|
7c164938c9 | ||
|
|
b08c4a147b | ||
|
|
8f259b7a40 | ||
|
|
f117c99cc7 | ||
|
|
3a9a87e67c | ||
|
|
cce254e976 | ||
|
|
418b2acc4c | ||
|
|
d26699cc1f | ||
|
|
9efb1cea4a | ||
|
|
ba6eeb38a6 | ||
|
|
2053c0f0bc | ||
|
|
8bef73001c | ||
|
|
c9d9628326 | ||
|
|
2f1619b4c5 | ||
|
|
33db66dbc3 | ||
|
|
bbbdab906d | ||
|
|
74264224a5 | ||
|
|
ce75d40f76 | ||
|
|
406e34c4bb | ||
|
|
38140ea2be | ||
|
|
4c3d3a688f | ||
|
|
a03b649904 | ||
|
|
aa3b506a96 | ||
|
|
13db97b6d8 | ||
|
|
4da2fcf913 | ||
|
|
c6792277f3 | ||
|
|
5bc061b300 | ||
|
|
b3441a58b1 | ||
|
|
60ba2e41b0 | ||
|
|
7bebff61a8 | ||
|
|
3576bcbf45 | ||
|
|
d266301917 | ||
|
|
1f3d8f41eb | ||
|
|
4ac58f0fc4 | ||
|
|
7696dd923a | ||
|
|
45b82e57ac | ||
|
|
6d6e616640 | ||
|
|
4ba402a129 | ||
|
|
52d36baae2 |
34
README.md
34
README.md
@@ -1,23 +1,18 @@
|
||||
# RPST (Reddit Post Scraping Tool)
|
||||
Given a subreddit name and a keyword, this script will return all posts from a specified listing (default is 'top') that contain the provided keyword.
|
||||
|
||||
[](https://github.com/rly0nheart/reddit-post-scraping-tool/actions/workflows/python-publish.yml) [](https://github.com/rly0nheart/reddit-post-scraping-tool/actions/workflows/codeql.yml)  
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
Given a subreddit name and a keyword, RPST will return all posts from a specified listing (default is 'top') that contain the provided keyword.
|
||||
|
||||
[](https://github.com/rly0nheart/reddit-post-scraping-tool/actions/workflows/python-publish.yml) [](https://github.com/rly0nheart/reddit-post-scraping-tool/actions/workflows/codeql.yml)  
|
||||
|
||||
# ✅ Features
|
||||
## GUI
|
||||
- [x] Dark mode (Right-click)
|
||||
- [x] Saves results to a JSON (Right-click)
|
||||
- [x] Dark mode (*Right-click*)
|
||||
- [x] Saves results to a JSON file (*Right-click*)
|
||||
- [x] Logs errors to a file
|
||||
|
||||
## CLI
|
||||
- [x] Saves results to a JSON (-j/--json)
|
||||
- [x] Automatically checks for new updates. Notifies user if update were found.
|
||||
- [x] Saves results to JSON (*specifiy* `--json`)
|
||||
- [x] Saves results to CSV (*specify* `--csv`)
|
||||
- [x] Automatically checks for new updates, and notifies user if updates were found.
|
||||
|
||||
# 📃 TODO
|
||||
## GUI
|
||||
@@ -25,8 +20,21 @@ Given a subreddit name and a keyword, this script will return all posts from a s
|
||||
- [x] Add manual dark mode option, that will be persistent in all sessions
|
||||
- [ ] Make it save results to a CSV file
|
||||
|
||||
# Images & Screenshots
|
||||
## GUI
|
||||
* 
|
||||
* 
|
||||
|
||||
## CLI
|
||||
* 
|
||||
* 
|
||||
* 
|
||||
|
||||
|
||||
|
||||
|
||||
# 📖 Wiki
|
||||
[Refer to the Wiki](https://github.com/rly0nheart/reddit-post-scraping-tool/wiki) for installation instructions, in addition to all other documentation.
|
||||
[Refer to the Wiki](https://github.com/bellingcat/reddit-post-scraping-tool/wiki) for installation instructions, in addition to all other documentation.
|
||||
|
||||
# 😁 Donations
|
||||
If you like `RPST` and would like to show support, you can Buy A Coffee for the developer using the button below
|
||||
|
||||
36
RPST GUI/RPST/AboutBox.Designer.vb
generated
36
RPST GUI/RPST/AboutBox.Designer.vb
generated
@@ -40,7 +40,7 @@ Partial Class AboutBox
|
||||
PictureBoxLogo.Image = CType(resources.GetObject("PictureBoxLogo.Image"), Image)
|
||||
PictureBoxLogo.Location = New Point(12, 12)
|
||||
PictureBoxLogo.Name = "PictureBoxLogo"
|
||||
PictureBoxLogo.Size = New Size(99, 111)
|
||||
PictureBoxLogo.Size = New Size(88, 93)
|
||||
PictureBoxLogo.SizeMode = PictureBoxSizeMode.StretchImage
|
||||
PictureBoxLogo.TabIndex = 0
|
||||
PictureBoxLogo.TabStop = False
|
||||
@@ -48,43 +48,43 @@ Partial Class AboutBox
|
||||
' LabelProgramName
|
||||
'
|
||||
LabelProgramName.AutoSize = True
|
||||
LabelProgramName.Font = New Font("Ink Free", 14.25F, FontStyle.Bold, GraphicsUnit.Point)
|
||||
LabelProgramName.Font = New Font("Segoe Script", 9.75F, FontStyle.Bold, GraphicsUnit.Point)
|
||||
LabelProgramName.ForeColor = SystemColors.ControlText
|
||||
LabelProgramName.Location = New Point(4, 11)
|
||||
LabelProgramName.Location = New Point(3, 15)
|
||||
LabelProgramName.Name = "LabelProgramName"
|
||||
LabelProgramName.Size = New Size(60, 23)
|
||||
LabelProgramName.Size = New Size(48, 20)
|
||||
LabelProgramName.TabIndex = 3
|
||||
LabelProgramName.Text = "Name"
|
||||
'
|
||||
' LabelProgramDescription
|
||||
'
|
||||
LabelProgramDescription.AutoSize = True
|
||||
LabelProgramDescription.Font = New Font("Comic Sans MS", 9F, FontStyle.Regular, GraphicsUnit.Point)
|
||||
LabelProgramDescription.Font = New Font("Segoe UI Semibold", 9F, FontStyle.Bold, GraphicsUnit.Point)
|
||||
LabelProgramDescription.ForeColor = SystemColors.ControlText
|
||||
LabelProgramDescription.Location = New Point(4, 54)
|
||||
LabelProgramDescription.Location = New Point(3, 43)
|
||||
LabelProgramDescription.Name = "LabelProgramDescription"
|
||||
LabelProgramDescription.Size = New Size(73, 17)
|
||||
LabelProgramDescription.Size = New Size(68, 15)
|
||||
LabelProgramDescription.TabIndex = 4
|
||||
LabelProgramDescription.Text = "Description"
|
||||
'
|
||||
' LabelVersion
|
||||
'
|
||||
LabelVersion.AutoSize = True
|
||||
LabelVersion.Font = New Font("Comic Sans MS", 9F, FontStyle.Underline, GraphicsUnit.Point)
|
||||
LabelVersion.Font = New Font("Segoe UI", 9F, FontStyle.Underline, GraphicsUnit.Point)
|
||||
LabelVersion.ForeColor = SystemColors.ControlText
|
||||
LabelVersion.Location = New Point(372, 17)
|
||||
LabelVersion.Location = New Point(347, 17)
|
||||
LabelVersion.Name = "LabelVersion"
|
||||
LabelVersion.Size = New Size(50, 17)
|
||||
LabelVersion.Size = New Size(45, 15)
|
||||
LabelVersion.TabIndex = 5
|
||||
LabelVersion.Text = "Version"
|
||||
'
|
||||
' LinkLabelReadtheWiki
|
||||
'
|
||||
LinkLabelReadtheWiki.AutoSize = True
|
||||
LinkLabelReadtheWiki.Font = New Font("Comic Sans MS", 9F, FontStyle.Regular, GraphicsUnit.Point)
|
||||
LinkLabelReadtheWiki.Location = New Point(337, 54)
|
||||
LinkLabelReadtheWiki.Font = New Font("Segoe UI", 9F, FontStyle.Regular, GraphicsUnit.Point)
|
||||
LinkLabelReadtheWiki.Location = New Point(313, 43)
|
||||
LinkLabelReadtheWiki.Name = "LinkLabelReadtheWiki"
|
||||
LinkLabelReadtheWiki.Size = New Size(85, 17)
|
||||
LinkLabelReadtheWiki.Size = New Size(79, 15)
|
||||
LinkLabelReadtheWiki.TabIndex = 6
|
||||
LinkLabelReadtheWiki.TabStop = True
|
||||
LinkLabelReadtheWiki.Text = "Read the Wiki"
|
||||
@@ -96,18 +96,18 @@ Partial Class AboutBox
|
||||
Panel1.Controls.Add(LabelProgramName)
|
||||
Panel1.Controls.Add(LinkLabelReadtheWiki)
|
||||
Panel1.Controls.Add(LabelVersion)
|
||||
Panel1.Location = New Point(117, 12)
|
||||
Panel1.Location = New Point(106, 12)
|
||||
Panel1.Name = "Panel1"
|
||||
Panel1.Size = New Size(440, 111)
|
||||
Panel1.Size = New Size(409, 93)
|
||||
Panel1.TabIndex = 7
|
||||
'
|
||||
' LicenseRichTextBox
|
||||
'
|
||||
LicenseRichTextBox.Font = New Font("Cambria", 9.75F, FontStyle.Regular, GraphicsUnit.Point)
|
||||
LicenseRichTextBox.Location = New Point(12, 135)
|
||||
LicenseRichTextBox.Location = New Point(12, 113)
|
||||
LicenseRichTextBox.Name = "LicenseRichTextBox"
|
||||
LicenseRichTextBox.ReadOnly = True
|
||||
LicenseRichTextBox.Size = New Size(545, 305)
|
||||
LicenseRichTextBox.Size = New Size(503, 329)
|
||||
LicenseRichTextBox.TabIndex = 1
|
||||
LicenseRichTextBox.Text = "License notice"
|
||||
'
|
||||
@@ -116,7 +116,7 @@ Partial Class AboutBox
|
||||
AutoScaleDimensions = New SizeF(7F, 15F)
|
||||
AutoScaleMode = AutoScaleMode.Font
|
||||
BackColor = Color.Gainsboro
|
||||
ClientSize = New Size(569, 454)
|
||||
ClientSize = New Size(526, 453)
|
||||
Controls.Add(LicenseRichTextBox)
|
||||
Controls.Add(Panel1)
|
||||
Controls.Add(PictureBoxLogo)
|
||||
|
||||
@@ -21,8 +21,8 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE."
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Handles the Load event for the AboutBox form.
|
||||
@@ -33,10 +33,10 @@ SOFTWARE."
|
||||
settings.LoadSettings()
|
||||
settings.ToggleDarkMode(settings.DarkMode)
|
||||
|
||||
LabelProgramName.Text = My.Application.Info.AssemblyName
|
||||
LabelProgramName.Text = My.Application.Info.ProductName
|
||||
LabelProgramDescription.Text = "Given a subreddit name and a keyword,
|
||||
RPST returns all top posts
|
||||
(by default) that contain the specified keyword."
|
||||
RPST returns all top posts (by default)
|
||||
that contain the specified keyword."
|
||||
LabelVersion.Text = $"v{My.Application.Info.Version}"
|
||||
LicenseRichTextBox.Text = LicenseText
|
||||
End Sub
|
||||
|
||||
@@ -7,31 +7,31 @@ Public Class DataGridViewHandler
|
||||
''' <param name="dataGridView">The DataGridView to be initialized.</param>
|
||||
Public Shared Sub AddColumn(dataGridView As DataGridView)
|
||||
' Clear the Columns and Rows before adding Items to them
|
||||
PostsForm.DataGridViewPosts.Rows.Clear()
|
||||
PostsForm.DataGridViewPosts.Columns.Clear()
|
||||
dataGridView.Rows.Clear()
|
||||
dataGridView.Columns.Clear()
|
||||
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostCount", "Post Number")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostAuthor", "Author")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostID", "ID")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostSubreddit", "Subreddit")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("SubredditVisibility", "Subreddit Visibility")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostThumbnail", "Thumbnail")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostIsNSFW", "NSFW")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostIsGilded", "Gilded")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostUpvotes", "Upvotes")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostUpvoteRatio", "Upvote Ratio")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostDownvotes", "Downvotes")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostAwards", "Awards")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostTopAward", "Top Award")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostIsCrosspostable", "Is Crosspostable?")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostScore", "Score")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostText", "Text")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostCategory", "Category")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostDomain", "Domain")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostPermalink", "Permalink")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostCreatedAt", "Created At")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostApprovedAt", "Approved At")
|
||||
PostsForm.DataGridViewPosts.Columns.Add("PostApprovedBy", "Approved By")
|
||||
dataGridView.Columns.Add("PostCount", "INDEX")
|
||||
dataGridView.Columns.Add("PostAuthor", "AUTHOR")
|
||||
dataGridView.Columns.Add("PostID", "ID")
|
||||
dataGridView.Columns.Add("PostText", "TEXT")
|
||||
dataGridView.Columns.Add("PostSubreddit", "SUBREDDIT")
|
||||
dataGridView.Columns.Add("SubredditVisibility", "VISIBILITY")
|
||||
dataGridView.Columns.Add("PostThumbnail", "THUMBNAIL")
|
||||
dataGridView.Columns.Add("PostIsNSFW", "NSFW")
|
||||
dataGridView.Columns.Add("PostIsGilded", "GILDED")
|
||||
dataGridView.Columns.Add("PostUpvotes", "UPVOTES")
|
||||
dataGridView.Columns.Add("PostUpvoteRatio", "UPVOTE RATIO")
|
||||
dataGridView.Columns.Add("PostDownvotes", "DOWNVOTES")
|
||||
dataGridView.Columns.Add("PostAwards", "AWARDS")
|
||||
dataGridView.Columns.Add("PostTopAward", "TOP AWARD")
|
||||
dataGridView.Columns.Add("PostIsCrosspostable", "IS CROSS-POSTABLE?")
|
||||
dataGridView.Columns.Add("PostScore", "SCORE")
|
||||
dataGridView.Columns.Add("PostCategory", "CATEGORY")
|
||||
dataGridView.Columns.Add("PostDomain", "DOMAIN")
|
||||
dataGridView.Columns.Add("PostPermalink", "PERMALINK")
|
||||
dataGridView.Columns.Add("PostCreatedAt", "CREATED AT")
|
||||
dataGridView.Columns.Add("PostApprovedAt", "APPROVED ATt")
|
||||
dataGridView.Columns.Add("PostApprovedBy", "APPROVED BY")
|
||||
End Sub
|
||||
|
||||
Public Shared Sub AddRow(dataGridView As DataGridView, post As JObject, postNumber As Integer)
|
||||
@@ -41,9 +41,10 @@ Public Class DataGridViewHandler
|
||||
''' <param name="dataGridView">The DataGridView to which the row will be added.</param>
|
||||
''' <param name="post">A JObject representing the Reddit post.</param>
|
||||
''' <param name="postNumber">The number of the post.</param>
|
||||
PostsForm.DataGridViewPosts.Rows.Add(postNumber,
|
||||
dataGridView.Rows.Add(postNumber,
|
||||
post("data")("author"),
|
||||
post("data")("id"),
|
||||
post("data")("selftext"),
|
||||
post("data")("subreddit_name_prefixed"),
|
||||
post("data")("subreddit_type"),
|
||||
post("data")("thumbnail"),
|
||||
@@ -56,7 +57,6 @@ Public Class DataGridViewHandler
|
||||
post("data")("top_awarded_type"),
|
||||
post("data")("is_crosspostable"),
|
||||
post("data")("score"),
|
||||
post("data")("selftext"),
|
||||
post("data")("category"),
|
||||
post("data")("domain"),
|
||||
post("data")("permalink"),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()>
|
||||
Partial Class DeveloperForm
|
||||
Partial Class DeveloperBox
|
||||
Inherits System.Windows.Forms.Form
|
||||
|
||||
'Form overrides dispose to clean up the component list.
|
||||
@@ -22,7 +22,7 @@ Partial Class DeveloperForm
|
||||
'Do not modify it using the code editor.
|
||||
<System.Diagnostics.DebuggerStepThrough()>
|
||||
Private Sub InitializeComponent()
|
||||
Dim resources As ComponentModel.ComponentResourceManager = New ComponentModel.ComponentResourceManager(GetType(DeveloperForm))
|
||||
Dim resources As ComponentModel.ComponentResourceManager = New ComponentModel.ComponentResourceManager(GetType(DeveloperBox))
|
||||
AboutMeLinkLabel = New LinkLabel()
|
||||
LinkLabelBuyMeACoffee = New LinkLabel()
|
||||
GreetingLabel = New Label()
|
||||
@@ -1,4 +1,4 @@
|
||||
Public Class DeveloperForm
|
||||
Public Class DeveloperBox
|
||||
Private Sub DeveloperForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
|
||||
GreetingLabel.BackColor = Color.Transparent
|
||||
AboutMeLinkLabel.BackColor = Color.Transparent
|
||||
311
RPST GUI/RPST/FormMain.Designer.vb
generated
Normal file
311
RPST GUI/RPST/FormMain.Designer.vb
generated
Normal file
@@ -0,0 +1,311 @@
|
||||
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()>
|
||||
Partial Class FormMain
|
||||
Inherits System.Windows.Forms.Form
|
||||
|
||||
'Form overrides dispose to clean up the component list.
|
||||
<System.Diagnostics.DebuggerNonUserCode()>
|
||||
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
|
||||
Try
|
||||
If disposing AndAlso components IsNot Nothing Then
|
||||
components.Dispose()
|
||||
End If
|
||||
Finally
|
||||
MyBase.Dispose(disposing)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
'Required by the Windows Form Designer
|
||||
Private components As System.ComponentModel.IContainer
|
||||
|
||||
'NOTE: The following procedure is required by the Windows Form Designer
|
||||
'It can be modified using the Windows Form Designer.
|
||||
'Do not modify it using the code editor.
|
||||
<System.Diagnostics.DebuggerStepThrough()>
|
||||
Private Sub InitializeComponent()
|
||||
components = New ComponentModel.Container()
|
||||
Dim resources As ComponentModel.ComponentResourceManager = New ComponentModel.ComponentResourceManager(GetType(FormMain))
|
||||
TextBoxKeyword = New TextBox()
|
||||
TextBoxSubreddit = New TextBox()
|
||||
ButtonScrape = New Button()
|
||||
ComboBoxTimeframe = New ComboBox()
|
||||
ComboBoxListing = New ComboBox()
|
||||
LabelKeyword = New Label()
|
||||
LabelSubreddit = New Label()
|
||||
LabelLimit = New Label()
|
||||
LabelListing = New Label()
|
||||
LabelTimeframe = New Label()
|
||||
ContextMenuStripRightClick = New ContextMenuStrip(components)
|
||||
ToolStripMenuItemDarkMode = New ToolStripMenuItem()
|
||||
ToolStripMenuItemSavePosts = New ToolStripMenuItem()
|
||||
ToolStripMenuItemtoJSON = New ToolStripMenuItem()
|
||||
ToolStripMenuItemtoCSV = New ToolStripMenuItem()
|
||||
ToolStripMenuItemAbout = New ToolStripMenuItem()
|
||||
ToolStripMenuItemDeveloper = New ToolStripMenuItem()
|
||||
ToolStripMenuItemCheckUpdates = New ToolStripMenuItem()
|
||||
ToolStripMenuItemQuit = New ToolStripMenuItem()
|
||||
NumericUpDownLimit = New NumericUpDown()
|
||||
ToolTip = New ToolTip(components)
|
||||
ContextMenuStripRightClick.SuspendLayout()
|
||||
CType(NumericUpDownLimit, ComponentModel.ISupportInitialize).BeginInit()
|
||||
SuspendLayout()
|
||||
'
|
||||
' TextBoxKeyword
|
||||
'
|
||||
TextBoxKeyword.BackColor = SystemColors.Window
|
||||
TextBoxKeyword.ForeColor = SystemColors.WindowText
|
||||
TextBoxKeyword.Location = New Point(118, 20)
|
||||
TextBoxKeyword.Name = "TextBoxKeyword"
|
||||
TextBoxKeyword.PlaceholderText = "Keyword"
|
||||
TextBoxKeyword.Size = New Size(100, 23)
|
||||
TextBoxKeyword.TabIndex = 0
|
||||
ToolTip.SetToolTip(TextBoxKeyword, "Enter the keyword you want to search for.")
|
||||
'
|
||||
' TextBoxSubreddit
|
||||
'
|
||||
TextBoxSubreddit.Location = New Point(118, 49)
|
||||
TextBoxSubreddit.Name = "TextBoxSubreddit"
|
||||
TextBoxSubreddit.PlaceholderText = "Subreddit"
|
||||
TextBoxSubreddit.Size = New Size(100, 23)
|
||||
TextBoxSubreddit.TabIndex = 4
|
||||
ToolTip.SetToolTip(TextBoxSubreddit, "Provide the subreddit to search in.")
|
||||
'
|
||||
' ButtonScrape
|
||||
'
|
||||
ButtonScrape.Location = New Point(167, 174)
|
||||
ButtonScrape.Name = "ButtonScrape"
|
||||
ButtonScrape.Size = New Size(51, 28)
|
||||
ButtonScrape.TabIndex = 6
|
||||
ButtonScrape.Text = "Scrape"
|
||||
ToolTip.SetToolTip(ButtonScrape, "You can also just hit ENTER to start scraping.")
|
||||
ButtonScrape.UseVisualStyleBackColor = True
|
||||
'
|
||||
' ComboBoxTimeframe
|
||||
'
|
||||
ComboBoxTimeframe.AutoCompleteCustomSource.AddRange(New String() {"Hour", "Day", "Week", "Month", "Year"})
|
||||
ComboBoxTimeframe.AutoCompleteMode = AutoCompleteMode.Append
|
||||
ComboBoxTimeframe.AutoCompleteSource = AutoCompleteSource.CustomSource
|
||||
ComboBoxTimeframe.FormattingEnabled = True
|
||||
ComboBoxTimeframe.Items.AddRange(New Object() {"Hour", "Day", "Week", "Month", "Year"})
|
||||
ComboBoxTimeframe.Location = New Point(118, 136)
|
||||
ComboBoxTimeframe.Name = "ComboBoxTimeframe"
|
||||
ComboBoxTimeframe.Size = New Size(100, 23)
|
||||
ComboBoxTimeframe.TabIndex = 8
|
||||
ComboBoxTimeframe.Text = "All"
|
||||
ToolTip.SetToolTip(ComboBoxTimeframe, "Select the time period for the posts. Default value is `All`.")
|
||||
'
|
||||
' ComboBoxListing
|
||||
'
|
||||
ComboBoxListing.AutoCompleteCustomSource.AddRange(New String() {"Controversial", "Hot", "Best", "New", "Rising"})
|
||||
ComboBoxListing.AutoCompleteMode = AutoCompleteMode.Append
|
||||
ComboBoxListing.AutoCompleteSource = AutoCompleteSource.CustomSource
|
||||
ComboBoxListing.FormattingEnabled = True
|
||||
ComboBoxListing.Items.AddRange(New Object() {"Controversial", "Hot", "Best", "New", "Rising"})
|
||||
ComboBoxListing.Location = New Point(118, 107)
|
||||
ComboBoxListing.Name = "ComboBoxListing"
|
||||
ComboBoxListing.Size = New Size(100, 23)
|
||||
ComboBoxListing.TabIndex = 9
|
||||
ComboBoxListing.Text = "Top"
|
||||
ToolTip.SetToolTip(ComboBoxListing, "Choose the type of post listings. Default value is `Top`.")
|
||||
'
|
||||
' LabelKeyword
|
||||
'
|
||||
LabelKeyword.AutoEllipsis = True
|
||||
LabelKeyword.Font = New Font("Segoe UI Semibold", 9F, FontStyle.Bold Or FontStyle.Underline, GraphicsUnit.Point)
|
||||
LabelKeyword.ForeColor = Color.Black
|
||||
LabelKeyword.Location = New Point(19, 23)
|
||||
LabelKeyword.Name = "LabelKeyword"
|
||||
LabelKeyword.Size = New Size(71, 20)
|
||||
LabelKeyword.TabIndex = 10
|
||||
LabelKeyword.Text = "Keyword:"
|
||||
'
|
||||
' LabelSubreddit
|
||||
'
|
||||
LabelSubreddit.AutoEllipsis = True
|
||||
LabelSubreddit.Font = New Font("Segoe UI Semibold", 9F, FontStyle.Bold Or FontStyle.Underline, GraphicsUnit.Point)
|
||||
LabelSubreddit.ForeColor = Color.Black
|
||||
LabelSubreddit.Location = New Point(19, 52)
|
||||
LabelSubreddit.Name = "LabelSubreddit"
|
||||
LabelSubreddit.Size = New Size(71, 23)
|
||||
LabelSubreddit.TabIndex = 11
|
||||
LabelSubreddit.Text = "Subreddit:"
|
||||
'
|
||||
' LabelLimit
|
||||
'
|
||||
LabelLimit.AutoEllipsis = True
|
||||
LabelLimit.Font = New Font("Segoe UI Semibold", 9F, FontStyle.Bold Or FontStyle.Underline, GraphicsUnit.Point)
|
||||
LabelLimit.ForeColor = Color.Black
|
||||
LabelLimit.Location = New Point(19, 75)
|
||||
LabelLimit.Name = "LabelLimit"
|
||||
LabelLimit.Size = New Size(56, 23)
|
||||
LabelLimit.TabIndex = 12
|
||||
LabelLimit.Text = "Limit:"
|
||||
'
|
||||
' LabelListing
|
||||
'
|
||||
LabelListing.AutoEllipsis = True
|
||||
LabelListing.Font = New Font("Segoe UI Semibold", 9F, FontStyle.Bold Or FontStyle.Underline, GraphicsUnit.Point)
|
||||
LabelListing.ForeColor = Color.Black
|
||||
LabelListing.Location = New Point(19, 107)
|
||||
LabelListing.Name = "LabelListing"
|
||||
LabelListing.Size = New Size(56, 23)
|
||||
LabelListing.TabIndex = 13
|
||||
LabelListing.Text = "Listing:"
|
||||
'
|
||||
' LabelTimeframe
|
||||
'
|
||||
LabelTimeframe.AutoEllipsis = True
|
||||
LabelTimeframe.Font = New Font("Segoe UI Semibold", 9F, FontStyle.Bold Or FontStyle.Underline, GraphicsUnit.Point)
|
||||
LabelTimeframe.ForeColor = Color.Black
|
||||
LabelTimeframe.Location = New Point(19, 136)
|
||||
LabelTimeframe.Name = "LabelTimeframe"
|
||||
LabelTimeframe.Size = New Size(81, 23)
|
||||
LabelTimeframe.TabIndex = 14
|
||||
LabelTimeframe.Text = "Timeframe:"
|
||||
'
|
||||
' ContextMenuStripRightClick
|
||||
'
|
||||
ContextMenuStripRightClick.Items.AddRange(New ToolStripItem() {ToolStripMenuItemDarkMode, ToolStripMenuItemSavePosts, ToolStripMenuItemAbout, ToolStripMenuItemDeveloper, ToolStripMenuItemCheckUpdates, ToolStripMenuItemQuit})
|
||||
ContextMenuStripRightClick.Name = "ContextMenuStrip1"
|
||||
ContextMenuStripRightClick.Size = New Size(154, 136)
|
||||
'
|
||||
' ToolStripMenuItemDarkMode
|
||||
'
|
||||
ToolStripMenuItemDarkMode.AutoToolTip = True
|
||||
ToolStripMenuItemDarkMode.CheckOnClick = True
|
||||
ToolStripMenuItemDarkMode.Image = CType(resources.GetObject("ToolStripMenuItemDarkMode.Image"), Image)
|
||||
ToolStripMenuItemDarkMode.Name = "ToolStripMenuItemDarkMode"
|
||||
ToolStripMenuItemDarkMode.Size = New Size(153, 22)
|
||||
ToolStripMenuItemDarkMode.Text = "Dark Mode"
|
||||
'
|
||||
' ToolStripMenuItemSavePosts
|
||||
'
|
||||
ToolStripMenuItemSavePosts.AutoToolTip = True
|
||||
ToolStripMenuItemSavePosts.DropDownItems.AddRange(New ToolStripItem() {ToolStripMenuItemtoJSON, ToolStripMenuItemtoCSV})
|
||||
ToolStripMenuItemSavePosts.Image = CType(resources.GetObject("ToolStripMenuItemSavePosts.Image"), Image)
|
||||
ToolStripMenuItemSavePosts.Name = "ToolStripMenuItemSavePosts"
|
||||
ToolStripMenuItemSavePosts.Size = New Size(153, 22)
|
||||
ToolStripMenuItemSavePosts.Text = "Save Posts"
|
||||
ToolStripMenuItemSavePosts.ToolTipText = "Save found posts to..."
|
||||
'
|
||||
' ToolStripMenuItemtoJSON
|
||||
'
|
||||
ToolStripMenuItemtoJSON.AutoToolTip = True
|
||||
ToolStripMenuItemtoJSON.CheckOnClick = True
|
||||
ToolStripMenuItemtoJSON.Image = CType(resources.GetObject("ToolStripMenuItemtoJSON.Image"), Image)
|
||||
ToolStripMenuItemtoJSON.Name = "ToolStripMenuItemtoJSON"
|
||||
ToolStripMenuItemtoJSON.Size = New Size(116, 22)
|
||||
ToolStripMenuItemtoJSON.Text = "to JSON"
|
||||
'
|
||||
' ToolStripMenuItemtoCSV
|
||||
'
|
||||
ToolStripMenuItemtoCSV.AutoToolTip = True
|
||||
ToolStripMenuItemtoCSV.Enabled = False
|
||||
ToolStripMenuItemtoCSV.Image = CType(resources.GetObject("ToolStripMenuItemtoCSV.Image"), Image)
|
||||
ToolStripMenuItemtoCSV.Name = "ToolStripMenuItemtoCSV"
|
||||
ToolStripMenuItemtoCSV.Size = New Size(116, 22)
|
||||
ToolStripMenuItemtoCSV.Text = "to CSV"
|
||||
'
|
||||
' ToolStripMenuItemAbout
|
||||
'
|
||||
ToolStripMenuItemAbout.AutoToolTip = True
|
||||
ToolStripMenuItemAbout.Image = CType(resources.GetObject("ToolStripMenuItemAbout.Image"), Image)
|
||||
ToolStripMenuItemAbout.Name = "ToolStripMenuItemAbout"
|
||||
ToolStripMenuItemAbout.Size = New Size(153, 22)
|
||||
ToolStripMenuItemAbout.Text = "About"
|
||||
'
|
||||
' ToolStripMenuItemDeveloper
|
||||
'
|
||||
ToolStripMenuItemDeveloper.AutoToolTip = True
|
||||
ToolStripMenuItemDeveloper.Image = CType(resources.GetObject("ToolStripMenuItemDeveloper.Image"), Image)
|
||||
ToolStripMenuItemDeveloper.Name = "ToolStripMenuItemDeveloper"
|
||||
ToolStripMenuItemDeveloper.Size = New Size(153, 22)
|
||||
ToolStripMenuItemDeveloper.Text = "Developer"
|
||||
'
|
||||
' ToolStripMenuItemCheckUpdates
|
||||
'
|
||||
ToolStripMenuItemCheckUpdates.AutoToolTip = True
|
||||
ToolStripMenuItemCheckUpdates.Image = CType(resources.GetObject("ToolStripMenuItemCheckUpdates.Image"), Image)
|
||||
ToolStripMenuItemCheckUpdates.Name = "ToolStripMenuItemCheckUpdates"
|
||||
ToolStripMenuItemCheckUpdates.Size = New Size(153, 22)
|
||||
ToolStripMenuItemCheckUpdates.Text = "Check Updates"
|
||||
'
|
||||
' ToolStripMenuItemQuit
|
||||
'
|
||||
ToolStripMenuItemQuit.AutoToolTip = True
|
||||
ToolStripMenuItemQuit.Image = CType(resources.GetObject("ToolStripMenuItemQuit.Image"), Image)
|
||||
ToolStripMenuItemQuit.Name = "ToolStripMenuItemQuit"
|
||||
ToolStripMenuItemQuit.Size = New Size(153, 22)
|
||||
ToolStripMenuItemQuit.Text = "Quit"
|
||||
'
|
||||
' NumericUpDownLimit
|
||||
'
|
||||
NumericUpDownLimit.Location = New Point(118, 78)
|
||||
NumericUpDownLimit.Minimum = New Decimal(New Integer() {5, 0, 0, 0})
|
||||
NumericUpDownLimit.Name = "NumericUpDownLimit"
|
||||
NumericUpDownLimit.ReadOnly = True
|
||||
NumericUpDownLimit.Size = New Size(100, 23)
|
||||
NumericUpDownLimit.TabIndex = 15
|
||||
ToolTip.SetToolTip(NumericUpDownLimit, "Set how many posts you want to go through. Default value is `10`.")
|
||||
NumericUpDownLimit.Value = New Decimal(New Integer() {10, 0, 0, 0})
|
||||
'
|
||||
' ToolTip
|
||||
'
|
||||
ToolTip.AutoPopDelay = 5000
|
||||
ToolTip.BackColor = Color.Gainsboro
|
||||
ToolTip.InitialDelay = 500
|
||||
ToolTip.ReshowDelay = 100
|
||||
ToolTip.ToolTipIcon = ToolTipIcon.Info
|
||||
ToolTip.ToolTipTitle = "Tip"
|
||||
'
|
||||
' FormMain
|
||||
'
|
||||
AutoScaleDimensions = New SizeF(7F, 15F)
|
||||
AutoScaleMode = AutoScaleMode.Font
|
||||
BackColor = SystemColors.Control
|
||||
ClientSize = New Size(239, 221)
|
||||
ContextMenuStrip = ContextMenuStripRightClick
|
||||
Controls.Add(ComboBoxTimeframe)
|
||||
Controls.Add(TextBoxKeyword)
|
||||
Controls.Add(LabelTimeframe)
|
||||
Controls.Add(LabelKeyword)
|
||||
Controls.Add(ComboBoxListing)
|
||||
Controls.Add(NumericUpDownLimit)
|
||||
Controls.Add(LabelListing)
|
||||
Controls.Add(ButtonScrape)
|
||||
Controls.Add(LabelLimit)
|
||||
Controls.Add(LabelSubreddit)
|
||||
Controls.Add(TextBoxSubreddit)
|
||||
FormBorderStyle = FormBorderStyle.FixedSingle
|
||||
Icon = CType(resources.GetObject("$this.Icon"), Icon)
|
||||
MaximizeBox = False
|
||||
Name = "FormMain"
|
||||
StartPosition = FormStartPosition.CenterScreen
|
||||
Text = "RPST"
|
||||
ContextMenuStripRightClick.ResumeLayout(False)
|
||||
CType(NumericUpDownLimit, ComponentModel.ISupportInitialize).EndInit()
|
||||
ResumeLayout(False)
|
||||
PerformLayout()
|
||||
End Sub
|
||||
|
||||
Friend WithEvents TextBoxKeyword As TextBox
|
||||
Friend WithEvents TextBoxSubreddit As TextBox
|
||||
Friend WithEvents ButtonScrape As Button
|
||||
Friend WithEvents ComboBoxTimeframe As ComboBox
|
||||
Friend WithEvents ComboBoxListing As ComboBox
|
||||
Friend WithEvents LabelKeyword As Label
|
||||
Friend WithEvents LabelSubreddit As Label
|
||||
Friend WithEvents LabelLimit As Label
|
||||
Friend WithEvents LabelListing As Label
|
||||
Friend WithEvents LabelTimeframe As Label
|
||||
Friend WithEvents ContextMenuStripRightClick As ContextMenuStrip
|
||||
Friend WithEvents ToolStripMenuItemSavePosts As ToolStripMenuItem
|
||||
Friend WithEvents ToolStripMenuItemtoJSON As ToolStripMenuItem
|
||||
Friend WithEvents ToolStripMenuItemtoCSV As ToolStripMenuItem
|
||||
Friend WithEvents NumericUpDownLimit As NumericUpDown
|
||||
Friend WithEvents ToolStripMenuItemDarkMode As ToolStripMenuItem
|
||||
Friend WithEvents ToolStripMenuItemAbout As ToolStripMenuItem
|
||||
Friend WithEvents ToolStripMenuItemDeveloper As ToolStripMenuItem
|
||||
Friend WithEvents ToolStripMenuItemCheckUpdates As ToolStripMenuItem
|
||||
Friend WithEvents ToolStripMenuItemQuit As ToolStripMenuItem
|
||||
Friend WithEvents ToolTip As ToolTip
|
||||
End Class
|
||||
File diff suppressed because it is too large
Load Diff
184
RPST GUI/RPST/FormMain.vb
Normal file
184
RPST GUI/RPST/FormMain.vb
Normal file
@@ -0,0 +1,184 @@
|
||||
Imports System.IO
|
||||
Imports System.Windows.Forms.VisualStyles.VisualStyleElement
|
||||
Imports Newtonsoft.Json
|
||||
Imports Newtonsoft.Json.Linq
|
||||
|
||||
Public Class FormMain
|
||||
ReadOnly settings As New SettingsManager()
|
||||
ReadOnly ApiHandler As New ApiHandler()
|
||||
|
||||
''' <summary>
|
||||
''' Event handler for the form load event.
|
||||
''' It loads settings, toggles dark mode if necessary, checks for directories, logs first time launch, and sets the form title.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">An EventArgs that contains the event data.</param>
|
||||
Private Sub FormMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
|
||||
settings.LoadSettings()
|
||||
settings.ToggleDarkMode(settings.DarkMode)
|
||||
Utilities.PathFinder()
|
||||
Utilities.LogFirstTimeLaunch()
|
||||
Me.Text = My.Application.Info.AssemblyName
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Event handler for the 'Dark Mode' checkbox change event.
|
||||
''' It toggles the dark mode of the application based on the checkbox status.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">An EventArgs that contains the event data.</param>
|
||||
Private Sub DarkModeToolStripMenuItem_CheckedChanged(sender As Object, e As EventArgs) Handles ToolStripMenuItemDarkMode.CheckedChanged
|
||||
settings.ToggleDarkMode(ToolStripMenuItemDarkMode.Checked)
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Event handler for the 'About' menu item click.
|
||||
''' It shows the 'About' box.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">An EventArgs that contains the event data.</param>
|
||||
Private Sub ToolStripMenuItemAbout_Click(sender As Object, e As EventArgs) Handles ToolStripMenuItemAbout.Click
|
||||
AboutBox.Show()
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Event handler for the 'Developer' menu item click.
|
||||
''' It shows the 'Developer' dialog box.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">An EventArgs that contains the event data.</param>
|
||||
Private Sub ToolStripMenuItemDeveloper_Click(sender As Object, e As EventArgs) Handles ToolStripMenuItemDeveloper.Click
|
||||
DeveloperBox.ShowDialog()
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Event handler for the 'Check Updates' menu item click.
|
||||
''' It checks for application updates and provides update information if a newer version is available.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">An EventArgs that contains the event data.</param>
|
||||
Private Sub ToolStripMenuItemCheckUpdates_Click(sender As Object, e As EventArgs) Handles ToolStripMenuItemCheckUpdates.Click
|
||||
Dim data As JObject = ApiHandler.CheckUpdates()
|
||||
If data("tag_name").ToString = My.Application.Info.Version.ToString Then
|
||||
MessageBox.Show($"You're running the latest version v{My.Application.Info.Version} of {Me.Text}. Check again soon! :)", $"{Me.Text} v{My.Application.Info.Version}", MessageBoxButtons.OK, MessageBoxIcon.Information)
|
||||
Else
|
||||
Dim confirm As DialogResult = MessageBox.Show($"A new version v{data("tag_name")} of {Me.Text} is available, would you like to get it?
|
||||
|
||||
{data("body")}
|
||||
", $"{Me.Text} v{data("tag_name")}".ToString, MessageBoxButtons.YesNo, MessageBoxIcon.Question)
|
||||
If confirm = DialogResult.Yes Then
|
||||
Shell($"cmd /c start {data("html_url")}")
|
||||
End If
|
||||
End If
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Event handler for the 'Quit' menu item click.
|
||||
''' It asks the user for confirmation and closes the program if the user agrees.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">An EventArgs that contains the event data.</param>
|
||||
Private Sub ToolStripMenuItemQuit_Click(sender As Object, e As EventArgs) Handles ToolStripMenuItemQuit.Click
|
||||
Dim result As DialogResult = MessageBox.Show("This will close the program, continue?", "Quit", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
|
||||
If result = DialogResult.Yes Then
|
||||
Me.Close()
|
||||
End If
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Handles the click event of the ScrapeButton.
|
||||
''' Collects inputs, fetches Reddit posts based on the inputs,
|
||||
''' and processes Reddit posts.
|
||||
''' </summary>
|
||||
''' <param name="sender">The sender of the event.</param>
|
||||
''' <param name="e">The EventArgs instance containing the event data.</param>
|
||||
Private Sub ButtonScrape_Click(sender As Object, e As EventArgs) Handles ButtonScrape.Click
|
||||
Utilities.ProcessRedditPosts(ToolStripMenuItemtoJSON)
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Handles the KeyDown event for the TextBoxKeyword.
|
||||
''' Processes Reddit posts when the Enter key is pressed.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">The <see cref="KeyEventArgs"/> instance containing the event data.</param>
|
||||
Private Sub TextBoxKeyword_KeyDown(sender As Object, e As KeyEventArgs) Handles TextBoxKeyword.KeyDown
|
||||
' Check if the Enter key is pressed
|
||||
If e.KeyCode = Keys.Enter Then
|
||||
' Prevent the beep sound that usually comes with the Enter key in a single-line TextBox
|
||||
e.SuppressKeyPress = True
|
||||
Utilities.ProcessRedditPosts(ToolStripMenuItemtoJSON)
|
||||
End If
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Handles the KeyDown event for the TextBoxSubreddit.
|
||||
''' Processes Reddit posts when the Enter key is pressed.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">The <see cref="KeyEventArgs"/> instance containing the event data.</param>
|
||||
Private Sub TextBoxSubreddit_KeyDown(sender As Object, e As KeyEventArgs) Handles TextBoxSubreddit.KeyDown
|
||||
' Check if the Enter key is pressed
|
||||
If e.KeyCode = Keys.Enter Then
|
||||
' Prevent the beep sound that usually comes with the Enter key in a single-line TextBox
|
||||
e.SuppressKeyPress = True
|
||||
Utilities.ProcessRedditPosts(ToolStripMenuItemtoJSON)
|
||||
End If
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Handles the KeyDown event for the NumericUpDownLimit.
|
||||
''' Processes Reddit posts when the Enter key is pressed.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">The <see cref="KeyEventArgs"/> instance containing the event data.</param>
|
||||
Private Sub NumericUpDownLimit_KeyDown(sender As Object, e As KeyEventArgs) Handles NumericUpDownLimit.KeyDown
|
||||
' Check if the Enter key is pressed
|
||||
If e.KeyCode = Keys.Enter Then
|
||||
' Prevent the beep sound that usually comes with the Enter key in a single-line TextBox
|
||||
e.SuppressKeyPress = True
|
||||
Utilities.ProcessRedditPosts(ToolStripMenuItemtoJSON)
|
||||
End If
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Handles the KeyDown event for the ComboBoxListing.
|
||||
''' Processes Reddit posts when the Enter key is pressed.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">The <see cref="KeyEventArgs"/> instance containing the event data.</param>
|
||||
Private Sub ComboBoxListing_KeyDown(sender As Object, e As KeyEventArgs) Handles ComboBoxListing.KeyDown
|
||||
' Check if the Enter key is pressed
|
||||
If e.KeyCode = Keys.Enter Then
|
||||
' Prevent the beep sound that usually comes with the Enter key in a single-line TextBox
|
||||
e.SuppressKeyPress = True
|
||||
Utilities.ProcessRedditPosts(ToolStripMenuItemtoJSON)
|
||||
End If
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Handles the KeyDown event for the ComboBoxTimeframe.
|
||||
''' Processes Reddit posts when the Enter key is pressed.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">The <see cref="KeyEventArgs"/> instance containing the event data.</param>
|
||||
Private Sub ComboBoxTimeframe_KeyDown(sender As Object, e As KeyEventArgs) Handles ComboBoxTimeframe.KeyDown
|
||||
' Check if the Enter key is pressed
|
||||
If e.KeyCode = Keys.Enter Then
|
||||
' Prevent the beep sound that usually comes with the Enter key in a single-line TextBox
|
||||
e.SuppressKeyPress = True
|
||||
Utilities.ProcessRedditPosts(ToolStripMenuItemtoJSON)
|
||||
End If
|
||||
End Sub
|
||||
End Class
|
||||
@@ -1,9 +1,9 @@
|
||||
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
|
||||
Partial Class PostsForm
|
||||
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()>
|
||||
Partial Class FormPosts
|
||||
Inherits System.Windows.Forms.Form
|
||||
|
||||
'Form overrides dispose to clean up the component list.
|
||||
<System.Diagnostics.DebuggerNonUserCode()> _
|
||||
<System.Diagnostics.DebuggerNonUserCode()>
|
||||
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
|
||||
Try
|
||||
If disposing AndAlso components IsNot Nothing Then
|
||||
@@ -20,8 +20,9 @@ Partial Class PostsForm
|
||||
'NOTE: The following procedure is required by the Windows Form Designer
|
||||
'It can be modified using the Windows Form Designer.
|
||||
'Do not modify it using the code editor.
|
||||
<System.Diagnostics.DebuggerStepThrough()> _
|
||||
<System.Diagnostics.DebuggerStepThrough()>
|
||||
Private Sub InitializeComponent()
|
||||
Dim resources As ComponentModel.ComponentResourceManager = New ComponentModel.ComponentResourceManager(GetType(FormPosts))
|
||||
DataGridViewPosts = New DataGridView()
|
||||
CType(DataGridViewPosts, ComponentModel.ISupportInitialize).BeginInit()
|
||||
SuspendLayout()
|
||||
@@ -36,19 +37,20 @@ Partial Class PostsForm
|
||||
DataGridViewPosts.ReadOnly = True
|
||||
DataGridViewPosts.RowHeadersVisible = False
|
||||
DataGridViewPosts.RowTemplate.Height = 25
|
||||
DataGridViewPosts.Size = New Size(800, 450)
|
||||
DataGridViewPosts.Size = New Size(501, 365)
|
||||
DataGridViewPosts.TabIndex = 3
|
||||
'
|
||||
' PostsForm
|
||||
' FormPosts
|
||||
'
|
||||
AutoScaleDimensions = New SizeF(7F, 15F)
|
||||
AutoScaleMode = AutoScaleMode.Font
|
||||
ClientSize = New Size(800, 450)
|
||||
ClientSize = New Size(501, 365)
|
||||
Controls.Add(DataGridViewPosts)
|
||||
Name = "PostsForm"
|
||||
ShowIcon = False
|
||||
Icon = CType(resources.GetObject("$this.Icon"), Icon)
|
||||
Name = "FormPosts"
|
||||
ShowInTaskbar = False
|
||||
Text = "PostsForm"
|
||||
StartPosition = FormStartPosition.CenterScreen
|
||||
Text = "Posts"
|
||||
CType(DataGridViewPosts, ComponentModel.ISupportInitialize).EndInit()
|
||||
ResumeLayout(False)
|
||||
End Sub
|
||||
393
RPST GUI/RPST/FormPosts.resx
Normal file
393
RPST GUI/RPST/FormPosts.resx
Normal file
@@ -0,0 +1,393 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing"">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
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
|
||||
</value>
|
||||
</data>
|
||||
</root>
|
||||
5
RPST GUI/RPST/FormPosts.vb
Normal file
5
RPST GUI/RPST/FormPosts.vb
Normal file
@@ -0,0 +1,5 @@
|
||||
Public Class FormPosts
|
||||
Private Sub FormResults_Load(sender As Object, e As EventArgs) Handles MyBase.Load
|
||||
Me.Text = $"{Me.Text} - {FormMain.TextBoxKeyword.Text}, r/{FormMain.TextBoxSubreddit.Text}, {FormMain.NumericUpDownLimit.Text}, {FormMain.ComboBoxListing.Text}, {FormMain.ComboBoxTimeframe.Text}"
|
||||
End Sub
|
||||
End Class
|
||||
2
RPST GUI/RPST/My Project/Application.Designer.vb
generated
2
RPST GUI/RPST/My Project/Application.Designer.vb
generated
@@ -33,7 +33,7 @@ Namespace My
|
||||
|
||||
<Global.System.Diagnostics.DebuggerStepThroughAttribute()> _
|
||||
Protected Overrides Sub OnCreateMainForm()
|
||||
Me.MainForm = Global.RPST.StartForm
|
||||
Me.MainForm = Global.RPST.FormMain
|
||||
End Sub
|
||||
End Class
|
||||
End Namespace
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -1,3 +0,0 @@
|
||||
Public Class PostsForm
|
||||
|
||||
End Class
|
||||
@@ -1,7 +1,7 @@
|
||||
Imports Newtonsoft.Json.Linq
|
||||
|
||||
Public Class PostsProcessor
|
||||
Private ApiHandler As New ApiHandler
|
||||
Private ReadOnly ApiHandler As New ApiHandler
|
||||
|
||||
''' <summary>
|
||||
''' Fetches Reddit posts based on the given parameters and returns them as a JObject.
|
||||
@@ -23,7 +23,7 @@ Public Class PostsProcessor
|
||||
''' <param name="keyword">The keyword to check for.</param>
|
||||
''' <returns>True if the post contains the keyword, False otherwise.</returns>
|
||||
Public Shared Function PostContainsKeyword(post As JObject, keyword As String) As Boolean
|
||||
Return post("data")("selftext").ToString.ToLower(System.Globalization.CultureInfo.InvariantCulture).Contains(keyword.ToLower(System.Globalization.CultureInfo.InvariantCulture))
|
||||
Return post("data")("selftext").ToString.ToLower(Globalization.CultureInfo.InvariantCulture).Contains(keyword.ToLower(System.Globalization.CultureInfo.InvariantCulture))
|
||||
End Function
|
||||
|
||||
End Class
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
# RPST (Reddit Post Scraping Tool)
|
||||
Given a subreddit name and a keyword, this script will return all posts from a specified listing (default is 'top') that contain the provided keyword.
|
||||
|
||||
[](https://github.com/rly0nheart/reddit-post-scraping-tool/actions/workflows/python-publish.yml) [](https://github.com/rly0nheart/reddit-post-scraping-tool/actions/workflows/codeql.yml)  
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
Given a subreddit name and a keyword, RPST will return all posts from a specified listing (default is 'top') that contain the provided keyword.
|
||||
|
||||
[](https://github.com/rly0nheart/reddit-post-scraping-tool/actions/workflows/python-publish.yml) [](https://github.com/rly0nheart/reddit-post-scraping-tool/actions/workflows/codeql.yml)  
|
||||
|
||||
# ✅ Features
|
||||
## GUI
|
||||
- [x] Dark mode (Right-click)
|
||||
- [x] Saves results to a JSON (Right-click)
|
||||
- [x] Dark mode (*Right-click*)
|
||||
- [x] Saves results to a JSON file (*Right-click*)
|
||||
- [x] Logs errors to a file
|
||||
|
||||
## CLI
|
||||
- [x] Saves results to a JSON (-j/--json)
|
||||
- [x] Automatically checks for new updates. Notifies user if update were found.
|
||||
- [x] Saves results to JSON (*specifiy* `--json`)
|
||||
- [x] Saves results to CSV (*specify* `--csv`)
|
||||
- [x] Automatically checks for new updates, and notifies user if updates were found.
|
||||
|
||||
# 📃 TODO
|
||||
## GUI
|
||||
@@ -25,8 +20,21 @@ Given a subreddit name and a keyword, this script will return all posts from a s
|
||||
- [x] Add manual dark mode option, that will be persistent in all sessions
|
||||
- [ ] Make it save results to a CSV file
|
||||
|
||||
# Images & Screenshots
|
||||
## GUI
|
||||
* 
|
||||
* 
|
||||
|
||||
## CLI
|
||||
* 
|
||||
* 
|
||||
* 
|
||||
|
||||
|
||||
|
||||
|
||||
# 📖 Wiki
|
||||
[Refer to the Wiki](https://github.com/rly0nheart/reddit-post-scraping-tool/wiki) for installation instructions, in addition to all other documentation.
|
||||
[Refer to the Wiki](https://github.com/bellingcat/reddit-post-scraping-tool/wiki) for installation instructions, in addition to all other documentation.
|
||||
|
||||
# 😁 Donations
|
||||
If you like `RPST` and would like to show support, you can Buy A Coffee for the developer using the button below
|
||||
|
||||
@@ -9,23 +9,23 @@
|
||||
<ApplicationIcon>icon.ico</ApplicationIcon>
|
||||
<Company>Bellingcat</Company>
|
||||
<Description>Given a subreddit name and a keyword, RPST (Reddit Post Scraping Tool) returns all top (by default) posts that contain the specified keyword. </Description>
|
||||
<Copyright>Copyright (c) 2023-2024 Richard Mwewa</Copyright>
|
||||
<Copyright>Copyright (c) 2023 Richard Mwewa</Copyright>
|
||||
<PackageProjectUrl>https://github.com/bellingcat/reddit-post-scraping-tool</PackageProjectUrl>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/bellingcat/reddit-post-scraping-tool</RepositoryUrl>
|
||||
<AssemblyVersion>1.4.1.0</AssemblyVersion>
|
||||
<FileVersion>1.4.1.0</FileVersion>
|
||||
<AssemblyVersion>1.7.0.0</AssemblyVersion>
|
||||
<FileVersion>1.7.0.0</FileVersion>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
||||
<Version>1.4.1</Version>
|
||||
<Version>1.7.0</Version>
|
||||
<PackageTags>reddit;scraper;reddit-scraper;osint</PackageTags>
|
||||
<PackageReleaseNotes></PackageReleaseNotes>
|
||||
<AnalysisLevel>6.0-recommended</AnalysisLevel>
|
||||
<PackageId>RPST (Reddit Post Scraping Tool)</PackageId>
|
||||
<PackageId>RPST</PackageId>
|
||||
<Authors>Richard Mwewa</Authors>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
<Product>$(AssemblyName)</Product>
|
||||
<AssemblyName>RPST (Reddit Post Scraping Tool)</AssemblyName>
|
||||
<Product>$(AssemblyName) (Reddit Post Scraping Tool)</Product>
|
||||
<AssemblyName>RPST</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -39,7 +39,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -78,4 +78,4 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<Compile Update="AboutBox.vb">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="DeveloperForm.vb">
|
||||
<Compile Update="DeveloperBox.vb">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="PostsForm.vb">
|
||||
<Compile Update="FormMain.vb">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Update="StartForm.vb">
|
||||
<Compile Update="FormPosts.vb">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -25,7 +25,7 @@ Public Class SettingsManager
|
||||
Dim options As New JsonSerializerOptions With {.PropertyNameCaseInsensitive = True}
|
||||
Dim settings = Text.Json.JsonSerializer.Deserialize(Of SettingsManager)(json, options)
|
||||
Me.DarkMode = settings.DarkMode
|
||||
StartForm.DarkModeToolStripMenuItem.Checked = settings.DarkMode
|
||||
FormMain.ToolStripMenuItemDarkMode.Checked = settings.DarkMode
|
||||
Else
|
||||
' Settings file does not exist
|
||||
' Create a new file with default settings 'False'
|
||||
@@ -34,7 +34,7 @@ Public Class SettingsManager
|
||||
File.WriteAllText(settingsFilePath, jsonOutput)
|
||||
|
||||
Me.DarkMode = False
|
||||
StartForm.DarkModeToolStripMenuItem.Checked = False
|
||||
FormMain.ToolStripMenuItemDarkMode.Checked = False
|
||||
End If
|
||||
End Sub
|
||||
|
||||
@@ -46,7 +46,7 @@ Public Class SettingsManager
|
||||
Public Sub ToggleDarkMode(enabled As Boolean)
|
||||
Dim json As String = File.ReadAllText(settingsFilePath)
|
||||
Dim options As New JsonSerializerOptions With {.PropertyNameCaseInsensitive = True}
|
||||
Dim settings As SettingsManager = Text.Json.JsonSerializer.Deserialize(Of SettingsManager)(json, options)
|
||||
Dim settings As SettingsManager = JsonSerializer.Deserialize(Of SettingsManager)(json, options)
|
||||
settings.DarkMode = enabled
|
||||
SaveSettings(settings)
|
||||
ApplyTheme()
|
||||
@@ -57,7 +57,7 @@ Public Class SettingsManager
|
||||
''' </summary>
|
||||
''' <param name="settings">An instance of the SettingsManager containing the configurations to be saved.</param>
|
||||
Private Sub SaveSettings(settings)
|
||||
Dim jsonOutput = Text.Json.JsonSerializer.Serialize(settings)
|
||||
Dim jsonOutput = JsonSerializer.Serialize(settings)
|
||||
File.WriteAllText(settingsFilePath, jsonOutput)
|
||||
End Sub
|
||||
|
||||
@@ -70,69 +70,125 @@ Public Class SettingsManager
|
||||
Dim DarkMode As Boolean = GetDarkMode()
|
||||
If DarkMode Then
|
||||
' Enable dark mode for the Main form
|
||||
StartForm.BackColor = ColorTranslator.FromHtml("#FF121212")
|
||||
StartForm.ToolsToolStripMenuTools.ForeColor = ColorTranslator.FromHtml("#FFFFFFFF")
|
||||
StartForm.KeywordTextBox.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
StartForm.KeywordTextBox.ForeColor = SystemColors.Control
|
||||
StartForm.SubredditTextBox.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
StartForm.SubredditTextBox.ForeColor = SystemColors.Control
|
||||
StartForm.LimitNumericUpDown.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
StartForm.LimitNumericUpDown.ForeColor = SystemColors.Control
|
||||
StartForm.LimitNumericUpDown.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
StartForm.LimitNumericUpDown.ForeColor = SystemColors.Control
|
||||
StartForm.ListingComboBox.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
StartForm.ListingComboBox.ForeColor = SystemColors.Control
|
||||
StartForm.TimeframeComboBox.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
StartForm.TimeframeComboBox.ForeColor = SystemColors.Control
|
||||
StartForm.LabelKeyword.ForeColor = SystemColors.Control
|
||||
StartForm.LabelSubreddit.ForeColor = SystemColors.Control
|
||||
StartForm.LabelLimit.ForeColor = SystemColors.Control
|
||||
StartForm.LabelListing.ForeColor = SystemColors.Control
|
||||
StartForm.LabelTimeframe.ForeColor = SystemColors.Control
|
||||
' Background colours (I know 'Colours'/'Colors'😆)
|
||||
FormMain.BackColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.TextBoxSubreddit.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
FormMain.TextBoxKeyword.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
FormMain.NumericUpDownLimit.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
FormMain.NumericUpDownLimit.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
FormMain.ComboBoxListing.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
FormMain.ComboBoxTimeframe.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
' Foreground colours
|
||||
FormMain.TextBoxKeyword.ForeColor = SystemColors.Control
|
||||
FormMain.TextBoxSubreddit.ForeColor = SystemColors.Control
|
||||
FormMain.NumericUpDownLimit.ForeColor = SystemColors.Control
|
||||
FormMain.NumericUpDownLimit.ForeColor = SystemColors.Control
|
||||
FormMain.ComboBoxListing.ForeColor = SystemColors.Control
|
||||
FormMain.ComboBoxTimeframe.ForeColor = SystemColors.Control
|
||||
FormMain.LabelKeyword.ForeColor = SystemColors.Control
|
||||
FormMain.LabelSubreddit.ForeColor = SystemColors.Control
|
||||
FormMain.LabelLimit.ForeColor = SystemColors.Control
|
||||
FormMain.LabelListing.ForeColor = SystemColors.Control
|
||||
FormMain.LabelTimeframe.ForeColor = SystemColors.Control
|
||||
|
||||
|
||||
' Enable dark mode on 'Right Click Menu' items
|
||||
' Background colours
|
||||
FormMain.ToolStripMenuItemDarkMode.BackColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.ToolStripMenuItemSavePosts.BackColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.ToolStripMenuItemtoJSON.BackColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.ToolStripMenuItemtoCSV.BackColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.ToolStripMenuItemAbout.BackColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.ToolStripMenuItemDeveloper.BackColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.ToolStripMenuItemCheckUpdates.BackColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.ToolStripMenuItemQuit.BackColor = ColorTranslator.FromHtml("#FF121212")
|
||||
' Foreground colours
|
||||
FormMain.ToolStripMenuItemDarkMode.ForeColor = SystemColors.Control
|
||||
FormMain.ToolStripMenuItemSavePosts.ForeColor = SystemColors.Control
|
||||
FormMain.ToolStripMenuItemtoJSON.ForeColor = SystemColors.Control
|
||||
FormMain.ToolStripMenuItemtoCSV.ForeColor = SystemColors.Control
|
||||
FormMain.ToolStripMenuItemAbout.ForeColor = SystemColors.Control
|
||||
FormMain.ToolStripMenuItemDeveloper.ForeColor = SystemColors.Control
|
||||
FormMain.ToolStripMenuItemCheckUpdates.ForeColor = SystemColors.Control
|
||||
FormMain.ToolStripMenuItemQuit.ForeColor = SystemColors.Control
|
||||
|
||||
|
||||
' Enable dark mode for the About box
|
||||
' Background colours
|
||||
AboutBox.BackColor = ColorTranslator.FromHtml("#FF121212")
|
||||
AboutBox.ForeColor = SystemColors.Control
|
||||
AboutBox.LicenseRichTextBox.BackColor = ColorTranslator.FromHtml("#FF2E2E2E")
|
||||
AboutBox.LicenseRichTextBox.ForeColor = SystemColors.Control
|
||||
AboutBox.Panel1.BackColor = ColorTranslator.FromHtml("#FF121212")
|
||||
' Foreground colours
|
||||
AboutBox.ForeColor = SystemColors.Control
|
||||
AboutBox.LicenseRichTextBox.ForeColor = SystemColors.Control
|
||||
AboutBox.LabelProgramName.ForeColor = SystemColors.Control
|
||||
AboutBox.LabelProgramDescription.ForeColor = SystemColors.Control
|
||||
AboutBox.LabelVersion.ForeColor = SystemColors.Control
|
||||
|
||||
' If dark mode is enabled, set the 'Dark Mode' text value to 'Light mode'
|
||||
FormMain.ToolStripMenuItemDarkMode.Text = "Light Mode"
|
||||
Else
|
||||
StartForm.BackColor = SystemColors.Control
|
||||
StartForm.ToolsToolStripMenuTools.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
StartForm.KeywordTextBox.BackColor = SystemColors.Control
|
||||
StartForm.KeywordTextBox.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
StartForm.SubredditTextBox.BackColor = SystemColors.Control
|
||||
StartForm.SubredditTextBox.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
StartForm.LimitNumericUpDown.BackColor = SystemColors.Control
|
||||
StartForm.LimitNumericUpDown.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
StartForm.LimitNumericUpDown.BackColor = SystemColors.Control
|
||||
StartForm.LimitNumericUpDown.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
StartForm.ListingComboBox.BackColor = SystemColors.Control
|
||||
StartForm.ListingComboBox.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
StartForm.TimeframeComboBox.BackColor = SystemColors.Control
|
||||
StartForm.TimeframeComboBox.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
StartForm.LabelKeyword.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
StartForm.LabelSubreddit.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
StartForm.LabelLimit.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
StartForm.LabelListing.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
StartForm.LabelTimeframe.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
' Disable dark mode for the Main Form
|
||||
' Background colours
|
||||
FormMain.BackColor = Color.Gainsboro
|
||||
FormMain.TextBoxKeyword.BackColor = SystemColors.Control
|
||||
FormMain.TextBoxSubreddit.BackColor = SystemColors.Control
|
||||
FormMain.NumericUpDownLimit.BackColor = SystemColors.Control
|
||||
FormMain.NumericUpDownLimit.BackColor = SystemColors.Control
|
||||
FormMain.ComboBoxTimeframe.BackColor = SystemColors.Control
|
||||
FormMain.ComboBoxListing.BackColor = SystemColors.Control
|
||||
' Foreground colours
|
||||
FormMain.TextBoxKeyword.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.TextBoxSubreddit.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.NumericUpDownLimit.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.NumericUpDownLimit.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.ComboBoxListing.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.ComboBoxTimeframe.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.LabelKeyword.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.LabelSubreddit.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.LabelLimit.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.LabelListing.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
FormMain.LabelTimeframe.ForeColor = ColorTranslator.FromHtml("#FF121212")
|
||||
|
||||
' Disable dark mode on 'Right Click Menu' items
|
||||
' Background colours
|
||||
FormMain.ToolStripMenuItemDarkMode.BackColor = Color.Gainsboro
|
||||
FormMain.ToolStripMenuItemSavePosts.BackColor = Color.Gainsboro
|
||||
FormMain.ToolStripMenuItemtoJSON.BackColor = Color.Gainsboro
|
||||
FormMain.ToolStripMenuItemtoCSV.BackColor = Color.Gainsboro
|
||||
FormMain.ToolStripMenuItemAbout.BackColor = Color.Gainsboro
|
||||
FormMain.ToolStripMenuItemDeveloper.BackColor = Color.Gainsboro
|
||||
FormMain.ToolStripMenuItemCheckUpdates.BackColor = Color.Gainsboro
|
||||
FormMain.ToolStripMenuItemQuit.BackColor = Color.Gainsboro
|
||||
' Foreground colours
|
||||
FormMain.ToolStripMenuItemDarkMode.ForeColor = Color.Black
|
||||
FormMain.ToolStripMenuItemSavePosts.ForeColor = Color.Black
|
||||
FormMain.ToolStripMenuItemtoJSON.ForeColor = Color.Black
|
||||
FormMain.ToolStripMenuItemtoCSV.ForeColor = Color.Black
|
||||
FormMain.ToolStripMenuItemAbout.ForeColor = Color.Black
|
||||
FormMain.ToolStripMenuItemDeveloper.ForeColor = Color.Black
|
||||
FormMain.ToolStripMenuItemCheckUpdates.ForeColor = Color.Black
|
||||
FormMain.ToolStripMenuItemQuit.ForeColor = Color.Black
|
||||
|
||||
' Disable dark mode for the About box
|
||||
' Background colours
|
||||
AboutBox.BackColor = Color.Gainsboro
|
||||
AboutBox.ForeColor = SystemColors.WindowText
|
||||
AboutBox.LicenseRichTextBox.BackColor = SystemColors.Control
|
||||
AboutBox.LicenseRichTextBox.ForeColor = SystemColors.WindowText
|
||||
AboutBox.Panel1.BackColor = Color.Gainsboro
|
||||
' Foreground colours
|
||||
AboutBox.Panel1.ForeColor = SystemColors.WindowText
|
||||
AboutBox.LabelProgramName.ForeColor = SystemColors.WindowText
|
||||
AboutBox.LabelProgramDescription.ForeColor = SystemColors.WindowText
|
||||
AboutBox.LabelVersion.ForeColor = SystemColors.WindowText
|
||||
|
||||
' If dark mode is disabled, set the 'Light Mode' text value to 'Dark Mode'
|
||||
FormMain.ToolStripMenuItemDarkMode.Text = "Dark Mode"
|
||||
End If
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Retrieves the Dark Mode setting value from 'settings.json'.
|
||||
''' If the settings file doesn't exist, defaults to returning 'False' (Dark Mode off).
|
||||
|
||||
324
RPST GUI/RPST/StartForm.Designer.vb
generated
324
RPST GUI/RPST/StartForm.Designer.vb
generated
@@ -1,324 +0,0 @@
|
||||
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()>
|
||||
Partial Class StartForm
|
||||
Inherits System.Windows.Forms.Form
|
||||
|
||||
'Form overrides dispose to clean up the component list.
|
||||
<System.Diagnostics.DebuggerNonUserCode()>
|
||||
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
|
||||
Try
|
||||
If disposing AndAlso components IsNot Nothing Then
|
||||
components.Dispose()
|
||||
End If
|
||||
Finally
|
||||
MyBase.Dispose(disposing)
|
||||
End Try
|
||||
End Sub
|
||||
|
||||
'Required by the Windows Form Designer
|
||||
Private components As System.ComponentModel.IContainer
|
||||
|
||||
'NOTE: The following procedure is required by the Windows Form Designer
|
||||
'It can be modified using the Windows Form Designer.
|
||||
'Do not modify it using the code editor.
|
||||
<System.Diagnostics.DebuggerStepThrough()>
|
||||
Private Sub InitializeComponent()
|
||||
components = New ComponentModel.Container()
|
||||
Dim resources As ComponentModel.ComponentResourceManager = New ComponentModel.ComponentResourceManager(GetType(StartForm))
|
||||
KeywordTextBox = New TextBox()
|
||||
SubredditTextBox = New TextBox()
|
||||
ScrapeButton = New Button()
|
||||
TimeframeComboBox = New ComboBox()
|
||||
ListingComboBox = New ComboBox()
|
||||
LabelKeyword = New Label()
|
||||
LabelSubreddit = New Label()
|
||||
LabelLimit = New Label()
|
||||
LabelListing = New Label()
|
||||
LabelTimeframe = New Label()
|
||||
ContextMenuStrip1 = New ContextMenuStrip(components)
|
||||
SaveResultsStripMenuItem = New ToolStripMenuItem()
|
||||
JSONToolStripMenuItem = New ToolStripMenuItem()
|
||||
CSVToolStripMenuItem = New ToolStripMenuItem()
|
||||
DarkModeToolStripMenuItem = New ToolStripMenuItem()
|
||||
FileMenuStrip = New MenuStrip()
|
||||
ToolsToolStripMenuTools = New ToolStripMenuItem()
|
||||
AboutToolStripMenuItem = New ToolStripMenuItem()
|
||||
DeveloperToolStripMenuItem = New ToolStripMenuItem()
|
||||
CheckUpdatesToolStripMenuItem = New ToolStripMenuItem()
|
||||
ToolStripSeparator2 = New ToolStripSeparator()
|
||||
QuitToolStripMenuItem = New ToolStripMenuItem()
|
||||
LimitNumericUpDown = New NumericUpDown()
|
||||
ToolTip1 = New ToolTip(components)
|
||||
ContextMenuStrip1.SuspendLayout()
|
||||
FileMenuStrip.SuspendLayout()
|
||||
CType(LimitNumericUpDown, ComponentModel.ISupportInitialize).BeginInit()
|
||||
SuspendLayout()
|
||||
'
|
||||
' KeywordTextBox
|
||||
'
|
||||
KeywordTextBox.BackColor = SystemColors.Window
|
||||
KeywordTextBox.ForeColor = SystemColors.WindowText
|
||||
KeywordTextBox.Location = New Point(89, 60)
|
||||
KeywordTextBox.Name = "KeywordTextBox"
|
||||
KeywordTextBox.PlaceholderText = "Keyword"
|
||||
KeywordTextBox.Size = New Size(100, 23)
|
||||
KeywordTextBox.TabIndex = 0
|
||||
'
|
||||
' SubredditTextBox
|
||||
'
|
||||
SubredditTextBox.Location = New Point(89, 92)
|
||||
SubredditTextBox.Name = "SubredditTextBox"
|
||||
SubredditTextBox.PlaceholderText = "Subreddit"
|
||||
SubredditTextBox.Size = New Size(100, 23)
|
||||
SubredditTextBox.TabIndex = 4
|
||||
'
|
||||
' ScrapeButton
|
||||
'
|
||||
ScrapeButton.Location = New Point(257, 191)
|
||||
ScrapeButton.Name = "ScrapeButton"
|
||||
ScrapeButton.Size = New Size(76, 28)
|
||||
ScrapeButton.TabIndex = 6
|
||||
ScrapeButton.Text = "Scrape"
|
||||
ScrapeButton.UseVisualStyleBackColor = True
|
||||
'
|
||||
' TimeframeComboBox
|
||||
'
|
||||
TimeframeComboBox.FormattingEnabled = True
|
||||
TimeframeComboBox.Items.AddRange(New Object() {"Hour", "Day", "Week", "Month", "Year"})
|
||||
TimeframeComboBox.Location = New Point(89, 191)
|
||||
TimeframeComboBox.Name = "TimeframeComboBox"
|
||||
TimeframeComboBox.Size = New Size(100, 23)
|
||||
TimeframeComboBox.TabIndex = 8
|
||||
TimeframeComboBox.Text = "All"
|
||||
'
|
||||
' ListingComboBox
|
||||
'
|
||||
ListingComboBox.FormattingEnabled = True
|
||||
ListingComboBox.Items.AddRange(New Object() {"Controversial", "Hot", "Best", "New", "Rising"})
|
||||
ListingComboBox.Location = New Point(89, 157)
|
||||
ListingComboBox.Name = "ListingComboBox"
|
||||
ListingComboBox.Size = New Size(100, 23)
|
||||
ListingComboBox.TabIndex = 9
|
||||
ListingComboBox.Text = "Top"
|
||||
'
|
||||
' LabelKeyword
|
||||
'
|
||||
LabelKeyword.AutoEllipsis = True
|
||||
LabelKeyword.Font = New Font("Segoe UI Semibold", 9F, FontStyle.Underline, GraphicsUnit.Point)
|
||||
LabelKeyword.ForeColor = Color.Black
|
||||
LabelKeyword.Location = New Point(12, 60)
|
||||
LabelKeyword.Name = "LabelKeyword"
|
||||
LabelKeyword.Size = New Size(56, 23)
|
||||
LabelKeyword.TabIndex = 10
|
||||
LabelKeyword.Text = "Keyword"
|
||||
'
|
||||
' LabelSubreddit
|
||||
'
|
||||
LabelSubreddit.AutoEllipsis = True
|
||||
LabelSubreddit.Font = New Font("Segoe UI Semibold", 9F, FontStyle.Underline, GraphicsUnit.Point)
|
||||
LabelSubreddit.ForeColor = Color.Black
|
||||
LabelSubreddit.Location = New Point(12, 92)
|
||||
LabelSubreddit.Name = "LabelSubreddit"
|
||||
LabelSubreddit.Size = New Size(63, 23)
|
||||
LabelSubreddit.TabIndex = 11
|
||||
LabelSubreddit.Text = "Subreddit"
|
||||
'
|
||||
' LabelLimit
|
||||
'
|
||||
LabelLimit.AutoEllipsis = True
|
||||
LabelLimit.Font = New Font("Segoe UI Semibold", 9F, FontStyle.Bold Or FontStyle.Underline, GraphicsUnit.Point)
|
||||
LabelLimit.ForeColor = Color.Black
|
||||
LabelLimit.Location = New Point(12, 125)
|
||||
LabelLimit.Name = "LabelLimit"
|
||||
LabelLimit.Size = New Size(56, 23)
|
||||
LabelLimit.TabIndex = 12
|
||||
LabelLimit.Text = "Limit"
|
||||
'
|
||||
' LabelListing
|
||||
'
|
||||
LabelListing.AutoEllipsis = True
|
||||
LabelListing.Font = New Font("Segoe UI Semibold", 9F, FontStyle.Bold Or FontStyle.Underline, GraphicsUnit.Point)
|
||||
LabelListing.ForeColor = Color.Black
|
||||
LabelListing.Location = New Point(12, 157)
|
||||
LabelListing.Name = "LabelListing"
|
||||
LabelListing.Size = New Size(56, 23)
|
||||
LabelListing.TabIndex = 13
|
||||
LabelListing.Text = "Listing"
|
||||
'
|
||||
' LabelTimeframe
|
||||
'
|
||||
LabelTimeframe.AutoEllipsis = True
|
||||
LabelTimeframe.Font = New Font("Segoe UI Semibold", 9F, FontStyle.Bold Or FontStyle.Underline, GraphicsUnit.Point)
|
||||
LabelTimeframe.ForeColor = Color.Black
|
||||
LabelTimeframe.Location = New Point(12, 191)
|
||||
LabelTimeframe.Name = "LabelTimeframe"
|
||||
LabelTimeframe.Size = New Size(71, 23)
|
||||
LabelTimeframe.TabIndex = 14
|
||||
LabelTimeframe.Text = "Timeframe"
|
||||
'
|
||||
' ContextMenuStrip1
|
||||
'
|
||||
ContextMenuStrip1.Items.AddRange(New ToolStripItem() {SaveResultsStripMenuItem, DarkModeToolStripMenuItem})
|
||||
ContextMenuStrip1.Name = "ContextMenuStrip1"
|
||||
ContextMenuStrip1.Size = New Size(144, 48)
|
||||
'
|
||||
' SaveResultsStripMenuItem
|
||||
'
|
||||
SaveResultsStripMenuItem.AutoToolTip = True
|
||||
SaveResultsStripMenuItem.DropDownItems.AddRange(New ToolStripItem() {JSONToolStripMenuItem, CSVToolStripMenuItem})
|
||||
SaveResultsStripMenuItem.Image = CType(resources.GetObject("SaveResultsStripMenuItem.Image"), Image)
|
||||
SaveResultsStripMenuItem.Name = "SaveResultsStripMenuItem"
|
||||
SaveResultsStripMenuItem.Size = New Size(143, 22)
|
||||
SaveResultsStripMenuItem.Text = "Save posts to"
|
||||
'
|
||||
' JSONToolStripMenuItem
|
||||
'
|
||||
JSONToolStripMenuItem.AutoToolTip = True
|
||||
JSONToolStripMenuItem.CheckOnClick = True
|
||||
JSONToolStripMenuItem.Image = CType(resources.GetObject("JSONToolStripMenuItem.Image"), Image)
|
||||
JSONToolStripMenuItem.Name = "JSONToolStripMenuItem"
|
||||
JSONToolStripMenuItem.Size = New Size(185, 22)
|
||||
JSONToolStripMenuItem.Text = "JSON"
|
||||
'
|
||||
' CSVToolStripMenuItem
|
||||
'
|
||||
CSVToolStripMenuItem.AutoToolTip = True
|
||||
CSVToolStripMenuItem.Enabled = False
|
||||
CSVToolStripMenuItem.Image = CType(resources.GetObject("CSVToolStripMenuItem.Image"), Image)
|
||||
CSVToolStripMenuItem.Name = "CSVToolStripMenuItem"
|
||||
CSVToolStripMenuItem.Size = New Size(185, 22)
|
||||
CSVToolStripMenuItem.Text = "CSV (coming soon...)"
|
||||
'
|
||||
' DarkModeToolStripMenuItem
|
||||
'
|
||||
DarkModeToolStripMenuItem.AutoToolTip = True
|
||||
DarkModeToolStripMenuItem.CheckOnClick = True
|
||||
DarkModeToolStripMenuItem.Image = CType(resources.GetObject("DarkModeToolStripMenuItem.Image"), Image)
|
||||
DarkModeToolStripMenuItem.Name = "DarkModeToolStripMenuItem"
|
||||
DarkModeToolStripMenuItem.Size = New Size(143, 22)
|
||||
DarkModeToolStripMenuItem.Text = "Dark mode"
|
||||
'
|
||||
' FileMenuStrip
|
||||
'
|
||||
FileMenuStrip.BackColor = Color.Transparent
|
||||
FileMenuStrip.Items.AddRange(New ToolStripItem() {ToolsToolStripMenuTools})
|
||||
FileMenuStrip.Location = New Point(0, 0)
|
||||
FileMenuStrip.Name = "FileMenuStrip"
|
||||
FileMenuStrip.Size = New Size(355, 24)
|
||||
FileMenuStrip.TabIndex = 0
|
||||
FileMenuStrip.Text = "MenuStrip1"
|
||||
'
|
||||
' ToolsToolStripMenuTools
|
||||
'
|
||||
ToolsToolStripMenuTools.DropDownItems.AddRange(New ToolStripItem() {AboutToolStripMenuItem, DeveloperToolStripMenuItem, CheckUpdatesToolStripMenuItem, ToolStripSeparator2, QuitToolStripMenuItem})
|
||||
ToolsToolStripMenuTools.Font = New Font("Segoe UI", 9F, FontStyle.Regular, GraphicsUnit.Point)
|
||||
ToolsToolStripMenuTools.ForeColor = Color.White
|
||||
ToolsToolStripMenuTools.Image = CType(resources.GetObject("ToolsToolStripMenuTools.Image"), Image)
|
||||
ToolsToolStripMenuTools.Name = "ToolsToolStripMenuTools"
|
||||
ToolsToolStripMenuTools.Size = New Size(28, 20)
|
||||
'
|
||||
' AboutToolStripMenuItem
|
||||
'
|
||||
AboutToolStripMenuItem.AutoToolTip = True
|
||||
AboutToolStripMenuItem.Image = CType(resources.GetObject("AboutToolStripMenuItem.Image"), Image)
|
||||
AboutToolStripMenuItem.Name = "AboutToolStripMenuItem"
|
||||
AboutToolStripMenuItem.Size = New Size(152, 22)
|
||||
AboutToolStripMenuItem.Text = "About"
|
||||
'
|
||||
' DeveloperToolStripMenuItem
|
||||
'
|
||||
DeveloperToolStripMenuItem.AutoToolTip = True
|
||||
DeveloperToolStripMenuItem.Image = CType(resources.GetObject("DeveloperToolStripMenuItem.Image"), Image)
|
||||
DeveloperToolStripMenuItem.Name = "DeveloperToolStripMenuItem"
|
||||
DeveloperToolStripMenuItem.Size = New Size(152, 22)
|
||||
DeveloperToolStripMenuItem.Text = "Developer"
|
||||
'
|
||||
' CheckUpdatesToolStripMenuItem
|
||||
'
|
||||
CheckUpdatesToolStripMenuItem.AutoToolTip = True
|
||||
CheckUpdatesToolStripMenuItem.Image = CType(resources.GetObject("CheckUpdatesToolStripMenuItem.Image"), Image)
|
||||
CheckUpdatesToolStripMenuItem.Name = "CheckUpdatesToolStripMenuItem"
|
||||
CheckUpdatesToolStripMenuItem.Size = New Size(152, 22)
|
||||
CheckUpdatesToolStripMenuItem.Text = "Check updates"
|
||||
'
|
||||
' ToolStripSeparator2
|
||||
'
|
||||
ToolStripSeparator2.Name = "ToolStripSeparator2"
|
||||
ToolStripSeparator2.Size = New Size(149, 6)
|
||||
'
|
||||
' QuitToolStripMenuItem
|
||||
'
|
||||
QuitToolStripMenuItem.AutoToolTip = True
|
||||
QuitToolStripMenuItem.Image = CType(resources.GetObject("QuitToolStripMenuItem.Image"), Image)
|
||||
QuitToolStripMenuItem.Name = "QuitToolStripMenuItem"
|
||||
QuitToolStripMenuItem.Size = New Size(152, 22)
|
||||
QuitToolStripMenuItem.Text = "Quit"
|
||||
'
|
||||
' LimitNumericUpDown
|
||||
'
|
||||
LimitNumericUpDown.Location = New Point(89, 125)
|
||||
LimitNumericUpDown.Minimum = New Decimal(New Integer() {5, 0, 0, 0})
|
||||
LimitNumericUpDown.Name = "LimitNumericUpDown"
|
||||
LimitNumericUpDown.ReadOnly = True
|
||||
LimitNumericUpDown.Size = New Size(100, 23)
|
||||
LimitNumericUpDown.TabIndex = 15
|
||||
LimitNumericUpDown.Value = New Decimal(New Integer() {10, 0, 0, 0})
|
||||
'
|
||||
' StartForm
|
||||
'
|
||||
AutoScaleDimensions = New SizeF(7F, 15F)
|
||||
AutoScaleMode = AutoScaleMode.Font
|
||||
BackColor = SystemColors.Control
|
||||
ClientSize = New Size(355, 255)
|
||||
ContextMenuStrip = ContextMenuStrip1
|
||||
Controls.Add(LimitNumericUpDown)
|
||||
Controls.Add(FileMenuStrip)
|
||||
Controls.Add(LabelTimeframe)
|
||||
Controls.Add(LabelListing)
|
||||
Controls.Add(LabelLimit)
|
||||
Controls.Add(LabelSubreddit)
|
||||
Controls.Add(LabelKeyword)
|
||||
Controls.Add(ListingComboBox)
|
||||
Controls.Add(TimeframeComboBox)
|
||||
Controls.Add(SubredditTextBox)
|
||||
Controls.Add(ScrapeButton)
|
||||
Controls.Add(KeywordTextBox)
|
||||
FormBorderStyle = FormBorderStyle.FixedSingle
|
||||
Icon = CType(resources.GetObject("$this.Icon"), Icon)
|
||||
MainMenuStrip = FileMenuStrip
|
||||
MaximizeBox = False
|
||||
Name = "StartForm"
|
||||
StartPosition = FormStartPosition.CenterScreen
|
||||
Text = "ProgramName ProgramVersion"
|
||||
ContextMenuStrip1.ResumeLayout(False)
|
||||
FileMenuStrip.ResumeLayout(False)
|
||||
FileMenuStrip.PerformLayout()
|
||||
CType(LimitNumericUpDown, ComponentModel.ISupportInitialize).EndInit()
|
||||
ResumeLayout(False)
|
||||
PerformLayout()
|
||||
End Sub
|
||||
|
||||
Friend WithEvents KeywordTextBox As TextBox
|
||||
Friend WithEvents SubredditTextBox As TextBox
|
||||
Friend WithEvents ScrapeButton As Button
|
||||
Friend WithEvents TimeframeComboBox As ComboBox
|
||||
Friend WithEvents ListingComboBox As ComboBox
|
||||
Friend WithEvents LabelKeyword As Label
|
||||
Friend WithEvents LabelSubreddit As Label
|
||||
Friend WithEvents LabelLimit As Label
|
||||
Friend WithEvents LabelListing As Label
|
||||
Friend WithEvents LabelTimeframe As Label
|
||||
Friend WithEvents ContextMenuStrip1 As ContextMenuStrip
|
||||
Friend WithEvents FileMenuStrip As MenuStrip
|
||||
Friend WithEvents ToolsToolStripMenuTools As ToolStripMenuItem
|
||||
Friend WithEvents AboutToolStripMenuItem As ToolStripMenuItem
|
||||
Friend WithEvents DeveloperToolStripMenuItem As ToolStripMenuItem
|
||||
Friend WithEvents ToolStripSeparator2 As ToolStripSeparator
|
||||
Friend WithEvents QuitToolStripMenuItem As ToolStripMenuItem
|
||||
Friend WithEvents SaveResultsStripMenuItem As ToolStripMenuItem
|
||||
Friend WithEvents CheckUpdatesToolStripMenuItem As ToolStripMenuItem
|
||||
Friend WithEvents JSONToolStripMenuItem As ToolStripMenuItem
|
||||
Friend WithEvents CSVToolStripMenuItem As ToolStripMenuItem
|
||||
Friend WithEvents LimitNumericUpDown As NumericUpDown
|
||||
Friend WithEvents DarkModeToolStripMenuItem As ToolStripMenuItem
|
||||
Friend WithEvents ToolTip1 As ToolTip
|
||||
End Class
|
||||
@@ -1,105 +0,0 @@
|
||||
Imports System.IO
|
||||
Imports Newtonsoft.Json
|
||||
Imports Newtonsoft.Json.Linq
|
||||
|
||||
Public Class StartForm
|
||||
ReadOnly settings As New SettingsManager()
|
||||
ReadOnly ApiHandler As New ApiHandler()
|
||||
|
||||
''' <summary>
|
||||
''' Handles the click event of the ScrapeButton.
|
||||
''' Collects inputs, fetches Reddit posts based on the inputs,
|
||||
''' and updates the DataGridView with the fetched posts.
|
||||
''' </summary>
|
||||
''' <param name="sender">The sender of the event.</param>
|
||||
''' <param name="e">The EventArgs instance containing the event data.</param>
|
||||
Private Sub ScrapeButton_Click(sender As Object, e As EventArgs) Handles ScrapeButton.Click
|
||||
Utilities.ProcessRedditPosts(JSONToolStripMenuItem)
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Event handler for the form load event.
|
||||
''' It loads settings, toggles dark mode if necessary, checks for directories, logs first time launch, and sets the form title.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">An EventArgs that contains the event data.</param>
|
||||
Private Sub StartForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
|
||||
settings.LoadSettings()
|
||||
settings.ToggleDarkMode(settings.DarkMode)
|
||||
Utilities.PathFinder()
|
||||
Utilities.LogFirstTimeLaunch()
|
||||
Me.Text = My.Application.Info.AssemblyName
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Event handler for the 'About' menu item click.
|
||||
''' It shows the 'About' box.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">An EventArgs that contains the event data.</param>
|
||||
Private Sub AboutToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles AboutToolStripMenuItem.Click
|
||||
AboutBox.Show()
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
''' Event handler for the 'Quit' menu item click.
|
||||
''' It asks the user for confirmation and closes the program if the user agrees.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">An EventArgs that contains the event data.</param>
|
||||
Private Sub QuitToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles QuitToolStripMenuItem.Click
|
||||
Dim result As DialogResult = MessageBox.Show("This will close the program, continue?", "Quit", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
|
||||
If result = DialogResult.Yes Then
|
||||
Me.Close()
|
||||
End If
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Event handler for the 'Developer' menu item click.
|
||||
''' It shows the 'Developer' dialog box.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">An EventArgs that contains the event data.</param>
|
||||
Private Sub DeveloperToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles DeveloperToolStripMenuItem.Click
|
||||
DeveloperForm.ShowDialog()
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Event handler for the 'Check Updates' menu item click.
|
||||
''' It checks for application updates and provides update information if a newer version is available.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">An EventArgs that contains the event data.</param>
|
||||
Private Sub CheckUpdatesToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles CheckUpdatesToolStripMenuItem.Click
|
||||
|
||||
Dim data As JObject = ApiHandler.CheckUpdates()
|
||||
If data("tag_name").ToString = $"{My.Application.Info.Version}" Then
|
||||
MessageBox.Show($"You're running the current version v{My.Application.Info.Version} of {My.Application.Info.ProductName}. Check again soon! :)", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information)
|
||||
Else
|
||||
Dim confirm As DialogResult = MessageBox.Show($"A new version v{data("tag_name")} of {My.Application.Info.ProductName} is available, would you like to get it?
|
||||
|
||||
What's new in v{data("tag_name")}?
|
||||
{data("body")}
|
||||
", "Update", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
|
||||
If confirm = DialogResult.Yes Then
|
||||
Shell($"cmd /c start https://github.com/bellingcat/reddit-post-scraping-tool/releases/tag/{data("tag_name")}")
|
||||
End If
|
||||
End If
|
||||
|
||||
End Sub
|
||||
|
||||
|
||||
''' <summary>
|
||||
''' Event handler for the 'Dark Mode' checkbox change event.
|
||||
''' It toggles the dark mode of the application based on the checkbox status.
|
||||
''' </summary>
|
||||
''' <param name="sender">The source of the event.</param>
|
||||
''' <param name="e">An EventArgs that contains the event data.</param>
|
||||
Private Sub DarkModeToolStripMenuItem_CheckedChanged(sender As Object, e As EventArgs) Handles DarkModeToolStripMenuItem.CheckedChanged
|
||||
settings.ToggleDarkMode(DarkModeToolStripMenuItem.Checked)
|
||||
End Sub
|
||||
End Class
|
||||
@@ -17,7 +17,7 @@ Public Class Utilities
|
||||
|
||||
If inputs.HasValue Then
|
||||
' Initialize the DataGridView
|
||||
DataGridViewHandler.AddColumn(PostsForm.DataGridViewPosts)
|
||||
DataGridViewHandler.AddColumn(FormPosts.DataGridViewPosts)
|
||||
|
||||
' Fetch Reddit posts based on the inputs
|
||||
Dim processor As New PostsProcessor()
|
||||
@@ -29,17 +29,17 @@ Public Class Utilities
|
||||
For Each post In posts("data")("children")
|
||||
totalPosts += 1
|
||||
' Check if the post contains the keyword
|
||||
If PostsProcessor.PostContainsKeyword(post, inputs.Value.Keyword.ToLower(System.Globalization.CultureInfo.InvariantCulture)) Then
|
||||
If PostsProcessor.PostContainsKeyword(post, inputs.Value.Keyword.ToLower(Globalization.CultureInfo.InvariantCulture)) Then
|
||||
' Add the post to the DataGridView
|
||||
DataGridViewHandler.AddRow(PostsForm.DataGridViewPosts, post, totalPosts)
|
||||
PostsForm.Show()
|
||||
DataGridViewHandler.AddRow(FormPosts.DataGridViewPosts, post, totalPosts)
|
||||
FormPosts.Show()
|
||||
keywordFound = True
|
||||
End If
|
||||
Next
|
||||
|
||||
' Check if the keyword was found in any posts
|
||||
If Not keywordFound Then
|
||||
MessageBox.Show($"Keyword `{inputs.Value.Keyword}` was not found in any of the " + posts("data")("children").Count.ToString(System.Globalization.CultureInfo.InvariantCulture) _
|
||||
MessageBox.Show($"Keyword `{inputs.Value.Keyword}` was not found in any of the " + posts("data")("children").Count.ToString(Globalization.CultureInfo.InvariantCulture) _
|
||||
+ $" {inputs.Value.Listing} posts from r/{inputs.Value.Subreddit}", "Not Found", MessageBoxButtons.OK, MessageBoxIcon.Warning)
|
||||
End If
|
||||
|
||||
@@ -48,7 +48,6 @@ Public Class Utilities
|
||||
Utilities.SavePostsToJson(posts("data"))
|
||||
End If
|
||||
Else
|
||||
MessageBox.Show("Inputs cannot be empty. Please enter a keyword and a subreddit.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
|
||||
End If
|
||||
End Sub
|
||||
|
||||
@@ -76,8 +75,8 @@ Public Class Utilities
|
||||
''' </summary>
|
||||
''' <returns>
|
||||
''' Tuple containing:
|
||||
''' Keyword (String) - Keyword entered by user in the StartForm.
|
||||
''' Subreddit (String) - Subreddit entered by user in the StartForm.
|
||||
''' Keyword (String) - Keyword entered by user in theFormMain.
|
||||
''' Subreddit (String) - Subreddit entered by user in theFormMain.
|
||||
''' Listing (String) - Listing chosen by user in the StartForm, defaults to 'top' if none is selected.
|
||||
''' Limit (Integer) - Limit entered by user in the StartForm, defaults to 10 if the entered value is over 100.
|
||||
''' Timeframe (String) - Timeframe chosen by user in the StartForm, defaults to 'all' if none is selected.
|
||||
@@ -86,27 +85,24 @@ Public Class Utilities
|
||||
''' If keyword or subreddit are empty, Displays a warning and returns nothing.
|
||||
''' </remarks>
|
||||
Public Shared Function CollectInputs() As (Keyword As String, Subreddit As String, Listing As String, Limit As Integer, Timeframe As String)?
|
||||
Dim keyword As String = StartForm.KeywordTextBox.Text.Trim()
|
||||
Dim subreddit As String = StartForm.SubredditTextBox.Text.Trim()
|
||||
' Convert the Keyword and Subreddit to lowercase using InvariantCulture
|
||||
Dim listing As String = If(String.IsNullOrEmpty(StartForm.ListingComboBox.Text), "top", StartForm.ListingComboBox.Text.ToLower(System.Globalization.CultureInfo.InvariantCulture).Trim())
|
||||
Dim limit As Integer = StartForm.LimitNumericUpDown.Value
|
||||
Dim timeframe As String = If(String.IsNullOrEmpty(StartForm.TimeframeComboBox.Text), "all", StartForm.TimeframeComboBox.Text.ToLower(System.Globalization.CultureInfo.InvariantCulture).Trim())
|
||||
Dim keyword As String = FormMain.TextBoxKeyword.Text.Trim()
|
||||
Dim subreddit As String = FormMain.TextBoxSubreddit.Text.Trim()
|
||||
' Convert the Listing and Subreddit to lowercase using InvariantCulture
|
||||
Dim listing As String = If(String.IsNullOrEmpty(FormMain.ComboBoxListing.Text), "top", FormMain.ComboBoxListing.Text.ToLower(Globalization.CultureInfo.InvariantCulture).Trim())
|
||||
Dim timeframe As String = If(String.IsNullOrEmpty(FormMain.ComboBoxTimeframe.Text), "all", FormMain.ComboBoxTimeframe.Text.ToLower(Globalization.CultureInfo.InvariantCulture).Trim())
|
||||
Dim limit As Integer = FormMain.NumericUpDownLimit.Value
|
||||
|
||||
' Validate inputs
|
||||
If String.IsNullOrEmpty(keyword) Then
|
||||
MessageBox.Show("Keyword should not be empty", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
|
||||
If String.IsNullOrEmpty(keyword) AndAlso String.IsNullOrEmpty(subreddit) Then
|
||||
MessageBox.Show("Keyword and Subreddit fields should not be empty.", "Invalid Inputs", MessageBoxButtons.OK, MessageBoxIcon.Warning)
|
||||
Return Nothing
|
||||
ElseIf String.IsNullOrEmpty(keyword) Then
|
||||
MessageBox.Show("Keyword field should not be empty.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning)
|
||||
Return Nothing
|
||||
ElseIf String.IsNullOrEmpty(subreddit) Then
|
||||
MessageBox.Show("Subreddit field should not be empty.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Warning)
|
||||
Return Nothing
|
||||
End If
|
||||
If String.IsNullOrEmpty(subreddit) Then
|
||||
MessageBox.Show("Subreddit should not be empty", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
|
||||
Return Nothing
|
||||
End If
|
||||
If limit > 100 Then
|
||||
MessageBox.Show("Limit should not be over 100. Defaulting to 10", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
|
||||
limit = 10
|
||||
End If
|
||||
|
||||
Return (keyword, subreddit, listing, limit, timeframe)
|
||||
End Function
|
||||
|
||||
@@ -174,7 +170,6 @@ First launched on: {DateTime.Now}"
|
||||
LicenseNotice()
|
||||
File.WriteAllText(filePath, textToWrite)
|
||||
Else
|
||||
' DO NOTHING
|
||||
End If
|
||||
End Sub
|
||||
End Class
|
||||
|
||||
@@ -7,18 +7,18 @@ packages = ["rpst"]
|
||||
|
||||
[project]
|
||||
name = "reddit-post-scraping-tool"
|
||||
version = "1.4.1.0"
|
||||
version = "1.7.0.0"
|
||||
description = "Given a subreddit name and a keyword, RPST returns all top (by default) posts that contain the specified keyword."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
license = {file = "LICENSE"}
|
||||
keywords = ["osint", "reddit-crawler", "reddit-scraping", "reddit"]
|
||||
keywords = ["reddit-crawler", "reddit-scraping", "reddit", "reddit-api"]
|
||||
authors = [{name = "Richard Mwewa", email = "rly0nheart@duck.com"}]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Visual Basic",
|
||||
"Intended Audience :: Information Technology",
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Natural Language :: English"
|
||||
@@ -26,6 +26,7 @@ classifiers = [
|
||||
|
||||
dependencies = [
|
||||
"rich",
|
||||
"glyphoji",
|
||||
"requests",
|
||||
]
|
||||
|
||||
@@ -35,4 +36,4 @@ documentation = "https://github.com/bellingcat/reddit-post-scraping-tool/wiki"
|
||||
repository = "https://github.com/bellingcat/reddit-post-scraping-tool.git"
|
||||
|
||||
[project.scripts]
|
||||
rpst = "rpst.__main:run"
|
||||
rpst = "rpst.main:run"
|
||||
|
||||
1
rpst/__init__.py
Normal file
1
rpst/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
201
rpst/__rpst_.py
201
rpst/__rpst_.py
@@ -1,201 +0,0 @@
|
||||
import json
|
||||
import logging
|
||||
import argparse
|
||||
import requests
|
||||
from rich.tree import Tree
|
||||
from rich import print as xprint
|
||||
from rich.markdown import Markdown
|
||||
from rich.logging import RichHandler
|
||||
|
||||
|
||||
def write_post_data(post_data: dict, filename: str):
|
||||
"""
|
||||
Writes post data to a specified JSON file.
|
||||
|
||||
:param post_data: A dictionary containing post data.
|
||||
:param filename: The name of the file to which post data will be written.
|
||||
"""
|
||||
# Write the data to a JSON file
|
||||
with open(filename + ".json", 'a') as file:
|
||||
file.write(json.dumps(post_data))
|
||||
file.write('\n') # write a newline to separate posts
|
||||
|
||||
log.info(f"Post data written to '{file.name}'")
|
||||
|
||||
|
||||
def check_updates(version_tag: str):
|
||||
"""
|
||||
This function checks if there's a new release of a project on GitHub. If there is, it logs an
|
||||
information message and prints the release notes.
|
||||
|
||||
:param version_tag: A string representing the current version of the project.
|
||||
"""
|
||||
|
||||
# Make a GET request to the GitHub API to get the latest release of the project
|
||||
response = requests.get("https://api.github.com/repos/bellingcat/reddit-post-scraping-tool/releases/latest").json()
|
||||
|
||||
# Check if the latest release's tag matches the current version tag
|
||||
if response['tag_name'] != version_tag:
|
||||
|
||||
# If not, convert the release notes from Markdown to HTML
|
||||
raw_release_notes = response['body']
|
||||
markdown_release_notes = Markdown(raw_release_notes)
|
||||
|
||||
# Log an info message about the new release
|
||||
log.info(
|
||||
f"A new release of RPST is available ({response['tag_name']}). "
|
||||
f"Run 'pip install --upgrade reddit-post-scraping-tool' to get the updates."
|
||||
)
|
||||
|
||||
# Print the release notes
|
||||
xprint(markdown_release_notes)
|
||||
|
||||
|
||||
def format_post_data(post: dict, keyword: str, output: bool):
|
||||
"""
|
||||
This function extracts relevant data from a Reddit post and displays it in a tree structure,
|
||||
followed by the post's selftext.
|
||||
|
||||
:param post: A dictionary containing the data of a Reddit post.
|
||||
:param keyword: The keyword that is used to find posts, in his case gets uses as the filename.
|
||||
:param output: If specified, all found posts will be written to a json file.
|
||||
"""
|
||||
# Define the data to extract from the post
|
||||
post_data = {
|
||||
'Author': post['data']['author'],
|
||||
'ID': post['data']['id'],
|
||||
'Subreddit': post["data"]["subreddit_name_prefixed"],
|
||||
'Visibility': post['data']['subreddit_type'],
|
||||
'Thumbnail': post["data"]["thumbnail"],
|
||||
'NSFW': post['data']['over_18'],
|
||||
'Gilded': post['data']['gilded'],
|
||||
'Upvotes': post["data"]["ups"],
|
||||
'Upvote ratio': post["data"]["upvote_ratio"],
|
||||
'Downvotes': post["data"]["downs"],
|
||||
'Awards': post["data"]["total_awards_received"],
|
||||
'Top award': post['data']['top_awarded_type'],
|
||||
'Is crosspostable?': post['data']['is_crosspostable'],
|
||||
'Score': post["data"]["score"],
|
||||
'Category': post['data']['category'],
|
||||
'Domain': post["data"]["domain"],
|
||||
'Created': post['data']['created'],
|
||||
'Approved at': post['data']['approved_at_utc'],
|
||||
'Approved by': post['data']['approved_by'],
|
||||
}
|
||||
if output:
|
||||
write_post_data(filename=keyword, post_data=post_data)
|
||||
# Create a tree structure with the post's title as the root
|
||||
post_tree = Tree("\n" + post['data']['title'])
|
||||
|
||||
# Add each piece of extracted data as a branch of the tree
|
||||
for post_key, post_value in post_data.items():
|
||||
post_tree.add(f"{post_key}: {post_value}")
|
||||
|
||||
# Print the tree structure
|
||||
xprint(post_tree)
|
||||
|
||||
# Print the post's selftext
|
||||
print(post['data']['selftext'] + "\n")
|
||||
|
||||
|
||||
def get_posts(arguments: argparse):
|
||||
"""
|
||||
Scrapes a given subreddit for posts that contain a specified keyword.
|
||||
The search is limited by the number of posts and timeframe specified. The results are either
|
||||
printed to the console or saved to a specified file, based on the 'output' argument.
|
||||
|
||||
:param arguments: Namespace object from argparse.
|
||||
|
||||
Expected Object Attributes
|
||||
--------------------------
|
||||
- keyword: The keyword to search for in the posts.
|
||||
- subreddit: The subreddit to scrape.
|
||||
- listing: The type of posts to scrape. This could be 'hot', 'new', etc.
|
||||
- timeframe: The timeframe from which to scrape posts. This could be 'day', 'week', etc.
|
||||
- limit: The maximum number of posts to scrape.
|
||||
- json: If specified, all found posts will be written to a json file.
|
||||
|
||||
Also logs the number of posts in which the keyword was found.
|
||||
"""
|
||||
keyword = arguments.keyword
|
||||
subreddit = arguments.subreddit
|
||||
listing = arguments.listing
|
||||
timeframe = arguments.timeframe
|
||||
limit = arguments.limit
|
||||
json_output = arguments.json
|
||||
|
||||
# Start a new session
|
||||
session = requests.session()
|
||||
# Set the User-Agent to mimic a Safari browser on a Mac
|
||||
session.headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, '
|
||||
'like Gecko) Version/14.1.1 Safari/605.1.15'}
|
||||
|
||||
# Send a GET request to the specified subreddit and listing,
|
||||
# limiting the response by the specified limit and timeframe
|
||||
response = session.get(f'https://reddit.com/r/{subreddit}/{listing}'
|
||||
f'.json?limit={limit}&t={timeframe}').json()
|
||||
|
||||
# Initialize a counter for the number of posts found that contain the keyword
|
||||
found_posts = 0
|
||||
|
||||
# Loop through each post in the response
|
||||
for post in response['data']['children']:
|
||||
# If the keyword is found in the post's selftext or title, increment the counter and process the post
|
||||
if keyword.lower() in post['data']['selftext'] or keyword.lower() in post['data']['title']:
|
||||
found_posts += 1
|
||||
format_post_data(post=post, keyword=keyword, output=json_output)
|
||||
|
||||
# Log the number of posts in which the keyword was found
|
||||
log.info(f"Keyword ('{keyword}') was found in {found_posts}/{len(response['data']['children'])} "
|
||||
f"{listing} posts from r/{subreddit}.")
|
||||
|
||||
|
||||
def create_parser():
|
||||
"""
|
||||
Creates and configures an argument parser for the command line arguments.
|
||||
|
||||
:return: A configured argparse.ArgumentParser object ready to parse the command line arguments.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='RPST: Reddit Post Scraping Tool —by Richard Mwewa | https://about.me/rly0nheart',
|
||||
epilog='Given a subreddit name and a keyword, '
|
||||
'RPST returns all top (by default) posts that contain the specified keyword.'
|
||||
)
|
||||
|
||||
parser.add_argument('-k', '--keyword', help='The keyword to search for in the posts.', required=True)
|
||||
parser.add_argument('-s', '--subreddit', help='The subreddit to scrape.', required=True)
|
||||
parser.add_argument(
|
||||
'-c', '--limit',
|
||||
help='The maximum number of posts to scrape (1-100). (default: %(default)s)',
|
||||
default=10,
|
||||
type=int,
|
||||
choices=range(1, 101) # This enforces that the limit must be between 1 and 100 inclusive.
|
||||
)
|
||||
parser.add_argument(
|
||||
'-l', '--listing',
|
||||
default='top',
|
||||
const='top',
|
||||
nargs='?',
|
||||
choices=['controversial', 'hot', 'best', 'new', 'rising'],
|
||||
help='The type of posts to scrape (default: %(default)s)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-t', '--timeframe',
|
||||
default='all',
|
||||
const='all',
|
||||
nargs='?',
|
||||
choices=['hour', 'day', 'week', 'month', 'year', 'all'],
|
||||
help='The timeframe from which to scrape posts (default: %(default)s)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-j', '--json',
|
||||
help='Write all found posts to a json file.',
|
||||
action='store_true'
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
logging.basicConfig(level="NOTSET", format="%(message)s",
|
||||
handlers=[RichHandler(markup=True, log_time_format='[%H:%M:%S%p]')])
|
||||
log = logging.getLogger("rich")
|
||||
@@ -1,5 +1,7 @@
|
||||
from datetime import datetime
|
||||
from rpst.__rpst import log, get_posts, check_updates, create_parser
|
||||
|
||||
from .rpst import get_posts
|
||||
from .utils import create_parser, set_loglevel, check_updates
|
||||
|
||||
|
||||
def run():
|
||||
@@ -10,20 +12,22 @@ def run():
|
||||
|
||||
# Create a parser and parse the command line arguments
|
||||
parser = create_parser()
|
||||
arguments = parser.parse_args()
|
||||
args = parser.parse_args()
|
||||
|
||||
log = set_loglevel(args=args)
|
||||
|
||||
# Record the start time
|
||||
start_time = datetime.now()
|
||||
|
||||
try:
|
||||
# Check for updates
|
||||
check_updates(version_tag="1.4.1.0")
|
||||
check_updates(version_tag="1.7.0.0")
|
||||
|
||||
# Get posts with the provided/parsed arguments
|
||||
get_posts(arguments=arguments)
|
||||
get_posts(args=args)
|
||||
except KeyboardInterrupt:
|
||||
log.warning("User interruption detected.")
|
||||
except Exception as e:
|
||||
log.error(f"An error occurred: {e}")
|
||||
finally:
|
||||
log.info(f'Finished in {datetime.now() - start_time} seconds.')
|
||||
log.info(f"Finished in {datetime.now() - start_time} seconds.")
|
||||
131
rpst/rpst.py
Normal file
131
rpst/rpst.py
Normal file
@@ -0,0 +1,131 @@
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
from glyphoji import glyph
|
||||
from rich.tree import Tree
|
||||
from rich import print as xprint
|
||||
|
||||
from .utils import convert_timestamp_to_datetime, write_post_data
|
||||
|
||||
|
||||
def create_post_branch(post: dict, keyword: str, tree: Tree, args: argparse) -> Tree:
|
||||
"""
|
||||
This function extracts relevant data from a Reddit post and adds it in a tree branch structure,
|
||||
followed by the post's selftext.
|
||||
|
||||
:param post: A dictionary containing the data of a Reddit post.
|
||||
:param keyword: The keyword that is used to find posts, in his case gets uses as the filename.
|
||||
:param tree: Tree where the post branch will be added.
|
||||
:param args: A namespace object from argparse.
|
||||
:returns: The main tree with added post branches.
|
||||
"""
|
||||
# Define the data to extract from the post.
|
||||
post_data = {
|
||||
# "Author": post["data"]["author"],
|
||||
f"{glyph.id_button} ID": post["data"]["id"],
|
||||
f"{glyph.people_hugging} Subreddit": post["data"]["subreddit_name_prefixed"],
|
||||
f"{glyph.face_with_peeking_eye} Visibility": post["data"]["subreddit_type"],
|
||||
f"{glyph.framed_picture} Thumbnail": post["data"]["thumbnail"],
|
||||
f"{glyph.white_question_mark} Gilded": post["data"]["gilded"],
|
||||
f"{glyph.up_arrow} Upvotes": post["data"]["ups"],
|
||||
f"{glyph.chart_increasing} Upvote ratio": post["data"]["upvote_ratio"],
|
||||
f"{glyph.down_arrow} Downvotes": post["data"]["downs"],
|
||||
f"{glyph.trophy} Awards": post["data"]["total_awards_received"],
|
||||
f"{glyph.trophy} Top award": post["data"]["top_awarded_type"],
|
||||
f"{glyph.no_one_under_eighteen} Is NSFW?": post["data"]["over_18"],
|
||||
f"{glyph.left_arrow_curving_right} Is crosspostable?": post["data"][
|
||||
"is_crosspostable"
|
||||
],
|
||||
f"{glyph.bar_chart} Score": post["data"]["score"],
|
||||
f"{glyph.card_file_box} Category": post["data"]["category"],
|
||||
f"{glyph.globe_with_meridians} Domain": post["data"]["domain"],
|
||||
f"{glyph.calendar} Posted on": convert_timestamp_to_datetime(
|
||||
post["data"]["created"]
|
||||
),
|
||||
f"{glyph.calendar} Approved at": post["data"]["approved_at_utc"],
|
||||
f"{glyph.bust_in_silhouette} Approved by": post["data"]["approved_by"],
|
||||
}
|
||||
|
||||
# Add the post's branch to the main tree.
|
||||
post_branch = tree.add(f"{glyph.scroll} {post['data']['title']}")
|
||||
|
||||
# Add each piece of extracted data as a branch of the post_branch.
|
||||
for post_key, post_value in post_data.items():
|
||||
post_branch.add(f"{post_key}: {post_value}", style="dim")
|
||||
|
||||
# This ensures that the post's selftext is also added to the written json/csv file.
|
||||
post_data[f"{glyph.clipboard} Text"] = post["data"]["selftext"]
|
||||
write_post_data(
|
||||
filename=keyword, post_data=post_data, tree_branch=post_branch, args=args
|
||||
)
|
||||
post_branch.add(post["data"]["selftext"], style="italic")
|
||||
|
||||
return tree
|
||||
|
||||
|
||||
def get_posts(args: argparse):
|
||||
"""
|
||||
Scrapes a given subreddit for posts that contain a specified keyword.
|
||||
The search is limited by the number of posts and timeframe specified.
|
||||
|
||||
:param args: Namespace object from argparse.
|
||||
|
||||
Expected Object Attributes
|
||||
--------------------------
|
||||
- keyword: The keyword to search for in the posts.
|
||||
- subreddit: The subreddit to scrape.
|
||||
- listing: The type of posts to scrape. This could be 'hot', 'new', etc.
|
||||
- timeframe: The timeframe from which to scrape posts. This could be 'day', 'week', etc.
|
||||
- limit: The maximum number of posts to scrape.
|
||||
- json: If specified, all found posts will be written to a json file.
|
||||
"""
|
||||
keyword = args.keyword
|
||||
subreddit = args.subreddit
|
||||
listing = args.listing
|
||||
timeframe = args.timeframe
|
||||
limit = args.limit
|
||||
|
||||
# Create main result tree.
|
||||
main_tree = Tree(
|
||||
f"[bold]{glyph.calendar} {datetime.now()}[/]", guide_style="bold bright_blue"
|
||||
)
|
||||
|
||||
# Start a new session
|
||||
session = requests.session()
|
||||
# Set the User-Agent to mimic a Safari browser on a Mac.
|
||||
session.headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, "
|
||||
"like Gecko) Version/14.1.1 Safari/605.1.15"
|
||||
}
|
||||
|
||||
# Send a GET request to the specified subreddit and listing,
|
||||
# limiting the response by the specified limit and timeframe.
|
||||
response = session.get(
|
||||
f"https://reddit.com/r/{subreddit}/{listing}"
|
||||
f".json?limit={limit}&t={timeframe}"
|
||||
).json()
|
||||
|
||||
# Initialize a counter for the number of posts found that contain the keyword.
|
||||
found_posts = 0
|
||||
|
||||
# Loop through each post in the response
|
||||
for post_index, post in enumerate(response["data"]["children"], start=1):
|
||||
# If the keyword is found in the post's selftext or title, increment the counter and process the post.
|
||||
if (
|
||||
keyword.lower() in post["data"]["selftext"]
|
||||
or keyword.lower() in post["data"]["title"]
|
||||
):
|
||||
# Create a branch for found post(s) and show post index and post author as the title
|
||||
found_tree = main_tree.add(
|
||||
f"{glyph.bust_in_silhouette} #{post_index} by [bold]@{post['data']['author']}[/]"
|
||||
)
|
||||
found_posts += 1
|
||||
create_post_branch(post=post, keyword=keyword, tree=found_tree, args=args)
|
||||
|
||||
# Log the number of posts in which the keyword was found
|
||||
main_tree.add(
|
||||
f"{glyph.check_mark_button} Keyword ('{keyword}') was found in "
|
||||
f"{found_posts}/{len(response['data']['children'])} {listing} posts from r/{subreddit}."
|
||||
)
|
||||
xprint(main_tree)
|
||||
202
rpst/utils.py
Normal file
202
rpst/utils.py
Normal file
@@ -0,0 +1,202 @@
|
||||
import os
|
||||
import csv
|
||||
import json
|
||||
import logging
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
from glyphoji import glyph
|
||||
from rich.tree import Tree
|
||||
from rich import print as xprint
|
||||
from rich.markdown import Markdown
|
||||
from rich.logging import RichHandler
|
||||
|
||||
|
||||
def convert_timestamp_to_datetime(timestamp: int) -> str:
|
||||
"""
|
||||
Converts a Unix timestamp to a formatted datetime string.
|
||||
|
||||
:param timestamp: The Unix timestamp to be converted.
|
||||
:return: A formatted datetime string in the format "dd MMMM yyyy, hh:mm:ssAM/PM".
|
||||
"""
|
||||
utc_from_timestamp = datetime.utcfromtimestamp(timestamp)
|
||||
datetime_object = utc_from_timestamp.strftime("%d %B %Y, %I:%M:%S%p")
|
||||
return datetime_object
|
||||
|
||||
|
||||
def create_parser():
|
||||
"""
|
||||
Creates and configures an argument parser for the command line arguments.
|
||||
|
||||
:return: A configured argparse.ArgumentParser object ready to parse the command line arguments.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="RPST (Reddit Post Scraping Tool) —by Richard Mwewa | https://about.me/rly0nheart",
|
||||
epilog="Given a subreddit name and a keyword, "
|
||||
"RPST returns all top (by default) posts that contain the specified keyword.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-k", "--keyword", help="The keyword to search for in the posts.", required=True
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s", "--subreddit", help="The subreddit to scrape.", required=True
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--limit",
|
||||
help="The maximum number of posts to scrape (1-100). (default: %(default)s)",
|
||||
default=10,
|
||||
type=int,
|
||||
choices=range(
|
||||
1, 101
|
||||
), # This enforces that the limit must be between 1 and 100 inclusive.
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--listing",
|
||||
default="top",
|
||||
const="top",
|
||||
nargs="?",
|
||||
choices=["controversial", "hot", "best", "new", "rising"],
|
||||
help="The type of posts to scrape (default: %(default)s)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--timeframe",
|
||||
default="all",
|
||||
const="all",
|
||||
nargs="?",
|
||||
choices=["hour", "day", "week", "month", "year", "all"],
|
||||
help="The timeframe from which to scrape posts (default: %(default)s)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json",
|
||||
help="Write all found posts to a json file.",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--csv",
|
||||
help="Write all found posts to a csv file.",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--debug",
|
||||
help="run rpst in debug mode (show network logs)",
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def check_updates(version_tag: str):
|
||||
"""
|
||||
This function checks if there's a new release of a project on GitHub. If there is, it logs an
|
||||
information message and prints the release notes.
|
||||
|
||||
:param version_tag: A string representing the current version of the project.
|
||||
"""
|
||||
|
||||
# Make a GET request to the GitHub API to get the latest release of the project.
|
||||
response = requests.get(
|
||||
"https://api.github.com/repos/bellingcat/reddit-post-scraping-tool/releases/latest"
|
||||
).json()
|
||||
|
||||
# Check if the latest release's tag matches the current version tag.
|
||||
if response["tag_name"] != version_tag:
|
||||
# If not, convert the release notes from Markdown to HTML.
|
||||
raw_release_notes = response["body"]
|
||||
|
||||
# Log an info message about the new release.
|
||||
xprint(
|
||||
f"{glyph.up_arrow} A new release of RPST is available ({response['tag_name']}). "
|
||||
f"Run 'pip install --upgrade reddit-post-scraping-tool' to get the updates."
|
||||
)
|
||||
|
||||
# Print the release notes.
|
||||
xprint(Markdown(raw_release_notes))
|
||||
|
||||
|
||||
def set_loglevel(args: argparse) -> logging.getLogger:
|
||||
"""
|
||||
Configures the logging level based on the provided arguments.
|
||||
|
||||
If `args.debug` is True, the logging level is set to "NOTSET," allowing all log messages to be displayed.
|
||||
Otherwise, the logging level is set to "INFO," and only informational and higher-severity messages are displayed.
|
||||
|
||||
The function also configures a RichHandler for formatting the log messages,
|
||||
including a specific time format and hiding the log level.
|
||||
|
||||
:param args: A namespace object from argparse containing the debugging option (args.debug).
|
||||
:return: A logger object associated with the name "rich."
|
||||
"""
|
||||
if args.debug:
|
||||
logging.basicConfig(
|
||||
level="NOTSET",
|
||||
format="%(message)s",
|
||||
handlers=[
|
||||
RichHandler(
|
||||
markup=True, log_time_format="[%H:%M:%S%p]", show_level=False
|
||||
)
|
||||
],
|
||||
)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
level="INFO",
|
||||
format="%(message)s",
|
||||
handlers=[
|
||||
RichHandler(
|
||||
markup=True, log_time_format="[%H:%M:%S%p]", show_level=False
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
return logging.getLogger("rich")
|
||||
|
||||
|
||||
def write_post_data(post_data: dict, filename: str, args, tree_branch: Tree):
|
||||
"""
|
||||
Writes post data to a specified JSON or CSV file based on the args provided, and updates
|
||||
the provided tree with the status.
|
||||
|
||||
:param post_data: A dictionary containing post data.
|
||||
:param filename: The name of the file to which post data will be written.
|
||||
:param args: A namespace object from argparse containing the output format options (args.json or args.csv).
|
||||
:param tree_branch: A rich Tree object to which status information will be added.
|
||||
"""
|
||||
home_directory = os.path.expanduser("~")
|
||||
|
||||
if args.json:
|
||||
json_file_path = os.path.join(home_directory, f"{filename}.json")
|
||||
with open(json_file_path, "a", encoding="utf-8") as file:
|
||||
file.write(json.dumps(post_data, ensure_ascii=False))
|
||||
file.write("\n") # Separate posts with newline
|
||||
tree_branch.add(
|
||||
f"{glyph.page_facing_up} JSON data successfully written/appended to file: "
|
||||
f"[italic][link file://{json_file_path}]{json_file_path}[/]"
|
||||
)
|
||||
else:
|
||||
tree_branch.add(
|
||||
f"{glyph.cross_mark_button} JSON data writing operation was skipped. No changes made."
|
||||
)
|
||||
|
||||
if args.csv:
|
||||
csv_file_path = os.path.join(home_directory, f"{filename}.csv")
|
||||
with open(csv_file_path, "a", newline="", encoding="utf-8") as csvfile:
|
||||
writer = csv.DictWriter(csvfile, fieldnames=post_data.keys())
|
||||
|
||||
# Write headers if file is empty
|
||||
if csvfile.tell() == 0:
|
||||
writer.writeheader()
|
||||
|
||||
writer.writerow(post_data)
|
||||
tree_branch.add(
|
||||
f"{glyph.page_facing_up} CSV data successfully written/appended to file: "
|
||||
f"[italic][link file://{csv_file_path}]{csv_file_path}[/]"
|
||||
)
|
||||
else:
|
||||
tree_branch.add(
|
||||
f"{glyph.cross_mark_button} CSV data writing operation was skipped. No changes made."
|
||||
)
|
||||
Reference in New Issue
Block a user