Skip to content

Commit 2bc1096

Browse files
committed
merge
2 parents 277b918 + 6c3df60 commit 2bc1096

7 files changed

Lines changed: 113 additions & 32 deletions

File tree

xiaomiNoteExporter/ConsoleHelp.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Drawing;
2+
using Pastel;
3+
4+
using xiaomiNoteExporter.Extensions;
5+
6+
namespace xiaomiNoteExporter;
7+
8+
internal class ConsoleHelp(Version? version)
9+
{
10+
private readonly List<string> messages = new ()
11+
{
12+
$"{"Xiaomi Note Exporter".Pastel(Color.FromArgb(252, 106, 0))} {version.GetVersionString()}\n",
13+
$"Usage: xiaomiNoteExporter.exe {"[options]".Pastel(Color.DimGray)}\n",
14+
"Options:",
15+
" -h, --help\t\tShow this help message and exit\n",
16+
$" -d, --domain <domain> {"(default: us.i.mi.com)".Pastel(Color.DimGray)}\n\tMi Notes domain that you were redirected to.\n",
17+
$" -s, --split <timestamp> {"(default: dd-MM-yyyy_HH-mm-ss)".Pastel(Color.DimGray)}\n\tSplit notes into separate files with provided timestamp format. Must be compatible with:\n\thttps://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings"
18+
};
19+
20+
public void Print()
21+
{
22+
foreach (var item in messages)
23+
{
24+
Console.WriteLine(item);
25+
}
26+
}
27+
}

xiaomiNoteExporter/Driver.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public ChromeDriver Prepare()
1515
{
1616
ChromeOptions options = new();
1717
options.AddArguments(_args);
18+
1819
ChromeDriverService service = ChromeDriverService.CreateDefaultService();
1920
service.HideCommandPromptWindow = true;
2021

xiaomiNoteExporter/Program.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Drawing;
22
using System.Reflection;
3-
43
using OpenQA.Selenium.Chrome;
54
using Pastel;
65

@@ -63,15 +62,7 @@ public static void Main(string[] args)
6362
new Scraper(driver, ShutdownHandler_Handler).Start(domain, _timestampFormat, _shouldSplit);
6463
}
6564

66-
private static void ShowHelp()
67-
{
68-
Console.WriteLine($"{"Xiaomi Note Exporter".Pastel(Color.FromArgb(252, 106, 0))} {appVersion?.GetVersionString()}\n");
69-
Console.WriteLine($"Usage: xiaomiNoteExporter.exe {"[options]".Pastel(Color.DimGray)}\n");
70-
Console.WriteLine("Options:");
71-
Console.WriteLine(" -h, --help\n\tShow this help message.\n");
72-
Console.WriteLine($" -d, --domain <domain> {"(default: us.i.mi.com)".Pastel(Color.DimGray)}\n\tMi Notes domain that you were redirected to.\n");
73-
Console.WriteLine($" -s, --split <timestamp> {"(default: dd-MM-yyyy_HH-mm-ss)".Pastel(Color.DimGray)}\n\tSplit notes into separate files with provided timestamp format. Must be compatible with:\n\thttps://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings");
74-
}
65+
private static void ShowHelp() => new ConsoleHelp(appVersion).Print();
7566

7667
private static void ParseArgs(string[] args)
7768
{

xiaomiNoteExporter/Prompt.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Drawing;
2-
32
using Pastel;
43

54
namespace xiaomiNoteExporter;
@@ -17,10 +16,8 @@ private static string InsertAfterSpace(string str, string insertion, Color? colo
1716
{
1817
return str.Insert(spaceIndex, $" {insertion.Pastel(color ?? Color.Red)}");
1918
}
20-
else
21-
{
22-
return str;
23-
}
19+
20+
return str;
2421
}
2522

2623
/// <summary>

xiaomiNoteExporter/Scraper.cs

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System.Diagnostics;
33
using System.Text.RegularExpressions;
44
using System.Globalization;
5-
5+
using System.Net;
66
using OpenQA.Selenium;
77
using OpenQA.Selenium.Chrome;
88
using OpenQA.Selenium.Support.UI;
@@ -56,10 +56,10 @@ public void Start(string domain, string timeStampFormat, bool split = false)
5656
Console.ReadKey();
5757
}
5858

59-
Scrape(timeStampFormat, split);
59+
Scrape(timeStampFormat, domain, split);
6060
}
6161

62-
private void Scrape(string timeStampFormat, bool split)
62+
private void Scrape(string timeStampFormat, string domain, bool split)
6363
{
6464
if (_wait is null)
6565
{
@@ -95,6 +95,14 @@ private void Scrape(string timeStampFormat, bool split)
9595
Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, exportName));
9696
}
9797

98+
string imgDir = Path.Combine(
99+
AppDomain.CurrentDomain.BaseDirectory,
100+
split ? $"{exportName}\\images" : $"images_{currentExportDate}"
101+
);
102+
103+
// create directory for exported images
104+
Directory.CreateDirectory(imgDir);
105+
98106
bool isFirst = true; // this check is needed, because it usually opens first note automatically
99107

100108
while (true)
@@ -116,7 +124,7 @@ private void Scrape(string timeStampFormat, bool split)
116124
{
117125
// if element is not the first one, use following element
118126
element = _wait.Until(e => e.FindElement(By.XPath(@"//div[contains(@class, 'open')]/following::div")));
119-
}
127+
}
120128
else
121129
{
122130
element = _wait.Until(e => e.FindElement(By.XPath(@"//div[contains(@class, 'open')]")));
@@ -135,15 +143,15 @@ private void Scrape(string timeStampFormat, bool split)
135143
if (createdString.ToLower().Contains("yesterday"))
136144
{
137145
createdDate = DateTime.Now.AddDays(-1).Date; // get yesterday's date
138-
}
146+
}
139147
else if (createdString.EndsWith("ago"))
140148
{
141149
createdDate = RelativeTimeParser.Parse(createdString);
142-
}
150+
}
143151
else if (SimplifiedDateParser.TryParseMdHm(createdString, out DateTime parsedSimple))
144152
{
145153
createdDate = parsedSimple;
146-
}
154+
}
147155
else
148156
{
149157
createdDate = DateTime.Parse(createdString, new CultureInfo("en-US"));
@@ -152,12 +160,12 @@ private void Scrape(string timeStampFormat, bool split)
152160
try
153161
{
154162
innerWait.Until(e => e.FindElements(By.XPath(@"//div[contains(@class, 'open')]/div[2][not(./i)]")).Count == 1);
155-
}
163+
}
156164
catch
157165
{
158166
// found note that is not supported, log this fact and continue
159167
SaveToFile(
160-
!split ? fileName : $"{exportName}\\{$"note_{createdDate.ToString(timeStampFormat)}"}.md",
168+
!split ? fileName : $"{exportName}\\{$"note_{createdDate.ToString(timeStampFormat)}"}",
161169
$"** Unsupported note type (Mind-map or Sound note) (Created at: {createdDate:dd/MM/yyyy HH:mm})**"
162170
);
163171
ExecuteScroll(notesList, element);
@@ -167,14 +175,37 @@ private void Scrape(string timeStampFormat, bool split)
167175

168176
_wait.Until(e => e.FindElement(By.XPath(@"//div[contains(@class, 'origin-title')]/div")).Displayed);
169177

178+
var noteContainer = _wait.Until(e => e.FindElement(By.XPath(@"//div[contains(@class, 'pm-container')]")));
179+
170180
string title = _wait.Until(e => e.FindElement(By.XPath(@"//div[contains(@class, 'origin-title')]/div"))).Text;
171-
string value = _wait.Until(e => e.FindElement(By.XPath(@"//div[contains(@class, 'pm-container')]"))).Text;
181+
string value = noteContainer.Text;
172182

173183
SaveToFile(
174-
!split ? fileName : $"{exportName}\\{$"note_{createdDate.ToString(timeStampFormat)}"}.md",
184+
!split ? fileName : $"{exportName}\\{$"note_{createdDate.ToString(timeStampFormat)}"}",
175185
value,
176186
title
177187
);
188+
189+
var embeddedImages = noteContainer.FindElements(By.XPath(@".//div[contains(@class, 'image-view')]/img"));
190+
191+
if (embeddedImages.Count != 0)
192+
{
193+
var cookies = _driver.Manage().Cookies.AllCookies;
194+
195+
// IWebElement because non nullish type is needed (force typing)
196+
foreach (var t in embeddedImages.Select((item, idx) => (idx, (IWebElement)item)))
197+
{
198+
int idx = t.idx;
199+
IWebElement item = t.Item2;
200+
201+
var imgSrc = item.GetAttribute("src");
202+
string imgName = $"note_img_{idx}_{createdDate.ToString(timeStampFormat)}.png";
203+
string imgPath = Path.Combine(imgDir, imgName);
204+
205+
SaveImage(imgPath, imgSrc, domain, cookies);
206+
}
207+
}
208+
178209
ExecuteScroll(notesList, element);
179210
currentNote++;
180211
}
@@ -187,7 +218,7 @@ private void Scrape(string timeStampFormat, bool split)
187218
if (split)
188219
{
189220
Console.WriteLine($"Successfully exported notes to {exportName.Pastel(Color.WhiteSmoke)} directory\n".Pastel(Color.LimeGreen));
190-
}
221+
}
191222
else
192223
{
193224
Console.WriteLine($"Successfully exported notes to {fileName.Pastel(Color.WhiteSmoke)}\n".Pastel(Color.LimeGreen));
@@ -219,6 +250,40 @@ private static void SaveToFile(string fileName, string content, string? title =
219250
sw.WriteLine(content);
220251
}
221252

253+
private static void SaveImage(string path, string? src, string domain, IEnumerable<OpenQA.Selenium.Cookie> cookies)
254+
{
255+
if (File.Exists(path))
256+
{
257+
return;
258+
}
259+
260+
var handler = new HttpClientHandler
261+
{
262+
CookieContainer = new CookieContainer()
263+
};
264+
265+
var uri = new Uri($"https://{domain}{src}");
266+
267+
foreach (var cookie in cookies)
268+
{
269+
handler.CookieContainer.Add(
270+
new System.Net.Cookie(cookie.Name, cookie.Value, cookie.Path, cookie.Domain)
271+
);
272+
}
273+
274+
using var client = new HttpClient(handler);
275+
276+
try
277+
{
278+
byte[] imageBytes = client.GetByteArrayAsync(src).Result;
279+
File.WriteAllBytes(path, imageBytes);
280+
}
281+
catch (Exception)
282+
{
283+
Console.WriteLine($"{"[ERROR]".Pastel(Color.Red)} Couldn't fetch image.");
284+
}
285+
}
286+
222287
private void ExecuteScroll(IWebElement notesList, IWebElement currentElement)
223288
{
224289
((IJavaScriptExecutor)_driver).ExecuteScript("arguments[0].scrollBy(0, arguments[1]);", notesList, currentElement.Size.Height);

xiaomiNoteExporter/SimplifiedDateParser.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ public static bool TryParseMdHm(string input, out DateTime result)
2727

2828
return DateTime.TryParse(formatted, new CultureInfo("en-US"), out result);
2929
}
30-
}
30+
}

xiaomiNoteExporter/xiaomiNoteExporter.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
<PublishTrimmed>true</PublishTrimmed>
99
<TrimMode>link</TrimMode>
1010
<ApplicationIcon>icon.ico</ApplicationIcon>
11-
<AssemblyVersion>1.6.2</AssemblyVersion>
12-
<FileVersion>1.6.2</FileVersion>
11+
<AssemblyVersion>1.7.0</AssemblyVersion>
12+
<FileVersion>1.7.0</FileVersion>
1313
<VersionSuffix></VersionSuffix>
1414
</PropertyGroup>
1515

@@ -27,8 +27,8 @@
2727

2828
<ItemGroup>
2929
<PackageReference Include="Pastel" Version="6.0.1" />
30-
<PackageReference Include="Selenium.Support" Version="4.33.0" />
31-
<PackageReference Include="Selenium.WebDriver" Version="4.33.0" />
30+
<PackageReference Include="Selenium.Support" Version="4.31.0" />
31+
<PackageReference Include="Selenium.WebDriver" Version="4.31.0" />
3232
</ItemGroup>
3333

3434
</Project>

0 commit comments

Comments
 (0)